diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php index d323e66d..35d2b8aa 100644 --- a/VKAPI/Handlers/Audio.php +++ b/VKAPI/Handlers/Audio.php @@ -310,6 +310,17 @@ final class Audio extends VKAPIRequestHandler } $items = []; + + if($owner_id > 0) { + $user = (new \openvk\Web\Models\Repositories\Users)->get($owner_id); + + if(!$user) + $this->fail(50, "Invalid user"); + + if(!$user->getPrivacyPermission("audios.read", $this->getUser())) + $this->fail(15, "Access denied: this user chose to hide his audios"); + } + $audios = (new Audios)->getByEntityID($owner_id, $offset, $count); foreach($audios as $audio) $items[] = $this->toSafeAudioStruct($audio, $hash, $need_user == 1); diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 4c11f0a7..43c9dba6 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -148,7 +148,7 @@ class Audio extends Media function getName(): string { - return $this->getTitle() . " - " . $this->getPerformer(); + return $this->getPerformer() . " — " . $this->getTitle(); } function getGenre(): ?string diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index fbdc503b..d662d44d 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -371,6 +371,11 @@ class Club extends RowModel { return $this->getRecord()->alert; } + + function getRealId() + { + return $this->getId() * -1; + } function toVkApiStruct(?User $user = NULL): object { diff --git a/Web/Models/Entities/Playlist.php b/Web/Models/Entities/Playlist.php index 7bd0caf7..0da1dd49 100644 --- a/Web/Models/Entities/Playlist.php +++ b/Web/Models/Entities/Playlist.php @@ -5,6 +5,7 @@ use Nette\Database\Table\ActiveRow; use openvk\Web\Models\Repositories\Audios; use openvk\Web\Models\Repositories\Photos; use openvk\Web\Models\RowModel; +use openvk\Web\Models\Entities\Photo; /** * @method setName(string $name) @@ -125,6 +126,16 @@ class Playlist extends MediaCollection return $count > 0; } + + function getDescription(): ?string + { + return $this->getRecord()->description; + } + + function getDescriptionHTML(): ?string + { + return htmlspecialchars($this->getRecord()->description, ENT_DISALLOWED | ENT_XHTML); + } function toVkApiStruct(?User $user = NULL): object { @@ -152,6 +163,13 @@ class Playlist extends MediaCollection throw new \LogicException("Can't set length of playlist manually"); } + function resetLength(): bool + { + $this->stateChanges("length", 0); + + return true; + } + function delete(bool $softly = true): void { $ctx = DatabaseConnection::i()->getContext(); @@ -174,4 +192,34 @@ class Playlist extends MediaCollection { return $this->getRecord()->cover_photo_id; } + + function canBeModifiedBy(User $user): bool + { + if(!$user) + return false; + + if($this->getOwner() instanceof User) + return $user->getId() == $this->getOwner()->getId(); + else + return $this->getOwner()->canBeModifiedBy($user); + } + + function getLengthInMinutes(): int + { + return (int)round($this->getLength() / 60, PHP_ROUND_HALF_DOWN); + } + + function fastMakeCover(int $owner, array $file) + { + $cover = new Photo; + $cover->setOwner($owner); + $cover->setDescription("Playlist cover image"); + $cover->setFile($file); + $cover->setCreated(time()); + $cover->save(); + + $this->setCover_photo_id($cover->getId()); + + return $cover; + } } \ No newline at end of file diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index fa9eb28a..2114b5ab 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -1228,6 +1228,11 @@ class User extends RowModel return $response; } + function getRealId() + { + return $this->getId(); + } + function toVkApiStruct(): object { $res = (object) []; diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index 999c469c..313e3c8e 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -12,6 +12,7 @@ use openvk\Web\Models\Repositories\Users; final class AudioPresenter extends OpenVKPresenter { private $audios; + protected $presenterName = "audios"; const MAX_AUDIO_SIZE = 25000000; @@ -20,14 +21,6 @@ final class AudioPresenter extends OpenVKPresenter $this->audios = $audios; } - private function renderApp(string $playlistHandle): void - { - $this->assertUserLoggedIn(); - - $this->template->_template = "Audio/Player"; - $this->template->handle = $playlistHandle; - } - function renderPopular(): void { $this->renderList(NULL, "popular"); @@ -91,7 +84,7 @@ final class AudioPresenter extends OpenVKPresenter if(!$entity->getPrivacyPermission("audios.read", $this->user->identity)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); - $playlists = $this->audios->getPlaylistsByUser($entity, $page, 10); + $playlists = $this->audios->getPlaylistsByUser($entity, $page, 9); $playlistsCount = $this->audios->getUserPlaylistsCount($entity); } @@ -116,34 +109,6 @@ final class AudioPresenter extends OpenVKPresenter $this->template->page = $page; } - function renderView(int $owner, int $id): void - { - $this->assertUserLoggedIn(); - - $audio = $this->audios->getByOwnerAndVID($owner, $id); - if(!$audio || $audio->isDeleted()) - $this->notFound(); - - if(!$audio->canBeViewedBy($this->user->identity)) - $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); - - if ($_SERVER["REQUEST_METHOD"] === "POST") { - switch ($this->queryParam("act")) { - case "remove": - DatabaseConnection::i()->getContext()->query("DELETE FROM `audio_relations` WHERE `entity` = ? AND `audio` = ?", $this->user->id, $audio->getId()); - break; - - case "edit": - break; - - default: - $this->returnJson(["success" => false, "error" => "Action not implemented or not exists"]); - } - } else { - $this->renderApp("id=" . $audio->getId()); - } - } - function renderEmbed(int $owner, int $id): void { $audio = $this->audios->getByOwnerAndVID($owner, $id); @@ -239,6 +204,8 @@ final class AudioPresenter extends OpenVKPresenter $listen = $audio->listen($this->user->identity); $this->returnJson(["success" => $listen]); } + + $this->returnJson(["success" => false]); } } @@ -249,6 +216,9 @@ final class AudioPresenter extends OpenVKPresenter function renderNewPlaylist(): void { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(true); + $owner = $this->user->id; if ($this->requestParam("owner")) { @@ -266,29 +236,167 @@ final class AudioPresenter extends OpenVKPresenter if ($_SERVER["REQUEST_METHOD"] === "POST") { $title = $this->postParam("title"); $description = $this->postParam("description"); - $audios = !empty($this->postParam("audios")) ? explode(",", $this->postParam("audios")) : []; + $audios = !empty($this->postParam("audios")) ? array_slice(explode(",", $this->postParam("audios")), 0, 100) : []; if(empty($title) || iconv_strlen($title) < 1) - $this->flash("err", tr("error"), "ну там короч нету имени ну хз"); + $this->flashFail("err", tr("error"), tr("set_playlist_name")); $playlist = new Playlist; $playlist->setOwner($owner); $playlist->setName(substr($title, 0, 128)); $playlist->setDescription(substr($description, 0, 2048)); - $playlist->save(); - foreach ($audios as $audio) { - DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_relations` (`collection`, `media`) VALUES (?, ?)", $playlist->getId(), $audio); + if($_FILES["cover"]["error"] === UPLOAD_ERR_OK) { + if(!str_starts_with($_FILES["cover"]["type"], "image")) + $this->flashFail("err", tr("error"), tr("not_a_photo")); + + try { + $playlist->fastMakeCover($this->user->id, $_FILES["cover"]); + } catch(\ImagickException $e) { + $this->flashFail("err", tr("error"), tr("invalid_cover_photo")); + } } - DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_imports` (`entity`, `playlist`) VALUES (?, ?)", $owner, $playlist->getId()); + $playlist->save(); + foreach($audios as $audio) { + $audio = $this->audios->get((int)$audio); + + if(!$audio || $audio->isDeleted() || !$audio->canBeViewedBy($this->user->identity)) + continue; + + $playlist->add($audio); + } + + $playlist->bookmark(isset($club) ? $club : $this->user->identity); $this->redirect("/playlist" . $owner . "_" . $playlist->getId()); } else { - $this->template->audios = iterator_to_array($this->audios->getByUser($this->user->identity, 1, 10)); + if(isset($club)) { + $this->template->audios = iterator_to_array($this->audios->getByClub($club, 1, 10)); + $count = (new Audios)->getClubCollectionSize($club); + } else { + $this->template->audios = iterator_to_array($this->audios->getByUser($this->user->identity, 1, 10)); + $count = (new Audios)->getUserCollectionSize($this->user->identity); + } + + $this->template->pagesCount = ceil($count / 10); } } + function renderPlaylistAction(int $id) { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(true); + $this->assertNoCSRF(); + + if ($_SERVER["REQUEST_METHOD"] !== "POST") { + header("HTTP/1.1 405 Method Not Allowed"); + exit(","); + } + + $playlist = $this->audios->getPlaylist($id); + + if(!$playlist || $playlist->isDeleted()) + $this->flashFail("err", "error", tr("invalid_playlist"), null, true); + + switch ($this->queryParam("act")) { + case "bookmark": + if(!$playlist->isBookmarkedBy($this->user->identity)) + $playlist->bookmark($this->user->identity); + else + $this->flashFail("err", "error", tr("playlist_already_bookmarked"), null, true); + + break; + case "unbookmark": + if($playlist->isBookmarkedBy($this->user->identity)) + $playlist->unbookmark($this->user->identity); + else + $this->flashFail("err", "error", tr("playlist_not_bookmarked"), null, true); + + break; + case "delete": + if($playlist->canBeModifiedBy($this->user->identity)) { + $tmOwner = $playlist->getOwner(); + $playlist->delete(); + } else + $this->flashFail("err", "error", tr("access_denied"), null, true); + + $this->returnJson(["success" => true, "id" => $tmOwner->getRealId()]); + break; + default: + break; + } + + $this->returnJson(["success" => true]); + } + + function renderEditPlaylist(int $owner_id, int $virtual_id) + { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(); + + $playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id); + $page = (int)($this->queryParam("p") ?? 1); + if (!$playlist || $playlist->isDeleted() || !$playlist->canBeModifiedBy($this->user->identity)) + $this->notFound(); + + $this->template->playlist = $playlist; + $this->template->page = $page; + + $audios = iterator_to_array($playlist->getAudios()); + $this->template->audios = array_slice($audios, 0, 10); + $audiosIds = []; + + foreach($audios as $aud) + $audiosIds[] = $aud->getId(); + + $this->template->audiosIds = implode(",", array_unique($audiosIds)) . ","; + $this->template->ownerId = $owner_id; + $this->template->owner = $playlist->getOwner(); + $this->template->pagesCount = $pagesCount = ceil($playlist->size() / 10); + + if($_SERVER["REQUEST_METHOD"] !== "POST") + return; + + $title = $this->postParam("title"); + $description = $this->postParam("description"); + $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : []; + + if(empty($title) || iconv_strlen($title) < 1) + $this->flashFail("err", tr("error"), tr("set_playlist_name")); + + $playlist->setName(ovk_proc_strtr($title, 128)); + $playlist->setDescription(ovk_proc_strtr($description, 2048)); + $playlist->resetLength(); + + if($_FILES["new_cover"]["error"] === UPLOAD_ERR_OK) { + if(!str_starts_with($_FILES["new_cover"]["type"], "image")) + $this->flashFail("err", tr("error"), tr("not_a_photo")); + + try { + $playlist->fastMakeCover($this->user->id, $_FILES["new_cover"]); + } catch(\Throwable $e) { + $this->flashFail("err", tr("error"), tr("invalid_cover_photo")); + } + } + + $playlist->save(); + + DatabaseConnection::i()->getContext()->table("playlist_relations")->where([ + "collection" => $playlist->getId() + ])->delete(); + + foreach ($new_audios as $new_audio) { + $audio = (new Audios)->get((int)$new_audio); + + if(!$audio || $audio->isDeleted()) + continue; + + $playlist->add($audio); + } + + $this->redirect("/playlist".$playlist->getPrettyId()); + } + function renderPlaylist(int $owner_id, int $virtual_id): void { $playlist = $this->audios->getPlaylistByOwnerAndVID($owner_id, $virtual_id); @@ -299,57 +407,11 @@ final class AudioPresenter extends OpenVKPresenter $this->template->playlist = $playlist; $this->template->page = $page; $this->template->audios = iterator_to_array($playlist->fetch($page, 10)); + $this->template->ownerId = $owner_id; + $this->template->owner = $playlist->getOwner(); $this->template->isBookmarked = $playlist->isBookmarkedBy($this->user->identity); $this->template->isMy = $playlist->getOwner()->getId() === $this->user->id; - $this->template->canEdit = ($this->template->isMy || ($playlist->getOwner() instanceof Club && $playlist->getOwner()->canBeModifiedBy($this->user->identity))); - - /*if ($_SERVER["REQUEST_METHOD"] === "POST") { - if (!$this->template->canEdit) { - $this->flashFail("err", tr("error"), tr("forbidden")); - } - - $title = $this->postParam("title"); - $description = $this->postParam("description"); - $audios = !empty($this->postParam("audios")) ? explode(",", $this->postParam("audios")) : []; - - $playlist->setName(substr($title, 0, 128)); - $playlist->setDescription(substr($description, 0, 2048)); - $playlist->setEdited(time()); - - if ($_FILES["cover"]["error"] === UPLOAD_ERR_OK) { - $photo = new Photo; - $photo->setOwner($this->user->id); - $photo->setDescription("Playlist #" . $playlist->getId() . " cover image"); - $photo->setFile($_FILES["cover"]); - $photo->setCreated(time()); - $photo->save(); - - $playlist->setCover_Photo_Id($photo->getId()); - } - - $playlist->save(); - - $_ids = []; - - foreach ($playlist->getAudios() as $audio) { - $_ids[] = $audio->getId(); - } - - foreach ($playlist->getAudios() as $audio) { - if (!in_array($audio->getId(), $audios)) { - DatabaseConnection::i()->getContext()->query("DELETE FROM `playlist_relations` WHERE `collection` = ? AND `media` = ?", $playlist->getId(), $audio->getId()); - } - } - - foreach ($audios as $audio) { - if (!in_array($audio, $_ids)) { - DatabaseConnection::i()->getContext()->query("INSERT INTO `playlist_relations` (`collection`, `media`) VALUES (?, ?)", $playlist->getId(), $audio); - } - } - - $this->flash("succ", tr("changes_saved")); - $this->redirect("/playlist" . $playlist->getOwner()->getId() . "_" . $playlist->getId()); - }*/ + $this->template->canEdit = $playlist->canBeModifiedBy($this->user->identity); } function renderAction(int $audio_id): void @@ -386,6 +448,13 @@ final class AudioPresenter extends OpenVKPresenter else $this->flashFail("err", "error", tr("do_not_have_audio"), null, true); + break; + case "delete": + if($audio->canBeModifiedBy($this->user->identity)) + $audio->delete(); + else + $this->flashFail("err", "error", tr("access_denied"), null, true); + break; case "edit": $audio = $this->audios->get($audio_id); diff --git a/Web/Presenters/templates/Admin/Music.xml b/Web/Presenters/templates/Admin/Music.xml index e56da16e..cf96b1ad 100644 --- a/Web/Presenters/templates/Admin/Music.xml +++ b/Web/Presenters/templates/Admin/Music.xml @@ -129,7 +129,7 @@
{/block} diff --git a/Web/Presenters/templates/Audio/apiGetContext.xml b/Web/Presenters/templates/Audio/ApiGetContext.xml similarity index 100% rename from Web/Presenters/templates/Audio/apiGetContext.xml rename to Web/Presenters/templates/Audio/ApiGetContext.xml diff --git a/Web/Presenters/templates/Audio/EditPlaylist.xml b/Web/Presenters/templates/Audio/EditPlaylist.xml new file mode 100644 index 00000000..bd46efa7 --- /dev/null +++ b/Web/Presenters/templates/Audio/EditPlaylist.xml @@ -0,0 +1,88 @@ +{extends "../@layout.xml"} + +{block title}{_edit_playlist}{/block} + +{block header} + {$owner->getCanonicalName()} + » + {_audios} + » + {_playlist} + » + {_edit_playlist} +{/block} + +{block content} +