diff --git a/Web/Models/Entities/User.php b/Web/Models/Entities/User.php index 81d6cc08..0e85d775 100644 --- a/Web/Models/Entities/User.php +++ b/Web/Models/Entities/User.php @@ -698,16 +698,18 @@ class User extends RowModel ]); } - function ban(string $reason): void + function ban(string $reason, bool $deleteSubscriptions = true): void { - $subs = DatabaseConnection::i()->getContext()->table("subscriptions"); - $subs = $subs->where( - "follower = ? OR (target = ? AND model = ?)", - $this->getId(), - $this->getId(), - get_class($this), - ); - $subs->delete(); + if($deleteSubscriptions) { + $subs = DatabaseConnection::i()->getContext()->table("subscriptions"); + $subs = $subs->where( + "follower = ? OR (target = ? AND model = ?)", + $this->getId(), + $this->getId(), + get_class($this), + ); + $subs->delete(); + } $this->setBlock_Reason($reason); $this->save(); diff --git a/Web/Presenters/GroupPresenter.php b/Web/Presenters/GroupPresenter.php index 7788c09e..7b9a20f2 100644 --- a/Web/Presenters/GroupPresenter.php +++ b/Web/Presenters/GroupPresenter.php @@ -219,10 +219,17 @@ final class GroupPresenter extends OpenVKPresenter if($_FILES["ava"]["error"] === UPLOAD_ERR_OK) { $photo = new Photo; try { + $anon = OPENVK_ROOT_CONF["openvk"]["preferences"]["wall"]["anonymousPosting"]["enable"]; + if($anon && $this->user->id === $club->getOwner()->getId()) + $anon = $club->isOwnerHidden(); + else if($anon) + $anon = $club->getManager($this->user->identity)->isHidden(); + $photo->setOwner($this->user->id); $photo->setDescription("Profile image"); $photo->setFile($_FILES["ava"]); $photo->setCreated(time()); + $photo->setAnonymous($anon); $photo->save(); (new Albums)->getClubAvatarAlbum($club)->addPhoto($photo); diff --git a/Web/Presenters/OpenVKPresenter.php b/Web/Presenters/OpenVKPresenter.php index 18a7c14b..48d6a9af 100755 --- a/Web/Presenters/OpenVKPresenter.php +++ b/Web/Presenters/OpenVKPresenter.php @@ -39,14 +39,26 @@ abstract class OpenVKPresenter extends SimplePresenter Session::i()->set("_tempTheme", $theme); } - protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL): void + protected function flashFail(string $type, string $title, ?string $message = NULL, ?int $code = NULL, bool $json = false): void { - $this->flash($type, $title, $message, $code); - $referer = $_SERVER["HTTP_REFERER"] ?? "/"; - - header("HTTP/1.1 302 Found"); - header("Location: $referer"); - exit; + if($json) { + $this->returnJson([ + "success" => $type !== "err", + "flash" => [ + "type" => $type, + "title" => $title, + "message" => $message, + "code" => $code, + ], + ]); + } else { + $this->flash($type, $title, $message, $code); + $referer = $_SERVER["HTTP_REFERER"] ?? "/"; + + header("HTTP/1.1 302 Found"); + header("Location: $referer"); + exit; + } } protected function logInUserWithToken(): void @@ -120,18 +132,18 @@ abstract class OpenVKPresenter extends SimplePresenter $this->flashFail("err", tr("captcha_error"), tr("captcha_error_comment")); } - protected function willExecuteWriteAction(): void + protected function willExecuteWriteAction(bool $json = false): void { $ip = (new IPs)->get(CONNECTING_IP); $res = $ip->rateLimit(); if(!($res === IP::RL_RESET || $res === IP::RL_CANEXEC)) { if($res === IP::RL_BANNED && OPENVK_ROOT_CONF["openvk"]["preferences"]["security"]["rateLimits"]["autoban"]) { - $this->user->identity->ban("Account has possibly been stolen"); + $this->user->identity->ban("Account has possibly been stolen", false); exit("Хакеры? Интересно..."); } - $this->flashFail("err", tr("rate_limit_error"), tr("rate_limit_error_comment", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"], $res)); + $this->flashFail("err", tr("rate_limit_error"), tr("rate_limit_error_comment", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"], $res), NULL, $json); } } @@ -241,4 +253,13 @@ abstract class OpenVKPresenter extends SimplePresenter Session::i()->set("_error", NULL); } } + + protected function returnJson(array $json): void + { + $payload = json_encode($json); + $size = strlen($payload); + header("Content-Type: application/json"); + header("Content-Length: $size"); + exit($payload); + } } diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index e34eecab..a94fe855 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -164,9 +164,10 @@ final class SupportPresenter extends OpenVKPresenter $this->notFound(); $ticketComments = $this->comments->getCommentsById($id); - $this->template->ticket = $ticket; - $this->template->comments = $ticketComments; - $this->template->id = $id; + $this->template->ticket = $ticket; + $this->template->comments = $ticketComments; + $this->template->id = $id; + $this->template->fastAnswers = OPENVK_ROOT_CONF["openvk"]["preferences"]["support"]["fastAnswers"]; } function renderAnswerTicketReply(int $id): void diff --git a/Web/Presenters/UserPresenter.php b/Web/Presenters/UserPresenter.php index 8bb33d50..43f4100d 100644 --- a/Web/Presenters/UserPresenter.php +++ b/Web/Presenters/UserPresenter.php @@ -101,11 +101,11 @@ final class UserPresenter extends OpenVKPresenter $this->notFound(); if(!$club->canBeModifiedBy($this->user->identity ?? NULL)) - $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс."); + $this->flashFail("err", "Ошибка доступа", "У вас недостаточно прав, чтобы изменять этот ресурс.", NULL, true); $isClubPinned = $this->user->identity->isClubPinned($club); if(!$isClubPinned && $this->user->identity->getPinnedClubCount() > 10) - $this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп"); + $this->flashFail("err", "Ошибка", "Находится в левом меню могут максимум 10 групп", NULL, true); if($club->getOwner()->getId() === $this->user->identity->getId()) { $club->setOwner_Club_Pinned(!$isClubPinned); @@ -118,10 +118,9 @@ final class UserPresenter extends OpenVKPresenter } } - if($isClubPinned) - $this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно удалена из левого меню"); - else - $this->flashFail("succ", "Операция успешна", "Группа " . $club->getName() . " была успешно добавлена в левое меню"); + $this->returnJson([ + "success" => true + ]); } function renderEdit(): void @@ -135,7 +134,7 @@ final class UserPresenter extends OpenVKPresenter $user = $this->users->get($id); if($_SERVER["REQUEST_METHOD"] === "POST") { - $this->willExecuteWriteAction(); + $this->willExecuteWriteAction($_GET['act'] === "status"); if($_GET['act'] === "main" || $_GET['act'] == NULL) { $user->setFirst_Name(empty($this->postParam("first_name")) ? $user->getFirstName() : $this->postParam("first_name")); @@ -197,15 +196,15 @@ final class UserPresenter extends OpenVKPresenter } elseif($_GET['act'] === "status") { if(mb_strlen($this->postParam("status")) > 255) { $statusLength = (string) mb_strlen($this->postParam("status")); - $this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)"); + $this->flashFail("err", "Ошибка", "Статус слишком длинный ($statusLength символов вместо 255 символов)", NULL, true); } $user->setStatus(empty($this->postParam("status")) ? NULL : $this->postParam("status")); $user->save(); - header("HTTP/1.1 302 Found"); - header("Location: /id" . $user->getId()); - exit; + $this->returnJson([ + "success" => true + ]); } try { diff --git a/Web/Presenters/WallPresenter.php b/Web/Presenters/WallPresenter.php index acb8f633..999bf53c 100644 --- a/Web/Presenters/WallPresenter.php +++ b/Web/Presenters/WallPresenter.php @@ -320,7 +320,7 @@ final class WallPresenter extends OpenVKPresenter (new RepostNotification($post->getOwner(false), $post, $this->user->identity))->emit(); }; - exit(json_encode(["wall_owner" => $this->user->identity->getId()])); + $this->returnJson(["wall_owner" => $this->user->identity->getId()]); } function renderDelete(int $wall, int $post_id): void diff --git a/Web/Presenters/templates/Auth/Register.xml b/Web/Presenters/templates/Auth/Register.xml index 90ae6053..bf6ba594 100644 --- a/Web/Presenters/templates/Auth/Register.xml +++ b/Web/Presenters/templates/Auth/Register.xml @@ -39,7 +39,7 @@ {_"surname"}: - + diff --git a/Web/Presenters/templates/Group/View.xml b/Web/Presenters/templates/Group/View.xml index a817b72c..76c35b68 100644 --- a/Web/Presenters/templates/Group/View.xml +++ b/Web/Presenters/templates/Group/View.xml @@ -91,13 +91,18 @@ {presenter "openvk!Wall->wallEmbedded", -$club->getId()}
- + {var avatarPhoto = $club->getAvatarPhoto()} + {var avatarLink = ((is_null($avatarPhoto) ? FALSE : $avatarPhoto->isAnonymous()) ? "/photo" . ("s/" . base_convert((string) $avatarPhoto->getId(), 10, 32)) : $club->getAvatarLink())} +
- +
-
- - +
+
+ + +
+
+ {_fast_answers} +
+

@@ -93,17 +99,17 @@ {tr("support_greeting_hi", $ticket->getUser()->getFullName())}

- + {$comment->getText()|noescape}

- + {tr("support_greeting_regards", OPENVK_ROOT_CONF["openvk"]["appearance"]["name"])|noescape} {else} {$comment->getText()|noescape} {/if}
- + {if $comment->getUType() === 0}
{_delete} @@ -126,4 +132,12 @@ + + {/block} diff --git a/Web/Presenters/templates/User/Settings.xml b/Web/Presenters/templates/User/Settings.xml index daa7a0c3..f142a5e3 100644 --- a/Web/Presenters/templates/User/Settings.xml +++ b/Web/Presenters/templates/User/Settings.xml @@ -348,16 +348,40 @@ {elseif $isFinanceTU}

{_voucher_explanation} {_voucher_explanation_ex}

-
- - - - - - - + + - + - + - +

+ {elseif $isInterface} diff --git a/Web/Presenters/templates/User/View.xml b/Web/Presenters/templates/User/View.xml index 46a50eb6..17bbf6c3 100644 --- a/Web/Presenters/templates/User/View.xml +++ b/Web/Presenters/templates/User/View.xml @@ -316,12 +316,12 @@
{$alert}
{var thatIsThisUser = isset($thisUser) && $user->getId() == $thisUser->getId()}
@@ -543,9 +543,11 @@ } -
diff --git a/Web/Presenters/templates/components/post/microblogpost.xml b/Web/Presenters/templates/components/post/microblogpost.xml index 8c22b51b..b5893981 100644 --- a/Web/Presenters/templates/components/post/microblogpost.xml +++ b/Web/Presenters/templates/components/post/microblogpost.xml @@ -99,34 +99,40 @@ {/if}
- -
- {if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if} -
+ {if !($forceNoShareLink ?? false)} + +
+ {if $post->getRepostCount() > 0}{$post->getRepostCount()}{/if} +
+ {/if} - {var liked = $post->hasLikeFrom($thisUser)} - -
- {if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if} -
+ {if !($forceNoLike ?? false)} + {var liked = $post->hasLikeFrom($thisUser)} + +
+ {if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if} +
+ {/if}
{/if}
-
- {if $commentsCount > 3} - {_view_other_comments} - {/if} - {foreach $comments as $comment} - {include "../comment.xml", comment => $comment, $compact => true} - {/foreach} -
- {var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()} - {include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post} + {if !($forceNoCommentsLink ?? false)} +
+ {if $commentsCount > 3} + {_view_other_comments} + {/if} + {foreach $comments as $comment} + {include "../comment.xml", comment => $comment, $compact => true} + {/foreach} +
+ {var commentsURL = "/al_comments.pl/create/posts/" . $post->getId()} + {include "../textArea.xml", route => $commentsURL, postOpts => false, graffiti => (bool) ovkGetQuirk("comments.allow-graffiti"), post => $post} +
-
+ {/if} diff --git a/Web/Presenters/templates/components/post/oldpost.xml b/Web/Presenters/templates/components/post/oldpost.xml index ad256ff9..f59a9cf7 100644 --- a/Web/Presenters/templates/components/post/oldpost.xml +++ b/Web/Presenters/templates/components/post/oldpost.xml @@ -91,29 +91,35 @@ {_"comments"} {/if} + {/if} + + {if !($forceNoCommentsLink ?? false) && !($forceNoShareLink ?? false)}  |  {/if} - - {if $post->getRepostCount() > 0} - {_"share"} - ({$post->getRepostCount()}) - {else} - {_"share"} - {/if} - - - -
- {var liked = $post->hasLikeFrom($thisUser)} - -
- {if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if} + {if !($forceNoShareLink ?? false)} +
+ {if $post->getRepostCount() > 0} + {_"share"} + ({$post->getRepostCount()}) + {else} + {_"share"} + {/if} -
+ {/if} + + {if !($forceNoLike ?? false)} +
+ {var liked = $post->hasLikeFrom($thisUser)} + +
+ {if $post->getLikesCount() > 0}{$post->getLikesCount()}{/if} +
+
+ {/if}
diff --git a/Web/static/css/style.css b/Web/static/css/style.css index 067d4eac..d26fb93f 100644 --- a/Web/static/css/style.css +++ b/Web/static/css/style.css @@ -440,7 +440,15 @@ table { } .button:hover { - color: #e8e8e8; + color: #e8e8e8; +} + +.button-loading { + display: inline-block; + background-image: url('/assets/packages/static/openvk/img/loading_mini.gif'); + width: 30px; + height: 7px; + margin-left: -3px; } input[class=button] { @@ -1768,3 +1776,16 @@ body.scrolled .toTop:hover { margin: -20px; padding: 10px; } + +.hover-box { + background-color: white; + padding: 10px; + margin: 5px; + border: 1px solid #C0CAD5; + cursor: pointer; + user-select: none; +} + +.hover-box:hover { + background-color: #C0CAD5; +} diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js index 6d80626e..9c5a4c50 100644 --- a/Web/static/js/openvk.cls.js +++ b/Web/static/js/openvk.cls.js @@ -38,6 +38,22 @@ function hidePanel(panel, count = 0) } +function parseAjaxResponse(responseString) { + try { + const response = JSON.parse(responseString); + if(response.flash) + NewNotification(response.flash.title, response.flash.message || "", null); + + return response.success || false; + } catch(error) { + if(responseString === "Хакеры? Интересно...") { + location.reload(); + return false; + } else { + throw error; + } + } +} document.addEventListener("DOMContentLoaded", function() { //BEGIN @@ -100,6 +116,15 @@ document.addEventListener("DOMContentLoaded", function() { //BEGIN let req = await ky(link); if(req.ok == false) { NewNotification(tr('error'), tr('error_1'), null); + thisButton.nodes[0].classList.remove('loading'); + thisButton.nodes[0].classList.remove('disable'); + return; + } + + if(!parseAjaxResponse(await req.text())) { + thisButton.nodes[0].classList.remove('loading'); + thisButton.nodes[0].classList.remove('disable'); + return; } // Adding a divider if not already there @@ -145,7 +170,7 @@ function repostPost(id, hash) { xhr.open("POST", "/wall"+id+"/repost?hash="+hash, true); xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded'); xhr.onload = (function() { - if(xhr.responseText.indexOf("wall_owner") === -1) + if(xhr.responseText.indexOf("wall_owner") === -1) MessageBox(tr('error'), tr('error_repost_fail'), tr('ok'), [Function.noop]); else { let jsonR = JSON.parse(xhr.responseText); @@ -223,3 +248,44 @@ function showCoinsTransferDialog(coinsCount, hash) { ]); } +function chunkSubstr(string, size) { + const numChunks = Math.ceil(string.length / size); + const chunks = new Array(numChunks); + + for (let i = 0, o = 0; i < numChunks; ++i, o += size) { + chunks[i] = string.substr(o, size); + } + + return chunks; +} + +function autoTab(original, next, previous) { + if(original.getAttribute && original.value.length == original.getAttribute("maxlength") && next !== undefined) + next.focus(); + else if(original.value.length == 0 && previous !== undefined) + previous.focus(); +} + +function showSupportFastAnswerDialog(answers) { + let html = ""; + for(const [index, answer] of Object.entries(answers)) { + html += ` +
+ ${answer.replace(/\n/g, "
")} +
+ `; + } + + MessageBox(tr("fast_answers"), html, [tr("close")], [ + Function.noop + ]); +} + +function supportFastAnswerDialogOnClick(answer) { + u("body").removeClass("dimmed"); + u(".ovk-diag-cont").remove(); + + const answerInput = document.querySelector("#answer_text"); + answerInput.value = answer; + answerInput.focus(); +} diff --git a/docs/centos8_install.md b/docs/centos8_install.md index 97437858..f29ebc67 100644 --- a/docs/centos8_install.md +++ b/docs/centos8_install.md @@ -2,6 +2,10 @@ Based on [@rem-pai](https://github.com/rem-pai)'s way to install OpenVK modified using my experience. +!!WARNING!! + +CentOS 8 is reaching it's end-of-life soon. There are other supported similar distributions like Rocky Linux or AlmaLinux. + ## SELinux 🖥Run the command: diff --git a/install/sqls/00004-kromer-typechange.sql b/install/sqls/00004-kromer-typechange.sql index 15449bc6..aa3aef6a 100644 --- a/install/sqls/00004-kromer-typechange.sql +++ b/install/sqls/00004-kromer-typechange.sql @@ -1 +1 @@ -ALTER TABLE `profiles` CHANGE `coins` `coins` REAL(20) UNSIGNED NOT NULL DEFAULT '0'; \ No newline at end of file +ALTER TABLE `profiles` CHANGE `coins` `coins` REAL UNSIGNED NOT NULL DEFAULT '0'; diff --git a/locales/en.strings b/locales/en.strings index 8410abf7..d143f5ab 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -610,6 +610,8 @@ "support_rated_bad" = "You left a negative feedback about the answer."; "wrong_parameters" = "Invalid request parameters."; +"fast_answers" = "Fast answers"; + "comment" = "Comment"; "sender" = "Sender"; @@ -743,6 +745,7 @@ "login_as" = "Login as $1"; "manage_user_action" = "Manage user"; +"manage_group_action" = "Manage group"; "ban_user_action" = "Ban user"; "warn_user_action" = "Warn user"; @@ -764,6 +767,7 @@ "cancel" = "Cancel"; "edit_action" = "Change"; "transfer" = "Transfer"; +"close" = "Close"; "warning" = "Warning"; "question_confirm" = "This action can't be undone. Do you really wanna do it?"; diff --git a/locales/ru.strings b/locales/ru.strings index 3e90c5c5..e743d236 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -639,6 +639,8 @@ "support_rated_bad" = "Вы оставили негативный отзыв об ответе."; "wrong_parameters" = "Неверные параметры запроса."; +"fast_answers" = "Быстрые ответы"; + "comment" = "Комментарий"; "sender" = "Отправитель"; @@ -778,6 +780,7 @@ "login_as" = "Войти как $1"; "manage_user_action" = "Управление пользователем"; +"manage_group_action" = "Управление группой"; "ban_user_action" = "Заблокировать пользователя"; "warn_user_action" = "Предупредить пользователя"; @@ -799,6 +802,7 @@ "cancel" = "Отмена"; "edit_action" = "Изменить"; "transfer" = "Передать"; +"close" = "Закрыть"; "warning" = "Внимание"; "question_confirm" = "Это действие нельзя отменить. Вы действительно уверены в том что хотите сделать?"; diff --git a/openvk-example.yml b/openvk-example.yml index 441afd19..0f8a9b55 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -31,6 +31,10 @@ openvk: support: supportName: "Moderator" adminAccount: 1 # Change this ok + fastAnswers: + - "This is a list of quick answers to common questions for support. Post your responses here and agents can send it quickly with just 3 clicks" + - "There can be as many answers as you want, but it is best to have a maximum of 10.\n\nYou can also remove all answers from the list to disable this feature" + - "Good luck filling! If you are a regular support agent, inform the administrator that he forgot to fill the config" messages: strict: false wall: