From 6966ec0b89852b1ec08247494145c809ec20b79f Mon Sep 17 00:00:00 2001 From: lalka2018 <99399973+lalka2016@users.noreply.github.com> Date: Sat, 14 Oct 2023 10:03:49 +0300 Subject: [PATCH] Interface updade --- VKAPI/Handlers/Wall.php | 9 + Web/Models/Entities/Audio.php | 21 +- Web/Models/Repositories/Audios.php | 45 ++- Web/Presenters/AdminPresenter.php | 12 +- Web/Presenters/AudioPresenter.php | 90 +++-- Web/Presenters/SearchPresenter.php | 28 +- Web/Presenters/UserPresenter.php | 4 +- Web/Presenters/templates/@layout.xml | 15 +- Web/Presenters/templates/Admin/EditMusic.xml | 14 +- Web/Presenters/templates/Admin/Music.xml | 20 +- Web/Presenters/templates/Audio/List.xml | 145 ++++---- Web/Presenters/templates/Audio/New.xml | 29 -- Web/Presenters/templates/Audio/Playlists.xml | 15 + Web/Presenters/templates/Audio/Popular.xml | 29 -- Web/Presenters/templates/Audio/Search.xml | 77 ----- Web/Presenters/templates/Audio/Upload.xml | 180 +++++----- Web/Presenters/templates/Audio/player.js.xml | 144 -------- Web/Presenters/templates/Audio/player.xml | 70 ++-- Web/Presenters/templates/Audio/tabs.xml | 83 +---- Web/Presenters/templates/Search/Index.xml | 42 ++- Web/Presenters/templates/User/View.xml | 19 ++ Web/Presenters/templates/_includeCSS.xml | 2 + .../templates/components/attachment.xml | 4 + .../templates/components/textArea.xml | 5 + Web/routes.yml | 2 + Web/static/css/audios.css | 322 ++++++++++++++++++ Web/static/css/main.css | 178 ++-------- Web/static/img/audios_controls.png | Bin 0 -> 3405 bytes Web/static/img/play_buttons.gif | Bin 0 -> 110 bytes Web/static/js/al_music.js | 250 ++++++++++++++ locales/en.strings | 59 ++++ locales/ru.strings | 38 ++- 32 files changed, 1170 insertions(+), 781 deletions(-) delete mode 100644 Web/Presenters/templates/Audio/New.xml create mode 100644 Web/Presenters/templates/Audio/Playlists.xml delete mode 100644 Web/Presenters/templates/Audio/Popular.xml delete mode 100644 Web/Presenters/templates/Audio/Search.xml delete mode 100644 Web/Presenters/templates/Audio/player.js.xml create mode 100644 Web/static/css/audios.css create mode 100644 Web/static/img/audios_controls.png create mode 100644 Web/static/img/play_buttons.gif create mode 100644 Web/static/js/al_music.js diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 6b78a0b0..f188a0e3 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -15,6 +15,7 @@ use openvk\Web\Models\Entities\Video; use openvk\Web\Models\Repositories\Videos as VideosRepo; use openvk\Web\Models\Entities\Note; use openvk\Web\Models\Repositories\Notes as NotesRepo; +use openvk\Web\Models\Repositories\Audios as AudiosRepo; final class Wall extends VKAPIRequestHandler { @@ -450,6 +451,8 @@ final class Wall extends VKAPIRequestHandler $attachmentType = "video"; elseif(str_contains($attac, "note")) $attachmentType = "note"; + elseif(str_contains($attac, "audio")) + $attachmentType = "audio"; else $this->fail(205, "Unknown attachment type"); @@ -483,6 +486,12 @@ final class Wall extends VKAPIRequestHandler if(!$attacc->getOwner()->getPrivacyPermission('notes.read', $this->getUser())) $this->fail(11, "Access to note denied"); + $post->attach($attacc); + } elseif($attachmentType == "audio") { + $attacc = (new AudiosRepo)->getByOwnerAndVID($attachmentOwner, $attachmentId); + if(!$attacc || $attacc->isDeleted()) + $this->fail(100, "Audio does not exist"); + $post->attach($attacc); } } diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 03f0ed0b..cdcc73e3 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -101,7 +101,7 @@ class Audio extends Media $this->stateChanges("segment_size", $ss); $this->stateChanges("length", $duration); - try { + try { $args = [ str_replace("enabled", "available", OPENVK_ROOT), str_replace("enabled", "available", $this->getBaseDir()), @@ -114,23 +114,18 @@ class Audio extends Media $ss, ]; - if(Shell::isPowershell()) { Shell::powershell("-executionpolicy bypass", "-File", __DIR__ . "/../shell/processAudio.ps1", ...$args) - ->start(); + ->start(); } else { - Shell::bash(__DIR__ . "/../shell/processAudio.sh", ...$args)->start(); - // Shell::bash(__DIR__ . "/../shell/processAudio.sh", ...$args)->start(); - // exit("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log'); - // exit("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log'); - // Shell::bash("pwsh /opt/chandler/extensions/available/openvk/Web/Models/shell/processAudio.ps1 " . implode(" ", $args) . ' *> /opt/chandler/extensions/available/openvk/storage/log.log'); + exit("Linux uploads are not implemented"); } # Wait until processAudio will consume the file -// $start = time(); -// while(file_exists($filename)) -// if(time() - $start > 5) -// exit("Timed out waiting for ffmpeg"); // TODO replace with exception + $start = time(); + while(file_exists($filename)) + if(time() - $start > 5) + exit("Timed out waiting for ffmpeg"); // TODO replace with exception } catch(UnknownCommandException $ucex) { exit(OPENVK_ROOT_CONF["openvk"]["debug"] ? "bash/pwsh is not installed" : VIDEOS_FRIENDLY_ERROR); @@ -161,7 +156,7 @@ class Audio extends Media function getLyrics(): ?string { - return $this->getRecord()->lyrics ?? NULL; + return !is_null($this->getRecord()->lyrics) ? htmlspecialchars($this->getRecord()->lyrics, ENT_DISALLOWED | ENT_XHTML) : NULL; } function getLength(): int diff --git a/Web/Models/Repositories/Audios.php b/Web/Models/Repositories/Audios.php index 45a3b8df..70abd6e0 100644 --- a/Web/Models/Repositories/Audios.php +++ b/Web/Models/Repositories/Audios.php @@ -195,12 +195,12 @@ class Audios function getNew(): EntityStream { - return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->order("created DESC")->limit(25)); + return new EntityStream("Audio", $this->audios->where("created >= " . (time() - 259200))->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("created DESC")->limit(25)); } function getPopular(): EntityStream { - return new EntityStream("Audio", $this->audios->where("listens > 0")->order("listens DESC")->limit(25)); + return new EntityStream("Audio", $this->audios->where("listens > 0")->where(["withdrawn" => 0, "deleted" => 0, "unlisted" => 0])->order("listens DESC")->limit(25)); } function isAdded(int $user_id, int $audio_id): bool @@ -211,12 +211,47 @@ class Audios ])->fetch()); } - function find(string $query, int $page = 1, ?int $perPage = NULL): \Traversable + function find(string $query, array $pars = [], string $sort = "id DESC", int $page = 1, ?int $perPage = NULL): \Traversable { $query = "%$query%"; - $result = $this->audios->where("name LIKE ? OR performer LIKE ?", $query, $query); + $result = $this->audios->where([ + "unlisted" => 0, + "deleted" => 0, + ]); - return new Util\EntityStream("Audio", $result); + $notNullParams = []; + + foreach($pars as $paramName => $paramValue) + if($paramName != "before" && $paramName != "after" && $paramName != "only_performers") + $paramValue != NULL ? $notNullParams+=["$paramName" => "%$paramValue%"] : NULL; + else + $paramValue != NULL ? $notNullParams+=["$paramName" => "$paramValue"] : NULL; + + $nnparamsCount = sizeof($notNullParams); + + if($notNullParams["only_performers"] == "1") { + $result->where("performer LIKE ?", $query); + } else { + $result->where("name LIKE ? OR performer LIKE ?", $query, $query); + } + + if($nnparamsCount > 0) { + foreach($notNullParams as $paramName => $paramValue) { + switch($paramName) { + case "before": + $result->where("created < ?", $paramValue); + break; + case "after": + $result->where("created > ?", $paramValue); + break; + case "with_lyrics": + $result->where("lyrics IS NOT NULL"); + break; + } + } + } + + return new Util\EntityStream("Audio", $result->order($sort)); } function findPlaylists(string $query, int $page = 1, ?int $perPage = NULL): \Traversable diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index ec132b08..0a1d5314 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -4,18 +4,18 @@ use Chandler\Database\Log; use Chandler\Database\Logs; use openvk\Web\Models\Entities\{Voucher, Gift, GiftCategory, User, BannedLink}; use openvk\Web\Models\Repositories\{Audios, - Bans, ChandlerGroups, ChandlerUsers, - Photos, - Posts, Users, Clubs, - Videos, Util\EntityStream, Vouchers, Gifts, - BannedLinks}; + BannedLinks, + Bans, + Photos, + Posts, + Videos}; use Chandler\Database\DatabaseConnection; final class AdminPresenter extends OpenVKPresenter @@ -39,7 +39,7 @@ final class AdminPresenter extends OpenVKPresenter $this->chandlerGroups = $chandlerGroups; $this->audios = $audios; $this->logs = DatabaseConnection::i()->getContext()->table("ChandlerLogs"); - + parent::__construct(); } diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index 70c23139..cec9c452 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -40,10 +40,9 @@ final class AudioPresenter extends OpenVKPresenter function renderList(?int $owner = NULL, ?string $mode = "list"): void { - $this->template->_template = "Audio/List"; - + $this->template->_template = "Audio/List.xml"; + $page = (int)($this->queryParam("p") ?? 1); $audios = []; - $playlists = []; if ($mode === "list") { $entity = NULL; @@ -52,15 +51,15 @@ final class AudioPresenter extends OpenVKPresenter if (!$entity || $entity->isBanned()) $this->redirect("/audios" . $this->user->id); - $audios = $this->audios->getByClub($entity); - $playlists = $this->audios->getPlaylistsByClub($entity); + $audios = $this->audios->getByClub($entity, $page, 10); + $audiosCount = $this->audios->getClubCollectionSize($entity); } else { $entity = (new Users)->get($owner); if (!$entity || $entity->isDeleted() || $entity->isBanned()) $this->redirect("/audios" . $this->user->id); - $audios = $this->audios->getByUser($entity); - $playlists = $this->audios->getPlaylistsByUser($entity); + $audios = $this->audios->getByUser($entity, $page, 10); + $audiosCount = $this->audios->getUserCollectionSize($entity); } if (!$entity) @@ -71,16 +70,20 @@ final class AudioPresenter extends OpenVKPresenter $this->template->isMyClub = ($owner < 0 && $entity->canBeModifiedBy($this->user->identity)); } else if ($mode === "new") { $audios = $this->audios->getNew(); + $audiosCount = $audios->size(); } else { $audios = $this->audios->getPopular(); + $audiosCount = $audios->size(); } // $this->renderApp("owner=$owner"); - if ($audios !== []) + if ($audios !== []) { $this->template->audios = iterator_to_array($audios); + $this->template->audiosCount = $audiosCount; + } - if ($playlists !== []) - $this->template->playlists = iterator_to_array($playlists); + $this->template->mode = $mode; + $this->template->page = $page; } function renderView(int $owner, int $id): void @@ -169,7 +172,7 @@ final class AudioPresenter extends OpenVKPresenter $name = $this->postParam("name"); $lyrics = $this->postParam("lyrics"); $genre = empty($this->postParam("genre")) ? "undefined" : $this->postParam("genre"); - $nsfw = ($this->postParam("nsfw") ?? "off") === "on"; + $nsfw = ($this->postParam("explicit") ?? "off") === "on"; if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars $this->flashFail("err", tr("error"), tr("error_insufficient_info")); @@ -212,12 +215,7 @@ final class AudioPresenter extends OpenVKPresenter function renderSearch(): void { - if ($this->queryParam("q")) { - $this->template->q = $this->queryParam("q"); - $this->template->by_performer = $this->queryParam("by_performer") === "on"; - $this->template->audios = iterator_to_array($this->audios->search($this->template->q, 1, $this->template->by_performer)); - $this->template->playlists = iterator_to_array($this->audios->searchPlaylists($this->template->q)); - } + $this->redirect("/search?type=audios"); } function renderNewPlaylist(): void @@ -348,30 +346,46 @@ final class AudioPresenter extends OpenVKPresenter function renderAction(int $audio_id): void { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(true); + $this->assertNoCSRF(); + + if ($_SERVER["REQUEST_METHOD"] !== "POST") { + header("HTTP/1.1 405 Method Not Allowed"); + exit(":)"); + } + + $audio = $this->audios->get($audio_id); + + if(!$audio || $audio->isDeleted()) + $this->flashFail("err", "error", tr("invalid_audio"), null, true); + switch ($this->queryParam("act")) { case "add": - if (!$this->audios->isAdded($this->user->id, $audio_id)) { - DatabaseConnection::i()->getContext()->query("INSERT INTO `audio_relations` (`entity`, `audio`) VALUES (?, ?)", $this->user->id, $audio_id); - } else { - $this->returnJson(["success" => false, "error" => "Аудиозапись уже добавлена"]); - } + if($audio->isWithdrawn()) + $this->flashFail("err", "error", tr("invalid_audio"), null, true); + + if(!$audio->isInLibraryOf($this->user->identity)) + $audio->add($this->user->identity); + else + $this->flashFail("err", "error", tr("do_have_audio"), null, true); + break; case "remove": - if ($this->audios->isAdded($this->user->id, $audio_id)) { - DatabaseConnection::i()->getContext()->query("DELETE FROM `audio_relations` WHERE `entity` = ? AND `audio` = ?", $this->user->id, $audio_id); - } else { - $this->returnJson(["success" => false, "error" => "Аудиозапись не добавлена"]); - } - break; + if($audio->isInLibraryOf($this->user->identity)) + $audio->remove($this->user->identity); + else + $this->flashFail("err", "error", tr("do_not_have_audio"), null, true); + break; case "edit": $audio = $this->audios->get($audio_id); if (!$audio || $audio->isDeleted() || $audio->isWithdrawn() || $audio->isUnlisted()) - $this->returnJson(["success" => false, "error" => "Аудиозапись не найдена"]); + $this->flashFail("err", "error", tr("invalid_audio"), null, true); if ($audio->getOwner()->getId() !== $this->user->id) - $this->returnJson(["success" => false, "error" => "Ошибка доступа"]); + $this->flashFail("err", "error", tr("access_denied"), null, true); $performer = $this->postParam("performer"); $name = $this->postParam("name"); @@ -379,13 +393,22 @@ final class AudioPresenter extends OpenVKPresenter $genre = empty($this->postParam("genre")) ? "undefined" : $this->postParam("genre"); $nsfw = ($this->postParam("nsfw") ?? "off") === "on"; if(empty($performer) || empty($name) || iconv_strlen($performer . $name) > 128) # FQN of audio must not be more than 128 chars - $this->flashFail("err", tr("error"), tr("error_insufficient_info")); + $this->flashFail("err", tr("error"), tr("error_insufficient_info"), null, true); $audio->setName($name); $audio->setPerformer($performer); $audio->setLyrics(empty($lyrics) ? NULL : $lyrics); $audio->setGenre($genre); $audio->save(); + + $this->returnJson(["success" => true, "new_info" => [ + "name" => ovk_proc_strtr($audio->getTitle(), 40), + "performer" => ovk_proc_strtr($audio->getPerformer(), 40), + "lyrics" => nl2br($audio->getLyrics() ?? ""), + "lyrics_unformatted" => $audio->getLyrics() ?? "", + "explicit" => $audio->isExplicit(), + "genre" => $audio->getGenre(), + ]]); break; default: @@ -394,4 +417,9 @@ final class AudioPresenter extends OpenVKPresenter $this->returnJson(["success" => true]); } + + function renderPlaylists(int $owner) + { + $this->assertUserLoggedIn(); + } } \ No newline at end of file diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php index fadf9954..e9dbf3d1 100644 --- a/Web/Presenters/SearchPresenter.php +++ b/Web/Presenters/SearchPresenter.php @@ -1,7 +1,7 @@ videos = new Videos; $this->apps = new Applications; $this->notes = new Notes; + $this->audios = new Audios; parent::__construct(); } function renderIndex(): void { + $this->assertUserLoggedIn(); + $query = $this->queryParam("query") ?? ""; $type = $this->queryParam("type") ?? "users"; $sorter = $this->queryParam("sort") ?? "id"; $invert = $this->queryParam("invert") == 1 ? "ASC" : "DESC"; $page = (int) ($this->queryParam("p") ?? 1); - $this->willExecuteWriteAction(); - if($query != "") - $this->assertUserLoggedIn(); - # https://youtu.be/pSAWM5YuXx8 $repos = [ @@ -47,7 +47,7 @@ final class SearchPresenter extends OpenVKPresenter "posts" => "posts", "comments" => "comments", "videos" => "videos", - "audios" => "posts", + "audios" => "audios", "apps" => "apps", "notes" => "notes" ]; @@ -62,7 +62,17 @@ final class SearchPresenter extends OpenVKPresenter break; case "rating": $sort = "rating " . $invert; - break; + break; + case "length": + if($type != "audios") break; + + $sort = "length " . $invert; + break; + case "listens": + if($type != "audios") break; + + $sort = "listens " . $invert; + break; } $parameters = [ @@ -86,7 +96,9 @@ final class SearchPresenter extends OpenVKPresenter "hometown" => $this->queryParam("hometown") != "" ? $this->queryParam("hometown") : NULL, "before" => $this->queryParam("datebefore") != "" ? strtotime($this->queryParam("datebefore")) : NULL, "after" => $this->queryParam("dateafter") != "" ? strtotime($this->queryParam("dateafter")) : NULL, - "gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL + "gender" => $this->queryParam("gender") != "" && $this->queryParam("gender") != 2 ? $this->queryParam("gender") : NULL, + "only_performers" => $this->queryParam("only_performers") == "on" ? "1" : NULL, + "with_lyrics" => $this->queryParam("with_lyrics") == "on" ? true : NULL, ]; $repo = $repos[$type] or $this->throwError(400, "Bad Request", "Invalid search entity $type."); diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 8ed50324..e568d4c5 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -5,7 +5,7 @@ use openvk\Web\Util\Sms; use openvk\Web\Themes\Themepacks; use openvk\Web\Models\Entities\{Photo, Post, EmailChangeVerification}; use openvk\Web\Models\Entities\Notifications\{CoinsTransferNotification, RatingUpNotification}; -use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications}; +use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios}; use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Util\Validator; use Chandler\Security\Authenticator; @@ -45,6 +45,8 @@ final class UserPresenter extends OpenVKPresenter $this->template->videosCount = (new Videos)->getUserVideosCount($user); $this->template->notes = (new Notes)->getUserNotes($user, 1, 4); $this->template->notesCount = (new Notes)->getUserNotesCount($user); + $this->template->audios = (new Audios)->getByUser($user, 1, 3); + $this->template->audiosCount = (new Audios)->getUserCollectionSize($user); $this->template->user = $user; } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 68b36816..d8a415de 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -16,6 +16,8 @@ {script "js/node_modules/umbrellajs/umbrella.min.js"} {script "js/l10n.js"} {script "js/openvk.cls.js"} + {script "js/node_modules/dashjs/dist/dash.all.min.js"} + {script "js/al_music.js"} {css "js/node_modules/tippy.js/dist/backdrop.css"} {css "js/node_modules/tippy.js/dist/border.css"} @@ -122,6 +124,7 @@ +
3