Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
85.29% covered (success)
85.29%
58 / 68
0.00% covered (danger)
0.00%
0 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
FormController
85.29% covered (success)
85.29%
58 / 68
0.00% covered (danger)
0.00%
0 / 5
15.72
0.00% covered (danger)
0.00%
0 / 1
 activateInput
83.33% covered (success)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
3.04
 setInputAsRequired
83.33% covered (success)
83.33%
10 / 12
0.00% covered (danger)
0.00%
0 / 1
3.04
 editTranslation
86.67% covered (success)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
3.02
 deleteTranslation
85.71% covered (success)
85.71%
12 / 14
0.00% covered (danger)
0.00%
0 / 1
3.03
 addTranslation
86.67% covered (success)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
3.02
1<?php
2
3/**
4 * The Admin Form Controller
5 * This Source Code Form is subject to the terms of the Mozilla Public License,
6 * v. 2.0. If a copy of the MPL was not distributed with this file, You can
7 * obtain one at https://mozilla.org/MPL/2.0/.
8 *
9 * @package   phpMyFAQ
10 * @author    Thorsten Rinne <thorsten@phpmyfaq.de>
11 * @author    Jan Harms <modelrailroader@gmx-topmail.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-03-09
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Administration\Api;
21
22use phpMyFAQ\Controller\AbstractController;
23use phpMyFAQ\Core\Exception;
24use phpMyFAQ\Enums\PermissionType;
25use phpMyFAQ\Filter;
26use phpMyFAQ\Forms;
27use phpMyFAQ\Session\Token;
28use phpMyFAQ\Translation;
29use Symfony\Component\HttpFoundation\JsonResponse;
30use Symfony\Component\HttpFoundation\Request;
31use Symfony\Component\HttpFoundation\Response;
32use Symfony\Component\Routing\Attribute\Route;
33
34final class FormController extends AbstractController
35{
36    #[Route(path: 'forms/activate', name: 'admin.api.forms.activate', methods: ['PUT'])]
37    public function activateInput(Request $request): JsonResponse
38    {
39        $this->userHasPermission(PermissionType::FORMS_EDIT);
40        $data = $this->getJsonObject($request);
41        // The frontend sends "checked" as a JSON boolean. FILTER_VALIDATE_INT turns false
42        // into null (validation failure), which breaks deactivating an input, so validate
43        // it as a boolean and cast to the 0/1 integer the Forms layer expects.
44        $checked = (int) Filter::filterVar($data->checked ?? false, FILTER_VALIDATE_BOOLEAN, false);
45        $formId = (int) Filter::filterVar($data->formid ?? null, FILTER_VALIDATE_INT);
46        $inputId = (int) Filter::filterVar($data->inputid ?? null, FILTER_VALIDATE_INT);
47
48        $forms = new Forms($this->configuration);
49        if (!Token::getInstance($this->session)->verifyToken('activate-input', (string) ($data->csrf ?? ''))) {
50            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
51        }
52
53        try {
54            $forms->saveActivateInputStatus($formId, $inputId, $checked);
55            return $this->json(['success' => Translation::get(key: 'msgEditFormsSuccessful')], Response::HTTP_OK);
56        } catch (Exception $exception) {
57            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
58        }
59    }
60
61    #[Route(path: 'forms/required', name: 'admin.api.forms.required', methods: ['PUT'])]
62    public function setInputAsRequired(Request $request): JsonResponse
63    {
64        $this->userHasPermission(PermissionType::FORMS_EDIT);
65        $data = $this->getJsonObject($request);
66        // The frontend sends "checked" as a JSON boolean. FILTER_VALIDATE_INT turns false
67        // into null (validation failure), which breaks marking an input as not required, so
68        // validate it as a boolean and cast to the 0/1 integer the Forms layer expects.
69        $checked = (int) Filter::filterVar($data->checked ?? false, FILTER_VALIDATE_BOOLEAN, false);
70        $formId = (int) Filter::filterVar($data->formid ?? null, FILTER_VALIDATE_INT);
71        $inputId = (int) Filter::filterVar($data->inputid ?? null, FILTER_VALIDATE_INT);
72
73        $forms = new Forms($this->configuration);
74        if (!Token::getInstance($this->session)->verifyToken('require-input', (string) ($data->csrf ?? ''))) {
75            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
76        }
77
78        try {
79            $forms->saveRequiredInputStatus($formId, $inputId, $checked);
80            return $this->json(['success' => Translation::get(key: 'msgEditFormsSuccessful')], Response::HTTP_OK);
81        } catch (Exception $exception) {
82            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
83        }
84    }
85
86    #[Route(path: 'forms/translation-edit', name: 'admin.api.forms.translation-edit', methods: ['PUT'])]
87    public function editTranslation(Request $request): JsonResponse
88    {
89        $this->userHasPermission(PermissionType::FORMS_EDIT);
90        $data = $this->getJsonObject($request);
91        $label = Filter::filterVar($data->label ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
92        $formId = (int) Filter::filterVar($data->formId ?? null, FILTER_VALIDATE_INT);
93        $inputId = (int) Filter::filterVar($data->inputId ?? null, FILTER_VALIDATE_INT);
94        $lang = Filter::filterVar($data->lang ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
95
96        $forms = new Forms($this->configuration);
97        if (!Token::getInstance($this->session)->verifyToken('edit-translation', (string) ($data->csrf ?? ''))) {
98            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
99        }
100
101        try {
102            $forms->editTranslation($label, $formId, $inputId, $lang);
103            return $this->json([
104                'success' => Translation::get(key: 'msgFormsEditTranslationSuccessful'),
105            ], Response::HTTP_OK);
106        } catch (Exception $exception) {
107            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
108        }
109    }
110
111    /**
112     * @throws \Exception
113     */
114    #[Route(
115        path: 'admin/api/forms/translation-delete',
116        name: 'admin.api.forms.translation-delete',
117        methods: ['DELETE'],
118    )]
119    public function deleteTranslation(Request $request): JsonResponse
120    {
121        $this->userHasPermission(PermissionType::FORMS_EDIT);
122        $data = $this->getJsonObject($request);
123        $formId = (int) Filter::filterVar($data->formId ?? null, FILTER_VALIDATE_INT);
124        $inputId = (int) Filter::filterVar($data->inputId ?? null, FILTER_VALIDATE_INT);
125        $lang = Filter::filterVar($data->lang ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
126
127        $forms = new Forms($this->configuration);
128        if (!Token::getInstance($this->session)->verifyToken('delete-translation', (string) ($data->csrf ?? ''))) {
129            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
130        }
131
132        try {
133            $forms->deleteTranslation($formId, $inputId, $lang);
134            return $this->json([
135                'success' => Translation::get('msgFormsDeleteTranslationSuccessful'),
136            ], Response::HTTP_OK);
137        } catch (Exception $exception) {
138            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
139        }
140    }
141
142    #[Route(path: 'forms/translation-add', name: 'admin.api.forms.translation-add', methods: ['POST'])]
143    public function addTranslation(Request $request): JsonResponse
144    {
145        $this->userHasPermission(PermissionType::FORMS_EDIT);
146
147        $data = $this->getJsonObject($request);
148
149        $formId = (int) Filter::filterVar($data->formId ?? null, FILTER_VALIDATE_INT);
150        $inputId = (int) Filter::filterVar($data->inputId ?? null, FILTER_VALIDATE_INT);
151        $lang = Filter::filterVar($data->lang ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
152        $translation = Filter::filterVar($data->translation ?? '', FILTER_SANITIZE_SPECIAL_CHARS, '');
153
154        $forms = new Forms($this->configuration);
155        if (!Token::getInstance($this->session)->verifyToken('add-translation', (string) ($data->csrf ?? ''))) {
156            return $this->json(['error' => Translation::get(key: 'msgNoPermission')], Response::HTTP_UNAUTHORIZED);
157        }
158
159        try {
160            $forms->addTranslation($formId, $inputId, $lang, $translation);
161            return $this->json([
162                'success' => Translation::get(key: 'msgFormsAddTranslationSuccessful'),
163            ], Response::HTTP_OK);
164        } catch (Exception $exception) {
165            return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
166        }
167    }
168}