Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.30% covered (success)
97.30%
36 / 37
0.00% covered (danger)
0.00%
0 / 1
CRAP
0.00% covered (danger)
0.00%
0 / 1
RegistrationController
97.30% covered (success)
97.30%
36 / 37
0.00% covered (danger)
0.00%
0 / 1
18
0.00% covered (danger)
0.00%
0 / 1
 create
97.30% covered (success)
97.30%
36 / 37
0.00% covered (danger)
0.00%
0 / 1
18
1<?php
2
3/**
4 * The Registration 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-03-03
16 */
17
18declare(strict_types=1);
19
20namespace phpMyFAQ\Controller\Frontend\Api;
21
22use JsonException;
23use phpMyFAQ\Controller\AbstractController;
24use phpMyFAQ\Core\Exception;
25use phpMyFAQ\Filter;
26use phpMyFAQ\Helper\RegistrationHelper;
27use phpMyFAQ\Translation;
28use Symfony\Component\HttpFoundation\JsonResponse;
29use Symfony\Component\HttpFoundation\Request;
30use Symfony\Component\HttpFoundation\Response;
31use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
32use Symfony\Component\Routing\Attribute\Route;
33
34final class RegistrationController extends AbstractController
35{
36    /**
37     * @throws JsonException|Exception
38     */
39    #[Route(path: 'register', name: 'api.private.register', methods: ['POST'])]
40    public function create(Request $request): JsonResponse
41    {
42        if (!$this->configuration->get(item: 'security.enableRegistration')) {
43            return $this->json(['error' => 'User registration is disabled.'], Response::HTTP_FORBIDDEN);
44        }
45
46        $registrationHelper = new RegistrationHelper($this->configuration);
47
48        $data = json_decode($request->getContent(), associative: false, depth: 512, flags: JSON_THROW_ON_ERROR);
49        if (!is_object($data)) {
50            throw new Exception('Invalid request payload');
51        }
52
53        if (!property_exists($data, 'realname')) {
54            throw new Exception('Missing realname');
55        }
56
57        if (!property_exists($data, 'name')) {
58            throw new Exception('Missing username');
59        }
60
61        if (!property_exists($data, 'email') || trim((string) $data->email) === '') {
62            throw new Exception('Missing or empty email');
63        }
64
65        if (property_exists($data, 'isVisible')) {
66            throw new Exception('isVisible parameter not allowed');
67        }
68
69        $fullName = trim(strip_tags((string) $data->realname));
70        $userName = trim((string) Filter::filterVar($data->name, FILTER_SANITIZE_SPECIAL_CHARS));
71        $email = trim((string) Filter::filterEmail($data->email));
72
73        if (!$email) {
74            throw new Exception('Invalid email address');
75        }
76
77        $email = Filter::filterVar($email, FILTER_SANITIZE_SPECIAL_CHARS, '');
78
79        $isVisible = (bool) Filter::filterVar($data->isVisible ?? false, FILTER_SANITIZE_SPECIAL_CHARS) ?? false;
80
81        if (!$this->captchaCodeIsValid($request)) {
82            return $this->json(['error' => Translation::get(key: 'msgCaptcha')], Response::HTTP_BAD_REQUEST);
83        }
84
85        if (!$registrationHelper->isDomainAllowed($email)) {
86            return $this->json(['error' => 'The domain is not allowed.'], Response::HTTP_BAD_REQUEST);
87        }
88
89        if (
90            $userName !== ''
91            && $userName !== '0'
92            && $email !== ''
93            && $email !== '0'
94            && ($fullName !== '' && $fullName !== '0')
95        ) {
96            try {
97                return $this->json(
98                    $registrationHelper->createUser($userName, $fullName, $email, $isVisible),
99                    Response::HTTP_CREATED,
100                );
101            } catch (Exception|TransportExceptionInterface $exception) {
102                return $this->json(['error' => $exception->getMessage()], Response::HTTP_BAD_REQUEST);
103            }
104        }
105
106        return $this->json(['error' => Translation::get(key: 'err_sendMail')], Response::HTTP_BAD_REQUEST);
107    }
108}