From 7bc5c3f207f8c77f0bf0cfa221dba44a7f546e42 Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Thu, 3 Jul 2025 19:50:02 +0200 Subject: [PATCH 1/6] update package.xml --- package.xml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/package.xml b/package.xml index d18cc11..8ba4551 100644 --- a/package.xml +++ b/package.xml @@ -6,7 +6,7 @@ Simple FAQ A simple and powerful FAQ for your WSC. Ein simples und leistungsstarkes FAQ für Ihr WSC. - 2.2.8 + 2.3.0 2025-07-03 @@ -14,10 +14,10 @@ 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 - + - + + From ea3403da9d15ebf85c95cea0fdf9a6b41e258780 Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Thu, 3 Jul 2025 19:51:21 +0200 Subject: [PATCH 2/6] replace fetch with render --- files/lib/system/bbcode/FaqBBCode.class.php | 2 +- .../controller/hanashi/questions/search/GetSearch.class.php | 2 +- .../controller/hanashi/questions/search/RenderSearch.class.php | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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/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', []), ]); } } From 6a9bf926f82dee9ead0c905fc4f31aa61d8427da Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Thu, 3 Jul 2025 19:56:39 +0200 Subject: [PATCH 3/6] replace deprecated prepare --- files/lib/data/faq/QuestionEditor.class.php | 26 ++++++++++----------- 1 file changed, 13 insertions(+), 13 deletions(-) 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(); From 10fdd2cce21b82d6217f4a1c40dd8ab6609f9351 Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Thu, 3 Jul 2025 20:01:06 +0200 Subject: [PATCH 4/6] fix error on detail page --- files/lib/page/FaqQuestionListPage.class.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lib/page/FaqQuestionListPage.class.php b/files/lib/page/FaqQuestionListPage.class.php index 38a6d1c..e00a75d 100644 --- a/files/lib/page/FaqQuestionListPage.class.php +++ b/files/lib/page/FaqQuestionListPage.class.php @@ -45,7 +45,7 @@ public function readParameters() EOT ); - $this->category = FaqCategory::getCategory($queryParameters['id']); + $this->category = FaqCategory::getCategory((int)$queryParameters['id']); } catch (MappingError) { throw new IllegalLinkException(); } From 7ad2ba035e55cd712022dc7c2017407bd9b282cb Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Wed, 12 Nov 2025 18:31:19 +0100 Subject: [PATCH 5/6] update package.xml --- package.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.xml b/package.xml index 8ba4551..75a64ea 100644 --- a/package.xml +++ b/package.xml @@ -7,14 +7,14 @@ A simple and powerful FAQ for your WSC. Ein simples und leistungsstarkes FAQ für Ihr WSC. 2.3.0 - 2025-07-03 + 2025-11-12 Hanashi Development, Titus Kirch https://hanashi.dev - com.woltlab.wcf + com.woltlab.wcf com.woltlab.wcf From d57db77d2dec2da03b9f3fb40a65311386af1d96 Mon Sep 17 00:00:00 2001 From: Peter Lohse Date: Sat, 22 Nov 2025 22:10:46 +0100 Subject: [PATCH 6/6] grid view --- acptemplates/faqQuestionList.tpl | 125 +++--------------- .../acp/page/FaqQuestionListPage.class.php | 72 +--------- files/lib/bootstrap/dev.tkirch.wsc.faq.php | 10 ++ ...aqQuestionsInteractionCollecting.class.php | 13 ++ .../questions/ChangeShowOrder.class.php | 59 +++++++++ .../questions/DeleteQuestion.class.php | 30 +++++ .../questions/DisableQuestion.class.php | 32 +++++ .../questions/EnableQuestion.class.php | 32 +++++ .../hanashi/questions/GetShowOrder.class.php | 33 +++++ .../admin/FaqQuestionGridView.class.php | 106 +++++++++++++++ .../interaction/FaqCopyInteraction.class.php | 29 ++++ .../admin/FaqQuestionsInteractions.class.php | 30 +++++ package.xml | 2 +- 13 files changed, 398 insertions(+), 175 deletions(-) create mode 100644 files/lib/event/interaction/admin/FaqQuestionsInteractionCollecting.class.php create mode 100644 files/lib/system/endpoint/controller/hanashi/questions/ChangeShowOrder.class.php create mode 100644 files/lib/system/endpoint/controller/hanashi/questions/DeleteQuestion.class.php create mode 100644 files/lib/system/endpoint/controller/hanashi/questions/DisableQuestion.class.php create mode 100644 files/lib/system/endpoint/controller/hanashi/questions/EnableQuestion.class.php create mode 100644 files/lib/system/endpoint/controller/hanashi/questions/GetShowOrder.class.php create mode 100644 files/lib/system/gridView/admin/FaqQuestionGridView.class.php create mode 100644 files/lib/system/interaction/FaqCopyInteraction.class.php create mode 100644 files/lib/system/interaction/admin/FaqQuestionsInteractions.class.php 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/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 @@ +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/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 75a64ea..ef2fbca 100644 --- a/package.xml +++ b/package.xml @@ -7,7 +7,7 @@ A simple and powerful FAQ for your WSC. Ein simples und leistungsstarkes FAQ für Ihr WSC. 2.3.0 - 2025-11-12 + 2025-11-22 Hanashi Development, Titus Kirch