diff --git a/acptemplates/faqQuestionList.tpl b/acptemplates/faqQuestionList.tpl index 2bd3f2e..36944af 100644 --- a/acptemplates/faqQuestionList.tpl +++ b/acptemplates/faqQuestionList.tpl @@ -1,15 +1,5 @@ {include file='header' pageTitle='wcf.acp.menu.link.faq.questions.list'} - -

{lang}wcf.acp.menu.link.faq.questions.list{/lang}

@@ -17,6 +7,11 @@
-
-
-

{lang}wcf.global.filter{/lang}

- -
-
-
-
- -
-
- -
-
-
- -
-
- -
-
-
- -
-
- - {event name='filterFields'} -
- -
- - {csrfToken} -
-
-
- -{hascontent} -
- {content} - {assign var='linkParameters' value=''} - {if $categoryID}{capture append=linkParameters}&categoryID={$categoryID}{/capture}{/if} - {if $question}{capture append=linkParameters}&question={unsafe:$question|rawurlencode}{/capture}{/if} - {if $answer}{capture append=linkParameters}&answer={unsafe:$answer|rawurlencode}{/capture}{/if} - - {pages print=true assign=pagesLinks controller="FaqQuestionList" link="pageNo=%d&sortField=$sortField&sortOrder=$sortOrder$linkParameters"} - {/content} -
-{/hascontent} - -{if $objects|count} -
-
    - {foreach from=$objects item=question} -
  1. - - ({$question->getCategory()->getTitle()})  - {$question->getTitle()} +
    + {unsafe:$gridView->render()} +
    - - {objectAction action="toggle" isDisabled=$question->isDisabled} - {icon name='copy' size=16} - {icon name='pencil' size=16} - {objectAction action="delete" objectTitle=$question->getTitle()} - - {event name='itemButtons'} - -
    -
  2. - {/foreach} -
-
- -
- -
+ {include file='faqQuestionAddDialog'} diff --git a/files/lib/acp/page/FaqQuestionListPage.class.php b/files/lib/acp/page/FaqQuestionListPage.class.php index 582ad7a..d9e9405 100644 --- a/files/lib/acp/page/FaqQuestionListPage.class.php +++ b/files/lib/acp/page/FaqQuestionListPage.class.php @@ -3,13 +3,12 @@ namespace wcf\acp\page; use Override; -use wcf\data\category\CategoryNodeTree; -use wcf\data\faq\QuestionList; -use wcf\page\SortablePage; +use wcf\page\AbstractGridViewPage; +use wcf\system\gridView\AbstractGridView; +use wcf\system\gridView\admin\FaqQuestionGridView; use wcf\system\WCF; -use wcf\util\StringUtil; -class FaqQuestionListPage extends SortablePage +final class FaqQuestionListPage extends AbstractGridViewPage { /** * @inheritDoc @@ -21,31 +20,6 @@ class FaqQuestionListPage extends SortablePage */ public $neededPermissions = ['admin.faq.canViewQuestion']; - /** - * @inheritDoc - */ - public $objectListClassName = QuestionList::class; - - /** - * @inheritDoc - */ - public $validSortFields = ['questionID', 'categoryID', 'showOrder']; - - /** - * category id - */ - public int $categoryID = 0; - - /** - * question - */ - public string $question = ''; - - /** - * answer - */ - public string $answer = ''; - public int $showFaqAddDialog = 0; #[Override] @@ -53,45 +27,15 @@ public function readParameters() { parent::readParameters(); - if (isset($_REQUEST['categoryID'])) { - $this->categoryID = (int)$_REQUEST['categoryID']; - } - if (!empty($_REQUEST['question'])) { - $this->question = StringUtil::trim($_REQUEST['question']); - } - if (!empty($_REQUEST['answer'])) { - $this->answer = StringUtil::trim($_REQUEST['answer']); - } if (!empty($_REQUEST['showFaqAddDialog'])) { $this->showFaqAddDialog = 1; } } #[Override] - protected function initObjectList() + protected function createGridView(): AbstractGridView { - parent::initObjectList(); - - if ($this->categoryID) { - $this->objectList->getConditionBuilder()->add( - 'faq_questions.categoryID = ?', - [$this->categoryID] - ); - } - - if (!empty($this->question)) { - $this->objectList->getConditionBuilder()->add( - 'faq_questions.question LIKE ?', - ['%' . WCF::getDB()->escapeLikeValue($this->question) . '%'] - ); - } - - if (!empty($this->answer)) { - $this->objectList->getConditionBuilder()->add( - 'faq_questions.answer LIKE ?', - ['%' . WCF::getDB()->escapeLikeValue($this->answer) . '%'] - ); - } + return new FaqQuestionGridView(); } #[Override] @@ -100,10 +44,6 @@ public function assignVariables() parent::assignVariables(); WCF::getTPL()->assign([ - 'categoryID' => $this->categoryID, - 'question' => $this->question, - 'answer' => $this->answer, - 'categoryNodeList' => (new CategoryNodeTree('dev.tkirch.wsc.faq.category'))->getIterator(), 'showFaqAddDialog' => $this->showFaqAddDialog, ]); } diff --git a/files/lib/bootstrap/dev.tkirch.wsc.faq.php b/files/lib/bootstrap/dev.tkirch.wsc.faq.php index 7683f72..c1c73c1 100644 --- a/files/lib/bootstrap/dev.tkirch.wsc.faq.php +++ b/files/lib/bootstrap/dev.tkirch.wsc.faq.php @@ -7,6 +7,11 @@ use wcf\event\acp\menu\item\ItemCollecting; use wcf\event\endpoint\ControllerCollecting; use wcf\event\worker\RebuildWorkerCollecting; +use wcf\system\endpoint\controller\hanashi\questions\ChangeShowOrder; +use wcf\system\endpoint\controller\hanashi\questions\DeleteQuestion; +use wcf\system\endpoint\controller\hanashi\questions\DisableQuestion; +use wcf\system\endpoint\controller\hanashi\questions\EnableQuestion; +use wcf\system\endpoint\controller\hanashi\questions\GetShowOrder; use wcf\system\endpoint\controller\hanashi\questions\search\GetSearch; use wcf\system\endpoint\controller\hanashi\questions\search\RenderSearch; use wcf\system\event\EventHandler; @@ -83,6 +88,11 @@ static function (RebuildWorkerCollecting $event) { EventHandler::getInstance()->register( ControllerCollecting::class, static function (ControllerCollecting $event) { + $event->register(new DeleteQuestion()); + $event->register(new DisableQuestion()); + $event->register(new EnableQuestion()); + $event->register(new GetShowOrder()); + $event->register(new ChangeShowOrder()); $event->register(new RenderSearch()); $event->register(new GetSearch()); } diff --git a/files/lib/data/faq/QuestionEditor.class.php b/files/lib/data/faq/QuestionEditor.class.php index c6dedc9..dc31cf8 100644 --- a/files/lib/data/faq/QuestionEditor.class.php +++ b/files/lib/data/faq/QuestionEditor.class.php @@ -37,11 +37,11 @@ public function updateShowOrder(int $showOrder): int //check showOrder if ($showOrder < $this->showOrder) { - $sql = "UPDATE " . static::getDatabaseTableName() . " - SET showOrder = showOrder + 1 - WHERE showOrder >= ? - AND showOrder < ?"; - $statement = WCF::getDB()->prepareStatement($sql); + $sql = "UPDATE wcf1_faq_questions + SET showOrder = showOrder + 1 + WHERE showOrder >= ? + AND showOrder < ?"; + $statement = WCF::getDB()->prepare($sql); $statement->execute([ $showOrder, $this->showOrder, @@ -56,11 +56,11 @@ public function updateShowOrder(int $showOrder): int } //update databse - $sql = "UPDATE " . static::getDatabaseTableName() . " - SET showOrder = showOrder - 1 - WHERE showOrder <= ? - AND showOrder > ?"; - $statement = WCF::getDB()->prepareStatement($sql); + $sql = "UPDATE wcf1_faq_questions + SET showOrder = showOrder - 1 + WHERE showOrder <= ? + AND showOrder > ?"; + $statement = WCF::getDB()->prepare($sql); $statement->execute([ $showOrder, $this->showOrder, @@ -76,9 +76,9 @@ public function updateShowOrder(int $showOrder): int */ public static function getShowOrder(): int { - $sql = "SELECT MAX(showOrder) AS showOrder - FROM " . static::getDatabaseTableName(); - $statement = WCF::getDB()->prepareStatement($sql); + $sql = "SELECT MAX(showOrder) AS showOrder + FROM wcf1_faq_questions"; + $statement = WCF::getDB()->prepare($sql); $statement->execute(); $row = $statement->fetchArray(); diff --git a/files/lib/event/interaction/admin/FaqQuestionsInteractionCollecting.class.php b/files/lib/event/interaction/admin/FaqQuestionsInteractionCollecting.class.php new file mode 100644 index 0000000..d509e2b --- /dev/null +++ b/files/lib/event/interaction/admin/FaqQuestionsInteractionCollecting.class.php @@ -0,0 +1,13 @@ +category = FaqCategory::getCategory($queryParameters['id']); + $this->category = FaqCategory::getCategory((int)$queryParameters['id']); } catch (MappingError) { throw new IllegalLinkException(); } diff --git a/files/lib/system/bbcode/FaqBBCode.class.php b/files/lib/system/bbcode/FaqBBCode.class.php index d6ba881..9879e2c 100644 --- a/files/lib/system/bbcode/FaqBBCode.class.php +++ b/files/lib/system/bbcode/FaqBBCode.class.php @@ -35,7 +35,7 @@ public function getParsedTag(array $openingTag, $content, array $closingTag, BBC $collapse = true; } - return WCF::getTPL()->fetch('faqBBCode', 'wcf', [ + return WCF::getTPL()->render('wcf', 'faqBBCode', [ 'question' => $question, 'collapseQuestion' => $collapse, ], true); diff --git a/files/lib/system/endpoint/controller/hanashi/questions/ChangeShowOrder.class.php b/files/lib/system/endpoint/controller/hanashi/questions/ChangeShowOrder.class.php new file mode 100644 index 0000000..3ddbc57 --- /dev/null +++ b/files/lib/system/endpoint/controller/hanashi/questions/ChangeShowOrder.class.php @@ -0,0 +1,59 @@ +checkPermissions(['admin.faq.canAddQuestion']); + + $questionList = new QuestionList(); + $questionList->sqlOrderBy = 'showOrder ASC'; + $questionList->readObjects(); + + $items = \array_map( + static fn (Question $question) => new ShowOrderItem($question->questionID, $question->getTitle()), + $questionList->getObjects() + ); + + $sortedItems = (new ShowOrderHandler($items))->getSortedItemsFromRequest($request); + $this->saveShowOrder($sortedItems); + + return new JsonResponse([]); + } + + /** + * @param list $items + */ + private function saveShowOrder(array $items): void + { + WCF::getDB()->beginTransaction(); + $sql = "UPDATE wcf1_faq_questions + SET showOrder = ? + WHERE questionID = ?"; + $statement = WCF::getDB()->prepare($sql); + for ($i = 0, $length = \count($items); $i < $length; $i++) { + $statement->execute([ + $i + 1, + $items[$i]->id, + ]); + } + WCF::getDB()->commitTransaction(); + + FaqQuestionListCacheBuilder::getInstance()->reset(); + } +} diff --git a/files/lib/system/endpoint/controller/hanashi/questions/DeleteQuestion.class.php b/files/lib/system/endpoint/controller/hanashi/questions/DeleteQuestion.class.php new file mode 100644 index 0000000..2b7c5fd --- /dev/null +++ b/files/lib/system/endpoint/controller/hanashi/questions/DeleteQuestion.class.php @@ -0,0 +1,30 @@ +checkPermissions(['admin.faq.canAddQuestion']); + + (new QuestionAction([$question], 'delete'))->executeAction(); + + return new JsonResponse([]); + } +} diff --git a/files/lib/system/endpoint/controller/hanashi/questions/DisableQuestion.class.php b/files/lib/system/endpoint/controller/hanashi/questions/DisableQuestion.class.php new file mode 100644 index 0000000..6777bf2 --- /dev/null +++ b/files/lib/system/endpoint/controller/hanashi/questions/DisableQuestion.class.php @@ -0,0 +1,32 @@ +checkPermissions(['admin.faq.canAddQuestion']); + if ($question->isDisabled) { + throw new PermissionDeniedException(); + } + + (new QuestionAction([$question], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } +} diff --git a/files/lib/system/endpoint/controller/hanashi/questions/EnableQuestion.class.php b/files/lib/system/endpoint/controller/hanashi/questions/EnableQuestion.class.php new file mode 100644 index 0000000..2b5e6c0 --- /dev/null +++ b/files/lib/system/endpoint/controller/hanashi/questions/EnableQuestion.class.php @@ -0,0 +1,32 @@ +checkPermissions(['admin.faq.canAddQuestion']); + if (!$question->isDisabled) { + throw new PermissionDeniedException(); + } + + (new QuestionAction([$question], 'toggle'))->executeAction(); + + return new JsonResponse([]); + } +} diff --git a/files/lib/system/endpoint/controller/hanashi/questions/GetShowOrder.class.php b/files/lib/system/endpoint/controller/hanashi/questions/GetShowOrder.class.php new file mode 100644 index 0000000..aa539a9 --- /dev/null +++ b/files/lib/system/endpoint/controller/hanashi/questions/GetShowOrder.class.php @@ -0,0 +1,33 @@ +checkPermissions(['admin.faq.canAddQuestion']); + + $questionList = new QuestionList(); + $questionList->sqlOrderBy = 'showOrder ASC'; + $questionList->readObjects(); + + $items = \array_map( + static fn (Question $question) => new ShowOrderItem($question->questionID, $question->getTitle()), + $questionList->getObjects() + ); + + return (new ShowOrderHandler($items))->toJsonResponse(); + } +} diff --git a/files/lib/system/endpoint/controller/hanashi/questions/search/GetSearch.class.php b/files/lib/system/endpoint/controller/hanashi/questions/search/GetSearch.class.php index bf0b12c..eee7f38 100644 --- a/files/lib/system/endpoint/controller/hanashi/questions/search/GetSearch.class.php +++ b/files/lib/system/endpoint/controller/hanashi/questions/search/GetSearch.class.php @@ -29,7 +29,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $questionIDs = $this->getQuestionsIDs($parameters->query); return new JsonResponse([ - 'template' => WCF::getTPL()->fetch('shared_faqQuestionSearchResult', 'wcf', [ + 'template' => WCF::getTPL()->render('wcf', 'shared_faqQuestionSearchResult', [ 'questions' => $this->getQuestions($questionIDs), ]), ]); diff --git a/files/lib/system/endpoint/controller/hanashi/questions/search/RenderSearch.class.php b/files/lib/system/endpoint/controller/hanashi/questions/search/RenderSearch.class.php index 5b3ab04..0b7a096 100644 --- a/files/lib/system/endpoint/controller/hanashi/questions/search/RenderSearch.class.php +++ b/files/lib/system/endpoint/controller/hanashi/questions/search/RenderSearch.class.php @@ -17,7 +17,7 @@ final class RenderSearch implements IController public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface { return new JsonResponse([ - 'template' => WCF::getTPL()->fetch('shared_faqQuestionSearchDialog', 'wcf'), + 'template' => WCF::getTPL()->render('wcf', 'shared_faqQuestionSearchDialog', []), ]); } } diff --git a/files/lib/system/gridView/admin/FaqQuestionGridView.class.php b/files/lib/system/gridView/admin/FaqQuestionGridView.class.php new file mode 100644 index 0000000..bbee498 --- /dev/null +++ b/files/lib/system/gridView/admin/FaqQuestionGridView.class.php @@ -0,0 +1,106 @@ +addColumns([ + GridViewColumn::for('questionID') + ->label('wcf.global.objectID') + ->renderer(new ObjectIdColumnRenderer()) + ->filter(ObjectIdFilter::class) + ->sortable(), + GridViewColumn::for('question') + ->label('wcf.acp.faq.question.question') + ->renderer(new PhraseColumnRenderer()) + ->filter(I18nTextFilter::class) + ->titleColumn() + ->sortable(), + GridViewColumn::for('categoryID') + ->label('wcf.global.category') + ->renderer(new CategoryColumnRenderer()) + ->filter( + new CategoryFilter( + (new CategoryNodeTree('dev.tkirch.wsc.faq.category'))->getIterator(), + 'categoryID', + 'wcf.global.category' + ) + ) + ->sortable(), + GridViewColumn::for('showOrder') + ->label('wcf.global.showOrder') + ->renderer(new NumberColumnRenderer()) + ->filter(IntegerFilter::class) + ->sortable(), + ]); + + $this->addAvailableFilter( + new I18nTextFilter( + 'answer', + 'wcf.acp.faq.question.answer' + ) + ); + + $provider = new FaqQuestionsInteractions(); + $provider->addInteractions([ + new Divider(), + new EditInteraction(FaqQuestionEditForm::class), + new FaqCopyInteraction('copy'), + ]); + $this->setInteractionProvider($provider); + + $this->addQuickInteraction( + new ToggleInteraction( + 'isDisabled', + 'hanashi/questions/%s/enable', + 'hanashi/questions/%s/disable', + ) + ); + + $this->addRowLink( + new GridViewRowLink( + FaqQuestionEditForm::class + ) + ); + + $this->setDefaultSortField('showOrder'); + $this->setDefaultSortOrder('ASC'); + } + + #[Override] + protected function createObjectList(): DatabaseObjectList + { + return new QuestionList(); + } + + #[Override] + public function isAccessible(): bool + { + return WCF::getSession()->getPermission('admin.faq.canViewQuestion'); + } +} diff --git a/files/lib/system/interaction/FaqCopyInteraction.class.php b/files/lib/system/interaction/FaqCopyInteraction.class.php new file mode 100644 index 0000000..487da24 --- /dev/null +++ b/files/lib/system/interaction/FaqCopyInteraction.class.php @@ -0,0 +1,29 @@ +%s', + LinkHandler::getInstance()->getControllerLink( + FaqQuestionAddForm::class, + [ + 'duplicateID' => $object->questionID, + 'isMultilingual' => $object->isMultilingual, + ] + ), + WCF::getLanguage()->get('wcf.acp.faqQuestion.copy') + ); + } +} diff --git a/files/lib/system/interaction/admin/FaqQuestionsInteractions.class.php b/files/lib/system/interaction/admin/FaqQuestionsInteractions.class.php new file mode 100644 index 0000000..649f505 --- /dev/null +++ b/files/lib/system/interaction/admin/FaqQuestionsInteractions.class.php @@ -0,0 +1,30 @@ +addInteractions([ + new DeleteInteraction("hanashi/questions/%s"), + ]); + + EventHandler::getInstance()->fire( + new FaqQuestionsInteractionCollecting($this) + ); + } + + #[Override] + public function getObjectClassName(): string + { + return Question::class; + } +} diff --git a/package.xml b/package.xml index d18cc11..ef2fbca 100644 --- a/package.xml +++ b/package.xml @@ -6,18 +6,18 @@ Simple FAQ A simple and powerful FAQ for your WSC. Ein simples und leistungsstarkes FAQ für Ihr WSC. - 2.2.8 - 2025-07-03 + 2.3.0 + 2025-11-22 Hanashi Development, Titus Kirch https://hanashi.dev - com.woltlab.wcf + com.woltlab.wcf - com.woltlab.wcf + com.woltlab.wcf @@ -36,8 +36,9 @@ acp/database/install_dev.tkirch.wsc.faq.php - + - + +