Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.65% covered (success)
98.65%
440 / 446
33.33% covered (danger)
33.33%
3 / 9
CRAP
0.00% covered (danger)
0.00%
0 / 1
FaqController
98.65% covered (success)
98.65%
440 / 446
33.33% covered (danger)
33.33%
3 / 9
24
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 index
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 add
98.11% covered (success)
98.11%
52 / 53
0.00% covered (danger)
0.00%
0 / 1
3
 addInCategory
98.28% covered (success)
98.28%
57 / 58
0.00% covered (danger)
0.00%
0 / 1
3
 edit
98.94% covered (success)
98.94%
93 / 94
0.00% covered (danger)
0.00%
0 / 1
8
 copy
98.11% covered (success)
98.11%
52 / 53
0.00% covered (danger)
0.00%
0 / 1
2
 translate
98.11% covered (success)
98.11%
52 / 53
0.00% covered (danger)
0.00%
0 / 1
2
 answer
98.41% covered (success)
98.41%
62 / 63
0.00% covered (danger)
0.00%
0 / 1
3
 getBaseTemplateVars
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * The Administration FAQs Controller
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public License,
7 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
8 * obtain one at https://mozilla.org/MPL/2.0/.
9 *
10 * @package   phpMyFAQ
11 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
12 * @copyright 2024-2026 phpMyFAQ Team
13 * @license   https://www.mozilla.org/MPL/2.0/ Mozilla Public License Version 2.0
14 * @link      https://www.phpmyfaq.de
15 * @since     2024-12-23
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Administration;
21
22use phpMyFAQ\Administration\Changelog;
23use phpMyFAQ\Administration\Revision;
24use phpMyFAQ\Attachment\AttachmentFactory;
25use phpMyFAQ\Category;
26use phpMyFAQ\Category\Relation;
27use phpMyFAQ\Comments;
28use phpMyFAQ\Core\Exception;
29use phpMyFAQ\Database;
30use phpMyFAQ\Entity\SeoEntity;
31use phpMyFAQ\Enums\AdminLogType;
32use phpMyFAQ\Enums\PermissionType;
33use phpMyFAQ\Enums\SeoType;
34use phpMyFAQ\Faq;
35use phpMyFAQ\Faq\Permission;
36use phpMyFAQ\Faq\Permission as FaqPermission;
37use phpMyFAQ\Filter;
38use phpMyFAQ\Helper\CategoryHelper;
39use phpMyFAQ\Helper\LanguageHelper;
40use phpMyFAQ\Helper\UserHelper;
41use phpMyFAQ\Link;
42use phpMyFAQ\Link\Util\TitleSlugifier;
43use phpMyFAQ\Permission\MediumPermission;
44use phpMyFAQ\Question;
45use phpMyFAQ\Seo;
46use phpMyFAQ\Session\Token;
47use phpMyFAQ\Tags;
48use phpMyFAQ\Translation;
49use phpMyFAQ\Twig\Extensions\FormatBytesTwigExtension;
50use phpMyFAQ\Twig\Extensions\IsoDateTwigExtension;
51use phpMyFAQ\Twig\Extensions\UserNameTwigExtension;
52use phpMyFAQ\User\CurrentUser;
53use Symfony\Component\HttpFoundation\Request;
54use Symfony\Component\HttpFoundation\Response;
55use Symfony\Component\Routing\Attribute\Route;
56use Twig\Error\LoaderError;
57use Twig\Extension\AttributeExtension;
58
59final class FaqController extends AbstractAdministrationController
60{
61    /* @mago-expect lint:excessive-parameter-list - the controller dependencies are injected explicitly; a service split is planned with the admin rework */
62    public function __construct(
63        private readonly Comments $comments,
64        private readonly Faq $faq,
65        private readonly Tags $tags,
66        private readonly Seo $seo,
67        private readonly CategoryHelper $categoryHelper,
68        private readonly UserHelper $userHelper,
69        private readonly FaqPermission $faqPermission,
70        private readonly Changelog $changelog,
71        private readonly Question $question,
72    ) {
73        parent::__construct();
74    }
75
76    /**
77     * @throws Exception
78     * @throws LoaderError
79     * @throws \Exception
80     */
81    #[Route(path: '/faqs', name: 'admin.faqs', methods: ['GET'])]
82    public function index(Request $request): Response
83    {
84        $this->userHasPermission(PermissionType::FAQ_ADD);
85        $this->userHasPermission(PermissionType::FAQ_APPROVE);
86        $this->userHasPermission(PermissionType::FAQ_EDIT);
87        $this->userHasPermission(PermissionType::FAQ_DELETE);
88
89        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
90
91        $category = new Category($this->configuration, $currentAdminGroups, true);
92        $category->setUser($currentAdminUser);
93        $category->setGroups($currentAdminGroups);
94        $category->buildCategoryTree();
95
96        $categoryRelation = new Relation($this->configuration, $category);
97        $categoryRelation->setGroups($currentAdminGroups);
98
99        return $this->render('@admin/content/faq.overview.twig', [
100            ...$this->getHeader($request),
101            ...$this->getFooter(),
102            'csrfTokenSearch' => Token::getInstance($this->session)->getTokenInput('pmf-csrf-token'),
103            'csrfTokenOverview' => Token::getInstance($this->session)->getTokenString('pmf-csrf-token'),
104            'categories' => $category->getCategoryTree(),
105            'numberOfRecords' => $categoryRelation->getNumberOfFaqsPerCategory(),
106            'numberOfComments' => $this->comments->getNumberOfCommentsByCategory(),
107        ]);
108    }
109
110    /**
111     * @throws Exception
112     * @throws LoaderError
113     * @throws \Exception
114     * @todo refactor Twig template variables
115     */
116    #[Route(path: '/faq/add', name: 'admin.faq.add', methods: ['GET'])]
117    public function add(Request $request): Response
118    {
119        $this->userHasPermission(PermissionType::FAQ_ADD);
120
121        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
122
123        $category = new Category($this->configuration, $currentAdminGroups, true);
124        $category->setUser($currentAdminUser);
125        $category->setGroups($currentAdminGroups);
126        $category->buildCategoryTree();
127
128        $this->categoryHelper->setCategory($category);
129
130        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_ADD->value);
131        $categories = [];
132
133        $faqData = [
134            'id' => 0,
135            'lang' => $this->configuration->getLanguage()->getLanguage(),
136            'revision_id' => 0,
137            'author' => $this->currentUser->getUserData('display_name'),
138            'email' => $this->currentUser->getUserData('email'),
139            'comment' => $this->configuration->get(item: 'records.defaultAllowComments') ? 'checked' : null,
140        ];
141
142        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
143        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
144        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
145        return $this->render('@admin/content/faq.editor.twig', [
146            ...$this->getHeader($request),
147            ...$this->getFooter(),
148            ...$this->getBaseTemplateVars(),
149            'header' => Translation::get(key: 'msgAddFAQ'),
150            'editExistingFaq' => false,
151            'faqRevisionId' => 0,
152            'faqData' => $faqData,
153            'openQuestionId' => 0,
154            'notifyUser' => '',
155            'notifyEmail' => '',
156            'categoryTree' => $category->getCategoryTree(),
157            'selectedCategories' => $categories,
158            'languageOptions' => LanguageHelper::renderSelectLanguage($faqData['lang'], false, [], 'lang'),
159            'attachments' => [],
160            'allGroups' => true,
161            'restrictedGroups' => false,
162            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
163                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
164                : '',
165            'allUsers' => true,
166            'restrictedUsers' => false,
167            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
168            'changelogs' => [],
169            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
170                $this->currentUser->getUserId(),
171                PermissionType::FAQ_APPROVE->value,
172            ),
173            'isActive' => null,
174            'isInActive' => 'checked',
175            'nextSolutionId' => $this->faq->getNextSolutionId(),
176            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
177        ]);
178    }
179
180    #[Route(path: '/faq/add/{categoryId}/{categoryLanguage}', name: 'admin.faq.add.category', methods: ['GET'])]
181    public function addInCategory(Request $request): Response
182    {
183        $this->userHasPermission(PermissionType::FAQ_ADD);
184
185        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
186
187        $category = new Category($this->configuration, $currentAdminGroups, true);
188        $category->setUser($currentAdminUser);
189        $category->setGroups($currentAdminGroups);
190        $category->buildCategoryTree();
191
192        $categoryId = (int) Filter::filterVar($request->attributes->get('categoryId'), FILTER_VALIDATE_INT);
193        $categoryLanguage = Filter::filterVar(
194            $request->attributes->get('categoryLanguage'),
195            FILTER_SANITIZE_SPECIAL_CHARS,
196            '',
197        );
198
199        $this->categoryHelper->setCategory($category);
200
201        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_ADD->value);
202
203        $faqData = [
204            'id' => 0,
205            'lang' => $categoryLanguage,
206            'revision_id' => 0,
207            'author' => $this->currentUser->getUserData('display_name'),
208            'email' => $this->currentUser->getUserData('email'),
209            'comment' => $this->configuration->get(item: 'records.defaultAllowComments') ? 'checked' : null,
210        ];
211
212        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
213        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
214        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
215        return $this->render('@admin/content/faq.editor.twig', [
216            ...$this->getHeader($request),
217            ...$this->getFooter(),
218            ...$this->getBaseTemplateVars(),
219            'header' => Translation::get(key: 'msgAddFAQ'),
220            'editExistingFaq' => false,
221            'faqRevisionId' => 0,
222            'faqData' => $faqData,
223            'openQuestionId' => 0,
224            'notifyUser' => '',
225            'notifyEmail' => '',
226            'categoryTree' => $category->getCategoryTree(),
227            'selectedCategories' => $categoryId,
228            'languageOptions' => LanguageHelper::renderSelectLanguage($faqData['lang'], false, [], 'lang'),
229            'attachments' => [],
230            'allGroups' => true,
231            'restrictedGroups' => false,
232            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
233                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
234                : '',
235            'allUsers' => true,
236            'restrictedUsers' => false,
237            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
238            'changelogs' => [],
239            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
240                $this->currentUser->getUserId(),
241                PermissionType::FAQ_APPROVE->value,
242            ),
243            'isActive' => null,
244            'isInActive' => 'checked',
245            'nextSolutionId' => $this->faq->getNextSolutionId(),
246            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
247        ]);
248    }
249
250    /**
251     * @throws Exception
252     * @throws LoaderError
253     * @throws \Exception
254     * @todo refactor Twig template variables
255     */
256    #[Route(path: '/faq/edit/{faqId}/{faqLanguage}', name: 'admin.faq.edit', methods: ['GET', 'POST'])]
257    public function edit(Request $request): Response
258    {
259        $this->userHasPermission(PermissionType::FAQ_EDIT);
260
261        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
262
263        $faqId = (int) Filter::filterVar($request->attributes->get('faqId'), FILTER_VALIDATE_INT);
264        $faqLanguage = Filter::filterVar($request->attributes->get('faqLanguage'), FILTER_SANITIZE_SPECIAL_CHARS, '');
265        $selectedRevisionId = Filter::filterVar($request->request->get('selectedRevisionId'), FILTER_VALIDATE_INT);
266
267        $category = new Category($this->configuration, $currentAdminGroups, true, $faqLanguage);
268        $category->setUser($currentAdminUser);
269        $category->setGroups($currentAdminGroups);
270        $category->buildCategoryTree();
271
272        $this->categoryHelper->setCategory($category);
273
274        $categoryRelation = new Relation($this->configuration, $category);
275
276        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_EDIT->value . ':' . $faqId);
277
278        $categories = $categoryRelation->getCategories($faqId, $faqLanguage);
279
280        $this->faq->getFaq($faqId, null, true);
281        $faqData = $this->faq->faqRecord;
282
283        // Tags
284        $faqData['tags'] = implode(', ', $this->tags->getAllTagsById($faqId));
285
286        // SERP
287        $seoEntity = new SeoEntity();
288        $seoEntity->setSeoType(SeoType::FAQ)->setReferenceId($faqId)->setReferenceLanguage($faqLanguage);
289        $seoData = $this->seo->get($seoEntity);
290        $faqData['serp-title'] = $seoData->getTitle();
291        $faqData['serp-description'] = $seoData->getDescription();
292
293        $attachmentList = AttachmentFactory::fetchByRecordId($this->configuration, $faqId);
294
295        $faqRevision = new Revision($this->configuration);
296        $revisions = $faqRevision->get($faqId, $faqLanguage, (string) $faqData['author']);
297
298        $faqUrl = sprintf(
299            '%scontent/%d/%d/%s/%s.html',
300            $this->configuration->getDefaultUrl(),
301            $category->getCategoryIdFromFaq($faqId),
302            $faqId,
303            $faqLanguage,
304            TitleSlugifier::slug((string) $faqData['title']),
305        );
306
307        // User permissions
308        $userPermission = $this->faqPermission->get(Permission::USER, $faqId);
309        $allUsers = false;
310        $restrictedUsers = true;
311        if (count($userPermission) === 0 || $userPermission[0] === -1) {
312            $allUsers = true;
313            $restrictedUsers = false;
314            $userPermission[0] = -1;
315        }
316
317        // Group permissions
318        $groupPermission = $this->faqPermission->get(Permission::GROUP, $faqId);
319        $allGroups = false;
320        $restrictedGroups = true;
321        if (count($groupPermission) === 0 || $groupPermission[0] === -1) {
322            $allGroups = true;
323            $restrictedGroups = false;
324            $groupPermission[0] = -1;
325        }
326
327        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
328        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
329        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
330
331        return $this->render('@admin/content/faq.editor.twig', [
332            ...$this->getHeader($request),
333            ...$this->getFooter(),
334            ...$this->getBaseTemplateVars(),
335            'header' =>
336                Translation::getString(key: 'ad_entry_edit_1') . ' ' . Translation::getString(key: 'ad_entry_edit_2'),
337            'editExistingFaq' => true,
338            'currentRevision' => sprintf(
339                '%s 1.%d',
340                Translation::getString(key: 'msgRevision'),
341                $selectedRevisionId ?? (int) $faqData['revision_id'],
342            ),
343            'numberOfRevisions' => count($revisions),
344            'faqId' => $faqId,
345            'faqLang' => $faqLanguage,
346            'revisions' => $revisions,
347            'selectedRevisionId' => $selectedRevisionId ?? $faqData['revision_id'],
348            'faqRevisionId' => $faqData['revision_id'],
349            'faqData' => $faqData,
350            'faqUrl' => $faqUrl,
351            'openQuestionId' => 0,
352            'notifyUser' => '',
353            'notifyEmail' => '',
354            'categoryTree' => $category->getCategoryTree(),
355            'selectedCategories' => $categories,
356            'languageOptions' => LanguageHelper::renderSelectLanguage($faqLanguage, false, [], 'lang'),
357            'attachments' => $attachmentList,
358            'allGroups' => $allGroups,
359            'restrictedGroups' => $restrictedGroups,
360            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
361                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
362                : '',
363            'allUsers' => $allUsers,
364            'restrictedUsers' => $restrictedUsers,
365            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
366            'changelogs' => $this->changelog->getByFaqId($faqId),
367            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
368                $this->currentUser->getUserId(),
369                PermissionType::FAQ_APPROVE->value,
370            ),
371            'isActive' => $faqData['active'] === 'yes' ? 'checked' : null,
372            'isInActive' => $faqData['active'] !== 'yes' ? 'checked' : null,
373            'nextSolutionId' => $this->faq->getNextSolutionId(),
374            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
375        ]);
376    }
377
378    /**
379     * @throws Exception
380     * @throws LoaderError
381     * @throws \Exception
382     * @todo refactor Twig template variables
383     */
384    #[Route(path: '/faq/copy/{faqId}/{faqLanguage}', name: 'admin.faq.copy', methods: ['GET'])]
385    public function copy(Request $request): Response
386    {
387        $this->userHasPermission(PermissionType::FAQ_ADD);
388
389        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
390
391        $category = new Category($this->configuration, $currentAdminGroups, true);
392        $category->setUser($currentAdminUser);
393        $category->setGroups($currentAdminGroups);
394        $category->buildCategoryTree();
395
396        $this->categoryHelper->setCategory($category);
397
398        $faqId = (int) Filter::filterVar($request->attributes->get('faqId'), FILTER_VALIDATE_INT);
399        $faqLanguage = Filter::filterVar($request->attributes->get('faqLanguage'), FILTER_SANITIZE_SPECIAL_CHARS, '');
400
401        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_COPY->value . ':' . $faqId);
402
403        $categories = [];
404
405        $this->faq->getFaq($faqId, null, true);
406        $faqData = $this->faq->faqRecord;
407        $faqData['title'] = 'Copy of ' . (string) $faqData['title'];
408
409        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
410        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
411        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
412        return $this->render('@admin/content/faq.editor.twig', [
413            ...$this->getHeader($request),
414            ...$this->getFooter(),
415            ...$this->getBaseTemplateVars(),
416            'header' =>
417                Translation::getString(key: 'ad_entry_edit_1') . ' ' . Translation::getString(key: 'ad_entry_edit_2'),
418            'editExistingFaq' => false,
419            'faqId' => 0,
420            'faqLang' => $faqLanguage,
421            'faqRevisionId' => 0,
422            'faqData' => $faqData,
423            'openQuestionId' => 0,
424            'notifyUser' => '',
425            'notifyEmail' => '',
426            'categoryTree' => $category->getCategoryTree(),
427            'selectedCategories' => $categories,
428            'languageOptions' => LanguageHelper::renderSelectLanguage($faqLanguage, false, [], 'lang'),
429            'attachments' => [],
430            'allGroups' => true,
431            'restrictedGroups' => false,
432            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
433                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
434                : '',
435            'allUsers' => true,
436            'restrictedUsers' => false,
437            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
438            'changelogs' => [],
439            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
440                $this->currentUser->getUserId(),
441                PermissionType::FAQ_APPROVE->value,
442            ),
443            'isActive' => null,
444            'isInActive' => null,
445            'nextSolutionId' => $this->faq->getNextSolutionId(),
446            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
447        ]);
448    }
449
450    /**
451     * @throws Exception
452     * @throws LoaderError
453     * @throws \Exception
454     * @todo refactor Twig template variables
455     */
456    #[Route(path: '/faq/translate/{faqId}/{faqLanguage}', name: 'admin.faq.translate', methods: ['GET'])]
457    public function translate(Request $request): Response
458    {
459        $this->userHasPermission(PermissionType::FAQ_ADD);
460
461        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
462
463        $category = new Category($this->configuration, $currentAdminGroups, true);
464        $category->setUser($currentAdminUser);
465        $category->setGroups($currentAdminGroups);
466        $category->buildCategoryTree();
467
468        $this->categoryHelper->setCategory($category);
469
470        $faqId = (int) Filter::filterVar($request->attributes->get('faqId'), FILTER_VALIDATE_INT);
471        $faqLanguage = Filter::filterVar($request->attributes->get('faqLanguage'), FILTER_SANITIZE_SPECIAL_CHARS, '');
472
473        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_TRANSLATE->value . ':' . $faqId);
474
475        $categories = [];
476
477        $this->faq->getFaq($faqId, null, true);
478        $faqData = $this->faq->faqRecord;
479        $faqData['title'] = 'Translation of ' . (string) $faqData['title'];
480
481        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
482        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
483        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
484        return $this->render('@admin/content/faq.editor.twig', [
485            ...$this->getHeader($request),
486            ...$this->getFooter(),
487            ...$this->getBaseTemplateVars(),
488            'header' =>
489                Translation::getString(key: 'ad_entry_edit_1') . ' ' . Translation::getString(key: 'ad_entry_edit_2'),
490            'editExistingFaq' => false,
491            'faqId' => 0,
492            'faqLang' => $faqLanguage,
493            'faqRevisionId' => 0,
494            'faqData' => $faqData,
495            'openQuestionId' => 0,
496            'notifyUser' => '',
497            'notifyEmail' => '',
498            'categoryTree' => $category->getCategoryTree(),
499            'selectedCategories' => $categories,
500            'languageOptions' => LanguageHelper::renderSelectLanguage($faqLanguage, false, [], 'lang'),
501            'attachments' => [],
502            'allGroups' => true,
503            'restrictedGroups' => false,
504            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
505                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
506                : '',
507            'allUsers' => true,
508            'restrictedUsers' => false,
509            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
510            'changelogs' => [],
511            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
512                $this->currentUser->getUserId(),
513                PermissionType::FAQ_APPROVE->value,
514            ),
515            'isActive' => null,
516            'isInActive' => null,
517            'nextSolutionId' => $this->faq->getNextSolutionId(),
518            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
519        ]);
520    }
521
522    /**
523     * @throws Exception
524     * @throws LoaderError
525     * @throws \Exception
526     * @todo refactor Twig template variables
527     */
528    #[Route(path: '/faq/answer/{questionId}/{faqLanguage}', name: 'admin.faq.answer', methods: ['GET'])]
529    public function answer(Request $request): Response
530    {
531        $this->userHasPermission(PermissionType::FAQ_ADD);
532
533        [$currentAdminUser, $currentAdminGroups] = CurrentUser::getCurrentUserGroupId($this->currentUser);
534
535        $category = new Category($this->configuration, $currentAdminGroups, true);
536        $category->setUser($currentAdminUser);
537        $category->setGroups($currentAdminGroups);
538        $category->buildCategoryTree();
539
540        $this->categoryHelper->setCategory($category);
541
542        $questionId = (int) Filter::filterVar($request->attributes->get('questionId'), FILTER_VALIDATE_INT);
543        $faqLanguage = Filter::filterVar($request->attributes->get('faqLanguage'), FILTER_SANITIZE_SPECIAL_CHARS, '');
544
545        $this->adminLog->log($this->currentUser, AdminLogType::FAQ_ANSWER_ADD->value . ':' . $questionId);
546
547        $questionData = $this->question->get($questionId);
548
549        $faqData = [
550            'id' => 0,
551            'lang' => $this->configuration->getLanguage()->getLanguage(),
552            'title' => $questionData['question'] ?? '',
553            'revision_id' => 0,
554            'author' => $this->currentUser->getUserData('display_name'),
555            'email' => $this->currentUser->getUserData('email'),
556            'comment' => $this->configuration->get(item: 'records.defaultAllowComments') ? 'checked' : null,
557        ];
558
559        $categories = [
560            'category_id' => $questionData['category_id'] ?? [],
561            'category_lang' => $faqLanguage,
562        ];
563
564        $this->addExtension(new AttributeExtension(IsoDateTwigExtension::class));
565        $this->addExtension(new AttributeExtension(UserNameTwigExtension::class));
566        $this->addExtension(new AttributeExtension(FormatBytesTwigExtension::class));
567        return $this->render('@admin/content/faq.editor.twig', [
568            ...$this->getHeader($request),
569            ...$this->getFooter(),
570            ...$this->getBaseTemplateVars(),
571            'header' =>
572                Translation::getString(key: 'ad_entry_edit_1') . ' ' . Translation::getString(key: 'ad_entry_edit_2'),
573            'editExistingFaq' => false,
574            'faqId' => 0,
575            'faqLang' => $faqLanguage,
576            'faqRevisionId' => 0,
577            'faqData' => $faqData,
578            'openQuestionId' => 0,
579            'notifyUser' => $questionData['username'] ?? $this->currentUser->getUserData('display_name'),
580            'notifyEmail' => $questionData['email'] ?? $this->currentUser->getUserData('email'),
581            'categoryTree' => $category->getCategoryTree(),
582            'selectedCategories' => $categories,
583            'languageOptions' => LanguageHelper::renderSelectLanguage($faqLanguage, false, [], 'lang'),
584            'attachments' => [],
585            'allGroups' => true,
586            'restrictedGroups' => false,
587            'groupPermissionOptions' => $this->currentUser->perm instanceof MediumPermission
588                ? $this->currentUser->perm->getAllGroupsOptions([-1], $this->currentUser)
589                : '',
590            'allUsers' => true,
591            'restrictedUsers' => false,
592            'userSelection' => $this->userHelper->getAllUsersForTemplate(-1, true),
593            'changelogs' => [],
594            'hasPermissionForApprove' => $this->currentUser?->perm->hasPermission(
595                $this->currentUser->getUserId(),
596                PermissionType::FAQ_APPROVE->value,
597            ),
598            'isActive' => null,
599            'isInActive' => null,
600            'nextSolutionId' => $this->faq->getNextSolutionId(),
601            'nextFaqId' => $this->configuration->getDb()->nextId(Database::getTablePrefix() . 'faqdata', 'id'),
602        ]);
603    }
604
605    /**
606     * @throws \Exception
607     * @return array<string, mixed>
608     */
609    private function getBaseTemplateVars(): array
610    {
611        $token = Token::getInstance($this->session);
612
613        $canAddAttachments = $this->currentUser?->perm->hasPermission(
614            $this->currentUser->getUserId(),
615            PermissionType::ATTACHMENT_ADD->value,
616        );
617
618        $canDeleteAttachments = $this->currentUser->perm->hasPermission(
619            $this->currentUser->getUserId(),
620            PermissionType::ATTACHMENT_DELETE->value,
621        );
622
623        $canTranslateFaqs = $this->currentUser->perm->hasPermission(
624            $this->currentUser->getUserId(),
625            PermissionType::FAQ_TRANSLATE->value,
626        );
627
628        return [
629            'csrfToken' => $token->getTokenString('pmf-csrf-token'),
630            'csrfTokenDeleteAttachment' => $token->getTokenString('delete-attachment'),
631            'csrfTokenUploadAttachment' => $token->getTokenString('upload-attachment'),
632            'isEditorEnabled' => $this->configuration->get(item: 'main.enableWysiwygEditor'),
633            'isMarkdownEditorEnabled' => $this->configuration->get(item: 'main.enableMarkdownEditor'),
634            'isBasicPermission' => $this->configuration->get(item: 'security.permLevel') === 'basic',
635            'defaultUrl' => $this->configuration->getDefaultUrl(),
636            'canBeNewRevision' => !$this->configuration->get(item: 'records.enableAutoRevisions'),
637            'maxAttachmentSize' => $this->configuration->get(item: 'records.maxAttachmentSize'),
638            'hasPermissionForAddAttachments' => $canAddAttachments,
639            'hasPermissionForDeleteAttachments' => $canDeleteAttachments,
640            'hasPermissionForTranslateFaqs' => $canTranslateFaqs,
641            'ad_entry_restricted_groups' => Translation::get(key: 'ad_entry_restricted_groups'),
642            'ad_entry_userpermission' => Translation::get(key: 'ad_entry_userpermission'),
643            'ad_entry_restricted_users' => Translation::get(key: 'ad_entry_restricted_users'),
644            'ad_entry_changelog' => Translation::get(key: 'ad_entry_changelog'),
645            'ad_entry_changed' => Translation::get(key: 'ad_entry_changed'),
646            'ad_admin_notes_hint' => Translation::get(key: 'ad_admin_notes_hint'),
647            'ad_admin_notes' => Translation::get(key: 'ad_admin_notes'),
648            'ad_entry_changelog_history' => Translation::get(key: 'ad_entry_changelog_history'),
649            'ad_gen_reset' => Translation::get(key: 'ad_gen_reset'),
650            'ad_entry_save' => Translation::get(key: 'ad_entry_save'),
651            'ad_entry_status' => Translation::get(key: 'ad_entry_status'),
652            'ad_entry_visibility' => Translation::get(key: 'ad_entry_visibility'),
653            'ad_entry_not_visibility' => Translation::get(key: 'ad_entry_not_visibility'),
654            'ad_entry_new_revision' => Translation::get(key: 'ad_entry_new_revision'),
655            'ad_gen_yes' => Translation::get(key: 'ad_gen_yes'),
656            'ad_gen_no' => Translation::get(key: 'ad_gen_no'),
657            'ad_entry_allowComments' => Translation::get(key: 'ad_entry_allowComments'),
658            'ad_entry_solution_id' => Translation::get(key: 'ad_entry_solution_id'),
659            'ad_att_addto' => Translation::get(key: 'ad_att_addto'),
660            'ad_att_addto_2' => Translation::get(key: 'ad_att_addto_2'),
661            'ad_att_att' => Translation::get(key: 'ad_att_att'),
662            'ad_att_butt' => Translation::get(key: 'ad_att_butt'),
663            'ad_changerev' => Translation::get(key: 'ad_changerev'),
664            'ad_view_faq' => Translation::get(key: 'ad_view_faq'),
665        ];
666    }
667}