From fead37f0a83c7ee8138c1b9410ba4186eb86ea55 Mon Sep 17 00:00:00 2001 From: lalka2016 <99399973+lalka2016@users.noreply.github.com> Date: Sat, 5 Aug 2023 18:29:37 +0300 Subject: [PATCH] De#910fy --- ServiceAPI/Video.php | 156 +++++++ VKAPI/Handlers/Likes.php | 162 +++++-- VKAPI/Handlers/Video.php | 12 +- VKAPI/Handlers/Wall.php | 4 +- Web/Models/Entities/Comment.php | 10 +- Web/Models/Entities/Postable.php | 13 +- Web/Models/Entities/Video.php | 25 +- Web/Models/Repositories/Videos.php | 7 + Web/Presenters/VideosPresenter.php | 34 +- Web/Presenters/templates/@layout.xml | 5 + Web/Presenters/templates/User/View.xml | 4 +- Web/Presenters/templates/Videos/Edit.xml | 6 +- Web/Presenters/templates/Videos/List.xml | 6 +- Web/Presenters/templates/Videos/View.xml | 21 +- .../templates/components/attachment.xml | 7 +- Web/Presenters/templates/components/video.xml | 6 +- Web/routes.yml | 2 + Web/static/css/bsdn.css | 16 +- Web/static/css/dialog.css | 246 +++++++++++ Web/static/img/bsdn/repeat.gif | Bin 0 -> 153 bytes Web/static/img/bsdn/repeat_hover.gif | Bin 0 -> 154 bytes Web/static/img/left_arr.png | Bin 0 -> 1583 bytes Web/static/img/miniplayer_close.png | Bin 0 -> 351 bytes Web/static/img/miniplayer_open.png | Bin 0 -> 153 bytes Web/static/img/right_arr.png | Bin 0 -> 1577 bytes Web/static/js/al_wall.js | 402 +++++++++++++++++- Web/static/js/messagebox.js | 7 +- Web/static/js/player.js | 28 +- locales/en.strings | 44 ++ locales/ru.strings | 44 ++ 30 files changed, 1168 insertions(+), 99 deletions(-) create mode 100644 ServiceAPI/Video.php create mode 100644 Web/static/img/bsdn/repeat.gif create mode 100644 Web/static/img/bsdn/repeat_hover.gif create mode 100644 Web/static/img/left_arr.png create mode 100644 Web/static/img/miniplayer_close.png create mode 100644 Web/static/img/miniplayer_open.png create mode 100644 Web/static/img/right_arr.png diff --git a/ServiceAPI/Video.php b/ServiceAPI/Video.php new file mode 100644 index 00000000..680b4ced --- /dev/null +++ b/ServiceAPI/Video.php @@ -0,0 +1,156 @@ +user = $user; + $this->videos = new Videos; + $this->comments = new Comments; + $this->groups = new Clubs; + } + + function getVideo(int $id, callable $resolve, callable $reject) + { + $video = $this->videos->get($id); + + if(!$video || $video->isDeleted()) { + $reject(2, "Video does not exists"); + } + + if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) { + $reject(4, "Access to video denied"); + } + + if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) { + $reject(8, "Access to video denied: this user chose to hide his videos"); + } + + $prevVideo = NULL; + $nextVideo = NULL; + $lastVideo = $this->videos->getLastVideo($video->getOwner()); + + if($video->getVirtualId() - 1 != 0) { + for($i = $video->getVirtualId(); $i != 0; $i--) { + $maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i); + + if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) { + if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) { + continue; + } + + $prevVideo = $maybeVideo; + break; + } + } + } + + if(is_null($lastVideo) || $lastVideo->getId() == $video->getId()) { + $nextVideo = NULL; + } else { + for($i = $video->getVirtualId(); $i <= $lastVideo->getVirtualId(); $i++) { + $maybeVideo = (new Videos)->getByOwnerAndVID($video->getOwner()->getId(), $i); + + if(!is_null($maybeVideo) && !$maybeVideo->isDeleted() && $maybeVideo->getId() != $video->getId()) { + if(method_exists($maybeVideo, "canBeViewedBy") && !$maybeVideo->canBeViewedBy($this->user)) { + continue; + } + + $nextVideo = $maybeVideo; + break; + } + } + } + + $res = [ + "id" => $video->getId(), + "title" => $video->getName(), + "owner" => $video->getOwner()->getId(), + "commentsCount" => $video->getCommentsCount(), + "description" => $video->getDescription(), + "type" => $video->getType(), + "name" => $video->getOwner()->getCanonicalName(), + "pretty_id" => $video->getPrettyId(), + "virtual_id" => $video->getVirtualId(), + "published" => (string)$video->getPublicationTime(), + "likes" => $video->getLikesCount(), + "has_like" => $video->hasLikeFrom($this->user), + "author" => $video->getOwner()->getCanonicalName(), + "canBeEdited" => $video->getOwner()->getId() == $this->user->getId(), + "isProcessing" => $video->getType() == 0 && $video->getURL() == "/assets/packages/static/openvk/video/rendering.mp4", + "prevVideo" => !is_null($prevVideo) ? $prevVideo->getId() : null, + "nextVideo" => !is_null($nextVideo) ? $nextVideo->getId() : null, + ]; + + if($video->getType() == 1) { + $res["embed"] = $video->getVideoDriver()->getEmbed(); + } else { + $res["url"] = $video->getURL(); + } + + $resolve($res); + } + + function shareVideo(int $owner, int $vid, int $type, string $message, int $club, bool $signed, bool $asGroup, callable $resolve, callable $reject) + { + $video = $this->videos->getByOwnerAndVID($owner, $vid); + + if(!$video || $video->isDeleted()) { + $reject(16, "Video does not exists"); + } + + if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user)) { + $reject(32, "Access to video denied"); + } + + if(!$video->getOwner()->getPrivacyPermission('videos.read', $this->user)) { + $reject(8, "Access to video denied: this user chose to hide his videos"); + } + + $flags = 0; + + $nPost = new Post; + $nPost->setOwner($this->user->getId()); + + if($type == 0) { + $nPost->setWall($this->user->getId()); + } else { + $club = $this->groups->get($club); + + if(!$club || $club->isDeleted() || !$club->canBeModifiedBy($this->user)) { + $reject(64, "Can't do repost to this club"); + } + + if($asGroup) + $flags |= 0b10000000; + + if($signed) + $flags |= 0b01000000; + + $nPost->setWall($club->getId() * -1); + } + + $nPost->setContent($message); + $nPost->setFlags($flags); + $nPost->save(); + + $nPost->attach($video); + + $res = [ + "id" => $nPost->getId(), + "pretty_id" => $nPost->getPrettyId(), + ]; + + $resolve($res); + } +} \ No newline at end of file diff --git a/VKAPI/Handlers/Likes.php b/VKAPI/Handlers/Likes.php index 9501b433..38210ee7 100644 --- a/VKAPI/Handlers/Likes.php +++ b/VKAPI/Handlers/Likes.php @@ -2,70 +2,154 @@ namespace openvk\VKAPI\Handlers; use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Posts as PostsRepo; +use openvk\Web\Models\Repositories\Comments as CommentsRepo; +use openvk\Web\Models\Repositories\Videos as VideosRepo; +use openvk\Web\Models\Repositories\Photos as PhotosRepo; +use openvk\Web\Models\Repositories\Notes as NotesRepo; final class Likes extends VKAPIRequestHandler { - function add(string $type, int $owner_id, int $item_id): object - { - $this->requireUser(); + function add(string $type, int $owner_id, int $item_id): object + { + $this->requireUser(); $this->willExecuteWriteAction(); + $postable = NULL; switch($type) { case "post": $post = (new PostsRepo)->getPostById($owner_id, $item_id); - if(is_null($post)) - $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); - - $post->setLike(true, $this->getUser()); - - return (object) [ - "likes" => $post->getLikesCount() - ]; + $postable = $post; + break; + case "comment": + $comment = (new CommentsRepo)->get($item_id); + $postable = $comment; + break; + case "video": + $video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $video; + break; + case "photo": + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $photo; + break; + case "note": + $note = (new NotesRepo)->getNoteById($owner_id, $item_id); + $postable = $note; + break; default: $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); } - } - function delete(string $type, int $owner_id, int $item_id): object - { - $this->requireUser(); + if(is_null($postable) || $postable->isDeleted()) + $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); + + if(!$postable->canBeViewedBy($this->getUser() ?? NULL)) { + $this->fail(2, "Access to postable denied"); + } + + $postable->setLike(true, $this->getUser()); + + return (object) [ + "likes" => $postable->getLikesCount() + ]; + } + + function delete(string $type, int $owner_id, int $item_id): object + { + $this->requireUser(); $this->willExecuteWriteAction(); + $postable = NULL; switch($type) { case "post": $post = (new PostsRepo)->getPostById($owner_id, $item_id); - if (is_null($post)) - $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); - - $post->setLike(false, $this->getUser()); - return (object) [ - "likes" => $post->getLikesCount() - ]; + $postable = $post; + break; + case "comment": + $comment = (new CommentsRepo)->get($item_id); + $postable = $comment; + break; + case "video": + $video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $video; + break; + case "photo": + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $photo; + break; + case "note": + $note = (new NotesRepo)->getNoteById($owner_id, $item_id); + $postable = $note; + break; default: $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); } - } - + + if(is_null($postable) || $postable->isDeleted()) + $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); + + if(!$postable->canBeViewedBy($this->getUser() ?? NULL)) { + $this->fail(2, "Access to postable denied"); + } + + if(!is_null($postable)) { + $postable->setLike(false, $this->getUser()); + + return (object) [ + "likes" => $postable->getLikesCount() + ]; + } + } + function isLiked(int $user_id, string $type, int $owner_id, int $item_id): object - { - $this->requireUser(); + { + $this->requireUser(); + $user = (new UsersRepo)->get($user_id); + + if(is_null($user) || $user->isDeleted()) + $this->fail(100, "One of the parameters specified was missing or invalid: user not found"); + + if(!$user->canBeViewedBy($this->getUser())) { + $this->fail(1984, "Access denied: you can't see this user"); + } + + $postable = NULL; switch($type) { case "post": - $user = (new UsersRepo)->get($user_id); - if (is_null($user)) - $this->fail(100, "One of the parameters specified was missing or invalid: user not found"); - $post = (new PostsRepo)->getPostById($owner_id, $item_id); - if (is_null($post)) - $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); - - return (object) [ - "liked" => (int) $post->hasLikeFrom($user), - "copied" => 0 # TODO: handle this - ]; + $postable = $post; + break; + case "comment": + $comment = (new CommentsRepo)->get($item_id); + $postable = $comment; + break; + case "video": + $video = (new VideosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $video; + break; + case "photo": + $photo = (new PhotosRepo)->getByOwnerAndVID($owner_id, $item_id); + $postable = $photo; + break; + case "note": + $note = (new NotesRepo)->getNoteById($owner_id, $item_id); + $postable = $note; + break; default: $this->fail(100, "One of the parameters specified was missing or invalid: incorrect type"); } - } -} + + if(is_null($postable) || $postable->isDeleted()) + $this->fail(100, "One of the parameters specified was missing or invalid: object not found"); + + if(!$postable->canBeViewedBy($this->getUser())) { + $this->fail(665, "Access to postable denied"); + } + + return (object) [ + "liked" => (int) $postable->hasLikeFrom($user), + "copied" => 0 + ]; + } +} \ No newline at end of file diff --git a/VKAPI/Handlers/Video.php b/VKAPI/Handlers/Video.php index 740ccd54..8f720256 100755 --- a/VKAPI/Handlers/Video.php +++ b/VKAPI/Handlers/Video.php @@ -26,7 +26,7 @@ final class Video extends VKAPIRequestHandler $video = (new VideosRepo)->getByOwnerAndVID(intval($id[0]), intval($id[1])); if($video) { - $items[] = $video->getApiStructure(); + $items[] = $video->getApiStructure($this->getUser()); } } @@ -38,14 +38,18 @@ final class Video extends VKAPIRequestHandler if ($owner_id > 0) $user = (new UsersRepo)->get($owner_id); else - $this->fail(1, "Not implemented"); + $this->fail(1, "Not implemented"); + + if(!$user->getPrivacyPermission('videos.read', $this->getUser())) { + $this->fail(20, "Access denied: this user chose to hide his videos"); + } $videos = (new VideosRepo)->getByUser($user, $offset + 1, $count); $videosCount = (new VideosRepo)->getUserVideosCount($user); $items = []; foreach ($videos as $video) { - $items[] = $video->getApiStructure(); + $items[] = $video->getApiStructure($this->getUser()); } return (object) [ @@ -54,4 +58,4 @@ final class Video extends VKAPIRequestHandler ]; } } -} +} \ No newline at end of file diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index d52dfce1..a5052197 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -55,7 +55,7 @@ final class Wall extends VKAPIRequestHandler } else if($attachment instanceof \openvk\Web\Models\Entities\Poll) { $attachments[] = $this->getApiPoll($attachment, $this->getUser()); } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { - $attachments[] = $attachment->getApiStructure(); + $attachments[] = $attachment->getApiStructure($this->getUser()); } else if ($attachment instanceof \openvk\Web\Models\Entities\Note) { $attachments[] = $attachment->toVkApiStruct(); } else if ($attachment instanceof \openvk\Web\Models\Entities\Post) { @@ -230,7 +230,7 @@ final class Wall extends VKAPIRequestHandler } else if($attachment instanceof \openvk\Web\Models\Entities\Poll) { $attachments[] = $this->getApiPoll($attachment, $user); } else if ($attachment instanceof \openvk\Web\Models\Entities\Video) { - $attachments[] = $attachment->getApiStructure(); + $attachments[] = $attachment->getApiStructure($this->getUser()); } else if ($attachment instanceof \openvk\Web\Models\Entities\Note) { $attachments[] = $attachment->toVkApiStruct(); } else if ($attachment instanceof \openvk\Web\Models\Entities\Post) { diff --git a/Web/Models/Entities/Comment.php b/Web/Models/Entities/Comment.php index 7c6222e4..420a06de 100644 --- a/Web/Models/Entities/Comment.php +++ b/Web/Models/Entities/Comment.php @@ -74,8 +74,12 @@ class Comment extends Post foreach($this->getChildren() as $attachment) { if($attachment->isDeleted()) continue; - - $res->attachments[] = $attachment->toVkApiStruct(); + + if($attachment instanceof \openvk\Web\Models\Entities\Photo) { + $res->attachments[] = $attachment->toVkApiStruct(); + } else if($attachment instanceof \openvk\Web\Models\Entities\Video) { + $res->attachments[] = $attachment->toVkApiStruct($this->getUser()); + } } if($need_likes) { @@ -85,4 +89,4 @@ class Comment extends Post } return $res; } -} +} \ No newline at end of file diff --git a/Web/Models/Entities/Postable.php b/Web/Models/Entities/Postable.php index b743793f..b3a6f200 100644 --- a/Web/Models/Entities/Postable.php +++ b/Web/Models/Entities/Postable.php @@ -129,10 +129,15 @@ abstract class Postable extends Attachable "target" => $this->getRecord()->id, ]; - if($liked) - DB::i()->getContext()->table("likes")->insert($searchData); - else - DB::i()->getContext()->table("likes")->where($searchData)->delete(); + if($liked) { + if(!$this->hasLikeFrom($user)) { + DB::i()->getContext()->table("likes")->insert($searchData); + } + } else { + if($this->hasLikeFrom($user)) { + DB::i()->getContext()->table("likes")->where($searchData)->delete(); + } + } } function hasLikeFrom(User $user): bool diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index cef48e27..fed12c88 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -115,15 +115,15 @@ class Video extends Media return $this->getRecord()->owner; } - function getApiStructure(): object + function getApiStructure(?User $user = NULL): object { $fromYoutube = $this->getType() == Video::TYPE_EMBED; - return (object)[ + $res = (object)[ "type" => "video", "video" => [ "can_comment" => 1, - "can_like" => 0, // we don't h-have wikes in videos - "can_repost" => 0, + "can_like" => 1, // we don't h-have wikes in videos + "can_repost" => 1, "can_subscribe" => 1, "can_add_to_faves" => 0, "can_add" => 0, @@ -155,21 +155,26 @@ class Video extends Media "repeat" => 0, "type" => "video", "views" => 0, - "likes" => [ - "count" => 0, - "user_likes" => 0 - ], "reposts" => [ "count" => 0, "user_reposted" => 0 ] ] ]; + + if(!is_null($user)) { + $res->likes = [ + "count" => $this->getLikesCount(), + "user_likes" => $this->hasLikeFrom($user) + ]; + } + + return $res; } - function toVkApiStruct(): object + function toVkApiStruct(?User $user): object { - return $this->getApiStructure(); + return $this->getApiStructure($user); } function setLink(string $link): string diff --git a/Web/Models/Repositories/Videos.php b/Web/Models/Repositories/Videos.php index 63273349..2d41c3f9 100644 --- a/Web/Models/Repositories/Videos.php +++ b/Web/Models/Repositories/Videos.php @@ -77,4 +77,11 @@ class Videos return new Util\EntityStream("Video", $result->order("$sort")); } + + function getLastVideo(User $user) + { + $video = $this->videos->where("owner", $user->getId())->where(["deleted" => 0, "unlisted" => 0])->order("id DESC")->fetch(); + + return new Video($video); + } } diff --git a/Web/Presenters/VideosPresenter.php b/Web/Presenters/VideosPresenter.php index 4e4d484a..c491a2b5 100644 --- a/Web/Presenters/VideosPresenter.php +++ b/Web/Presenters/VideosPresenter.php @@ -74,18 +74,18 @@ final class VideosPresenter extends OpenVKPresenter else if(!empty($this->postParam("link"))) $video->setLink($this->postParam("link")); else - $this->flashFail("err", "Нету видеозаписи", "Выберите файл или укажите ссылку."); + $this->flashFail("err", tr("no_video_error"), tr("no_video_description")); } catch(\DomainException $ex) { - $this->flashFail("err", "Произошла ошибка", "Файл повреждён или не содержит видео." ); + $this->flashFail("err", tr("error_video"), tr("file_corrupted")); } catch(ISE $ex) { - $this->flashFail("err", "Произошла ошибка", "Возможно, ссылка некорректна."); + $this->flashFail("err", tr("error_video"), tr("link_incorrect")); } $video->save(); $this->redirect("/video" . $video->getPrettyId()); } else { - $this->flashFail("err", "Произошла ошибка", "Видео не может быть опубликовано без названия."); + $this->flashFail("err", tr("error_video"), tr("no_name_error")); } } } @@ -99,14 +99,14 @@ final class VideosPresenter extends OpenVKPresenter if(!$video) $this->notFound(); if(is_null($this->user) || $this->user->id !== $owner) - $this->flashFail("err", "Ошибка доступа", "Вы не имеете права редактировать этот ресурс."); + $this->flashFail("err", tr("access_denied_error"), tr("access_denied_error_description")); if($_SERVER["REQUEST_METHOD"] === "POST") { $video->setName(empty($this->postParam("name")) ? NULL : $this->postParam("name")); $video->setDescription(empty($this->postParam("desc")) ? NULL : $this->postParam("desc")); $video->save(); - $this->flash("succ", "Изменения сохранены", "Обновлённое описание появится на странице с видосиком."); + $this->flash("succ", tr("changes_saved"), tr("changes_saved_video_comment")); $this->redirect("/video" . $video->getPrettyId()); } @@ -128,9 +128,29 @@ final class VideosPresenter extends OpenVKPresenter $video->deleteVideo($owner, $vid); } } else { - $this->flashFail("err", "Не удалось удалить пост", "Вы не вошли в аккаунт."); + $this->flashFail("err", tr("cant_delete_video"), tr("cant_delete_video_comment")); } $this->redirect("/videos" . $owner); } + + function renderLike(int $owner, int $video_id): void + { + $this->assertUserLoggedIn(); + $this->willExecuteWriteAction(); + $this->assertNoCSRF(); + + $video = $this->videos->getByOwnerAndVID($owner, $video_id); + if(!$video || $video->isDeleted() || $video->getOwner()->isDeleted()) $this->notFound(); + + if(method_exists($video, "canBeViewedBy") && !$video->canBeViewedBy($this->user->identity)) { + $this->flashFail("err", tr("error"), tr("forbidden")); + } + + if(!is_null($this->user)) { + $video->toggleLike($this->user->identity); + } + + $this->redirect("$_SERVER[HTTP_REFERER]"); + } } diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 1718c499..dceedd5e 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -13,6 +13,7 @@ {script "js/node_modules/jquery/dist/jquery.min.js"} + {script "js/node_modules/jquery-ui/dist/jquery-ui.min.js"} {script "js/node_modules/umbrellajs/umbrella.min.js"} {script "js/l10n.js"} {script "js/openvk.cls.js"} @@ -347,6 +348,10 @@
+