Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
98.70% covered (success)
98.70%
76 / 77
66.67% covered (warning)
66.67%
2 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
QuestionsController
98.70% covered (success)
98.70%
76 / 77
66.67% covered (warning)
66.67%
2 / 3
10
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%
31 / 31
100.00% covered (success)
100.00%
1 / 1
2
 ask
97.78% covered (success)
97.78%
44 / 45
0.00% covered (danger)
0.00%
0 / 1
7
1<?php
2
3/**
4 * Open Questions 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 2002-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     2002-09-17
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Frontend;
21
22use phpMyFAQ\Captcha\CaptchaInterface;
23use phpMyFAQ\Captcha\Helper\CaptchaHelperInterface;
24use phpMyFAQ\Category;
25use phpMyFAQ\Core\Exception;
26use phpMyFAQ\Enums\PermissionType;
27use phpMyFAQ\Faq\QuestionService;
28use phpMyFAQ\Filter;
29use phpMyFAQ\Helper\QuestionHelper;
30use phpMyFAQ\Translation;
31use phpMyFAQ\User\UserSession;
32use Symfony\Component\HttpFoundation\RedirectResponse;
33use Symfony\Component\HttpFoundation\Request;
34use Symfony\Component\HttpFoundation\Response;
35use Symfony\Component\Routing\Attribute\Route;
36use Twig\TwigFilter;
37
38final class QuestionsController extends AbstractFrontController
39{
40    public function __construct(
41        private readonly UserSession $faqSession,
42        private readonly CaptchaInterface $captcha,
43        private readonly CaptchaHelperInterface $captchaHelper,
44    ) {
45        parent::__construct();
46    }
47
48    /**
49     * Displays the open questions page.
50     *
51     * @throws Exception
52     * @throws \Exception
53     */
54    #[Route(path: '/open-questions.html', name: 'public.open-questions', methods: ['GET'])]
55    public function index(Request $request): Response
56    {
57        $this->faqSession->setCurrentUser($this->currentUser);
58        $this->faqSession->userTracking('open_questions', 0);
59
60        $category = new Category($this->configuration);
61        $questionHelper = new QuestionHelper();
62        $questionHelper->setConfiguration($this->configuration)->setCategory($category);
63
64        return $this->render('open-questions.twig', [
65            ...$this->getHeader($request),
66            'title' => sprintf(
67                '%s - %s',
68                Translation::getString(key: 'msgOpenQuestions'),
69                $this->configuration->getTitle(),
70            ),
71            'metaDescription' => sprintf(
72                Translation::getString(key: 'msgOpenQuestionsMetaDesc'),
73                $this->configuration->getTitle(),
74            ),
75            'pageHeader' => Translation::get(key: 'msgOpenQuestions'),
76            'msgQuestionText' => Translation::get(key: 'msgQuestionText'),
77            'msgDate_User' => Translation::get(key: 'msgDate_User'),
78            'msgQuestion2' => Translation::get(key: 'msgQuestion2'),
79            'openQuestions' => $questionHelper->getOpenQuestions(),
80            'isCloseQuestionEnabled' => $this->configuration->get('records.enableCloseQuestion'),
81            'userHasPermissionToAnswer' =>
82                $this->currentUser->perm->hasPermission($this->currentUser->getUserId(), PermissionType::FAQ_ADD->value)
83                    || (bool) $this->configuration->get('records.allowNewFaqsForGuests'),
84            'msgQuestionsWaiting' => Translation::get(key: 'msgQuestionsWaiting'),
85            'msgNoQuestionsAvailable' => Translation::get(key: 'msgNoQuestionsAvailable'),
86            'msg2answerFAQ' => Translation::get(key: 'msg2answerFAQ'),
87            'msg2answer' => Translation::get(key: 'msg2answer'),
88            'msgEmailTo' => Translation::get(key: 'msgEmailTo'),
89        ]);
90    }
91
92    /**
93     * Displays the form to ask a new question
94     *
95     * @throws \Exception
96     */
97    #[Route(path: '/add-question.html', name: 'public.question.ask', methods: ['GET'])]
98    public function ask(Request $request): Response
99    {
100        $this->faqSession->setCurrentUser($this->currentUser);
101        $this->faqSession->userTracking('ask_question', 0);
102
103        // Get current groups
104        $currentGroups = $this->currentUser->perm->getUserGroups($this->currentUser->getUserId());
105
106        $questionService = new QuestionService($this->configuration, $this->currentUser, $currentGroups);
107
108        if (!$questionService->canUserAskQuestion()) {
109            return new RedirectResponse($this->configuration->getDefaultUrl() . 'login');
110        }
111
112        $categoryId = Filter::filterVar($request->query->get('category_id'), FILTER_VALIDATE_INT, 0);
113
114        $questionData = $questionService->prepareAskQuestionData($categoryId);
115
116        // Add Twig filter
117        $this->addFilter(new TwigFilter('repeat', static fn(
118            $string,
119            $times,
120        ): string => str_repeat((string) $string, max(0, (int) $times))));
121
122        // Prepare template variables
123        $templateVars = [
124            ...$this->getHeader($request),
125            'title' => sprintf('%s - %s', Translation::getString(key: 'msgQuestion'), $this->configuration->getTitle()),
126            'metaDescription' => sprintf(
127                Translation::getString(key: 'msgQuestionMetaDesc'),
128                $this->configuration->getTitle(),
129            ),
130            'msgMatchingQuestions' => Translation::get(key: 'msgMatchingQuestions'),
131            'msgFinishSubmission' => Translation::get(key: 'msgFinishSubmission'),
132            'lang' => $this->configuration->getLanguage()->getLanguage(),
133            'defaultContentMail' => $questionService->getDefaultUserEmail(),
134            'defaultContentName' => $questionService->getDefaultUserName(),
135            'selectedCategory' => $questionData['selectedCategory'],
136            'categories' => $questionData['categories'],
137            'captchaFieldset' => $this->captchaHelper->renderCaptcha(
138                $this->captcha,
139                'ask',
140                Translation::getString(key: 'msgCaptcha'),
141                $this->currentUser->isLoggedIn(),
142            ),
143            'msgNewContentSubmit' => Translation::get(key: 'msgNewContentSubmit'),
144            'noCategories' => $questionData['noCategories'],
145            'msgFormDisabledDueToMissingCategories' => Translation::get(key: 'msgFormDisabledDueToMissingCategories'),
146        ];
147
148        // Collect data for displaying form
149        $formData = $questionData['formData'] ?? [];
150        foreach (is_array($formData) ? $formData : [] as $input) {
151            if (!$input instanceof \stdClass || (int) $input->input_active === 0) {
152                continue;
153            }
154
155            $label = sprintf('id%d_label', (int) $input->input_id);
156            $required = sprintf('id%d_required', (int) $input->input_id);
157            $templateVars[$label] = (string) $input->input_label;
158            $templateVars[$required] = (int) $input->input_required !== 0 ? 'required' : '';
159        }
160
161        return $this->render('ask.twig', $templateVars);
162    }
163}