From 198bf7472d3170279389eb5537ca3456892ada35 Mon Sep 17 00:00:00 2001 From: veselcraft Date: Fri, 13 Dec 2024 16:23:33 +0300 Subject: [PATCH 1/5] fix(datetime): adjust to timezone --- Web/Util/DateTime.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Web/Util/DateTime.php b/Web/Util/DateTime.php index b90de19d..991d8032 100644 --- a/Web/Util/DateTime.php +++ b/Web/Util/DateTime.php @@ -1,5 +1,6 @@ timestamp); $now = date_create(); $diff = date_diff($now, $then); + + $sessionOffset = intval(Session::i()->get("_timezoneOffset")); if($diff->invert === 0) return ovk_strftime_safe("%e %B %Y ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp); - if($this->timestamp >= strtotime("midnight")) { # Today + if(($this->timestamp + $sessionOffset) >= (strtotime("midnight") + $sessionOffset)) { # Today if($diff->h >= 1) return tr("time_today") . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp); else if($diff->i < 2) return tr("time_just_now"); else return $diff->i === 5 ? tr("time_exactly_five_minutes_ago") : tr("time_minutes_ago", $diff->i); - } else if($this->timestamp >= strtotime("-1day midnight")) { # Yesterday + } else if(($this->timestamp + $sessionOffset) >= (strtotime("-1day midnight") + $sessionOffset)) { # Yesterday return tr("time_yesterday") . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp); } else if(ovk_strftime_safe("%Y", $this->timestamp) === ovk_strftime_safe("%Y", time())) { # In this year return ovk_strftime_safe("%e %h ", $this->timestamp) . tr("time_at_sp") . ovk_strftime_safe(" %R", $this->timestamp); From 0a1f717b45601bcae7f14c3efd781b5e5f349292 Mon Sep 17 00:00:00 2001 From: mrilyew <99399973+mrilyew@users.noreply.github.com> Date: Fri, 13 Dec 2024 16:43:34 +0300 Subject: [PATCH 2/5] fix(xss): fix #1181 --- Web/Presenters/templates/Audio/Upload.xml | 6 +++--- Web/static/js/al_music.js | 8 ++++---- Web/static/js/al_wall.js | 2 +- Web/static/js/router.js | 8 ++++++++ 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/Web/Presenters/templates/Audio/Upload.xml b/Web/Presenters/templates/Audio/Upload.xml index 8433ea6e..7be039d9 100644 --- a/Web/Presenters/templates/Audio/Upload.xml +++ b/Web/Presenters/templates/Audio/Upload.xml @@ -164,11 +164,11 @@ ${tr('performer')}: - + ${tr('audio_name')}: - + ${tr('genre')}: @@ -178,7 +178,7 @@ ${tr('lyrics')}: - + diff --git a/Web/static/js/al_music.js b/Web/static/js/al_music.js index 8b4b0a82..d9029763 100644 --- a/Web/static/js/al_music.js +++ b/Web/static/js/al_music.js @@ -1306,12 +1306,12 @@ u(document).on("click", ".musicIcon.edit-icon", (e) => { MessageBox(tr("edit_audio"), `
${tr("performer")} - +
${tr("audio_name")} - +
@@ -1359,7 +1359,7 @@ u(document).on("click", ".musicIcon.edit-icon", (e) => { e.target.setAttribute("data-performer", escapeHtml(response.new_info.performer)) e.target.setAttribute("data-title", escapeHtml(response.new_info.name)) - e.target.setAttribute("data-lyrics", response.new_info.lyrics_unformatted) + e.target.setAttribute("data-lyrics", escapeHtml(response.new_info.lyrics_unformatted)) e.target.setAttribute("data-explicit", Number(response.new_info.explicit)) e.target.setAttribute("data-searchable", Number(!response.new_info.unlisted)) player.setAttribute("data-genre", response.new_info.genre) @@ -1374,7 +1374,7 @@ u(document).on("click", ".musicIcon.edit-icon", (e) => { } else { player.insertAdjacentHTML("beforeend", `
- ${response.new_info.lyrics} + ${escapeHtml(response.new_info.lyrics)}
`) diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index 95aeebeb..06420ceb 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -2579,7 +2579,7 @@ async function changeStatus() { document.querySelector("#page_status_text").innerHTML = `[ ${tr("change_status")} ]`; document.querySelector("#page_status_text").className = "edit_link page_status_edit_button"; } else { - document.querySelector("#page_status_text").innerHTML = status; + document.querySelector("#page_status_text").innerHTML = escapeHtml(status); document.querySelector("#page_status_text").className = "page_status page_status_edit_button"; } diff --git a/Web/static/js/router.js b/Web/static/js/router.js index d72249c5..8ef87123 100644 --- a/Web/static/js/router.js +++ b/Web/static/js/router.js @@ -234,6 +234,10 @@ window.router = new class { } u(document).on('click', 'a', async (e) => { + if(e.defaultPrevented) { + return + } + const target = u(e.target).closest('a') const dom_url = target.attr('href') const id = target.attr('id') @@ -289,6 +293,10 @@ u(document).on('click', 'a', async (e) => { }) u(document).on('submit', 'form', async (e) => { + if(e.defaultPrevented) { + return + } + if(u('#ajloader').hasClass('shown')) { e.preventDefault() return From 29f4de2dab4b56ffc60de7f3acc0c7a08abca324 Mon Sep 17 00:00:00 2001 From: veselcraft Date: Fri, 13 Dec 2024 17:05:26 +0300 Subject: [PATCH 3/5] fix(video): tweak ffmpeg args --- Web/Models/shell/processVideo.ps1 | 2 +- Web/Models/shell/processVideo.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Web/Models/shell/processVideo.ps1 b/Web/Models/shell/processVideo.ps1 index 59f53c72..27bc2e06 100644 --- a/Web/Models/shell/processVideo.ps1 +++ b/Web/Models/shell/processVideo.ps1 @@ -13,7 +13,7 @@ Move-Item $file $temp # video stub logic was implicitly deprecated, so we start processing at once ffmpeg -i $temp -ss 00:00:01.000 -vframes 1 "$dir$hashT/$hash.gif" -ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=480:-1,setsar=1" -y $temp2 +ffmpeg -i $temp -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=iw*min(1\,if(gt(iw\,ih)\,640/iw\,(640*sar)/ih)):(floor((ow/dar)/2))*2" -y $temp2 Move-Item $temp2 "$dir$hashT/$hash.mp4" Remove-Item $temp diff --git a/Web/Models/shell/processVideo.sh b/Web/Models/shell/processVideo.sh index f906544d..9fc74b98 100644 --- a/Web/Models/shell/processVideo.sh +++ b/Web/Models/shell/processVideo.sh @@ -3,7 +3,7 @@ tmpfile="$RANDOM-$(date +%s%N)" cp $2 "/tmp/vid_$tmpfile.bin" nice ffmpeg -i "/tmp/vid_$tmpfile.bin" -ss 00:00:01.000 -vframes 1 $3${4:0:2}/$4.gif -nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=480:-1,setsar=1" -y "/tmp/ffmOi$tmpfile.mp4" +nice -n 20 ffmpeg -i "/tmp/vid_$tmpfile.bin" -c:v libx264 -q:v 7 -c:a libmp3lame -q:a 4 -tune zerolatency -vf "scale=iw*min(1\,if(gt(iw\,ih)\,640/iw\,(640*sar)/ih)):(floor((ow/dar)/2))*2" -y "/tmp/ffmOi$tmpfile.mp4" rm -rf $3${4:0:2}/$4.mp4 mv "/tmp/ffmOi$tmpfile.mp4" $3${4:0:2}/$4.mp4 From 2e70a262835cb28a40c0358a1bbcacd5efc1c43d Mon Sep 17 00:00:00 2001 From: n1rwana Date: Fri, 13 Dec 2024 17:11:26 +0300 Subject: [PATCH 4/5] feat(api): reports (#959) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * API для отправки жалобы * make compatible with vk api --------- Co-authored-by: mrilyew <99399973+mrilyew@users.noreply.github.com> --- VKAPI/Handlers/Reports.php | 53 +++++++++++++++++++ Web/Presenters/ReportPresenter.php | 3 ++ .../templates/Report/ViewContent.xml | 2 +- 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 VKAPI/Handlers/Reports.php diff --git a/VKAPI/Handlers/Reports.php b/VKAPI/Handlers/Reports.php new file mode 100644 index 00000000..3a5a1d19 --- /dev/null +++ b/VKAPI/Handlers/Reports.php @@ -0,0 +1,53 @@ +requireUser(); + $this->willExecuteWriteAction(); + + $allowed_types = ["post", "photo", "video", "group", "comment", "note", "app", "user", "audio"]; + if($type == "" || !in_array($type, $allowed_types)) { + $this->fail(100, "One of the parameters specified was missing or invalid: type should be ".implode(", ", $allowed_types)); + } + + if($owner_id <= 0) { + $this->fail(100, "One of the parameters specified was missing or invalid: Bad input"); + } + + if(mb_strlen($comment) === 0) { + $this->fail(100, "One of the parameters specified was missing or invalid: Comment can't be empty"); + } + + if($type == "user" && $owner_id == $this->getUser()->getId()) { + return 1; + } + + if($this->getUser()->isBannedInSupport()) { + return 0; + } + + if(sizeof(iterator_to_array((new ReportsRepo)->getDuplicates($type, $owner_id, NULL, $this->getUser()->getId()))) > 0) { + return 1; + } + + try { + $report = new Report; + $report->setUser_id($this->getUser()->getId()); + $report->setTarget_id($owner_id); + $report->setType($type); + $report->setReason($comment); + $report->setCreated(time()); + + $report->save(); + } catch(\Throwable $e) { + $this->fail(-1, "Unknown error failed"); + } + + return 1; + } +} diff --git a/Web/Presenters/ReportPresenter.php b/Web/Presenters/ReportPresenter.php index a627efa4..dfd2b962 100644 --- a/Web/Presenters/ReportPresenter.php +++ b/Web/Presenters/ReportPresenter.php @@ -89,6 +89,9 @@ final class ReportPresenter extends OpenVKPresenter if(!$id) exit(json_encode([ "error" => tr("error_segmentation") ])); + + if ($this->queryParam("type") === "user" && $id === $this->user->id) + exit(json_encode([ "error" => "You can't report yourself" ])); if(in_array($this->queryParam("type"), ["post", "photo", "video", "group", "comment", "note", "app", "user", "audio"])) { if (count(iterator_to_array($this->reports->getDuplicates($this->queryParam("type"), $id, NULL, $this->user->id))) <= 0) { diff --git a/Web/Presenters/templates/Report/ViewContent.xml b/Web/Presenters/templates/Report/ViewContent.xml index 1f5918d5..677dc37b 100644 --- a/Web/Presenters/templates/Report/ViewContent.xml +++ b/Web/Presenters/templates/Report/ViewContent.xml @@ -16,7 +16,7 @@ {elseif $type == "group" || $type == "user"} {include "../components/group.xml", group => $object, isUser => $type == "user"} {elseif $type == "comment"} - {include "../components/comment.xml", comment => $object, timeOnly => true, linkWithPost => true} + {include "../components/comment.xml", comment => $object, timeOnly => true, correctLink => true} {elseif $type == "note"} {include "./content/note.xml", note => $object} {elseif $type == "app"} From bec9079e363b7622988f8a8a2bab16da2dd3e8e8 Mon Sep 17 00:00:00 2001 From: mrilyew <99399973+mrilyew@users.noreply.github.com> Date: Fri, 13 Dec 2024 17:51:10 +0300 Subject: [PATCH 5/5] feat(privacy): blacklist v2 (#1183) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Перенос ветки blacklist (#900) * Blacklist * Config * upd * Added restrictions in the users.get method * ok * Update en.strings * ok 2.0 --------- Co-authored-by: Vladimir Barinov * Create 00038-blacklist.sql * typo xd * Blacklists: Make it barely work (xd) * БЛЯЯЯЯЯЯЯЯТЬ * remove all * account.ban, account.unban, account.getBanned * rewrite ui * add link * add ignore button to blacklisted users * fields blacklisted_by_me, blacklisted * ad ability to blacklist when you ar blacklisted --------- Co-authored-by: n1rwana Co-authored-by: Vladimir Barinov Co-authored-by: n1rwana --- VKAPI/Handlers/Account.php | 68 ++++++++++++ VKAPI/Handlers/Users.php | 14 +++ Web/Models/Entities/User.php | 103 +++++++++++++++++- Web/Presenters/GroupPresenter.php | 2 +- Web/Presenters/PhotosPresenter.php | 3 + Web/Presenters/UserPresenter.php | 30 ++++- Web/Presenters/templates/User/Settings.xml | 28 ++++- Web/Presenters/templates/User/View.xml | 3 + Web/Presenters/templates/User/blacklisted.xml | 42 +++++++ .../templates/User/blacklisted_pov.xml | 40 +++++++ Web/Presenters/templates/User/private.xml | 1 + Web/static/css/main.css | 14 +++ Web/static/js/al_wall.js | 39 +++++++ Web/static/js/router.js | 2 +- install/sqls/00052-blacklist.sql | 8 ++ locales/en.strings | 23 ++++ locales/ru.strings | 26 +++++ openvk-example.yml | 3 + 18 files changed, 439 insertions(+), 10 deletions(-) create mode 100644 Web/Presenters/templates/User/blacklisted.xml create mode 100644 Web/Presenters/templates/User/blacklisted_pov.xml create mode 100644 install/sqls/00052-blacklist.sql diff --git a/VKAPI/Handlers/Account.php b/VKAPI/Handlers/Account.php index dcdf18c0..c1f3ef23 100644 --- a/VKAPI/Handlers/Account.php +++ b/VKAPI/Handlers/Account.php @@ -228,4 +228,72 @@ final class Account extends VKAPIRequestHandler return (object) ['votes' => $this->getUser()->getCoins()]; } + + function ban(int $owner_id): int + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($owner_id < 0) + return 1; + + if($owner_id == $this->getUser()->getId()) + $this->fail(15, "Access denied: cannot blacklist yourself"); + + $config_limit = OPENVK_ROOT_CONF['openvk']['preferences']['blacklists']['limit'] ?? 100; + $user_blocks = $this->getUser()->getBlacklistSize(); + if(($user_blocks + 1) > $config_limit) + $this->fail(-7856, "Blacklist limit exceeded"); + + $entity = get_entity_by_id($owner_id); + if(!$entity || $entity->isDeleted()) + return 0; + + if($entity->isBlacklistedBy($this->getUser())) + return 1; + + $this->getUser()->addToBlacklist($entity); + + return 1; + } + + function unban(int $owner_id): int + { + $this->requireUser(); + $this->willExecuteWriteAction(); + + if($owner_id < 0) + return 1; + + if($owner_id == $this->getUser()->getId()) + return 1; + + $entity = get_entity_by_id($owner_id); + if(!$entity || $entity->isDeleted()) + return 0; + + if(!$entity->isBlacklistedBy($this->getUser())) + return 1; + + $this->getUser()->removeFromBlacklist($entity); + + return 1; + } + + function getBanned(int $offset = 0, int $count = 100, string $fields = ""): object + { + $this->requireUser(); + + $result = (object)[ + 'count' => $this->getUser()->getBlacklistSize(), + 'items' => [], + ]; + $banned = $this->getUser()->getBlacklist($offset, $count); + foreach($banned as $ban) { + if(!$ban) continue; + $result->items[] = $ban->toVkApiStruct($this->getUser(), $fields); + } + + return $result; + } } diff --git a/VKAPI/Handlers/Users.php b/VKAPI/Handlers/Users.php index 704823be..7dc68f3b 100644 --- a/VKAPI/Handlers/Users.php +++ b/VKAPI/Handlers/Users.php @@ -266,6 +266,20 @@ final class Users extends VKAPIRequestHandler case 'nickname': $response[$i]->nickname = $usr->getPseudo(); break; + case 'blacklisted_by_me': + if(!$authuser) { + continue; + } + + $response[$i]->blacklisted_by_me = (int)$usr->isBlacklistedBy($this->getUser()); + break; + case 'blacklisted': + if(!$authuser) { + continue; + } + + $response[$i]->blacklisted = (int)$this->getUser()->isBlacklistedBy($usr); + break; } } diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index a0574f3d..61ea120b 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -5,7 +5,7 @@ use openvk\Web\Themes\{Themepack, Themepacks}; use openvk\Web\Util\DateTime; use openvk\Web\Models\RowModel; use openvk\Web\Models\Entities\{Photo, Message, Correspondence, Gift, Audio}; -use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos}; +use openvk\Web\Models\Repositories\{Applications, Bans, Comments, Notes, Posts, Users, Clubs, Albums, Gifts, Notifications, Videos, Photos}; use openvk\Web\Models\Exceptions\InvalidUserNameException; use Nette\Database\Table\ActiveRow; use Chandler\Database\DatabaseConnection; @@ -511,7 +511,7 @@ class User extends RowModel else if($user->getId() === $this->getId()) return true; - if($permission != "messages.write" && !$this->canBeViewedBy($user)) + if(/*$permission != "messages.write" && */!$this->canBeViewedBy($user, true)) return false; switch($permStatus) { @@ -1228,11 +1228,16 @@ class User extends RowModel return (bool) $this->getRecord()->activated; } + function isAdmin(): bool + { + return $this->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL); + } + function isDead(): bool { return $this->onlineStatus() == 2; } - + function getUnbanTime(): ?string { $ban = (new Bans)->get((int) $this->getRecord()->block_reason); @@ -1289,17 +1294,21 @@ class User extends RowModel return $this->getRecord()->profile_type; } - function canBeViewedBy(?User $user = NULL): bool + function canBeViewedBy(?User $user = NULL, bool $blacklist_check = true): bool { if(!is_null($user)) { if($this->getId() == $user->getId()) { return true; } - if($user->getChandlerUser()->can("access")->model("admin")->whichBelongsTo(NULL)) { + if($user->isAdmin() && !(OPENVK_ROOT_CONF['openvk']['preferences']['blacklists']['applyToAdmins'] ?? true)) { return true; } + if($blacklist_check && ($this->isBlacklistedBy($user) || $user->isBlacklistedBy($this))) { + return false; + } + if($this->getProfileType() == 0) { return true; } else { @@ -1409,6 +1418,20 @@ class User extends RowModel case 'real_id': $res->real_id = $this->getRealId(); break; + case "blacklisted_by_me": + if(!$user) { + continue; + } + + $res->blacklisted_by_me = (int)$this->isBlacklistedBy($user); + break; + case "blacklisted": + if(!$user) { + continue; + } + + $res->blacklisted = (int)$user->isBlacklistedBy($this); + break; } } @@ -1486,6 +1509,76 @@ class User extends RowModel return DatabaseConnection::i()->getContext()->table("ignored_sources")->where("owner", $this->getId())->count(); } + function isBlacklistedBy(?User $user = NULL): bool + { + if(!$user) + return false; + + $ctx = DatabaseConnection::i()->getContext(); + $data = [ + "author" => $user->getId(), + "target" => $this->getRealId(), + ]; + + $sub = $ctx->table("blacklist_relations")->where($data); + return $sub->count() > 0; + } + + function addToBlacklist(?User $user) + { + DatabaseConnection::i()->getContext()->table("blacklist_relations")->insert([ + "author" => $this->getRealId(), + "target" => $user->getRealId(), + "created" => time(), + ]); + + DatabaseConnection::i()->getContext()->table("subscriptions")->where([ + "follower" => $user->getId(), + "model" => static::class, + "target" => $this->getId(), + ])->delete(); + + DatabaseConnection::i()->getContext()->table("subscriptions")->where([ + "follower" => $this->getId(), + "model" => static::class, + "target" => $user->getId(), + ])->delete(); + + return true; + } + + function removeFromBlacklist(?User $user): bool + { + DatabaseConnection::i()->getContext()->table("blacklist_relations")->where([ + "author" => $this->getRealId(), + "target" => $user->getRealId(), + ])->delete(); + + return true; + } + + function getBlacklist(int $offset = 0, int $limit = 10) + { + $sources = DatabaseConnection::i()->getContext()->table("blacklist_relations")->where("author", $this->getId())->limit($limit, $offset)->order('created ASC'); + $output_array = []; + + foreach($sources as $source) { + $entity_id = (int)$source->target ; + $entity = (new Users)->get($entity_id); + if(!$entity) + continue; + + $output_array[] = $entity; + } + + return $output_array; + } + + function getBlacklistSize() + { + return DatabaseConnection::i()->getContext()->table("blacklist_relations")->where("author", $this->getId())->count(); + } + use Traits\TBackDrops; use Traits\TSubscribable; use Traits\TAudioStatuses; diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 61b6d67c..fd0e9a3d 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -119,7 +119,7 @@ final class GroupPresenter extends OpenVKPresenter $this->template->paginatorConf = (object) [ "count" => $this->template->count, "page" => $this->queryParam("p") ?? 1, - "amount" => NULL, + "amount" => 10, "perPage" => OPENVK_DEFAULT_PER_PAGE, ]; } diff --git a/Web/Presenters/PhotosPresenter.php b/Web/Presenters/PhotosPresenter.php index cec426b8..029d2aac 100644 --- a/Web/Presenters/PhotosPresenter.php +++ b/Web/Presenters/PhotosPresenter.php @@ -27,6 +27,7 @@ final class PhotosPresenter extends OpenVKPresenter if(!$user) $this->notFound(); if (!$user->getPrivacyPermission('photos.read', $this->user->identity ?? NULL)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); + $this->template->albums = $this->albums->getUserAlbums($user, (int)($this->queryParam("p") ?? 1)); $this->template->count = $this->albums->getUserAlbumsCount($user); $this->template->owner = $user; @@ -161,8 +162,10 @@ final class PhotosPresenter extends OpenVKPresenter { $photo = $this->photos->getByOwnerAndVID($ownerId, $photoId); if(!$photo || $photo->isDeleted()) $this->notFound(); + if(!$photo->canBeViewedBy($this->user->identity)) $this->flashFail("err", tr("forbidden"), tr("forbidden_comment")); + if(!is_null($this->queryParam("from"))) { if(preg_match("%^album([0-9]++)$%", $this->queryParam("from"), $matches) === 1) { $album = $this->albums->get((int) $matches[1]); diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 6120bfec..21f837b6 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -22,17 +22,29 @@ final class UserPresenter extends OpenVKPresenter function __construct(Users $users) { $this->users = $users; - + parent::__construct(); } function renderView(int $id): void { $user = $this->users->get($id); + if(!$user || $user->isDeleted() || !$user->canBeViewedBy($this->user->identity)) { if(!is_null($user) && $user->isDeactivated()) { $this->template->_template = "User/deactivated.xml"; + $this->template->user = $user; + } else if($this->user->identity->isBlacklistedBy($user)) { + $this->template->_template = "User/blacklisted.xml"; + + $this->template->blacklist_status = $user->isBlacklistedBy($this->user->identity); + $this->template->ignore_status = $user->isIgnoredBy($this->user->identity); + $this->template->user = $user; + } else if($user->isBlacklistedBy($this->user->identity)) { + $this->template->_template = "User/blacklisted_pov.xml"; + + $this->template->ignore_status = $user->isIgnoredBy($this->user->identity); $this->template->user = $user; } else if(!is_null($user) && !$user->canBeViewedBy($this->user->identity)) { $this->template->_template = "User/private.xml"; @@ -57,6 +69,7 @@ final class UserPresenter extends OpenVKPresenter if($id !== $this->user->id) { $this->template->ignore_status = $user->isIgnoredBy($this->user->identity); + $this->template->blacklist_status = $user->isBlacklistedBy($this->user->identity); } } } @@ -578,7 +591,7 @@ final class UserPresenter extends OpenVKPresenter $this->flash("succ", tr("changes_saved"), tr("changes_saved_comment")); } $this->template->mode = in_array($this->queryParam("act"), [ - "main", "security", "privacy", "finance", "finance.top-up", "interface" + "main", "security", "privacy", "finance", "finance.top-up", "interface", "blacklist" ]) ? $this->queryParam("act") : "main"; @@ -591,6 +604,19 @@ final class UserPresenter extends OpenVKPresenter $this->template->qrCodeType = substr($qrCode[0], 5); $this->template->qrCodeData = $qrCode[1]; + } else if($this->template->mode === "blacklist") { + $page = (int)($this->queryParam('p') ?? 1); + $count = 10; + $offset = ($page - 1) * $count; + + $this->template->blSize = $this->user->identity->getBlacklistSize(); + $this->template->blItems = $this->user->identity->getBlacklist($offset, $count); + $this->template->paginatorConf = (object) [ + "count" => $this->template->blSize, + "page" => $page, + "amount" => sizeof($this->template->blItems), + "perPage" => OPENVK_DEFAULT_PER_PAGE, + ]; } $this->template->user = $user; diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index c08c9d2c..e7f4b5ac 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -13,6 +13,7 @@ {var $isFinance = $mode === 'finance'} {var $isFinanceTU = $mode === 'finance.top-up'} {var $isInterface = $mode === 'interface'} +{var $isBl = $mode === 'blacklist'}
@@ -24,6 +25,9 @@ + @@ -713,7 +717,29 @@ - + {elseif $isBl} + {if $blSize < 1} + {include "../components/error.xml", description => tr("bl_count_zero_desc")} + {else} +

{tr("bl_count", $blSize)}.

+ + {include "../components/paginator.xml", conf => $paginatorConf} + {/if} {/if}
diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index 10801053..cbcb1f1f 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -165,6 +165,9 @@ {/if} + {_bl_add} + {* 4 admins *} + {_bl_remove} {_report} {if !$ignore_status}{_ignore_user}{else}{_unignore_user}{/if} diff --git a/Web/Presenters/templates/User/blacklisted.xml b/Web/Presenters/templates/User/blacklisted.xml new file mode 100644 index 00000000..e062b6f3 --- /dev/null +++ b/Web/Presenters/templates/User/blacklisted.xml @@ -0,0 +1,42 @@ +{extends "../@layout.xml"} +{block title}{$user->getCanonicalName()}{/block} + +{block header} + {$user->getCanonicalName()} + +{/block} + +{block content} + + +
+
+
+
+

{$user->getFullName()}

+
+
+
+ {var $m = $user->isFemale() ? "f" : "m"} + {tr("limited_access_to_page_$m", $user->getFirstName())} +
+
+
+{/block} diff --git a/Web/Presenters/templates/User/blacklisted_pov.xml b/Web/Presenters/templates/User/blacklisted_pov.xml new file mode 100644 index 00000000..1bf85653 --- /dev/null +++ b/Web/Presenters/templates/User/blacklisted_pov.xml @@ -0,0 +1,40 @@ +{extends "../@layout.xml"} +{block title}{$user->getCanonicalName()}{/block} + +{block header} + {$user->getCanonicalName()} + +{/block} + +{block content} + + +
+
+
+
+

{$user->getFullName()}

+
+
+
+ {tr("you_blacklisted", $user->getMorphedName("genitive", false))}. +
+
+
+{/block} diff --git a/Web/Presenters/templates/User/private.xml b/Web/Presenters/templates/User/private.xml index 5338cbcc..2633ec8e 100644 --- a/Web/Presenters/templates/User/private.xml +++ b/Web/Presenters/templates/User/private.xml @@ -41,6 +41,7 @@ {/if} + {_bl_add} {_report}
diff --git a/Web/static/css/main.css b/Web/static/css/main.css index 8f465432..82c8e6eb 100644 --- a/Web/static/css/main.css +++ b/Web/static/css/main.css @@ -3597,6 +3597,11 @@ hr { overflow-y: auto; } +.entity_vertical_list.scroll_container { + height: unset; + overflow-y: unset; +} + .entity_vertical_list .entity_vertical_list_item { display: flex; flex-direction: row; @@ -3611,6 +3616,15 @@ hr { gap: 4px; } +.entity_vertical_list.m_mini .entity_vertical_list_item .first_column { + gap: 10px; +} + +.entity_vertical_list.m_mini .entity_vertical_list_item .first_column .avatar img { + width: 30px; + height: 30px; +} + .entity_vertical_list .entity_vertical_list_item .avatar { display: block; } diff --git a/Web/static/js/al_wall.js b/Web/static/js/al_wall.js index 06420ceb..008ba3ec 100644 --- a/Web/static/js/al_wall.js +++ b/Web/static/js/al_wall.js @@ -2587,3 +2587,42 @@ async function changeStatus() { document.status_popup_form.submit.innerHTML = tr("send"); document.status_popup_form.submit.disabled = false; } + +u(document).on('click', '#_bl_toggler', async (e) => { + e.preventDefault() + + const target = u(e.target) + const val = Number(target.attr('data-val')) + const id = Number(target.attr('data-id')) + const name = target.attr('data-name') + + const fallback = (e) => { + fastError(e.message) + target.removeClass('lagged') + } + + if(val == 1) { + const msg = new CMessageBox({ + title: tr('addition_to_bl'), + body: `${escapeHtml(tr('adding_to_bl_sure', name))}`, + buttons: [tr('yes'), tr('no')], + callbacks: [async () => { + try { + target.addClass('lagged') + await window.OVKAPI.call('account.ban', {'owner_id': id}) + window.router.route(location.href) + } catch(e) { + fallback(e) + } + }, () => Function.noop] + }) + } else { + try { + target.addClass('lagged') + await window.OVKAPI.call('account.unban', {'owner_id': id}) + window.router.route(location.href) + } catch(e) { + fallback(e) + } + } +}) diff --git a/Web/static/js/router.js b/Web/static/js/router.js index 8ef87123..b45a23f3 100644 --- a/Web/static/js/router.js +++ b/Web/static/js/router.js @@ -296,7 +296,7 @@ u(document).on('submit', 'form', async (e) => { if(e.defaultPrevented) { return } - + if(u('#ajloader').hasClass('shown')) { e.preventDefault() return diff --git a/install/sqls/00052-blacklist.sql b/install/sqls/00052-blacklist.sql new file mode 100644 index 00000000..a665dc7c --- /dev/null +++ b/install/sqls/00052-blacklist.sql @@ -0,0 +1,8 @@ +CREATE TABLE `blacklist_relations` ( + `index` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `author` BIGINT UNSIGNED NOT NULL, + `target` BIGINT UNSIGNED NOT NULL, + `created` BIGINT UNSIGNED NOT NULL, + PRIMARY KEY (`index`) +) ENGINE = InnoDB; +ALTER TABLE `blacklist_relations` ADD INDEX(`author`, `target`); diff --git a/locales/en.strings b/locales/en.strings index 9909b736..cdf6ff26 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -1699,6 +1699,7 @@ "admin_commerce_disabled" = "Commerce has been disabled by the system administrator"; "admin_commerce_disabled_desc" = "The voucher and gift settings will be saved, but will have no effect."; +"admin_privacy_warning" = "Be careful with this information"; "admin_longpool_broken" = "Longpool is broken and will not work!"; "admin_longpool_broken_desc" = "Make sure file at the path $1 exist and have correct rights and ownership."; @@ -1815,6 +1816,28 @@ "cookies_popup_content" = "Just like how kids love cookies, this website uses Cookies to identify your session and nothing more. Check our privacy policy for more information."; "cookies_popup_agree" = "Accept"; +/* Blacklist */ + +"blacklist" = "Blacklist"; +"user_blacklisted_you" = "This user has blacklisted you."; +"user_blacklisted" = "$1 has been blacklisted"; +"user_removed_from_the_blacklist" = "$1 has been removed from the blacklist."; + +"adding_to_bl_sure" = "You sure you want to blacklist $1?"; + +"bl_count_zero_desc" = "There are no users on your blacklist yet."; +"bl_count_zero" = "There are no users on your blacklist"; +"bl_count_one" = "You have one user on your blacklist"; +"bl_count_few" = "You have $1 users on your blacklist"; +"bl_count_many" = "You have $1 users on your blacklist"; +"bl_count_other" = "You have $1 users on your blacklist"; + +"you_blacklisted" = "You blacklisted $1"; +"bl_add" = "Add to blacklist"; +"bl_remove" = "Remove from blacklist"; + +"addition_to_bl" = "Addition to blacklist"; + /* Away */ "transition_is_blocked" = "Transition is blocked"; diff --git a/locales/ru.strings b/locales/ru.strings index 7337b27f..67e8116d 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -1591,8 +1591,11 @@ "admin_about_instance" = "Инстанция"; "admin_commerce_disabled" = "Коммерция отключена системным администратором"; "admin_commerce_disabled_desc" = "Настройки ваучеров и подарков будут сохранены, но не будут оказывать никакого влияния."; + +"admin_privacy_warning" = "Будьте осторожны с этой информацией"; "admin_longpool_broken" = "Longpool сломан!"; "admin_longpool_broken_desc" = "Проверьте, существует ли файл по пути $1 и выданы ли у него правильные права на запись."; + "admin_banned_links" = "Заблокированные ссылки"; "admin_banned_link" = "Ссылка"; "admin_banned_domain" = "Домен"; @@ -1690,6 +1693,7 @@ "edit_action" = "Изменить"; "transfer" = "Передать"; "close" = "Закрыть"; +"success" = "Успех"; "warning" = "Внимание"; "question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?"; "confirm_m" = "Подтвердить"; @@ -1708,6 +1712,28 @@ "cookies_popup_content" = "Все дети любят печенье, поэтому этот веб-сайт использует Cookies для того, чтобы идентифицировать вашу сессию и ничего более. Ознакомьтесь с нашей политикой конфиденциальности для получения дополнительной информации."; "cookies_popup_agree" = "Согласен"; +/* Blacklist */ + +"blacklist" = "Чёрный список"; +"user_blacklisted_you" = "Пользователь внёс Вас в чёрный список."; +"user_blacklisted" = "$1 занесён в чёрный список."; +"user_removed_from_the_blacklist" = "$1 удалён из чёрного списка."; + +"adding_to_bl_sure" = "Вы уверены, что хотите внести $1 в чёрный список?"; + +"bl_count_zero_desc" = "В вашем чёрном списке ещё нет пользователей."; +"bl_count_zero" = "В вашем чёрном списке нет пользователей"; +"bl_count_one" = "В вашем чёрном списке один пользователь"; +"bl_count_few" = "В вашем чёрном списке $1 пользователя"; +"bl_count_many" = "В вашем чёрном списке $1 пользователей"; +"bl_count_other" = "В вашем чёрном списке $1 пользователей"; + +"you_blacklisted" = "Вы внесли $1 в чёрный список"; +"bl_add" = "Добавить в чёрный список"; +"bl_remove" = "Удалить из чёрного списка"; + +"addition_to_bl" = "Добавление в чёрный список"; + /* Away */ "transition_is_blocked" = "Переход по ссылке заблокирован"; diff --git a/openvk-example.yml b/openvk-example.yml index b58660f7..b64a589e 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -38,6 +38,9 @@ openvk: maxViolations: 50 maxViolationsAge: 120 autoban: true + blacklists: + limit: 100 + applyToAdmins: true registration: enable: true disablingReason: ""