diff --git a/Web/Models/Entities/FAQArticle.php b/Web/Models/Entities/FAQArticle.php new file mode 100644 index 00000000..a7d6f89e --- /dev/null +++ b/Web/Models/Entities/FAQArticle.php @@ -0,0 +1,44 @@ +getRecord()->id; + } + + function getTitle(): string + { + return $this->getRecord()->title; + } + + function getText(): string + { + return $this->getRecord()->text; + } + + function canSeeByUnloggedUsers(): bool + { + return (bool) $this->getRecord()->unlogged_can_see; + } + + function canSeeByUsers(): bool + { + return (bool) $this->getRecord()->users_can_see; + } + + function getCategory(): ?FAQCategory + { + return (new FAQCategories)->get($this->getRecord()->category); + } + + function getLanguage(): string + { + return $this->getRecord()->language; + } +} diff --git a/Web/Models/Entities/FAQCategory.php b/Web/Models/Entities/FAQCategory.php new file mode 100644 index 00000000..11789d98 --- /dev/null +++ b/Web/Models/Entities/FAQCategory.php @@ -0,0 +1,50 @@ +getRecord()->id; + } + + function getTitle(): string + { + return $this->getRecord()->title; + } + + function canSeeByUsers(): bool + { + return (bool) !$this->getRecord()->for_agents_only; + } + + function getIconBackgroundPosition(): int + { + return 28 * $this->getRecord()->icon; + } + + function getArticles(?int $limit = NULL, $isAgent): \Traversable + { + $filter = ["category" => $this->getId(), "deleted" => 0]; + if (!$isAgent) $filter["users_can_see"] = 1; + + $articles = DatabaseConnection::i()->getContext()->table("faq_articles")->where($filter)->limit($limit); + foreach ($articles as $article) { + yield new FAQArticle($article); + } + } + + function getIcon(): int + { + return $this->getRecord()->icon; + } + + function getLanguage(): string + { + return $this->getRecord()->language; + } +} diff --git a/Web/Models/Repositories/FAQArticles.php b/Web/Models/Repositories/FAQArticles.php new file mode 100644 index 00000000..a7070f35 --- /dev/null +++ b/Web/Models/Repositories/FAQArticles.php @@ -0,0 +1,27 @@ +context = DatabaseConnection::i()->getContext(); + $this->articles = $this->context->table("faq_articles"); + } + + function toFAQArticle(?ActiveRow $ar): ?FAQArticle + { + return is_null($ar) ? NULL : new FAQArticle($ar); + } + + function get(int $id): ?FAQArticle + { + return $this->toFAQArticle($this->articles->get($id)); + } +} diff --git a/Web/Models/Repositories/FAQCategories.php b/Web/Models/Repositories/FAQCategories.php new file mode 100644 index 00000000..2d74bfde --- /dev/null +++ b/Web/Models/Repositories/FAQCategories.php @@ -0,0 +1,37 @@ +context = DatabaseConnection::i()->getContext(); + $this->categories = $this->context->table("faq_categories"); + } + + function toFAQCategory(?ActiveRow $ar): ?FAQCategory + { + return is_null($ar) ? NULL : new FAQCategory($ar); + } + + function get(int $id): ?FAQCategory + { + return $this->toFAQCategory($this->categories->get($id)); + } + + function getList(string $language, bool $includeForAgents = false): \Traversable + { + $filter = ["deleted" => 0, "language" => $language]; + if (!$includeForAgents) $filter["for_agents_only"] = 0; + + foreach ($this->categories->where($filter) as $category) { + yield new FAQCategory($category); + } + } +} diff --git a/Web/Presenters/SupportPresenter.php b/Web/Presenters/SupportPresenter.php index c4d729ea..3d5259b4 100644 --- a/Web/Presenters/SupportPresenter.php +++ b/Web/Presenters/SupportPresenter.php @@ -1,7 +1,7 @@ assertUserLoggedIn(); $this->template->mode = in_array($this->queryParam("act"), ["faq", "new", "list"]) ? $this->queryParam("act") : "faq"; + $canEdit = $this->user->identity->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0); if($this->template->mode === "faq") { + $this->template->categories = (new FAQCategories)->getList($canEdit ? $this->queryParam("lang") ?? Session::i()->get("lang", "ru") : Session::i()->get("lang", "ru"), $canEdit); + $this->template->canEditFAQ = $canEdit; + $lang = Session::i()->get("lang", "ru"); $base = OPENVK_ROOT . "/data/knowledgebase/faq"; if(file_exists("$base.$lang.md")) @@ -104,6 +108,9 @@ final class SupportPresenter extends OpenVKPresenter $this->flashFail("err", tr("error"), tr("you_have_not_entered_name_or_text")); } } + + $this->template->languages = getLanguages(); + $this->template->activeLang = $this->queryParam("lang") ?? Session::i()->get("lang", "ru"); } function renderList(): void @@ -396,4 +403,191 @@ final class SupportPresenter extends OpenVKPresenter $this->flashFail("succ", "Успех", "Профиль создан. Теперь пользователи видят Ваши псевдоним и аватарку вместо стандартных аватарки и номера."); } } + + function renderFAQArticle(int $id): void + { + $article = (new FAQArticles)->get($id); + if (!$article || $article->isDeleted()) + $this->notFound(); + + $category = $article->getCategory(); + + if ($category->isDeleted()) + $this->notFound(); + + if (!$article->canSeeByUnloggedUsers()) + $this->assertUserLoggedIn(); + + if (!$category->canSeeByUsers() || (!$article->canSeeByUsers() && !$article->canSeeByUnloggedUsers())) + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $canEdit = false; + if ($this->user->identity) + $canEdit = $this->user->identity->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertNoCSRF(); + if (!$canEdit) { + $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); + } + + $cid = $this->postParam("category"); + if ($this->postParam("language") != $article->getLanguage()) { + $categories = iterator_to_array((new FAQCategories)->getList($this->postParam("language"))); + if (sizeof($categories) > 0) { + $cid = iterator_to_array((new FAQCategories)->getList($this->postParam("language")))[0]->getId(); + } else { + $this->flashFail("err", tr("error"), tr("support_cant_change_lang_no_cats")); + } + } + + $article->setTitle($this->postParam("title")); + $article->setText($this->postParam("text")); + $article->setUnlogged_Can_see(empty($this->postParam("unlogged_can_see") ? 0 : 1)); + $article->setUsers_Can_See(empty($this->postParam("users_can_see") ? 0 : 1)); + $article->setCategory($cid); + $article->setLanguage($this->postParam("language")); + $article->save(); + $this->flashFail("succ", tr("changes_saved")); + } else { + $this->template->mode = $canEdit ? in_array($this->queryParam("act"), ["view", "edit"]) ? $this->queryParam("act") : "view" : "view"; + $this->template->category = $category; + $this->template->article = $article; + $this->template->text = (new Parsedown())->text($article->getText()); + $this->template->canEditFAQ = $canEdit; + $this->template->categories = (new FAQCategories)->getList($this->queryParam("lang") ?? $article->getLanguage(), TRUE); + $this->template->languages = getLanguages(); + $this->template->activeLang = $this->queryParam("lang") ?? $article->getLanguage(); + } + } + + function renderFAQCategory(int $id): void + { + $category = (new FAQCategories)->get($id); + + if (!$category) + $this->notFound(); + + if (!$category->canSeeByUsers()) + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $canEdit = $this->user->identity->getChandlerUser()->can("write")->model('openvk\Web\Models\Entities\TicketReply')->whichBelongsTo(0); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $this->assertNoCSRF(); + if (!$canEdit) { + $this->flashFail("err", tr("not_enough_permissions"), tr("not_enough_permissions_comment")); + } + + if ($this->queryParam("mode") === "copy") { + $orig_cat = $category; + $category = new FAQCategory; + } + + $category->setTitle($this->postParam("title")); + $category->setIcon($orig_cat->getIcon() ?? $this->postParam("icon")); + $category->setFor_Agents_Only(empty($this->postParam("for_agents_only") ? 0 : 1)); + $category->setLanguage($this->postParam("language")); + $category->save(); + + if ($this->queryParam("mode") === "copy" && !empty($this->postParam("copy_with_articles"))) { + $articles = $orig_cat->getArticles(NULL, TRUE); + foreach ($articles as $article) { + $_article = new FAQArticle; + $_article->setCategory($category->getId()); + $_article->setTitle($article->getTitle()); + $_article->setText($article->getText()); + $_article->setUsers_Can_See($article->canSeeByUsers()); + $_article->setUnlogged_Can_See($article->canSeeByUnloggedUsers()); + $_article->setLanguage($category->getLanguage()); + $_article->save(); + } + } + + $this->flashFail("succ", tr("changes_saved")); + } + + $this->template->mode = $canEdit ? in_array($this->queryParam("act"), ["view", "edit"]) ? $this->queryParam("act") : "view" : "view"; + $this->template->copyMode = $canEdit ? $this->queryParam("mode") === "copy" : FALSE; + $this->template->category = $category; + $this->template->canEditFAQ = $canEdit; + $this->template->languages = getLanguages(); + } + + function renderFAQNewArticle(): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + if (!$this->postParam("category")) + $this->flashFail("err", tr("support_category_not_specified")); + + $article = new FAQArticle; + $article->setTitle($this->postParam("title")); + $article->setText($this->postParam("text")); + $article->setUnlogged_Can_see(empty($this->postParam("unlogged_can_see") ? 0 : 1)); + $article->setUsers_Can_See(empty($this->postParam("users_can_see") ? 0 : 1)); + $article->setCategory($this->postParam("category")); + $article->setLanguage(Session::i()->get("lang", "ru")); + $article->save(); + $this->redirect("/faq" . $article->getId()); + } else { + $this->template->categories = (new FAQCategories)->getList($this->queryParam("lang") ?? Session::i()->get("lang", "ru"), TRUE); + $this->template->category_id = $this->queryParam("cid"); + $this->template->activeLang = $this->queryParam("lang") ?? Session::i()->get("lang", "ru"); + $this->template->languages = getLanguages(); + } + } + + function renderFAQNewCategory(): void + { + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + if ($_SERVER["REQUEST_METHOD"] === "POST") { + $category = new FAQCategory; + $category->setTitle($this->postParam("title")); + $category->setIcon($this->postParam("icon")); + $category->setFor_Agents_Only(empty($this->postParam("for_agents_only") ? 0 : 1)); + $category->setLanguage($this->postParam("language")); + $category->save(); + $this->redirect("/faqs" . $category->getId()); + } + + $this->template->activeLang = $this->queryParam("lang") ?? Session::i()->get("lang", "ru"); + $this->template->languages = getLanguages(); + } + + function renderFAQDeleteArticle(int $id): void + { + $this->assertNoCSRF(); + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $article = (new FAQArticles)->get($id); + if (!$article || $article->isDeleted()) + $this->notFound(); + + $cid = $article->getCategory()->getId(); + $article->delete(); + $this->redirect("/faqs" . $cid); + } + + function renderFAQDeleteCategory(int $id): void + { + $this->assertNoCSRF(); + $this->assertPermission("openvk\Web\Models\Entities\TicketReply", "write", 0); + + $category = (new FAQCategories)->get($id); + if (!$category || $category->isDeleted()) + $this->notFound(); + + if (!empty($this->postParam("delete_articles"))) { + $articles = $category->getArticles(NULL, TRUE); + foreach ($articles as $article) { + $article->delete(); + } + } + + $category->delete(); + $this->redirect("/support"); + } } diff --git a/Web/Presenters/templates/Support/FAQArticle.xml b/Web/Presenters/templates/Support/FAQArticle.xml new file mode 100644 index 00000000..14d64588 --- /dev/null +++ b/Web/Presenters/templates/Support/FAQArticle.xml @@ -0,0 +1,73 @@ +{extends "../@layout.xml"} + +{block title}{$title}{/block} + +{block header} + {_menu_help} + > {$category->getTitle()} + > {$article->getTitle()} +
+ {_support_language}: + | ++ + | +