diff --git a/Web/Models/Entities/SupportTemplate.php b/Web/Models/Entities/SupportTemplate.php new file mode 100644 index 00000000..d8e01355 --- /dev/null +++ b/Web/Models/Entities/SupportTemplate.php @@ -0,0 +1,40 @@ +getRecord()->id; + } + + function getOwner(): ?User + { + return (new Users)->get($this->getRecord()->owner); + } + + function getDir(): ?SupportTemplateDir + { + return (new SupportTemplatesDirs)->get($this->getRecord()->dir); + } + + function isPublic(): bool + { + return $this->getDir()->isPublic(); + } + + function getTitle(): string + { + return $this->getRecord()->title; + } + + function getText(): string + { + return $this->getRecord()->text; + } +} diff --git a/Web/Models/Entities/SupportTemplateDir.php b/Web/Models/Entities/SupportTemplateDir.php new file mode 100644 index 00000000..b1c32ece --- /dev/null +++ b/Web/Models/Entities/SupportTemplateDir.php @@ -0,0 +1,40 @@ +getRecord()->id; + } + + function getOwner(): ?User + { + return (new Users)->get($this->getRecord()->owner); + } + + function isPublic(): bool + { + return (bool) $this->getRecord()->is_public; + } + + function getTitle(): string + { + return $this->getRecord()->title; + } + + function getText(): string + { + return $this->getRecord()->text; + } + + function getTemplates(): \Traversable + { + return (new SupportTemplates)->getListByDirId($this->getId()); + } +} diff --git a/Web/Models/Repositories/SupportTemplates.php b/Web/Models/Repositories/SupportTemplates.php new file mode 100644 index 00000000..91d94e48 --- /dev/null +++ b/Web/Models/Repositories/SupportTemplates.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->templates = $this->context->table("support_templates"); + } + + private function toTemplate(?ActiveRow $ar): ?SupportTemplate + { + return is_null($ar) ? NULL : new SupportTemplate($ar); + } + + function get(int $id): ?SupportTemplate + { + return $this->toTemplate($this->templates->get($id)); + } + + function getListByDirId(int $dir_id): \Traversable + { + foreach ($this->templates->where(["dir" => $dir_id, "deleted" => 0]) as $template) + yield new SupportTemplate($template); + } +} diff --git a/Web/Models/Repositories/SupportTemplatesDirs.php b/Web/Models/Repositories/SupportTemplatesDirs.php new file mode 100644 index 00000000..216334e2 --- /dev/null +++ b/Web/Models/Repositories/SupportTemplatesDirs.php @@ -0,0 +1,33 @@ +context = DatabaseConnection::i()->getContext(); + $this->dirs = $this->context->table("support_templates_dirs"); + } + + private function toDir(?ActiveRow $ar): ?SupportTemplateDir + { + return is_null($ar) ? NULL : new SupportTemplateDir($ar); + } + + function get(int $id): ?SupportTemplateDir + { + return $this->toDir($this->dirs->get($id)); + } + + function getList(int $uid): \Traversable + { + foreach ($this->dirs->where("(owner=$uid OR is_public=1) AND deleted=0") as $dir) + yield new SupportTemplateDir($dir); + } +} diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index c4d729ea..22135463 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -1,7 +1,12 @@ 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 @@ -396,4 +400,233 @@ final class SupportPresenter extends OpenVKPresenter $this->flashFail("succ", "Успех", "Профиль создан. Теперь пользователи видят Ваши псевдоним и аватарку вместо стандартных аватарки и номера."); } } + + function renderGetTemplatesDirs(): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $dirs = []; + foreach ((new SupportTemplatesDirs)->getList($this->user->identity->getId()) as $dir) { + $dirs[] = ["id" => $dir->getId(), "title" => $dir->getTitle()]; + } + + $this->returnJson(["success" => true, "dirs" => $dirs]); + } + + function renderGetTemplatesInDir(int $dirId): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $dir = (new SupportTemplatesDirs)->get($dirId); + + if (!$dir || $dir->getOwner()->getId() !== $this->user->identity->getId() && !$dir->isPublic()) { + $this->returnJson(["success" => false, "error" => tr("forbidden")]); + } + + $templates = []; + foreach ($dir->getTemplates() as $template) { + $templateData = [ + "id" => $template->getId(), + "title" => $template->getTitle(), + "text" => $template->getText(), + ]; + + if ($this->queryParam("tid")) { + $ticket = (new Tickets)->get((int) $this->queryParam("tid")); + $ticket_user = $ticket->getUser(); + $replacements = [ + "{user_name}" => $ticket_user->getFirstName(), + "{last_name}" => $ticket_user->getLastName(), + "{unban_time}" => $ticket_user->getUnbanTime(), + ]; + + if ($ticket->getId()) { + foreach ($replacements as $search => $replace) { + $templateData["text"] = str_replace($search, $replace, $templateData["text"]); + } + } + } + + $templates[] = $templateData; + } + + $this->returnJson(["success" => true, "templates" => $templates]); + } + + function renderCreateTemplatesDir(): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + $this->assertNoCSRF(); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $dir = new SupportTemplateDir; + $dir->setTitle($this->postParam("title")); + $dir->setOwner($this->user->identity->getId()); + $dir->setIs_Public(!empty($this->postParam("is_public"))); + $dir->save(); + + $this->flashFail("succ", tr("changes_saved")); + } + } + + function renderTemplates(): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $mode = in_array($this->queryParam("act"), ["dirs", "list", "create_dir", "create_template", "edit_dir", "edit_template"]) ? $this->queryParam("act") : "dirs"; + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertNoCSRF(); + + if ($mode === "create_dir" || $mode === "edit_dir") { + $dirId = (int) $this->queryParam("dir"); + $dir = ($mode === "create_dir") ? new SupportTemplateDir : (new SupportTemplatesDirs)->get($dirId); + + if ($mode === "edit_dir" && (!$dir || $dir->isDeleted())) { + $this->notFound(); + } + + if ($mode === "edit_dir" && $dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $dir->setTitle($this->postParam("title")); + $dir->setOwner($this->user->identity->getId()); + $dir->setIs_Public(!empty($this->postParam("is_public"))); + $dir->save(); + + if ($mode === "create_dir") { + $this->redirect("/support/templates?act=list&dir=" . $dir->getId()); + } else { + $this->flashFail("succ", tr("changes_saved")); + } + } else if ($mode === "create_template" || $mode === "edit_template") { + $templateId = (int) $this->queryParam("id"); + $template = ($mode === "create_template") ? new SupportTemplate : (new SupportTemplates)->get($templateId); + if (!$template || $template->isDeleted()) { + $this->notFound(); + } + + if ($mode === "edit_template" && $template->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $dirId = ($mode === "create_template") ? (int) $this->queryParam("dir") : $template->getDir()->getId(); + $dir = (new SupportTemplatesDirs)->get($dirId); + + if ($mode === "create_template" && $dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + if (!$dir || $dir->isDeleted()) { + $this->notFound(); + } + + if ($dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $template->setOwner($this->user->identity->getId()); + $template->setDir($dir->getId()); + $template->setTitle($this->postParam("title")); + $template->setText($this->postParam("text")); + $template->save(); + + if ($mode === "create_template") { + $this->redirect("/support/templates?act=list&dir=" . $dirId . "&id=" . $template->getId()); + } else { + $this->flashFail("succ", tr("changes_saved")); + } + } + } else { + $this->template->mode = $mode; + + if (!$this->queryParam("dir")) { + $dirs = (new SupportTemplatesDirs)->getList($this->user->identity->getId()); + $this->template->dirs = $dirs; + $this->template->dirsCount = count($dirs); + + if ($mode === "edit_template") { + $templateId = (int) $this->queryParam("id"); + $template = (new SupportTemplates)->get($templateId); + + if (!$template || $template->isDeleted()) { + $this->notFound(); + } + + if ($template->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $this->template->dir = $template->getDir(); + $this->template->activeTemplate = $template; + } + } else { + $dirId = (int) $this->queryParam("dir"); + $dir = (new SupportTemplatesDirs)->get($dirId); + + if (!$dir || $dir->isDeleted()) { + $this->notFound(); + } + + if ($mode === "edit_dir" && $dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + if ($mode === "create_template" && $dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $this->template->dir = $dir; + $templates = $dir->getTemplates(); + $this->template->templates = $templates; + $this->template->templatesCount = count($templates); + $this->template->selectedTemplate = (int) $this->queryParam("id"); + } + } + } + + function renderDeleteDir(int $id): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + $dir = (new SupportTemplatesDirs)->get($id); + + if (!$dir || $dir->isDeleted()) { + $this->notFound(); + } + + if ($dir->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $templates = $dir->getTemplates(); + + foreach ($templates as $template) { + $template->delete(); + } + + $dir->delete(); + + $this->redirect("/support/templates"); + } + + function renderDeleteTemplate(int $id): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + $template = (new SupportTemplates)->get($id); + + if (!$template || $template->isDeleted()) { + $this->notFound(); + } + + if ($template->getOwner()->getId() !== $this->user->identity->getId()) { + $this->flashFail("err", tr("forbidden")); + } + + $dir = $template->getDir()->getId(); + $template->delete(); + + $this->redirect("/support/templates?act=list&dir=$dir"); + } + } diff --git a/Web/Presenters/templates/Support/AnswerTicket.xml b/Web/Presenters/templates/Support/AnswerTicket.xml index 7f577f9d..98e55bf2 100644 --- a/Web/Presenters/templates/Support/AnswerTicket.xml +++ b/Web/Presenters/templates/Support/AnswerTicket.xml @@ -33,8 +33,8 @@ -
- {_fast_answers} +
+ {_fast_answers}

@@ -123,10 +123,9 @@ {/block} diff --git a/Web/Presenters/templates/Support/List.xml b/Web/Presenters/templates/Support/List.xml index b2cefbfa..431ca185 100644 --- a/Web/Presenters/templates/Support/List.xml +++ b/Web/Presenters/templates/Support/List.xml @@ -21,6 +21,9 @@
Мой профиль
+
+ {_support_my_templates} +
{/block} {* BEGIN ELEMENTS DESCRIPTION *} diff --git a/Web/Presenters/templates/Support/Templates.xml b/Web/Presenters/templates/Support/Templates.xml new file mode 100644 index 00000000..52d4a9ae --- /dev/null +++ b/Web/Presenters/templates/Support/Templates.xml @@ -0,0 +1,93 @@ +{extends "../@layout.xml"} + +{block header} +
{_support_my_templates}
+
{_support_my_templates} > {$dir->getTitle()}
+
{_support_my_templates} > {_support_create_templates_dir}
+
+ {_support_my_templates} + > {$dir->getTitle()} + > {_support_create_template} +
+
+ {_support_my_templates} + > {$dir->getTitle()} + > {_edit} +
+
+ {_support_my_templates} + > {$dir->getTitle()} + > {$activeTemplate->getTitle()} + > {_edit} +
+{/block} + +{block content} +
+
+ {_support_create_templates_dir} +
+
+
+
{_no_data_description}
+
+
+ +

+ {$dir->getTitle()} + ({_edit}) +

+ +
+
+
+
+ {_support_create_template} + {_support_remove_templates_dir_1} {_support_remove_templates_dir_2} +
+
+
+
{_no_data_description}
+
+
+

+ {$template->getTitle()} + ({_edit}) +

+ +
+
+
+
+ + {_support_public_templates_dir} + + +
+
+
+
+ + +
    +
  • {="{user_name}"} — {_support_template_replacement_user_name}
  • +
  • {="{last_name}"} — {_support_template_replacement_last_name}
  • +
  • {="{unban_time}"} — {_support_template_replacement_unban_time}
  • +
+ + + {_delete} +
+
+ +{/block} \ No newline at end of file diff --git a/Web/routes.yml b/Web/routes.yml index d1a0e7ae..5a5664fb 100644 --- a/Web/routes.yml +++ b/Web/routes.yml @@ -35,6 +35,18 @@ routes: handler: "Support->agent" - url: "/support/agent{num}/edit" handler: "Support->editAgent" + - url: "/al_helpdesk/get_templates_dirs" + handler: "Support->getTemplatesDirs" + - url: "/al_helpdesk/td{num}/templates" + handler: "Support->getTemplatesInDir" + - url: "/al_helpdesk/create_templates_dir" + handler: "Support->createTemplatesDir" + - url: "/support/templates" + handler: "Support->templates" + - url: "/support/templates_dir{num}/delete" + handler: "Support->deleteDir" + - url: "/support/template{num}/delete" + handler: "Support->deleteTemplate" - url: "/language" handler: "About->language" - url: "/language/{text}.js" diff --git a/Web/static/js/openvk.cls.js b/Web/static/js/openvk.cls.js index 22144cfc..4cea7710 100644 --- a/Web/static/js/openvk.cls.js +++ b/Web/static/js/openvk.cls.js @@ -344,6 +344,57 @@ function supportFastAnswerDialogOnClick(answer) { answerInput.focus(); } +function getTemplatesDirs(tid) { + $.ajax("/al_helpdesk/get_templates_dirs").done((response) => { + console.log(response, response.success); + + if (response.success) { + const dirsHtml = response.dirs.length > 0 ? response.dirs.map((dir) => ` +
  • +

    ${dir.title}

    +
  • + `).join("") : `
    ${tr("no_data_description")}
    `; + + const managementLink = `${tr("edit")}`; + if (document.getElementsByClassName('ovk-diag-body').length === 0) { + MessageBox(tr("fast_answers"), `${managementLink}`, [tr("close")], [ + Function.noop + ]); + } else { + document.getElementsByClassName('ovk-diag-body')[0].innerHTML = `${managementLink}`; + } + } + }); +} + +function getTemplatesInDir(dir, ticket) { + $.ajax(`/al_helpdesk/td${dir}/templates?tid=${ticket}`).done((response) => { + console.log(response, response.success); + + if (response.success) { + let html = ""; + + response.templates.forEach((template) => { + html += '\ +
  • \ +

    ' + template.title + '

    \ + \ +
  • '; + }); + + const templatesListHtml = response.templates.length > 0 + ? `< ${tr("paginator_back")}` + : `
    ${tr("no_data_description")}
    `; + + document.getElementsByClassName("ovk-diag-body")[0].innerHTML = templatesListHtml; + } + }); +} + + function ovk_proc_strtr(string, length = 0) { const newString = string.substring(0, length); return newString + (string !== newString ? "…" : ""); diff --git a/install/sqls/00038-support-templates.sql b/install/sqls/00038-support-templates.sql new file mode 100644 index 00000000..287b1db1 --- /dev/null +++ b/install/sqls/00038-support-templates.sql @@ -0,0 +1,33 @@ +CREATE TABLE `support_templates_dirs` +( + `id` bigint(20) UNSIGNED NOT NULL, + `owner` bigint(20) UNSIGNED NOT NULL, + `is_public` tinyint(1) NOT NULL DEFAULT 0, + `title` tinytext COLLATE utf8mb4_unicode_ci NOT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT 0 +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +ALTER TABLE `support_templates_dirs` + ADD PRIMARY KEY (`id`); + +ALTER TABLE `support_templates_dirs` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; +COMMIT; + +CREATE TABLE `support_templates` +( + `id` bigint(20) UNSIGNED NOT NULL, + `owner` bigint(20) UNSIGNED NOT NULL, + `dir` bigint(20) UNSIGNED NOT NULL, + `title` tinytext COLLATE utf8mb4_unicode_ci NOT NULL, + `text` longtext COLLATE utf8mb4_unicode_ci NOT NULL, + `deleted` tinyint(1) NOT NULL DEFAULT 0 +) ENGINE = InnoDB + DEFAULT CHARSET = utf8mb4; + +ALTER TABLE `support_templates` + ADD PRIMARY KEY (`id`); + +ALTER TABLE `support_templates` + MODIFY `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT; diff --git a/locales/en.strings b/locales/en.strings index 4aaa4483..de64b34a 100644 --- a/locales/en.strings +++ b/locales/en.strings @@ -913,6 +913,20 @@ "banned_in_support_1" = "Sorry, $1, but now you can't create tickets."; "banned_in_support_2" = "And the reason for this is simple: $1. Unfortunately, this time we had to take away this opportunity from you forever."; +"support_my_templates" = "My templates"; +"support_create_templates_dir" = "Create folder"; +"support_create_template" = "Create template"; +"support_public_templates_dir" = "Other agents can use"; +"support_remove_templates_dir_1" = "Delete folder"; +"support_remove_templates_dir_2" = "with templates"; +"support_templates_dir_name" = "Folder name"; +"support_template_replacement_user_name" = "User's first name"; +"support_template_replacement_last_name" = "User's last name"; +"support_template_replacement_unban_time" = "User's unban time"; +"support_template_text" = "Template Text"; +"support_template_name" = "Template Name"; +"support_apply_template" = "Use"; + /* Invite */ "invite" = "Invite"; diff --git a/locales/ru.strings b/locales/ru.strings index 6faa5e2e..8d43b0e7 100644 --- a/locales/ru.strings +++ b/locales/ru.strings @@ -841,6 +841,19 @@ "ticket_changed_comment" = "Изменения вступят силу через несколько секунд."; "banned_in_support_1" = "Извините, $1, но теперь вам нельзя создавать обращения."; "banned_in_support_2" = "А причина этому проста: $1. К сожалению, на этот раз нам пришлось отобрать у вас эту возможность навсегда."; +"support_my_templates" = "Мои шаблоны"; +"support_create_templates_dir" = "Создать папку"; +"support_create_template" = "Создать шаблон"; +"support_public_templates_dir" = "Видят другие агенты"; +"support_remove_templates_dir_1" = "Удалить папку"; +"support_remove_templates_dir_2" = "вместе с шаблонами"; +"support_templates_dir_name" = "Название папки с шаблонами"; +"support_template_replacement_user_name" = "Имя пользователя"; +"support_template_replacement_last_name" = "Фамилия пользователя"; +"support_template_replacement_unban_time" = "Время разблокировки"; +"support_template_text" = "Текст шаблона"; +"support_template_name" = "Название шаблона"; +"support_apply_template" = "Применить"; /* Invite */ diff --git a/openvk-example.yml b/openvk-example.yml index e3fd1c3a..49f7bdb6 100644 --- a/openvk-example.yml +++ b/openvk-example.yml @@ -44,10 +44,6 @@ openvk: support: supportName: "Agent" 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: