diff --git a/.github/workflows/analyse.yaml b/.github/workflows/analyse.yaml new file mode 100644 index 00000000..9e6d828f --- /dev/null +++ b/.github/workflows/analyse.yaml @@ -0,0 +1,36 @@ +name: Static analysis + +on: + push: + pull_request: + +jobs: + phpstan: + name: PHPStan + runs-on: ubuntu-latest + + # 'push' runs on inner branches, 'pull_request' will run only on outer PRs + if: > + github.event_name == 'push' + || (github.event_name == 'pull_request' + && github.event.pull_request.head.repo.full_name != github.repository) + + steps: + - name: Code Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build and start Docker container + working-directory: install/automated/docker + run: | + docker build -t openvk ../../.. -f openvk.Dockerfile + + - name: Run Docker container with PHPStan + working-directory: install/automated/docker + run: | + docker container run --rm \ + -v ./chandler.example.yml:/opt/chandler/chandler.yml \ + -v ./openvk.example.yml:/opt/chandler/extensions/available/openvk/openvk.yml \ + openvk vendor/bin/phpstan analyse --memory-limit 1G diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 7ab06d7f..f71bc26c 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -6,7 +6,7 @@ on: jobs: lint: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest # 'push' runs on inner branches, 'pull_request' will run only on outer PRs if: > diff --git a/CLI/FetchToncoinTransactions.php b/CLI/FetchToncoinTransactions.php index 9de2c998..8342197f 100755 --- a/CLI/FetchToncoinTransactions.php +++ b/CLI/FetchToncoinTransactions.php @@ -18,6 +18,7 @@ define("NANOTON", 1000000000); class FetchToncoinTransactions extends Command { private $images; + private $transactions; protected static $defaultName = "fetch-ton"; diff --git a/ServiceAPI/Wall.php b/ServiceAPI/Wall.php index 4066d4f3..db6c32b6 100644 --- a/ServiceAPI/Wall.php +++ b/ServiceAPI/Wall.php @@ -13,6 +13,7 @@ class Wall implements Handler protected $user; protected $posts; protected $notes; + protected $videos; public function __construct(?User $user) { diff --git a/VKAPI/Handlers/Audio.php b/VKAPI/Handlers/Audio.php index b84475a4..df9cf935 100644 --- a/VKAPI/Handlers/Audio.php +++ b/VKAPI/Handlers/Audio.php @@ -18,7 +18,7 @@ final class Audio extends VKAPIRequestHandler if (!$audio) { $this->fail(0o404, "Audio not found"); } elseif (!$audio->canBeViewedBy($this->getUser())) { - $this->fail(201, "Access denied to audio(" . $audio->getPrettyId() . ")"); + $this->fail(201, "Access denied to audio(" . $audio->getId() . ")"); } # рофлан ебало @@ -201,7 +201,7 @@ final class Audio extends VKAPIRequestHandler $this->fail(15, "Access denied"); } - if ($uploaded_only) { + if ($uploaded_only && $owner_id == $this->getUser()->getRealId()) { return DatabaseConnection::i()->getContext()->table("audios") ->where([ "deleted" => false, @@ -283,7 +283,7 @@ final class Audio extends VKAPIRequestHandler } $dbCtx = DatabaseConnection::i()->getContext(); - if ($uploaded_only == 1) { + if ($uploaded_only == 1 && $owner_id == $this->getUser()->getRealId()) { if ($owner_id <= 0) { $this->fail(8, "uploaded_only can only be used with owner_id > 0"); } diff --git a/VKAPI/Handlers/Board.php b/VKAPI/Handlers/Board.php index f2f3ec76..c5b55fa8 100644 --- a/VKAPI/Handlers/Board.php +++ b/VKAPI/Handlers/Board.php @@ -14,8 +14,7 @@ use openvk\Web\Models\Entities\{Topic, Comment, User, Photo, Video}; final class Board extends VKAPIRequestHandler { - # 13/13 - public function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true, string $attachments = "") + public function addTopic(int $group_id, string $title, string $text = "", bool $from_group = true) { $this->requireUser(); $this->willExecuteWriteAction(); @@ -23,15 +22,14 @@ final class Board extends VKAPIRequestHandler $club = (new ClubsRepo())->get($group_id); if (!$club) { - $this->fail(403, "Invalid club"); + $this->fail(15, "Access denied"); } if (!$club->canBeModifiedBy($this->getUser()) && !$club->isEveryoneCanCreateTopics()) { - $this->fail(403, "Access to club denied"); + $this->fail(15, "Access denied"); } $flags = 0; - if ($from_group == true && $club->canBeModifiedBy($this->getUser())) { $flags |= 0b10000000; } @@ -53,59 +51,6 @@ final class Board extends VKAPIRequestHandler $comment->setCreated(time()); $comment->setFlags($flags); $comment->save(); - - if (!empty($attachments)) { - $attachmentsArr = explode(",", $attachments); - # блин а мне это везде копировать типа - - if (sizeof($attachmentsArr) > 10) { - $this->fail(50, "Error: too many attachments"); - } - - foreach ($attachmentsArr as $attac) { - $attachmentType = null; - - if (str_contains($attac, "photo")) { - $attachmentType = "photo"; - } elseif (str_contains($attac, "video")) { - $attachmentType = "video"; - } else { - $this->fail(205, "Unknown attachment type"); - } - - $attachment = str_replace($attachmentType, "", $attac); - - $attachmentOwner = (int) explode("_", $attachment)[0]; - $attachmentId = (int) end(explode("_", $attachment)); - - $attacc = null; - - if ($attachmentType == "photo") { - $attacc = (new PhotosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Photo does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this photo"); - } - - $comment->attach($attacc); - } elseif ($attachmentType == "video") { - $attacc = (new VideosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Video does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this video"); - } - - $comment->attach($attacc); - } - - } - - } - } return $topic->getId(); @@ -118,7 +63,7 @@ final class Board extends VKAPIRequestHandler $topic = (new TopicsRepo())->getTopicById($group_id, $topic_id); - if (!$topic || !$topic->getClub() || !$topic->getClub()->canBeModifiedBy($this->getUser())) { + if (!$topic || !$topic->getClub()->canBeModifiedBy($this->getUser())) { return 0; } @@ -140,21 +85,15 @@ final class Board extends VKAPIRequestHandler } $topic = (new TopicsRepo())->getTopicById($group_id, $topic_id); - if (!$topic || $topic->isDeleted() || $topic->isClosed()) { - $this->fail(100, "Topic is deleted, closed or invalid."); + $this->fail(15, "Access denied"); } $flags = 0; - if ($from_group != 0 && !is_null($topic->getClub()) && $topic->getClub()->canBeModifiedBy($this->user)) { $flags |= 0b10000000; } - if (strlen($message) > 300) { - $this->fail(20, "Comment is too long."); - } - $comment = new Comment(); $comment->setOwner($this->getUser()->getId()); $comment->setModel(get_class($topic)); @@ -164,74 +103,9 @@ final class Board extends VKAPIRequestHandler $comment->setFlags($flags); $comment->save(); - if (!empty($attachments)) { - $attachmentsArr = explode(",", $attachments); - - if (sizeof($attachmentsArr) > 10) { - $this->fail(50, "Error: too many attachments"); - } - - foreach ($attachmentsArr as $attac) { - $attachmentType = null; - - if (str_contains($attac, "photo")) { - $attachmentType = "photo"; - } elseif (str_contains($attac, "video")) { - $attachmentType = "video"; - } else { - $this->fail(205, "Unknown attachment type"); - } - - $attachment = str_replace($attachmentType, "", $attac); - - $attachmentOwner = (int) explode("_", $attachment)[0]; - $attachmentId = (int) end(explode("_", $attachment)); - - $attacc = null; - - if ($attachmentType == "photo") { - $attacc = (new PhotosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Photo does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this photo"); - } - - $comment->attach($attacc); - } elseif ($attachmentType == "video") { - $attacc = (new VideosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Video does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this video"); - } - - $comment->attach($attacc); - } - } - } - return $comment->getId(); } - public function deleteComment(int $comment_id, int $group_id = 0, int $topic_id = 0) - { - $this->requireUser(); - $this->willExecuteWriteAction(); - - $comment = (new CommentsRepo())->get($comment_id); - - if ($comment->isDeleted() || !$comment || !$comment->canBeDeletedBy($this->getUser())) { - $this->fail(403, "Access to comment denied"); - } - - $comment->delete(); - - return 1; - } - public function deleteTopic(int $group_id, int $topic_id) { $this->requireUser(); @@ -248,24 +122,6 @@ final class Board extends VKAPIRequestHandler return 1; } - public function editComment(int $comment_id, int $group_id = 0, int $topic_id = 0, string $message, string $attachments) - { - /* - $this->requireUser(); - $this->willExecuteWriteAction(); - - $comment = (new CommentsRepo)->get($comment_id); - - if($comment->getOwner() != $this->getUser()->getId()) - $this->fail(15, "Access to comment denied"); - - $comment->setContent($message); - $comment->setEdited(time()); - $comment->save(); - */ - return 1; - } - public function editTopic(int $group_id, int $topic_id, string $title) { $this->requireUser(); diff --git a/VKAPI/Handlers/Groups.php b/VKAPI/Handlers/Groups.php index baf78210..7f80d108 100644 --- a/VKAPI/Handlers/Groups.php +++ b/VKAPI/Handlers/Groups.php @@ -45,7 +45,7 @@ final class Groups extends VKAPIRequestHandler $clbsCount = $user->getClubCount(); } - $rClubs; + $rClubs = []; $ic = sizeof($clbs); if (sizeof($clbs) > $count) { diff --git a/VKAPI/Handlers/Newsfeed.php b/VKAPI/Handlers/Newsfeed.php index 346d5312..5700ada7 100644 --- a/VKAPI/Handlers/Newsfeed.php +++ b/VKAPI/Handlers/Newsfeed.php @@ -52,7 +52,7 @@ final class Newsfeed extends VKAPIRequestHandler return $response; } - public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0) + public function getGlobal(string $fields = "", int $start_from = 0, int $start_time = 0, int $end_time = 0, int $offset = 0, int $count = 30, int $extended = 0, int $rss = 0, int $return_banned = 0) { $this->requireUser(); diff --git a/VKAPI/Handlers/Notes.php b/VKAPI/Handlers/Notes.php index cdf5aa45..bde9df75 100644 --- a/VKAPI/Handlers/Notes.php +++ b/VKAPI/Handlers/Notes.php @@ -185,12 +185,14 @@ final class Notes extends VKAPIRequestHandler $this->fail(15, "Access denied"); } + $nodez = (object) [ + "count" => 0, + "notes" => [], + ]; if (empty($note_ids)) { + $nodez->count = (new NotesRepo())->getUserNotesCount($user); + $notes = array_slice(iterator_to_array((new NotesRepo())->getUserNotes($user, 1, $count + $offset, $sort == 0 ? "ASC" : "DESC")), $offset); - $nodez = (object) [ - "count" => (new NotesRepo())->getUserNotesCount((new UsersRepo())->get($user_id)), - "notes" => [], - ]; foreach ($notes as $note) { if ($note->isDeleted()) { @@ -210,6 +212,7 @@ final class Notes extends VKAPIRequestHandler $note = (new NotesRepo())->getNoteById((int) $id[0], (int) $id[1]); if ($note && !$note->isDeleted()) { $nodez->notes[] = $note->toVkApiStruct(); + $nodez->count++; } } } diff --git a/VKAPI/Handlers/Photos.php b/VKAPI/Handlers/Photos.php index 1863daaf..4a483d0c 100644 --- a/VKAPI/Handlers/Photos.php +++ b/VKAPI/Handlers/Photos.php @@ -9,6 +9,7 @@ use Nette\Utils\ImageException; use openvk\Web\Models\Entities\{Photo, Album, Comment}; use openvk\Web\Models\Repositories\Albums; use openvk\Web\Models\Repositories\Photos as PhotosRepo; +use openvk\Web\Models\Repositories\Videos as VideosRepo; use openvk\Web\Models\Repositories\Clubs; use openvk\Web\Models\Repositories\Users as UsersRepo; use openvk\Web\Models\Repositories\Comments as CommentsRepo; @@ -247,7 +248,7 @@ final class Photos extends VKAPIRequestHandler ]; } - public function createAlbum(string $title, int $group_id = 0, string $description = "", int $privacy = 0) + public function createAlbum(string $title, int $group_id = 0, string $description = "") { $this->requireUser(); $this->willExecuteWriteAction(); @@ -256,7 +257,7 @@ final class Photos extends VKAPIRequestHandler $club = (new Clubs())->get((int) $group_id); if (!$club || !$club->canBeModifiedBy($this->getUser())) { - $this->fail(20, "Invalid club"); + $this->fail(15, "Access denied"); } } @@ -270,162 +271,133 @@ final class Photos extends VKAPIRequestHandler return $album->toVkApiStruct($this->getUser()); } - public function editAlbum(int $album_id, int $owner_id, string $title, string $description = "", int $privacy = 0) + public function editAlbum(int $album_id, int $owner_id, string $title = null, string $description = null, int $privacy = 0) { $this->requireUser(); $this->willExecuteWriteAction(); $album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id); - if (!$album || $album->isDeleted()) { - $this->fail(2, "Invalid album"); + if (!$album || $album->isDeleted() || $album->isCreatedBySystem()) { + $this->fail(114, "Invalid album id"); } - - if (empty($title)) { - $this->fail(25, "Title is empty"); - } - - if ($album->isCreatedBySystem()) { - $this->fail(40, "You can't change system album"); - } - if (!$album->canBeModifiedBy($this->getUser())) { - $this->fail(2, "Access to album denied"); + $this->fail(15, "Access denied"); } - $album->setName($title); - $album->setDescription($description); + if (!is_null($title) && !empty($title) && !ctype_space($title)) { + $album->setName($title); + } + if (!is_null($description)) { + $album->setDescription($description); + } - $album->save(); + try { + $album->save(); + } catch (\Throwable $e) { + return 1; + } - return $album->toVkApiStruct($this->getUser()); + return 1; } - public function getAlbums(int $owner_id, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false) + public function getAlbums(int $owner_id = null, string $album_ids = "", int $offset = 0, int $count = 100, bool $need_system = true, bool $need_covers = true, bool $photo_sizes = false) { $this->requireUser(); - $res = []; + $res = [ + "count" => 0, + "items" => [], + ]; + $albums_list = []; + if ($owner_id == null && empty($album_ids)) { + $owner_id = $this->getUser()->getId(); + } if (empty($album_ids)) { + $owner = get_entity_by_id($owner_id); + if (!$owner || !$owner->canBeViewedBy($this->getUser())) { + $this->fail(15, "Access denied"); + } + if ($owner_id > 0 && !$owner->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(15, "Access denied"); + } + + $albums_list = null; if ($owner_id > 0) { - $user = (new UsersRepo())->get($owner_id); - - $res = [ - "count" => (new Albums())->getUserAlbumsCount($user), - "items" => [], - ]; - - if (!$user || $user->isDeleted()) { - $this->fail(2, "Invalid user"); - } - - if (!$user->getPrivacyPermission('photos.read', $this->getUser())) { - $this->fail(21, "This user chose to hide his albums."); - } - - $albums = array_slice(iterator_to_array((new Albums())->getUserAlbums($user, 1, $count + $offset)), $offset); - - foreach ($albums as $album) { - if (!$need_system && $album->isCreatedBySystem()) { - continue; - } - $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); - } + # TODO rewrite to offset + $albums_list = array_slice(iterator_to_array((new Albums())->getUserAlbums($owner, 1, $count + $offset)), $offset); + $res["count"] = (new Albums())->getUserAlbumsCount($owner); } else { - $club = (new Clubs())->get($owner_id * -1); - - $res = [ - "count" => (new Albums())->getClubAlbumsCount($club), - "items" => [], - ]; - - if (!$club) { - $this->fail(2, "Invalid club"); - } - - $albums = array_slice(iterator_to_array((new Albums())->getClubAlbums($club, 1, $count + $offset)), $offset); - - foreach ($albums as $album) { - if (!$need_system && $album->isCreatedBySystem()) { - continue; - } - $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); - } + $albums_list = array_slice(iterator_to_array((new Albums())->getClubAlbums($owner, 1, $count + $offset)), $offset); + $res["count"] = (new Albums())->getClubAlbumsCount($owner); } - } else { - $albums = explode(',', $album_ids); - - $res = [ - "count" => sizeof($albums), - "items" => [], - ]; - - foreach ($albums as $album) { - $id = explode("_", $album); - - $album = (new Albums())->getAlbumByOwnerAndId((int) $id[0], (int) $id[1]); - if ($album && !$album->isDeleted()) { - if (!$need_system && $album->isCreatedBySystem()) { - continue; - } - $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); + $album_ids = explode(',', $album_ids); + foreach ($album_ids as $album_id) { + $album = (new Albums())->getAlbumByOwnerAndId((int) $owner_id, (int) $album_id); + if (!$album || $album->isDeleted() || !$album->canBeViewedBy($this->getUser())) { + continue; } + + $albums_list[] = $album; } } + foreach ($albums_list as $album) { + if (!$need_system && $album->isCreatedBySystem()) { # TODO use queries + continue; + } + + $res["items"][] = $album->toVkApiStruct($this->getUser(), $need_covers, $photo_sizes); + } + return $res; } - public function getAlbumsCount(int $user_id = 0, int $group_id = 0) + public function getAlbumsCount(int $user_id = null, int $group_id = null) { $this->requireUser(); - if ($user_id == 0 && $group_id == 0 || $user_id > 0 && $group_id > 0) { - $this->fail(21, "Select user_id or group_id"); + if (is_null($user_id) && is_null($group_id)) { + $user_id = $this->getUser()->getId(); } - if ($user_id > 0) { - $us = (new UsersRepo())->get($user_id); - if (!$us || $us->isDeleted()) { - $this->fail(21, "Invalid user"); + if (!is_null($user_id)) { + $__user = (new UsersRepo())->get($user_id); + if (!$__user || $__user->isDeleted() || !$__user->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(15, "Access denied"); } - if (!$us->getPrivacyPermission('photos.read', $this->getUser())) { - $this->fail(21, "This user chose to hide his albums."); + return (new Albums())->getUserAlbumsCount($__user); + } + if (!is_null($group_id)) { + $__club = (new Clubs())->get($group_id); + if (!$__club || !$__club->canBeViewedBy($this->getUser())) { + $this->fail(15, "Access denied"); } - return (new Albums())->getUserAlbumsCount($us); + return (new Albums())->getClubAlbumsCount($__club); } - if ($group_id > 0) { - $cl = (new Clubs())->get($group_id); - if (!$cl) { - $this->fail(21, "Invalid club"); - } - - return (new Albums())->getClubAlbumsCount($cl); - } + return 0; } public function getById(string $photos, bool $extended = false, bool $photo_sizes = false) { $this->requireUser(); - $phts = explode(",", $photos); + $photos_splitted_list = explode(",", $photos); $res = []; + if (sizeof($photos_splitted_list) > 78) { + $this->fail(-78, "Photos count must not exceed limit"); + } - foreach ($phts as $phota) { - $ph = explode("_", $phota); - $photo = (new PhotosRepo())->getByOwnerAndVID((int) $ph[0], (int) $ph[1]); - - if (!$photo || $photo->isDeleted()) { - $this->fail(21, "Invalid photo"); - } - - if (!$photo->canBeViewedBy($this->getUser())) { - $this->fail(15, "Access denied"); + foreach ($photos_splitted_list as $photo_id) { + $photo_s_id = explode("_", $photo_id); + $photo = (new PhotosRepo())->getByOwnerAndVID((int) $photo_s_id[0], (int) $photo_s_id[1]); + if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) { + continue; } $res[] = $photo->toVkApiStruct($photo_sizes, $extended); @@ -442,12 +414,7 @@ final class Photos extends VKAPIRequestHandler if (empty($photo_ids)) { $album = (new Albums())->getAlbumByOwnerAndId($owner_id, $album_id); - - if (!$album || $album->isDeleted()) { - $this->fail(21, "Invalid album"); - } - - if (!$album->canBeViewedBy($this->getUser())) { + if (!$album || $album->isDeleted() || !$album->canBeViewedBy($this->getUser())) { $this->fail(15, "Access denied"); } @@ -458,11 +425,15 @@ final class Photos extends VKAPIRequestHandler if (!$photo || $photo->isDeleted()) { continue; } + $res["items"][] = $photo->toVkApiStruct($photo_sizes, $extended); } } else { - $photos = explode(',', $photo_ids); + $photos = array_unique(explode(',', $photo_ids)); + if (sizeof($photos) > 78) { + $this->fail(-78, "Photos count must not exceed limit"); + } $res = [ "count" => sizeof($photos), @@ -472,10 +443,12 @@ final class Photos extends VKAPIRequestHandler foreach ($photos as $photo) { $id = explode("_", $photo); - $phot = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); - if ($phot && !$phot->isDeleted() && $phot->canBeViewedBy($this->getUser())) { - $res["items"][] = $phot->toVkApiStruct($photo_sizes, $extended); + $photo_entity = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); + if (!$photo_entity || $photo_entity->isDeleted() || !$photo_entity->canBeViewedBy($this->getUser())) { + continue; } + + $res["items"][] = $photo_entity->toVkApiStruct($photo_sizes, $extended); } } @@ -489,12 +462,8 @@ final class Photos extends VKAPIRequestHandler $album = (new Albums())->get($album_id); - if (!$album || $album->canBeModifiedBy($this->getUser())) { - $this->fail(21, "Invalid album"); - } - - if ($album->isDeleted()) { - $this->fail(22, "Album already deleted"); + if (!$album || $album->isDeleted() || $album->isCreatedBySystem() || !$album->canBeModifiedBy($this->getUser())) { + $this->fail(15, "Access denied"); } $album->delete(); @@ -509,12 +478,8 @@ final class Photos extends VKAPIRequestHandler $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); - if (!$photo) { - $this->fail(21, "Invalid photo"); - } - - if ($photo->isDeleted()) { - $this->fail(21, "Photo is deleted"); + if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) { + $this->fail(21, "Access denied"); } if (!empty($caption)) { @@ -525,60 +490,48 @@ final class Photos extends VKAPIRequestHandler return 1; } - public function delete(int $owner_id, int $photo_id, string $photos = "") + public function delete(int $owner_id = null, int $photo_id = null, string $photos = null) { $this->requireUser(); $this->willExecuteWriteAction(); - if (empty($photos)) { + if (!$owner_id) { + $owner_id = $this->getUser()->getId(); + } + + if (is_null($photos)) { + if (is_null($photo_id)) { + return 0; + } + $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); - - if ($this->getUser()->getId() !== $photo->getOwner()->getId()) { - $this->fail(21, "You can't delete another's photo"); - } - - if (!$photo) { - $this->fail(21, "Invalid photo"); - } - - if ($photo->isDeleted()) { - $this->fail(21, "Photo is already deleted"); + if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) { + return 1; } $photo->delete(); } else { - $photozs = explode(',', $photos); + $photos_list = array_unique(explode(',', $photos)); + if (sizeof($photos_list) > 10) { + $this->fail(-78, "Photos count must not exceed limit"); + } - foreach ($photozs as $photo) { - $id = explode("_", $photo); - - $phot = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); - - if ($this->getUser()->getId() !== $phot->getOwner()->getId()) { - $this->fail(21, "You can't delete another's photo"); + foreach ($photos_list as $photo_id) { + $id = explode("_", $photo_id); + $photo = (new PhotosRepo())->getByOwnerAndVID((int) $id[0], (int) $id[1]); + if (!$photo || $photo->isDeleted() || !$photo->canBeModifiedBy($this->getUser())) { + continue; } - if (!$phot) { - $this->fail(21, "Invalid photo"); - } - - if ($phot->isDeleted()) { - $this->fail(21, "Photo already deleted"); - } - - $phot->delete(); + $photo->delete(); } } return 1; } - public function getAllComments(int $owner_id, int $album_id, bool $need_likes = false, int $offset = 0, int $count = 100) - { - $this->fail(501, "Not implemented"); - } - - public function deleteComment(int $comment_id, int $owner_id = 0) + # Поскольку комментарии едины, можно использовать метод "wall.deleteComment". + /*public function deleteComment(int $comment_id, int $owner_id = 0) { $this->requireUser(); $this->willExecuteWriteAction(); @@ -595,9 +548,9 @@ final class Photos extends VKAPIRequestHandler $comment->delete(); return 1; - } + }*/ - public function createComment(int $owner_id, int $photo_id, string $message = "", string $attachments = "", bool $from_group = false) + public function createComment(int $owner_id, int $photo_id, string $message = "", bool $from_group = false) { $this->requireUser(); $this->willExecuteWriteAction(); @@ -608,12 +561,8 @@ final class Photos extends VKAPIRequestHandler $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); - if (!$photo || $photo->isDeleted()) { - $this->fail(180, "Invalid photo"); - } - - if (!$photo->canBeViewedBy($this->getUser())) { - $this->fail(15, "Access to photo denied"); + if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) { + $this->fail(15, "Access denied"); } $comment = new Comment(); @@ -624,55 +573,6 @@ final class Photos extends VKAPIRequestHandler $comment->setCreated(time()); $comment->save(); - if (!empty($attachments)) { - $attachmentsArr = explode(",", $attachments); - - if (sizeof($attachmentsArr) > 10) { - $this->fail(50, "Error: too many attachments"); - } - - foreach ($attachmentsArr as $attac) { - $attachmentType = null; - - if (str_contains($attac, "photo")) { - $attachmentType = "photo"; - } elseif (str_contains($attac, "video")) { - $attachmentType = "video"; - } else { - $this->fail(205, "Unknown attachment type"); - } - - $attachment = str_replace($attachmentType, "", $attac); - - $attachmentOwner = (int) explode("_", $attachment)[0]; - $attachmentId = (int) end(explode("_", $attachment)); - - $attacc = null; - - if ($attachmentType == "photo") { - $attacc = (new PhotosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Photo does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this photo"); - } - - $comment->attach($attacc); - } elseif ($attachmentType == "video") { - $attacc = (new VideosRepo())->getByOwnerAndVID($attachmentOwner, $attachmentId); - if (!$attacc || $attacc->isDeleted()) { - $this->fail(100, "Video does not exists"); - } - if ($attacc->getOwner()->getId() != $this->getUser()->getId()) { - $this->fail(43, "You do not have access to this video"); - } - - $comment->attach($attacc); - } - } - } - return $comment->getId(); } @@ -681,16 +581,12 @@ final class Photos extends VKAPIRequestHandler $this->requireUser(); if ($owner_id < 0) { - $this->fail(4, "This method doesn't works with clubs"); + $this->fail(-413, "Clubs are not supported"); } $user = (new UsersRepo())->get($owner_id); - if (!$user) { - $this->fail(4, "Invalid user"); - } - - if (!$user->getPrivacyPermission('photos.read', $this->getUser())) { - $this->fail(21, "This user chose to hide his albums."); + if (!$user || !$user->getPrivacyPermission('photos.read', $this->getUser())) { + $this->fail(15, "Access denied"); } $photos = (new PhotosRepo())->getEveryUserPhoto($user, $offset, $count); @@ -716,12 +612,8 @@ final class Photos extends VKAPIRequestHandler $photo = (new PhotosRepo())->getByOwnerAndVID($owner_id, $photo_id); $comms = array_slice(iterator_to_array($photo->getComments(1, $offset + $count)), $offset); - if (!$photo || $photo->isDeleted()) { - $this->fail(4, "Invalid photo"); - } - - if (!$photo->canBeViewedBy($this->getUser())) { - $this->fail(21, "Access denied"); + if (!$photo || $photo->isDeleted() || !$photo->canBeViewedBy($this->getUser())) { + $this->fail(15, "Access denied"); } $res = [ diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index e3d27073..bafce3d8 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -292,14 +292,14 @@ final class Users extends VKAPIRequestHandler break; case 'blacklisted_by_me': if (!$authuser) { - continue; + break; } $response[$i]->blacklisted_by_me = (int) $usr->isBlacklistedBy($this->getUser()); break; case 'blacklisted': if (!$authuser) { - continue; + break; } $response[$i]->blacklisted = (int) $this->getUser()->isBlacklistedBy($usr); @@ -383,7 +383,8 @@ final class Users extends VKAPIRequestHandler string $fav_music = "", string $fav_films = "", string $fav_shows = "", - string $fav_books = "" + string $fav_books = "", + string $interests = "" ) { if ($count > 100) { $this->fail(100, "One of the parameters specified was missing or invalid: count should be less or equal to 100"); diff --git a/VKAPI/Handlers/VKAPIRequestHandler.php b/VKAPI/Handlers/VKAPIRequestHandler.php index b67c4797..4804a8dd 100644 --- a/VKAPI/Handlers/VKAPIRequestHandler.php +++ b/VKAPI/Handlers/VKAPIRequestHandler.php @@ -20,7 +20,7 @@ abstract class VKAPIRequestHandler $this->platform = $platform; } - protected function fail(int $code, string $message): void + protected function fail(int $code, string $message): never { throw new APIErrorException($message, $code); } diff --git a/VKAPI/Handlers/Wall.php b/VKAPI/Handlers/Wall.php index 3bc56b93..798aa5a3 100644 --- a/VKAPI/Handlers/Wall.php +++ b/VKAPI/Handlers/Wall.php @@ -53,7 +53,7 @@ final class Wall extends VKAPIRequestHandler $this->fail(15, "Access denied: wall is disabled"); } // Don't search for logic here pls - $iteratorv; + $iteratorv = null; switch ($filter) { case "all": @@ -722,7 +722,7 @@ final class Wall extends VKAPIRequestHandler $post->attach($attachment); } - if ($wall > 0 && $wall !== $this->user->identity->getId()) { + if ($owner_id > 0 && $owner_id !== $this->getUser()->getId()) { (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); } @@ -734,7 +734,7 @@ final class Wall extends VKAPIRequestHandler $this->requireUser(); $this->willExecuteWriteAction(); - $postArray; + $postArray = []; if (preg_match('/(wall|video|photo)((?:-?)[0-9]+)_([0-9]+)/', $object, $postArray) == 0) { $this->fail(100, "One of the parameters specified was missing or invalid: object is incorrect"); } @@ -1176,8 +1176,9 @@ final class Wall extends VKAPIRequestHandler if ($from_group == 1 && $wallOwner instanceof Club && $wallOwner->canBeModifiedBy($this->getUser())) { $flags |= 0b10000000; } - /*if($signed == 1) - $flags |= 0b01000000;*/ + if ($post->isSigned() && $from_group == 1) { + $flags |= 0b01000000; + } $post->setFlags($flags); $post->save(true); diff --git a/Web/Models/Entities/Album.php b/Web/Models/Entities/Album.php index b2debf92..0e768dee 100644 --- a/Web/Models/Entities/Album.php +++ b/Web/Models/Entities/Album.php @@ -26,7 +26,9 @@ class Album extends MediaCollection { $coverPhoto = $this->getCoverPhoto(); if (!$coverPhoto) { - return "/assets/packages/static/openvk/img/camera_200.png"; + $server_url = ovk_scheme(true) . $_SERVER["HTTP_HOST"]; + + return $server_url . "/assets/packages/static/openvk/img/camera_200.png"; } return $coverPhoto->getURL(); @@ -92,14 +94,13 @@ class Album extends MediaCollection { $res = (object) []; - $res->id = $this->getPrettyId(); - $res->vid = $this->getId(); + $res->id = $this->getId(); $res->thumb_id = !is_null($this->getCoverPhoto()) ? $this->getCoverPhoto()->getPrettyId() : 0; - $res->owner_id = $this->getOwner()->getId(); + $res->owner_id = $this->getOwner()->getRealId(); $res->title = $this->getName(); $res->description = $this->getDescription(); $res->created = $this->getCreationTime()->timestamp(); - $res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : null; + $res->updated = $this->getEditTime() ? $this->getEditTime()->timestamp() : $res->created; $res->size = $this->size(); $res->privacy_comment = 1; $res->upload_by_admins_only = 1; diff --git a/Web/Models/Entities/Audio.php b/Web/Models/Entities/Audio.php index 7e742db7..9676d6cd 100644 --- a/Web/Models/Entities/Audio.php +++ b/Web/Models/Entities/Audio.php @@ -435,6 +435,10 @@ class Audio extends Media $obj->keys = $this->getKeys(); } + if ($obj->editable) { + $obj->listens = $this->getListens(); + } + return $obj; } diff --git a/Web/Models/Entities/Club.php b/Web/Models/Entities/Club.php index 01d75412..892a1647 100644 --- a/Web/Models/Entities/Club.php +++ b/Web/Models/Entities/Club.php @@ -323,7 +323,7 @@ class Club extends RowModel return sizeof($this->getFollowersQuery()); } - public function getFollowers(int $page = 1, int $perPage = 6, string $sort = "follower ASC"): \Traversable + public function getFollowers(int $page = 1, int $perPage = 6, string $sort = "target DESC"): \Traversable { $rels = $this->getFollowersQuery($sort)->page($page, $perPage); diff --git a/Web/Models/Entities/Document.php b/Web/Models/Entities/Document.php index 286f9c0b..a264ad71 100644 --- a/Web/Models/Entities/Document.php +++ b/Web/Models/Entities/Document.php @@ -351,7 +351,7 @@ class Document extends Media return $this->getRecord()->owner; } - public function toApiPreview(): object + public function toApiPreview(): ?object { $preview = $this->getPreview(); if ($preview instanceof Photo) { @@ -360,6 +360,8 @@ class Document extends Media "sizes" => array_values($preview->getVkApiSizes()), ], ]; + } else { + return null; } } diff --git a/Web/Models/Entities/Gift.php b/Web/Models/Entities/Gift.php index f63b7b81..f4a76b35 100644 --- a/Web/Models/Entities/Gift.php +++ b/Web/Models/Entities/Gift.php @@ -112,7 +112,6 @@ class Gift extends RowModel public function setImage(string $file): bool { - $imgBlob; try { $image = Image::fromFile($file); $image->resize(512, 512, Image::SHRINK_ONLY); diff --git a/Web/Models/Entities/Message.php b/Web/Models/Entities/Message.php index 4e9b8455..1d3364c5 100644 --- a/Web/Models/Entities/Message.php +++ b/Web/Models/Entities/Message.php @@ -33,6 +33,8 @@ class Message extends RowModel return (new Users())->get($this->getRecord()->sender_id); } elseif ($this->getRecord()->sender_type === 'openvk\Web\Models\Entities\Club') { return (new Clubs())->get($this->getRecord()->sender_id); + } else { + return null; } } @@ -49,6 +51,8 @@ class Message extends RowModel return (new Users())->get($this->getRecord()->recipient_id); } elseif ($this->getRecord()->recipient_type === 'openvk\Web\Models\Entities\Club') { return (new Clubs())->get($this->getRecord()->recipient_id); + } else { + return null; } } @@ -147,7 +151,7 @@ class Message extends RowModel "id" => $author->getId(), "link" => $_SERVER['REQUEST_SCHEME'] . "://" . $_SERVER['HTTP_HOST'] . $author->getURL(), "avatar" => $author->getAvatarUrl(), - "name" => $author->getFirstName() . $unreadmsg, + "name" => $author->getFirstName(), ], "timing" => [ "sent" => (string) $this->getSendTimeHumanized(), diff --git a/Web/Models/Entities/Notifications/Notification.php b/Web/Models/Entities/Notifications/Notification.php index 67d36497..8b39c380 100644 --- a/Web/Models/Entities/Notifications/Notification.php +++ b/Web/Models/Entities/Notifications/Notification.php @@ -64,13 +64,15 @@ class Notification return $this->recipient; } - public function getModel(int $index): RowModel + public function getModel(int $index): ?RowModel { switch ($index) { case 0: return $this->originModel; case 1: return $this->targetModel; + default: + return null; } } @@ -166,14 +168,13 @@ class Notification case 19: $info["type"] = "comment_video"; $info["parent"] = $this->getModel(0)->toNotifApiStruct(); - $info["feedback"] = null; # айди коммента не сохраняется в бд( ну пиздец блять + $info["feedback"] = null; # comment id is not saving at db break; case 13: $info["type"] = "comment_photo"; $info["parent"] = $this->getModel(0)->toNotifApiStruct(); $info["feedback"] = null; break; - # unstandart (vk forgor about notes) case 10: $info["type"] = "comment_note"; $info["parent"] = $this->getModel(0)->toVkApiStruct(); diff --git a/Web/Models/Entities/Photo.php b/Web/Models/Entities/Photo.php index d50cc854..4414f085 100644 --- a/Web/Models/Entities/Photo.php +++ b/Web/Models/Entities/Photo.php @@ -349,7 +349,6 @@ class Photo extends Media $res->width = $this->getDimensions()[0]; $res->height = $this->getDimensions()[1]; $res->date = $res->created = $this->getPublicationTime()->timestamp(); - if ($photo_sizes) { $res->sizes = array_values($this->getVkApiSizes()); $res->src_small = $res->photo_75 = $this->getURLBySizeId("miniscule"); @@ -359,14 +358,19 @@ class Photo extends Media $res->src_xxbig = $res->photo_1280 = $this->getURLBySizeId("larger"); $res->src_xxxbig = $res->photo_2560 = $this->getURLBySizeId("original"); $res->src_original = $res->url = $this->getURLBySizeId("UPLOADED_MAXRES"); + $res->orig_photo = [ + "height" => $res->height, + "width" => $res->width, + "type" => "base", + "url" => $this->getURL(), + ]; } if ($extended) { - $res->likes = $this->getLikesCount(); # их нету но пусть будут + $res->likes = $this->getLikesCount(); $res->comments = $this->getCommentsCount(); - $res->tags = 0; $res->can_comment = 1; - $res->can_repost = 0; + $res->can_repost = 1; } return $res; @@ -385,9 +389,9 @@ class Photo extends Media } } - public static function fastMake(int $owner, string $description = "", array $file, ?Album $album = null, bool $anon = false): Photo + public static function fastMake(int $owner, string $description, array $file, ?Album $album = null, bool $anon = false): Photo { - $photo = new static(); + $photo = new Photo(); $photo->setOwner($owner); $photo->setDescription(iconv_substr($description, 0, 36) . "..."); $photo->setAnonymous($anon); diff --git a/Web/Models/Entities/Report.php b/Web/Models/Entities/Report.php index db8d38e6..1d345fd4 100644 --- a/Web/Models/Entities/Report.php +++ b/Web/Models/Entities/Report.php @@ -45,11 +45,7 @@ class Report extends RowModel public function isDeleted(): bool { - if ($this->getRecord()->deleted === 0) { - return false; - } elseif ($this->getRecord()->deleted === 1) { - return true; - } + return $this->getRecord()->deleted === 1; } public function authorId(): int diff --git a/Web/Models/Entities/Traits/TRichText.php b/Web/Models/Entities/Traits/TRichText.php index f82626e2..17382dbc 100644 --- a/Web/Models/Entities/Traits/TRichText.php +++ b/Web/Models/Entities/Traits/TRichText.php @@ -41,11 +41,11 @@ trait TRichText return preg_replace_callback( "%(([A-z]++):\/\/(\S*?\.\S*?))([\s)\[\]{},\"\'<]|\.\s|$)%", (function (array $matches): string { - $href = str_replace("#", "#", $matches[1]); - $href = rawurlencode(str_replace(";", ";", $href)); - $link = str_replace("#", "#", $matches[3]); + $href = rawurlencode($matches[1]); + $href = str_replace("%26amp%3B", "%26", $href); + $link = $matches[3]; # this string breaks ampersands - $link = str_replace(";", ";", $link); + # $link = str_replace(";", ";", $link); $rel = $this->isAd() ? "sponsored" : "ugc"; /*$server_domain = str_replace(':' . $_SERVER['SERVER_PORT'], '', $_SERVER['HTTP_HOST']); diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 31d3d4fe..8749b725 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -306,10 +306,10 @@ class User extends RowModel $content_type = $matches[1]; $content_id = (int) $matches[2]; if (in_array($content_type, ["noSpamTemplate", "user"])) { - $reason = "Подозрительная активность"; + $reason = $this->getRawBanReason(); } else { if ($for !== "banned") { - $reason = "Подозрительная активность"; + $reason = $this->getRawBanReason(); } else { $reason = [$this->getTextForContentBan($content_type), $content_type]; switch ($content_type) { @@ -524,7 +524,10 @@ class User extends RowModel public function getAge(): ?int { - return (int) floor((time() - $this->getBirthday()->timestamp()) / YEAR); + $birthday = new \DateTime(); + $birthday->setTimestamp($this->getBirthday()->timestamp()); + $today = new \DateTime(); + return (int) $today->diff($birthday)->y; } public function get2faSecret(): ?string @@ -558,6 +561,7 @@ class User extends RowModel "poster", "apps", "docs", + "fave", ], ])->get($id); } @@ -932,6 +936,7 @@ class User extends RowModel case 1: return tr('female'); case 2: + default: return tr('neutral'); } } @@ -1195,6 +1200,7 @@ class User extends RowModel "poster", "apps", "docs", + "fave", ], ])->set($id, (int) $status)->toInteger(); @@ -1559,14 +1565,14 @@ class User extends RowModel break; case "blacklisted_by_me": if (!$user) { - continue; + break; } $res->blacklisted_by_me = (int) $this->isBlacklistedBy($user); break; case "blacklisted": if (!$user) { - continue; + break; } $res->blacklisted = (int) $user->isBlacklistedBy($this); diff --git a/Web/Models/Entities/Video.php b/Web/Models/Entities/Video.php index b8a9fcca..8dbfb7a1 100644 --- a/Web/Models/Entities/Video.php +++ b/Web/Models/Entities/Video.php @@ -9,12 +9,13 @@ use openvk\Web\Util\Shell\Exceptions\{ShellUnavailableException, UnknownCommandE use openvk\Web\Models\VideoDrivers\VideoDriver; use Nette\InvalidStateException as ISE; -define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<", false); +define("VIDEOS_FRIENDLY_ERROR", "Uploads are disabled on this instance :<"); class Video extends Media { - public const TYPE_DIRECT = 0; - public const TYPE_EMBED = 1; + public const TYPE_DIRECT = 0; + public const TYPE_EMBED = 1; + public const TYPE_UNKNOWN = -1; protected $tableName = "videos"; protected $fileExtension = "mp4"; @@ -108,6 +109,7 @@ class Video extends Media } elseif (!is_null($this->getRecord()->link)) { return Video::TYPE_EMBED; } + return Video::TYPE_UNKNOWN; } public function getVideoDriver(): ?VideoDriver @@ -238,7 +240,7 @@ class Video extends Media $this->save(); } - public static function fastMake(int $owner, string $name = "Unnamed Video.ogv", string $description = "", array $file, bool $unlisted = true, bool $anon = false): Video + public static function fastMake(int $owner, string $name, string $description, array $file, bool $unlisted = true, bool $anon = false): Video { if (OPENVK_ROOT_CONF['openvk']['preferences']['videos']['disableUploading']) { exit(VIDEOS_FRIENDLY_ERROR); @@ -269,7 +271,7 @@ class Video extends Media return false; } - $streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute($error); + $streams = Shell::ffprobe("-i", $path, "-show_streams", "-select_streams v", "-loglevel error")->execute(); $durations = []; preg_match_all('%duration=([0-9\.]++)%', $streams, $durations); diff --git a/Web/Models/Repositories/ChandlerGroups.php b/Web/Models/Repositories/ChandlerGroups.php index 3c7c62de..ad43da2e 100644 --- a/Web/Models/Repositories/ChandlerGroups.php +++ b/Web/Models/Repositories/ChandlerGroups.php @@ -13,6 +13,8 @@ class ChandlerGroups { private $context; private $groups; + private $members; + private $perms; public function __construct() { diff --git a/Web/Models/Repositories/Clubs.php b/Web/Models/Repositories/Clubs.php index b425c858..fcc9d5f8 100644 --- a/Web/Models/Repositories/Clubs.php +++ b/Web/Models/Repositories/Clubs.php @@ -91,7 +91,7 @@ class Clubs return (clone $this->clubs)->count('*'); } - public function getPopularClubs(): \Traversable + public function getPopularClubs(): ?\Traversable { // TODO rewrite @@ -106,6 +106,8 @@ class Clubs "subscriptions" => $entry["subscriptions"], ]; */ + trigger_error("Clubs::getPopularClubs() is currently commented out and returns null", E_USER_WARNING); + return null; } public function getWriteableClubs(int $id): \Traversable diff --git a/Web/Models/Repositories/Conversations.php b/Web/Models/Repositories/Conversations.php deleted file mode 100644 index e354411b..00000000 --- a/Web/Models/Repositories/Conversations.php +++ /dev/null @@ -1,53 +0,0 @@ -context = DB::i()->getContext(); - $this->convos = $this->context->table("conversations"); - } - - private function toConversation(?ActiveRow $ar): ?M\AbstractConversation - { - if (is_null($ar)) { - return null; - } elseif ($ar->is_pm) { - return new M\PrivateConversation($ar); - } else { - return new M\Conversation($ar); - } - } - - public function get(int $id): ?M\AbstractConversation - { - return $this->toConversation($this->convos->get($id)); - } - - public function getConversationsByUser(User $user, int $page = 1, ?int $perPage = null): \Traversable - { - $rels = $this->context->table("conversation_members")->where([ - "deleted" => false, - "user" => $user->getId(), - ])->page($page, $perPage ?? OPENVK_DEFAULT_PER_PAGE); - foreach ($rels as $rel) { - yield $this->get($rel->conversation); - } - } - - public function getPrivateConversation(User $user, int $peer): M\PrivateConversation - { - ; - } -} diff --git a/Web/Models/Repositories/Documents.php b/Web/Models/Repositories/Documents.php index cda95abc..db98fc5d 100644 --- a/Web/Models/Repositories/Documents.php +++ b/Web/Models/Repositories/Documents.php @@ -152,7 +152,7 @@ class Documents switch ($paramName) { case "type": if ($paramValue < 1 || $paramValue > 8) { - continue; + break; } $result->where("type", $paramValue); break; diff --git a/Web/Models/Repositories/Faves.php b/Web/Models/Repositories/Faves.php new file mode 100644 index 00000000..10d4932c --- /dev/null +++ b/Web/Models/Repositories/Faves.php @@ -0,0 +1,52 @@ +context = DatabaseConnection::i()->getContext(); + $this->likes = $this->context->table("likes"); + } + + private function fetchLikes(User $user, string $class = 'Post') + { + $fetch = $this->likes->where([ + "model" => "openvk\\Web\\Models\\Entities\\" . $class, + "origin" => $user->getRealId(), + ]); + + return $fetch; + } + + public function fetchLikesSection(User $user, string $class = 'Post', int $page = 1, ?int $perPage = null): \Traversable + { + $perPage ??= OPENVK_DEFAULT_PER_PAGE; + $fetch = $this->fetchLikes($user, $class)->page($page, $perPage)->order("index DESC"); + foreach ($fetch as $like) { + $className = "openvk\\Web\\Models\\Repositories\\" . $class . "s"; + $repo = new $className(); + if (!$repo) { + continue; + } + + $entity = $repo->get($like->target); + yield $entity; + } + } + + public function fetchLikesSectionCount(User $user, string $class = 'Post') + { + return $this->fetchLikes($user, $class)->count(); + } +} diff --git a/Web/Models/Repositories/Photos.php b/Web/Models/Repositories/Photos.php index 24ff8070..f4fd68b9 100644 --- a/Web/Models/Repositories/Photos.php +++ b/Web/Models/Repositories/Photos.php @@ -62,6 +62,7 @@ class Photos "deleted" => 0, "system" => 0, "private" => 0, + "anonymous" => 0, ])->order("id DESC"); foreach ($photos->limit($limit, $offset) as $photo) { @@ -76,6 +77,7 @@ class Photos "deleted" => 0, "system" => 0, "private" => 0, + "anonymous" => 0, ]); return sizeof($photos); diff --git a/Web/Models/Repositories/SupportAgents.php b/Web/Models/Repositories/SupportAgents.php index b0dfcbbc..d83d1974 100644 --- a/Web/Models/Repositories/SupportAgents.php +++ b/Web/Models/Repositories/SupportAgents.php @@ -11,7 +11,7 @@ use openvk\Web\Models\Entities\{User, SupportAgent}; class SupportAgents { private $context; - private $tickets; + private $agents; public function __construct() { diff --git a/Web/Models/Repositories/Tickets.php b/Web/Models/Repositories/Tickets.php index dd7379c8..6728931e 100644 --- a/Web/Models/Repositories/Tickets.php +++ b/Web/Models/Repositories/Tickets.php @@ -57,7 +57,7 @@ class Tickets { $requests = $this->tickets->where(["id" => $requestId])->fetch(); if (!is_null($requests)) { - return new Req($requests); + return new Ticket($requests); } else { return null; } diff --git a/Web/Models/Repositories/Users.php b/Web/Models/Repositories/Users.php index 407cc851..08e71a30 100644 --- a/Web/Models/Repositories/Users.php +++ b/Web/Models/Repositories/Users.php @@ -157,7 +157,7 @@ class Users { return (object) [ "all" => (clone $this->users)->count('*'), - "active" => (clone $this->users)->where("online > 0")->count('*'), + "active" => (clone $this->users)->where("online >= ?", time() - MONTH)->count('*'), "online" => (clone $this->users)->where("online >= ?", time() - 900)->count('*'), ]; } diff --git a/Web/Presenters/AdminPresenter.php b/Web/Presenters/AdminPresenter.php index f0f2b04d..8fad1052 100644 --- a/Web/Presenters/AdminPresenter.php +++ b/Web/Presenters/AdminPresenter.php @@ -255,7 +255,7 @@ final class AdminPresenter extends OpenVKPresenter { $this->warnIfNoCommerce(); - $cat; + $cat = null; $gen = false; if ($id !== 0) { $cat = $this->gifts->getCat($id); diff --git a/Web/Presenters/AudioPresenter.php b/Web/Presenters/AudioPresenter.php index db341f83..c5c1eecc 100644 --- a/Web/Presenters/AudioPresenter.php +++ b/Web/Presenters/AudioPresenter.php @@ -78,6 +78,10 @@ final class AudioPresenter extends OpenVKPresenter } elseif ($mode === "new") { $audios = $this->audios->getNew(); $audiosCount = $audios->size(); + } elseif ($mode === "uploaded") { + $stream = $this->audios->getByUploader($this->user->identity); + $audios = $stream->page($page, 10); + $audiosCount = $stream->size(); } elseif ($mode === "playlists") { if ($owner < 0) { $entity = (new Clubs())->get(abs($owner)); @@ -130,6 +134,11 @@ final class AudioPresenter extends OpenVKPresenter } } + public function renderUploaded() + { + $this->renderList(null, "uploaded"); + } + public function renderEmbed(int $owner, int $id): void { $audio = $this->audios->getByOwnerAndVID($owner, $id); @@ -499,7 +508,7 @@ final class AudioPresenter extends OpenVKPresenter $title = $this->postParam("title"); $description = $this->postParam("description"); $is_unlisted = (int) $this->postParam('is_unlisted'); - $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : []; + $new_audios = !empty($this->postParam("audios")) ? explode(",", rtrim($this->postParam("audios"), ",")) : null; if (empty($title) || iconv_strlen($title) < 1) { $this->flashFail("err", tr("error"), tr("set_playlist_name")); @@ -529,13 +538,15 @@ final class AudioPresenter extends OpenVKPresenter "collection" => $playlist->getId(), ])->delete(); - foreach ($new_audios as $new_audio) { - $audio = (new Audios())->get((int) $new_audio); - if (!$audio || $audio->isDeleted()) { - continue; - } + if (!is_null($new_audios)) { + foreach ($new_audios as $new_audio) { + $audio = (new Audios())->get((int) $new_audio); + if (!$audio || $audio->isDeleted()) { + continue; + } - $playlist->add($audio); + $playlist->add($audio); + } } if ($is_ajax) { @@ -841,6 +852,10 @@ final class AudioPresenter extends OpenVKPresenter $audios = [$found_audio]; $audiosCount = 1; break; + case "uploaded": + $stream = $this->audios->getByUploader($this->user->identity); + $audios = $stream->page($page, $perPage); + $audiosCount = $stream->size(); } $pagesCount = ceil($audiosCount / $perPage); diff --git a/Web/Presenters/AwayPresenter.php b/Web/Presenters/AwayPresenter.php index 59993935..2e236d0c 100644 --- a/Web/Presenters/AwayPresenter.php +++ b/Web/Presenters/AwayPresenter.php @@ -20,7 +20,7 @@ final class AwayPresenter extends OpenVKPresenter header("HTTP/1.0 302 Found"); header("X-Robots-Tag: noindex, nofollow, noarchive"); - header("Location: " . $this->queryParam("to")); + header("Location: " . rawurldecode($this->queryParam("to"))); exit; } diff --git a/Web/Presenters/BlobPresenter.php b/Web/Presenters/BlobPresenter.php index 99b57816..619d9d54 100644 --- a/Web/Presenters/BlobPresenter.php +++ b/Web/Presenters/BlobPresenter.php @@ -34,7 +34,8 @@ final class BlobPresenter extends OpenVKPresenter } if (isset($_SERVER["HTTP_IF_NONE_MATCH"])) { - exit(header("HTTP/1.1 304 Not Modified")); + header("HTTP/1.1 304 Not Modified"); + exit(); } header("Content-Type: " . mime_content_type($path)); diff --git a/Web/Presenters/CommentPresenter.php b/Web/Presenters/CommentPresenter.php index 0353acf8..f9317cff 100644 --- a/Web/Presenters/CommentPresenter.php +++ b/Web/Presenters/CommentPresenter.php @@ -7,6 +7,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Comment, Notifications\MentionNotification, Photo, Video, User, Topic, Post}; use openvk\Web\Models\Entities\Notifications\CommentNotification; use openvk\Web\Models\Repositories\{Comments, Clubs, Videos, Photos, Audios}; +use Nette\InvalidStateException as ISE; final class CommentPresenter extends OpenVKPresenter { diff --git a/Web/Presenters/ContentSearchPresenter.php b/Web/Presenters/ContentSearchPresenter.php index 6fadd687..e22f34fa 100644 --- a/Web/Presenters/ContentSearchPresenter.php +++ b/Web/Presenters/ContentSearchPresenter.php @@ -8,17 +8,17 @@ use openvk\Web\Models\Repositories\ContentSearchRepository; final class ContentSearchPresenter extends OpenVKPresenter { - private $repo; + protected $repo; - public function __construct(ContentSearchRepository $repo) + public function __construct(ContentSearchRepository $repository) { - $this->repo = $repo; + $this->repo = $repository; } public function renderIndex(): void { if ($_SERVER["REQUEST_METHOD"] === "POST") { - $this->template->results = $repo->find([ + $this->template->results = $this->repo->find([ "query" => $this->postParam("query"), ]); } diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 1aa14213..ba9ec1f4 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -9,6 +9,7 @@ use Nette\InvalidStateException; use openvk\Web\Models\Entities\Notifications\ClubModeratorNotification; use openvk\Web\Models\Repositories\{Clubs, Users, Albums, Managers, Topics, Audios, Posts, Documents}; use Chandler\Security\Authenticator; +use Nette\InvalidStateException as ISE; final class GroupPresenter extends OpenVKPresenter { @@ -288,7 +289,6 @@ final class GroupPresenter extends OpenVKPresenter (new Albums())->getClubAvatarAlbum($club)->addPhoto($photo); } catch (ISE $ex) { - $name = $album->getName(); $this->flashFail("err", tr("error"), tr("error_when_uploading_photo")); } } @@ -373,6 +373,7 @@ final class GroupPresenter extends OpenVKPresenter public function renderDeleteAvatar(int $id) { $this->assertUserLoggedIn(); + $this->assertNoCSRF(); $this->willExecuteWriteAction(); $club = $this->clubs->get($id); diff --git a/Web/Presenters/InternalAPIPresenter.php b/Web/Presenters/InternalAPIPresenter.php index b33f159c..2e3a33ca 100644 --- a/Web/Presenters/InternalAPIPresenter.php +++ b/Web/Presenters/InternalAPIPresenter.php @@ -165,7 +165,7 @@ final class InternalAPIPresenter extends OpenVKPresenter if ($type == 'post') { $this->template->_template = 'components/post.xml'; $this->template->post = $post; - $this->template->commentSection = false; + $this->template->commentSection = true; } elseif ($type == 'comment') { $this->template->_template = 'components/comment.xml'; $this->template->comment = $post; diff --git a/Web/Presenters/MessengerPresenter.php b/Web/Presenters/MessengerPresenter.php index 70054620..5c8c6a6e 100644 --- a/Web/Presenters/MessengerPresenter.php +++ b/Web/Presenters/MessengerPresenter.php @@ -93,9 +93,11 @@ final class MessengerPresenter extends OpenVKPresenter header("Content-Type: application/json"); if ($this->queryParam("act") !== "a_check") { - exit(header("HTTP/1.1 400 Bad Request")); + header("HTTP/1.1 400 Bad Request"); + exit(); } elseif (!$this->queryParam("key")) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } $key = $this->queryParam("key"); @@ -158,7 +160,8 @@ final class MessengerPresenter extends OpenVKPresenter $sel = $this->getCorrespondent($sel); if ($sel->getId() !== $this->user->id && !$sel->getPrivacyPermission('messages.write', $this->user->identity)) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } $cor = new Correspondence($this->user->identity, $sel); diff --git a/Web/Presenters/NoSpamPresenter.php b/Web/Presenters/NoSpamPresenter.php index 4f49d3b1..e6da910b 100644 --- a/Web/Presenters/NoSpamPresenter.php +++ b/Web/Presenters/NoSpamPresenter.php @@ -151,77 +151,6 @@ final class NoSpamPresenter extends OpenVKPresenter $this->assertNoCSRF(); $this->willExecuteWriteAction(); - function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null) - { - $db = DatabaseConnection::i()->getContext(); - if ($table && ($ip || $useragent || $ts || $te || $user)) { - $conditions = []; - - if ($ip) { - $conditions[] = "`ip` REGEXP '$ip'"; - } - if ($useragent) { - $conditions[] = "`useragent` REGEXP '$useragent'"; - } - if ($ts) { - $conditions[] = "`ts` < $ts"; - } - if ($te) { - $conditions[] = "`ts` > $te"; - } - if ($user) { - $users = new Users(); - - $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user)) - ?? $users->get((int) $user) - ?? $users->getByAddress($user) - ?? null; - - if ($_user) { - $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'"; - } - } - - $whereStart = "WHERE `object_table` = '$table'"; - if ($table === "profiles") { - $whereStart .= "AND `type` = 0"; - } - - $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : ""; - $response = []; - - if ($conditions) { - $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); - - foreach ($logs as $log) { - $log = (new Logs())->get($log->id); - $object = $log->getObject()->unwrap(); - - if (!$object) { - continue; - } - if ($where) { - if (str_starts_with($where, " AND")) { - $where = substr_replace($where, "", 0, strlen(" AND")); - } - - $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); - foreach ($a as $o) { - if ($object->id == $o["id"]) { - $response[] = $object; - } - } - - } else { - $response[] = $object; - } - } - } - - return $response; - } - } - try { $response = []; $processed = 0; @@ -290,7 +219,7 @@ final class NoSpamPresenter extends OpenVKPresenter } if ($ip || $useragent || $ts || $te || $user) { - $rows = searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); + $rows = $this->searchByAdditionalParams($table, $where, $ip, $useragent, $ts, $te, $user); } else { if (!$where) { $rows = []; @@ -408,4 +337,75 @@ final class NoSpamPresenter extends OpenVKPresenter $this->returnJson(["success" => false, "error" => $e->getMessage()]); } } + + private function searchByAdditionalParams(?string $table = null, ?string $where = null, ?string $ip = null, ?string $useragent = null, ?int $ts = null, ?int $te = null, $user = null) + { + $db = DatabaseConnection::i()->getContext(); + if ($table && ($ip || $useragent || $ts || $te || $user)) { + $conditions = []; + + if ($ip) { + $conditions[] = "`ip` REGEXP '$ip'"; + } + if ($useragent) { + $conditions[] = "`useragent` REGEXP '$useragent'"; + } + if ($ts) { + $conditions[] = "`ts` < $ts"; + } + if ($te) { + $conditions[] = "`ts` > $te"; + } + if ($user) { + $users = new Users(); + + $_user = $users->getByChandlerUser((new ChandlerUsers())->getById($user)) + ?? $users->get((int) $user) + ?? $users->getByAddress($user) + ?? null; + + if ($_user) { + $conditions[] = "`user` = '" . $_user->getChandlerGUID() . "'"; + } + } + + $whereStart = "WHERE `object_table` = '$table'"; + if ($table === "profiles") { + $whereStart .= "AND `type` = 0"; + } + + $conditions = count($conditions) > 0 ? "AND (" . implode(" AND ", $conditions) . ")" : ""; + $response = []; + + if ($conditions) { + $logs = $db->query("SELECT * FROM `ChandlerLogs` $whereStart $conditions GROUP BY `object_id`, `object_model`"); + + foreach ($logs as $log) { + $log = (new Logs())->get($log->id); + $object = $log->getObject()->unwrap(); + + if (!$object) { + continue; + } + if ($where) { + if (str_starts_with($where, " AND")) { + $where = substr_replace($where, "", 0, strlen(" AND")); + } + + $a = $db->query("SELECT * FROM `$table` WHERE $where")->fetchAll(); + foreach ($a as $o) { + if ($object->id == $o["id"]) { + $response[] = $object; + } + } + + } else { + $response[] = $object; + } + } + } + + return $response; + } + } } diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index 122e0966..76f4c845 100644 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -9,6 +9,7 @@ use Chandler\MVC\SimplePresenter; use Chandler\Session\Session; use Chandler\Security\Authenticator; use Latte\Engine as TemplatingEngine; +use Nette\InvalidStateException as ISE; use openvk\Web\Models\Entities\IP; use openvk\Web\Themes\Themepacks; use openvk\Web\Models\Repositories\{IPs, Users, APITokens, Tickets, Reports, CurrentUser, Posts}; @@ -74,7 +75,6 @@ abstract class OpenVKPresenter extends SimplePresenter protected function logInUserWithToken(): void { $header = $_SERVER["HTTP_AUTHORIZATION"] ?? ""; - $token; preg_match("%Bearer (.*)$%", $header, $matches); $token = $matches[1] ?? ""; @@ -130,7 +130,7 @@ abstract class OpenVKPresenter extends SimplePresenter } if ($throw) { - throw new SecurityPolicyViolationException("Permission error"); + throw new ISE("Permission error"); } else { $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); } @@ -371,7 +371,11 @@ abstract class OpenVKPresenter extends SimplePresenter $whichbrowser->isEngine('NetFront') || // PSP and other japanese portable systems $whichbrowser->isOs('Android') || $whichbrowser->isOs('iOS') || - $whichbrowser->isBrowser('Internet Explorer', '<=', '8')) { + $whichbrowser->isBrowser('BlackBerry Browser') || + $whichbrowser->isBrowser('Internet Explorer', '<=', '8') || + $whichbrowser->isBrowser('Firefox', '<=', '47') || + $whichbrowser->isBrowser('Safari', '<=', '7') || + $whichbrowser->isBrowser('Google Chrome', '<=', '35')) { // yeah, it's old, but ios and android are? if ($whichbrowser->isOs('iOS') && $whichbrowser->isOs('iOS', '<=', '9')) { return true; diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php index ac830f92..19c67fbe 100644 --- a/Web/Presenters/PhotosPresenter.php +++ b/Web/Presenters/PhotosPresenter.php @@ -321,7 +321,7 @@ final class PhotosPresenter extends OpenVKPresenter $photos = []; if ((int) $this->postParam("count") > 10) { - $this->flashFail("err", tr("no_photo"), "ты еблан", 500, true); + $this->flashFail("err", tr("no_photo"), "Too many photos (max is 7-8)", 500, true); } for ($i = 0; $i < $this->postParam("count"); $i++) { diff --git a/Web/Presenters/SearchPresenter.php b/Web/Presenters/SearchPresenter.php index 102a6435..bd79b08d 100644 --- a/Web/Presenters/SearchPresenter.php +++ b/Web/Presenters/SearchPresenter.php @@ -42,7 +42,6 @@ final class SearchPresenter extends OpenVKPresenter $page = (int) ($this->queryParam("p") ?? 1); # https://youtu.be/pSAWM5YuXx8 - # https://youtu.be/FfNZRhIn2Vk $repos = [ "groups" => "clubs", @@ -70,7 +69,7 @@ final class SearchPresenter extends OpenVKPresenter case 'marital_status': case 'polit_views': if ((int) $param_value == 0) { - continue; + break; } $parameters[$param_name] = $param_value; @@ -96,7 +95,7 @@ final class SearchPresenter extends OpenVKPresenter # дай бог работал этот case case 'from_me': if ((int) $param_value != 1) { - continue; + break; } $parameters['from_me'] = $this->user->id; diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index 2ab2de2b..ca2a0ff5 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -169,6 +169,13 @@ final class SupportPresenter extends OpenVKPresenter $_redirect = "/support?act=list"; } + $helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"]; + if ($helpdeskChat) { + $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"]; + $telegramText = "❌ Тикет под названием "{$ticket->getName()}" от {$ticket->getUser()->getCanonicalName()} ({$ticket->getUser()->getRegistrationIP()}) был удалён.\n"; + Telegram::send($helpdeskChat, $telegramText); + } + $ticket->delete(); $this->redirect($_redirect); } @@ -200,6 +207,16 @@ final class SupportPresenter extends OpenVKPresenter $comment->setCreated(time()); $comment->save(); + $helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"]; + if ($helpdeskChat) { + $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"]; + $commentText = ovk_proc_strtr($this->postParam("text"), 1500); + $telegramText = "💬 Новый комментарий от автора тикета "{$ticket->getName()}"\n"; + $telegramText .= "$commentText\n\n"; + $telegramText .= "Автор: {$ticket->getUser()->getCanonicalName()} ({$ticket->getUser()->getRegistrationIP()})\n"; + Telegram::send($helpdeskChat, $telegramText); + } + $this->redirect("/support/view/" . $id); } else { $this->flashFail("err", tr("error"), tr("you_have_not_entered_text")); @@ -227,15 +244,33 @@ final class SupportPresenter extends OpenVKPresenter { $this->assertPermission('openvk\Web\Models\Entities\TicketReply', 'write', 0); + $support_names = new SupportAgents(); + $agent = $support_names->get($this->user->id); $ticket = $this->tickets->get($id); if ($_SERVER["REQUEST_METHOD"] === "POST") { $this->willExecuteWriteAction(); + $helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"]; + if (!empty($this->postParam("text")) && !empty($this->postParam("status"))) { - $ticket->setType($this->postParam("status")); + $status = $this->postParam("status"); + $ticket->setType($status); $ticket->save(); + switch ($status) { + default: + # NOTICE falling through + case 0: + $state = "Вопрос на рассмотрении"; + break; + case 1: + $state = "Есть ответ"; + break; + case 2: + $state = "Закрыто"; + } + $comment = new TicketComment(); $comment->setUser_id($this->user->id); $comment->setUser_type(1); @@ -243,9 +278,42 @@ final class SupportPresenter extends OpenVKPresenter $comment->setTicket_Id($id); $comment->setCreated(time()); $comment->save(); + + if ($helpdeskChat) { + $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/agent" . $this->user->id; + $ticketUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/reply/" . $id; + $commentText = ovk_proc_strtr($this->postParam("text"), 1500); + $telegramText = "💬 Новый комментарий от агента к тикету "{$ticket->getName()}"\n"; + $telegramText .= "Статус: {$state}\n\n"; + $telegramText .= "$commentText\n\n"; + $telegramText .= "Агент: {$this->user->identity->getFullName()}\n"; + Telegram::send($helpdeskChat, $telegramText); + } } elseif (empty($this->postParam("text"))) { - $ticket->setType($this->postParam("status")); + $status = $this->postParam("status"); + $ticket->setType($status); $ticket->save(); + + switch ($status) { + default: + # NOTICE falling through + case 0: + $state = "Вопрос на рассмотрении"; + break; + case 1: + $state = "Есть ответ"; + break; + case 2: + $state = "Закрыто"; + } + + if ($helpdeskChat) { + $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/agent" . $this->user->id; + $ticketUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"] . "/support/reply/" . $id; + $telegramText = "🔔 Изменён статус тикета "{$ticket->getName()}": {$state}\n\n"; + $telegramText .= "Агент: {$this->user->identity->getFullName()}\n"; + Telegram::send($helpdeskChat, $telegramText); + } } $this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment")); @@ -314,17 +382,20 @@ final class SupportPresenter extends OpenVKPresenter $comment = $this->comments->get($id); if ($this->user->id !== $comment->getTicket()->getUser()->getId()) { - exit(header("HTTP/1.1 403 Forbidden")); + header("HTTP/1.1 403 Forbidden"); + exit(); } if ($mark !== 1 && $mark !== 2) { - exit(header("HTTP/1.1 400 Bad Request")); + header("HTTP/1.1 400 Bad Request"); + exit(); } $comment->setMark($mark); $comment->save(); - exit(header("HTTP/1.1 200 OK")); + header("HTTP/1.1 200 OK"); + exit(); } public function renderQuickBanInSupport(int $id): void @@ -439,6 +510,13 @@ final class SupportPresenter extends OpenVKPresenter $ticket->setType(2); $ticket->save(); + $helpdeskChat = OPENVK_ROOT_CONF["openvk"]["credentials"]["telegram"]["helpdeskChat"]; + if ($helpdeskChat) { + $serverUrl = ovk_scheme(true) . $_SERVER["SERVER_NAME"]; + $telegramText = "🔒 Тикет под названием "{$ticket->getName()}" от {$ticket->getUser()->getCanonicalName()} ({$ticket->getUser()->getRegistrationIP()}) был закрыт автором.\n"; + Telegram::send($helpdeskChat, $telegramText); + } + $this->flashFail("succ", tr("ticket_changed"), tr("ticket_changed_comment")); } } diff --git a/Web/Presenters/TopicsPresenter.php b/Web/Presenters/TopicsPresenter.php index dadbb300..04987b6e 100644 --- a/Web/Presenters/TopicsPresenter.php +++ b/Web/Presenters/TopicsPresenter.php @@ -6,6 +6,7 @@ namespace openvk\Web\Presenters; use openvk\Web\Models\Entities\{Topic, Club, Comment, Photo, Video}; use openvk\Web\Models\Repositories\{Topics, Clubs}; +use Nette\InvalidStateException as ISE; final class TopicsPresenter extends OpenVKPresenter { @@ -112,9 +113,6 @@ final class TopicsPresenter extends OpenVKPresenter $video = null; if ($_FILES["_pic_attachment"]["error"] === UPLOAD_ERR_OK) { $album = null; - if ($wall > 0 && $wall === $this->user->id) { - $album = (new Albums())->getUserWallAlbum($wallOwner); - } $photo = Photo::fastMake($this->user->id, $this->postParam("text"), $_FILES["_pic_attachment"], $album); } diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 8770b4de..7d152df4 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -9,7 +9,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, Audios}; +use openvk\Web\Models\Repositories\{Users, Clubs, Albums, Videos, Notes, Vouchers, EmailChangeVerifications, Audios, Faves}; use openvk\Web\Models\Exceptions\InvalidUserNameException; use openvk\Web\Util\Validator; use Chandler\Security\Authenticator; @@ -474,6 +474,7 @@ final class UserPresenter extends OpenVKPresenter public function renderDeleteAvatar() { $this->assertUserLoggedIn(); + $this->assertNoCSRF(); $this->willExecuteWriteAction(); $avatar = $this->user->identity->getAvatarPhoto(); @@ -666,6 +667,7 @@ final class UserPresenter extends OpenVKPresenter "menu_standardo" => "poster", "menu_aplikoj" => "apps", "menu_doxc" => "docs", + "menu_feva" => "fave", ]; foreach ($settings as $checkbox => $setting) { $user->setLeftMenuItemStatus($setting, $this->checkbox($checkbox)); @@ -942,4 +944,65 @@ final class UserPresenter extends OpenVKPresenter $this->redirect("/settings"); } } + + public function renderFave(): void + { + $this->assertUserLoggedIn(); + + $page = (int) ($this->queryParam("p") ?? 1); + $section = $this->queryParam("section") ?? "posts"; + $display_section = "posts"; + $data = null; + $count = 0; + + switch ($section) { + default: + $this->notFound(); + break; + case 'wall': + case 'post': + case 'posts': + $data = (new Faves())->fetchLikesSection($this->user->identity, 'Post', $page); + $count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Post'); + $display_section = "posts"; + break; + case 'comment': + case 'comments': + $data = (new Faves())->fetchLikesSection($this->user->identity, 'Comment', $page); + $count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Comment'); + $display_section = "comments"; + break; + case 'photo': + case 'photos': + $data = (new Faves())->fetchLikesSection($this->user->identity, 'Photo', $page); + $count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Photo'); + $display_section = "photos"; + break; + case 'video': + case 'videos': + $data = (new Faves())->fetchLikesSection($this->user->identity, 'Video', $page); + $count = (new Faves())->fetchLikesSectionCount($this->user->identity, 'Video'); + $display_section = "videos"; + break; + } + + $this->template->data = iterator_to_array($data); + $this->template->count = $count; + $this->template->page = $page; + $this->template->perPage = OPENVK_DEFAULT_PER_PAGE; + $this->template->section = $display_section; + + $this->template->paginatorConf = (object) [ + "page" => $page, + "count" => $count, + "amount" => sizeof($this->template->data), + "perPage" => $this->template->perPage, + "atBottom" => false, + "tidy" => true, + 'pageCount' => ceil($count / $this->template->perPage), + ]; + $this->template->extendedPaginatorConf = clone $this->template->paginatorConf; + $this->template->extendedPaginatorConf->space = 11; + $this->template->paginatorConf->atTop = true; + } } diff --git a/Web/Presenters/VKAPIPresenter.php b/Web/Presenters/VKAPIPresenter.php index 61ee33fa..2ab0818a 100644 --- a/Web/Presenters/VKAPIPresenter.php +++ b/Web/Presenters/VKAPIPresenter.php @@ -273,7 +273,7 @@ final class VKAPIPresenter extends OpenVKPresenter } } - define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100", false); + define("VKAPI_DECL_VER", $this->requestParam("v") ?? "4.100"); try { $res = $handler->{$method}(...$params); diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index b64c2af9..f4a8e898 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -265,8 +265,11 @@ final class WallPresenter extends OpenVKPresenter $this->assertUserLoggedIn(); $this->willExecuteWriteAction(); - $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) - ?? $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4")); + $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)); + + if ($wallOwner === null) { + $this->flashFail("err", tr("failed_to_publish_post"), tr("error_4")); + } if ($wallOwner->isBanned()) { $this->flashFail("err", tr("error"), tr("forbidden")); @@ -406,7 +409,12 @@ final class WallPresenter extends OpenVKPresenter } if ($wall > 0 && $wall !== $this->user->identity->getId()) { - (new WallPostNotification($wallOwner, $post, $this->user->identity))->emit(); + $disturber = $this->user->identity; + if ($anon) { + $disturber = $post->getOwner(); + } + + (new WallPostNotification($wallOwner, $post, $disturber))->emit(); } $excludeMentions = [$this->user->identity->getId()]; @@ -568,8 +576,11 @@ final class WallPresenter extends OpenVKPresenter } $user = $this->user->id; - $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)) - ?? $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4")); + $wallOwner = ($wall > 0 ? (new Users())->get($wall) : (new Clubs())->get($wall * -1)); + + if ($wallOwner === null) { + $this->flashFail("err", tr("failed_to_delete_post"), tr("error_4")); + } if ($wallOwner->isBanned()) { $this->flashFail("err", tr("error"), tr("forbidden")); diff --git a/Web/Presenters/templates/@layout.xml b/Web/Presenters/templates/@layout.xml index 2e8dc828..9e6475fe 100644 --- a/Web/Presenters/templates/@layout.xml +++ b/Web/Presenters/templates/@layout.xml @@ -202,6 +202,7 @@ {_apps} {_my_documents} + {*{_bookmarks_tab}*} {/if} {var $canAccessAdminPanel = $thisUser->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)} diff --git a/Web/Presenters/templates/Audio/List.xml b/Web/Presenters/templates/Audio/List.xml index 1483ce95..2f59d900 100644 --- a/Web/Presenters/templates/Audio/List.xml +++ b/Web/Presenters/templates/Audio/List.xml @@ -9,6 +9,8 @@ {/if} {elseif $mode == 'new'} {_audio_new} + {elseif $mode == 'uploaded'} + {_my_audios_small_uploaded} {elseif $mode == 'popular'} {_audio_popular} {elseif $mode == 'alone_audio'} @@ -32,6 +34,12 @@ +
+ {_my_audios_small} + » + {_my_audios_small_uploaded} +
+
{_audios} » @@ -58,7 +66,7 @@ {block content} {* ref: https://archive.li/P32em *} - {include "bigplayer.xml"} + {include "bigplayer.xml", buttonsShow_summary => $audiosCount > 10} + +
diff --git a/Web/Presenters/templates/Audio/bigplayer.xml b/Web/Presenters/templates/Audio/bigplayer.xml index fcda029a..d30b7a07 100644 --- a/Web/Presenters/templates/Audio/bigplayer.xml +++ b/Web/Presenters/templates/Audio/bigplayer.xml @@ -52,5 +52,9 @@
+ +
+
-
+
diff --git a/Web/Presenters/templates/Audio/tabs.xml b/Web/Presenters/templates/Audio/tabs.xml index 8ff7bb1b..b818d44b 100644 --- a/Web/Presenters/templates/Audio/tabs.xml +++ b/Web/Presenters/templates/Audio/tabs.xml @@ -2,6 +2,7 @@
{_my_music} + {_my_audios_small_uploaded} {* TODO: show upload link as and plusick (little plus) in button up*} {_upload_audio} {_audio_new} @@ -13,7 +14,7 @@ {_new_playlist} - {if !$isMy && $mode !== 'popular' && $mode !== 'new' && $mode != 'alone_audio'} + {if !$isMy && $mode !== 'popular' && $mode !== 'new' && $mode != 'alone_audio' && $mode != 'uploaded'}
{if $ownerId > 0}{_music_user}{else}{_music_club}{/if} diff --git a/Web/Presenters/templates/User/Fave.xml b/Web/Presenters/templates/User/Fave.xml new file mode 100644 index 00000000..29814c39 --- /dev/null +++ b/Web/Presenters/templates/User/Fave.xml @@ -0,0 +1,86 @@ +{extends "../@layout.xml"} + +{block title}{_bookmarks_tab}{/block} + +{block header} + {_bookmarks_tab} +{/block} + +{block wrap} +
+
+
+
+
+ {tr("faves", $count)} {*tr("showing_x_y", $page, $count)*} +
+ + {include "../components/paginator.xml", conf => $paginatorConf} +
+ +
+
+ + {if $count > 0} + {foreach $data as $dat} + {if $dat->isDeleted()} +
+ [deleted] +
+ {else} + {if $section == "posts"} +
+ {include "../components/post.xml", post => $dat, commentSection => true} +
+ {elseif $section == "comments"} +
+ {include "../components/comment.xml", comment => $dat, correctLink => true, no_reply_button => true} +
+ {elseif $section == "photos"} +
+ + {$dat->getDescription()} + +
+ {elseif $section == "videos"} +
+ {include "../components/video.xml", video => $dat} +
+ {/if} + {/if} + {/foreach} + {else} + {include "../components/content_error.xml", description => tr("faves_".$section."_empty_tip")} + {/if} +
+ +
+ +
+ {include "../components/paginator.xml", conf => $extendedPaginatorConf} +
+
+
+
+
+{/block} diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index 5ae6ee4b..10e0cd1e 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -120,6 +120,10 @@
+ {_bookmarks_tab} + • + {_s_posts} +

{_you_can_also} {_delete_your_page}.
@@ -595,6 +599,17 @@ + + + + + + + + +
+ {_ui_settings_window} +

{_ui_settings_sidebar}

@@ -696,6 +711,17 @@ {_my_documents} + + + + + + {_bookmarks_tab} + + isDeleted()} {var $link = "/photo" . ($attachment->isAnonymous() ? ("s/" . base_convert((string) $attachment->getId(), 10, 32)) : $attachment->getPrettyId())} - {$attachment->getDescription()} + {$attachment->getDescription()} {else} diff --git a/Web/Presenters/templates/components/comment.xml b/Web/Presenters/templates/components/comment.xml index f9bdee6c..c4e0de08 100644 --- a/Web/Presenters/templates/components/comment.xml +++ b/Web/Presenters/templates/components/comment.xml @@ -60,8 +60,9 @@ {_report} {/if}
+ {var $isLiked = $comment->hasLikeFrom($thisUser)} -
+
{if $likesCount > 0}{$likesCount}{/if}
diff --git a/Web/Themes/Themepack.php b/Web/Themes/Themepack.php index be2b2c11..01e41a3b 100644 --- a/Web/Themes/Themepack.php +++ b/Web/Themes/Themepack.php @@ -128,6 +128,6 @@ class Themepack throw new Exceptions\IncompatibleThemeException("Theme is built for newer OVK (themeEngine" . $manifest->openvk_version . ")"); } - return new static($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata); + return new Themepack($manifest->id, $manifest->version, (bool) ($manifest->inherit_master ?? true), (bool) ($manifest->override_templates ?? false), (bool) ($manifest->enabled ?? true), (object) $manifest->metadata); } } diff --git a/Web/Themes/Themepacks.php b/Web/Themes/Themepacks.php index 1907553a..7acc9dcd 100644 --- a/Web/Themes/Themepacks.php +++ b/Web/Themes/Themepacks.php @@ -88,25 +88,6 @@ class Themepacks implements \ArrayAccess /* /ArrayAccess */ - public function install(string $archivePath): bool - { - if (!file_exists($archivePath)) { - return false; - } - - $tmpDir = mkdir(tempnam(OPENVK_ROOT . "/tmp/themepack_artifacts/", "themex_")); - try { - $archive = new \CabArchive($archivePath); - $archive->extract($tmpDir); - - return $this->installUnpacked($tmpDir); - } catch (\Exception $e) { - return false; - } finally { - rmdir($tmpDir); - } - } - public function uninstall(string $id): bool { if (!isset($loadedThemepacks[$id])) { diff --git a/Web/Util/Bitmask.php b/Web/Util/Bitmask.php index c34fcdc6..c8fb7504 100644 --- a/Web/Util/Bitmask.php +++ b/Web/Util/Bitmask.php @@ -78,7 +78,7 @@ class Bitmask } elseif (gettype($key) === "int") { $this->setByOffset($key, $data); } else { - throw new TypeError("Key must be either offset (int) or a string index"); + throw new \TypeError("Key must be either offset (int) or a string index"); } return $this; @@ -89,7 +89,7 @@ class Bitmask if (gettype($key) === "string") { $key = $this->getOffsetByKey($key); } elseif (gettype($key) !== "int") { - throw new TypeError("Key must be either offset (int) or a string index"); + throw new \TypeError("Key must be either offset (int) or a string index"); } return $this->length === 1 ? $this->getBoolByOffset($key) : $this->getNumberByOffset($key); diff --git a/Web/Util/DateTime.php b/Web/Util/DateTime.php index 6a82fe88..102312ed 100644 --- a/Web/Util/DateTime.php +++ b/Web/Util/DateTime.php @@ -66,6 +66,7 @@ class DateTime case static::RELATIVE_FORMAT_LOWER: return $this->zmdate(); case static::RELATIVE_FORMAT_SHORT: + default: return ""; } } diff --git a/Web/Util/Makima/Makima.php b/Web/Util/Makima/Makima.php index 92bb277d..e903ad65 100644 --- a/Web/Util/Makima/Makima.php +++ b/Web/Util/Makima/Makima.php @@ -188,9 +188,9 @@ class Makima $tries = []; - $firstLine; - $secondLine; - $thirdLine; + $firstLine = null; + $secondLine = null; + $thirdLine = null; # Try one line: $tries[$firstLine = $count] = [$this->calculateMultiThumbsHeight($ratiosCropped, $maxWidth, $marginWidth)]; @@ -234,7 +234,7 @@ class Makima } } - if (!$optimalConfiguration || $confDigff < $optimalDifference) { + if (!$optimalConfiguration || $confDiff < $optimalDifference) { $optimalConfiguration = $config; $optimalDifference = $confDiff; } diff --git a/Web/di.yml b/Web/di.yml index b9821fec..55ac4483 100644 --- a/Web/di.yml +++ b/Web/di.yml @@ -54,5 +54,6 @@ services: - openvk\Web\Models\Repositories\BannedLinks - openvk\Web\Models\Repositories\ChandlerGroups - openvk\Web\Models\Repositories\Documents + - openvk\Web\Models\Repositories\Faves - openvk\Web\Presenters\MaintenancePresenter - openvk\Web\Presenters\NoSpamPresenter diff --git a/Web/routes.yml b/Web/routes.yml index 7b42dfbe..6a3598bb 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -203,6 +203,8 @@ routes: handler: "Audio->upload" - url: "/audios{num}" handler: "Audio->list" + - url: "/audios/uploaded" + handler: "Audio->uploaded" - url: "/audio{num}/listen" handler: "Audio->listen" - url: "/audio{num}_{num}" @@ -415,6 +417,8 @@ routes: handler: "InternalAPI->getPostTemplate" - url: "/tour" handler: "About->tour" + - url: "/fave" + handler: "User->fave" - url: "/{?shortCode}" handler: "UnknownTextRouteStrategy->delegate" placeholders: diff --git a/Web/static/css/audios.css b/Web/static/css/audios.css index cd49b006..89bce6f5 100644 --- a/Web/static/css/audios.css +++ b/Web/static/css/audios.css @@ -52,6 +52,34 @@ height: 46px; } +.bigPlayer .bigPlayerWrapper .absoluteButtons { + position: absolute; + bottom: 0; + right: 0; +} + +.bigPlayer .bigPlayerWrapper .absoluteButtons > div { + width: 8px; + height: 8px; + font-size: 9px; + + display: flex; + align-items: center; + justify-content: center; + background: #ebebeb; + border: 1px solid #c3c3c3; + border-bottom: unset; + border-right: unset; + color: #c3c3c3; + cursor: pointer; + user-select: none; +} + +.bigPlayer .bigPlayerWrapper .absoluteButtons > div:active { + background: #c3c3c3; + color: #ebebeb; +} + /* Play button and arrows */ .bigPlayer .playButtons { display: flex; @@ -313,7 +341,7 @@ opacity: 0.8; } -.audioEmbed.processed { +.audioEmbed.processed .playerButton { filter: opacity(0.6); } diff --git a/Web/static/css/bsdn.css b/Web/static/css/bsdn.css index 09575e92..c429ebbb 100644 --- a/Web/static/css/bsdn.css +++ b/Web/static/css/bsdn.css @@ -99,6 +99,7 @@ button.bsdn_playButton { padding-left: 0; font-size: 22px; cursor: pointer; + width: 22px; } .bsdn_fullScreenButton, .bsdn_repeatButton { diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 81601b67..0332d278 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -11,6 +11,7 @@ body { font-size: 11px; word-break: break-word; word-wrap: break-word; + line-height: 1.19; } body, .ovk-fullscreen-dimmer, .ovk-photo-view-dimmer { @@ -2919,7 +2920,6 @@ a.poll-retract-vote { position: relative; } -/* не говновёрстка, а пиксель-пёрфект) */ .page_header.search_expanded.search_expanded_at_all #search_and_one_more_wrapper { width: 547px; } @@ -3039,6 +3039,10 @@ a.poll-retract-vote { gap: 1px; } +.verticalGrayTabsPad { + padding: 0px 0px 0px 8px; +} + .searchList hr, .verticalGrayTabs hr { width: 153px; margin-left: 0px; @@ -4278,3 +4282,7 @@ hr { height: 30px; background-position-y: 9px; } + +.deleted_mark_average { + padding: 5px 61px; +} diff --git a/Web/static/js/al_feed.js b/Web/static/js/al_feed.js index dd833355..66eeddf1 100644 --- a/Web/static/js/al_feed.js +++ b/Web/static/js/al_feed.js @@ -68,7 +68,7 @@ u(document).on('click', '#__feed_settings_link', (e) => { ` MessageBox(tr("feed_settings"), body, [tr("close")], [Function.noop]) - u('.ovk-diag-body').attr('style', 'padding:0px;height: 255px;') + u('.ovk-diag-body').attr('style', 'padding:0px;height: 255px;overflow: hidden;') async function __switchTab(tab) { @@ -84,8 +84,6 @@ u(document).on('click', '#__feed_settings_link', (e) => { const CURRENT_PERPAGE = Number(__temp_url.searchParams.get('posts') ?? 10) const CURRENT_PAGE = Number(__temp_url.searchParams.get('p') ?? 1) const CURRENT_RETURN_BANNED = Number(__temp_url.searchParams.get('return_banned') ?? 0) - const CURRENT_AUTO_SCROLL = Number(localStorage.getItem('ux.auto_scroll') ?? 1) - const CURRENT_DISABLE_AJAX = Number(localStorage.getItem('ux.disable_ajax_routing') ?? 0) const COUNT = [1, 5, 10, 20, 30, 40, 50] u('#_feed_settings_container #__content').html(` @@ -116,26 +114,6 @@ u(document).on('click', '#__feed_settings_link', (e) => { - - - - - - - - @@ -299,3 +277,32 @@ u(document).on('change', `input[data-act='localstorage_item']`, (e) => { localStorage.setItem(e.target.name, Number(e.target.checked)) }) + +function openJsSettings() { + const CURRENT_AUTO_SCROLL = Number(localStorage.getItem('ux.auto_scroll') ?? 1) + const CURRENT_DISABLE_AJAX = Number(localStorage.getItem('ux.disable_ajax_routing') ?? 0) + + u("#_js_settings td").remove() + u("#_js_settings").append(` + + + + + + + + + `) +} diff --git a/Web/static/js/al_music.js b/Web/static/js/al_music.js index 9e166d58..9f3247a7 100644 --- a/Web/static/js/al_music.js +++ b/Web/static/js/al_music.js @@ -54,6 +54,9 @@ window.player = new class { current_track_id = 0 tracks = [] + // time type: + // 0 - shows remaining time before end + // 1 - shows full track time get timeType() { return localStorage.getItem('audio.timeType') ?? 0 } @@ -62,6 +65,7 @@ window.player = new class { localStorage.setItem('audio.timeType', value) } + //
- - - - - -
- - - - - -
+ + + + + +
+ + + + + +