VSVverkeerskunde/gvq-api

View on GitHub
src/Account/Controllers/AccountViewController.php

Summary

Maintainability
F
4 days
Test Coverage
<?php declare(strict_types=1);

namespace VSV\GVQ_API\Account\Controllers;

use Ramsey\Uuid\Uuid;
use Ramsey\Uuid\UuidFactoryInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Translation\TranslatorInterface;
use Symfony\Component\Validator\Constraints\GroupSequence;
use VSV\GVQ_API\Account\Forms\EditPasswordFormType;
use VSV\GVQ_API\Account\Forms\LoginFormType;
use VSV\GVQ_API\Account\Forms\RegistrationFormType;
use VSV\GVQ_API\Account\Forms\RequestPasswordFormType;
use VSV\GVQ_API\Account\Forms\ResetPasswordFormType;
use VSV\GVQ_API\Company\Repositories\CompanyRepository;
use VSV\GVQ_API\Mail\Service\MailService;
use VSV\GVQ_API\Registration\Models\Registration;
use VSV\GVQ_API\Registration\Repositories\RegistrationRepository;
use VSV\GVQ_API\Registration\ValueObjects\UrlSuffix;
use VSV\GVQ_API\Registration\ValueObjects\UrlSuffixGenerator;
use VSV\GVQ_API\User\Models\User;
use VSV\GVQ_API\User\Repositories\Entities\UserEntity;
use VSV\GVQ_API\User\Repositories\UserRepository;
use VSV\GVQ_API\User\ValueObjects\Email;
use VSV\GVQ_API\User\ValueObjects\Password;

class AccountViewController extends AbstractController
{
    /**
     * @var RegistrationFormType
     */
    private $registrationFormType;

    /**
     * @var RequestPasswordFormType
     */
    private $requestPasswordFormType;

    /**
     * @var ResetPasswordFormType
     */
    private $resetPasswordFormType;

    /**
     * @var EditPasswordFormType
     */
    private $editPasswordFormType;

    /**
     * @var LoginFormType
     */
    private $loginFormType;

    /**
     * @var TranslatorInterface
     */
    private $translator;

    /**
     * @var UserRepository
     */
    private $userRepository;

    /**
     * @var CompanyRepository
     */
    private $companyRepository;

    /**
     * @var RegistrationRepository
     */
    private $registrationRepository;

    /**
     * @var UuidFactoryInterface
     */
    private $uuidFactory;

    /**
     * @var UrlSuffixGenerator
     */
    private $urlSuffixGenerator;

    /**
     * @var MailService
     */
    private $mailService;

    /**
     * @var \DateTimeImmutable
     */
    private $quizStartDate;

    /**
     * @var \DateTimeImmutable
     */
    private $quizKickOffDate;

    /**
     * @var bool
     */
    private $registrationsClosed;

    /**
     * @param TranslatorInterface $translator
     * @param UuidFactoryInterface $uuidFactory
     * @param UserRepository $userRepository
     * @param CompanyRepository $companyRepository
     * @param RegistrationRepository $registrationRepository
     * @param UrlSuffixGenerator $urlSuffixGenerator
     * @param MailService $mailService
     * @param \DateTimeImmutable $quizStartDate
     * @param \DateTimeImmutable $quizKickOffDate
     * @param bool $registrationsClosed
     */
    public function __construct(
        TranslatorInterface $translator,
        UuidFactoryInterface $uuidFactory,
        UserRepository $userRepository,
        CompanyRepository $companyRepository,
        RegistrationRepository $registrationRepository,
        UrlSuffixGenerator $urlSuffixGenerator,
        MailService $mailService,
        \DateTimeImmutable $quizStartDate,
        \DateTimeImmutable $quizKickOffDate,
        bool $registrationsClosed = false
    ) {
        $this->translator = $translator;
        $this->uuidFactory = $uuidFactory;
        $this->userRepository = $userRepository;
        $this->companyRepository = $companyRepository;
        $this->urlSuffixGenerator = $urlSuffixGenerator;
        $this->registrationRepository = $registrationRepository;
        $this->mailService = $mailService;
        $this->quizStartDate = $quizStartDate;
        $this->quizKickOffDate = $quizKickOffDate;
        $this->registrationsClosed = $registrationsClosed;

        $this->registrationFormType = new RegistrationFormType();
        $this->requestPasswordFormType = new RequestPasswordFormType();
        $this->resetPasswordFormType = new ResetPasswordFormType();
        $this->loginFormType = new LoginFormType();
        $this->editPasswordFormType = new EditPasswordFormType();
    }

    /**
     * @return Response
     */
    public function index(): Response
    {
        return $this->redirectToLandingPage();
    }

    private function canRegister(): bool
    {
        return
            !$this->registrationsClosed ||
            $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN') ||
            $this->get('security.authorization_checker')->isGranted('ROLE_VSV');
    }

    /**
     * @param Request $request
     * @return Response
     * @throws \Exception
     */
    public function registerIframe(Request $request): Response
    {
        if (!$this->canRegister()) {
            return new Response('', Response::HTTP_FORBIDDEN);
        }

        $form = $this->createRegisterForm($request->getLocale() == 'nl');
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            if ($this->honeypotTricked($data)) {
                return $this->handleHoneypotField('accounts_view_register');
            }

            $language = $request->getLocale();

            $user = $this->registrationFormType->createUserFromData(
                $this->uuidFactory,
                $data,
                $language
            );
            $this->userRepository->save($user);

            $company = $this->registrationFormType->createCompanyFromData(
                $this->uuidFactory,
                $data,
                $user
            );
            $this->companyRepository->save($company);

            $registration = $this->createRegistrationForUser(
                $user,
                false
            );
            $this->registrationRepository->save($registration);

            $this->mailService->sendActivationMail($registration);

            return $this->redirectToRoute('accounts_view_register_success_iframe');
        }

        return $this->render(
            'accounts/register_iframe.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @param Request $request
     * @return Response
     * @throws \Exception
     */
    public function register(Request $request): Response
    {
        if (!$this->canRegister()) {
            return new Response('', Response::HTTP_FORBIDDEN);
        }

        $form = $this->createRegisterForm($request->getLocale() == 'nl');
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            if ($this->honeypotTricked($data)) {
                return $this->handleHoneypotField('accounts_view_register');
            }

            $language = $request->getLocale();

            $user = $this->registrationFormType->createUserFromData(
                $this->uuidFactory,
                $data,
                $language
            );
            $this->userRepository->save($user);

            $company = $this->registrationFormType->createCompanyFromData(
                $this->uuidFactory,
                $data,
                $user
            );
            $this->companyRepository->save($company);

            $registration = $this->createRegistrationForUser(
                $user,
                false
            );
            $this->registrationRepository->save($registration);

            $this->mailService->sendActivationMail($registration);

            return $this->redirectToRoute('accounts_view_register_success');
        }

        return $this->render(
            'accounts/register.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @return Response
     */
    public function info(): Response
    {
        return $this->render(
            'accounts/info.html.twig',
            [
                'registrations_closed' => $this->registrationsClosed,
            ]
        );
    }

    /**
     * @return Response
     */
    public function registerSuccess(): Response
    {
        return $this->render('accounts/register_success.html.twig');
    }

    /**
     * @return Response
     */
    public function registerSuccessIframe(): Response
    {
        return $this->render('accounts/register_success_iframe.html.twig');
    }

    /**
     * @param Request $request
     * @return Response
     * @throws \Exception
     */
    public function requestPassword(Request $request): Response
    {
        $form = $this->createRequestPasswordForm();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            if ($this->honeypotTricked($data)) {
                return $this->handleHoneypotField('accounts_view_request_password');
            }

            $user = $this->userRepository->getByEmail(new Email($data['email']));

            if ($user && $user->isActive()) {
                $existingRegistration = $this->registrationRepository->getByUserId($user->getId());
                if ($existingRegistration) {
                    $this->registrationRepository->delete($existingRegistration->getId());
                }
                $registration = $this->createRegistrationForUser(
                    $user,
                    true
                );

                $this->registrationRepository->save($registration);
                $this->mailService->sendPasswordRequestMail($registration);
            }

            if ($user && !$user->isActive()) {
                $this->addFlash('warning', $this->translator->trans('Account.inactive'));
            } else {
                return $this->redirectToRoute('accounts_view_request_password_success');
            }
        }

        return $this->render(
            'accounts/request_password.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @return Response
     */
    public function requestPasswordSuccess(): Response
    {
        return $this->render('accounts/request_password_success.html.twig');
    }

    /**
     * @param Request $request
     * @return Response
     * @throws \Exception
     */
    public function resetPassword(Request $request): Response
    {
        $registration = $this->registrationRepository->getByUrlSuffix(
            new UrlSuffix($request->get('urlSuffix'))
        );
        if (!$registration) {
            return $this->render('accounts/reset_password_link_error.html.twig');
        }

        $form = $this->createResetPasswordForm();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            $user = $registration->getUser();

            if ($user) {
                $user = $user->withPassword(Password::fromPlainText($data['password']));
                $this->userRepository->updatePassword($user);
                $this->registrationRepository->delete($registration->getId());
            }

            return $this->redirectToRoute('accounts_view_reset_password_success');
        }

        return $this->render(
            'accounts/reset_password.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @return Response
     */
    public function resetPasswordSuccess(): Response
    {
        return $this->render('accounts/reset_password_success.html.twig');
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function login(Request $request): Response
    {
        $form = $this->createLoginForm();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            $user = $this->userRepository->getByEmail(new Email($data['email']));

            if ($user && $user->getPassword() && $user->getPassword()->verifies($data['password'])) {
                if ($user->isActive()) {
                    $securityUser = UserEntity::fromUser($user);
                    $token = new UsernamePasswordToken(
                        $securityUser,
                        null,
                        'main',
                        $securityUser->getRoles()
                    );
                    $this->get('security.token_storage')->setToken($token);
                    $this->get('session')->set('_security_main', serialize($token));

                    return $this->redirectToLandingPage();
                }
                $this->addFlash('warning', $this->translator->trans('Account.inactive'));
            } else {
                $this->addFlash('danger', $this->translator->trans('Credentials.invalid'));
            }
        }

        return $this->render(
            'accounts/login.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @param string $urlSuffix
     * @return Response
     * @throws \Exception
     */
    public function activate(string $urlSuffix): Response
    {
        $registration = $this->registrationRepository->getByUrlSuffix(
            new UrlSuffix($urlSuffix)
        );

        if ($registration && $registration->isUsed()) {
            return $this->render('accounts/activate_already_used.html.twig');
        }
        elseif ($registration) {
            $user = $registration->getUser()->activate();
            $this->userRepository->update($user);

            $registration->setUsed();
            $this->registrationRepository->save($registration);

            $this->mailService->sendWelcomeMail($registration);

            $now = new \DateTimeImmutable();

            if ($now >= $this->quizStartDate) {
                $this->mailService->sendKickOffMailAfterLaunch($registration);
            } elseif ($now >= $this->quizKickOffDate) {
                $this->mailService->sendKickOffMail($registration);
            }

            return $this->render('accounts/activate.html.twig');
        } else {
            return $this->render('accounts/activate_error.html.twig');
        }
    }


    /**
     * @param Request $request
     * @param string $id
     * @return Response
     * @throws \Exception
     */
    public function sendActivation(Request $request, string $id): Response
    {
        $user = $this->userRepository->getById(Uuid::fromString($id));

        if (!$user) {
            $this->addFlash(
                'warning',
                $this->translator->trans(
                    'User.id.invalid',
                    [
                        '%id%' => $id,
                    ]
                )
            );

            return $this->redirectToRoute('users_view_index');
        }

        $existingRegistration = $this->registrationRepository->getByUserId($user->getId());

        if ($request->getMethod() === 'POST') {
            if ($existingRegistration) {
                $this->registrationRepository->delete(
                    $existingRegistration->getId()
                );
            }

            $registration = $this->createRegistrationForUser(
                $user,
                false
            );
            $this->registrationRepository->save($registration);

            $this->mailService->sendActivationMail($registration);

            $this->addFlash(
                'success',
                $this->translator->trans(
                    'Activation.send.success',
                    [
                        '%email%' => $user->getEmail()->toNative(),
                    ]
                )
            );

            return $this->redirectToRoute('users_view_index');
        }

        $current_activation_url = '';
        if ($existingRegistration) {
            $current_activation_url = $this->generateUrl(
                'accounts_view_activate',
                [
                    '_locale' => $existingRegistration->getUser()->getLanguage()->toNative(),
                    'urlSuffix' => $existingRegistration->getUrlSuffix()->toNative(),
                ],
                UrlGeneratorInterface::ABSOLUTE_URL
            );
        }

        return $this->render(
            'users/send_activation.html.twig',
            [
                'email' => $user->getEmail()->toNative(),
                'current_activation_url' => $current_activation_url,
            ]
        );
    }

    /**
     * @param Request $request
     * @return Response
     */
    public function editPassword(Request $request): Response
    {
        $form = $this->createEditPasswordForm();
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            $data = $form->getData();

            $user = $this->userRepository->getByEmail(new Email($this->getUser()->getUsername()));

            if ($user) {
                $user = $this->editPasswordFormType->editUserPassword($user, $data);
                $this->userRepository->updatePassword($user);
            }

            return $this->redirectToRoute('accounts_logout');
        }

        return $this->render(
            'accounts/edit_password.html.twig',
            [
                'form' => $form->createView(),
            ]
        );
    }

    /**
     * @return FormInterface
     */
    private function createRegisterForm(bool $use_company_type = false): FormInterface
    {
        $formBuilder = $this->createFormBuilder(
            null,
            [
                'validation_groups' => new GroupSequence(
                    [
                        'CorrectSyntax',
                        'Default',
                    ]
                ),
            ]
        );

        $this->registrationFormType->buildForm(
            $formBuilder,
            [
                'use_company_type' => $use_company_type,
                'translator' => $this->translator,
            ]
        );

        return $formBuilder->getForm();
    }

    /**
     * @return FormInterface
     */
    private function createLoginForm(): FormInterface
    {
        $formBuilder = $this->createFormBuilder();

        $this->loginFormType->buildForm(
            $formBuilder,
            [
                'translator' => $this->translator,
            ]
        );

        return $formBuilder->getForm();
    }

    /**
     * @return FormInterface
     */
    private function createRequestPasswordForm(): FormInterface
    {
        $formBuilder = $this->createFormBuilder();

        $this->requestPasswordFormType->buildForm(
            $formBuilder,
            [
                'translator' => $this->translator,
            ]
        );

        return $formBuilder->getForm();
    }

    /**
     * @return FormInterface
     */
    private function createResetPasswordForm(): FormInterface
    {
        $formBuilder = $this->createFormBuilder();

        $this->resetPasswordFormType->buildForm(
            $formBuilder,
            [
                'translator' => $this->translator,
            ]
        );

        return $formBuilder->getForm();
    }

    private function createEditPasswordForm(): FormInterface
    {
        $formBuilder = $this->createFormBuilder();

        $this->editPasswordFormType->buildForm(
            $formBuilder,
            [
                'translator' => $this->translator,
            ]
        );

        return $formBuilder->getForm();
    }

    /**
     * @param User $user
     * @param bool $passwordReset
     * @return Registration
     * @throws \Exception
     */
    private function createRegistrationForUser(
        User $user,
        bool $passwordReset
    ): Registration {
        return new Registration(
            $this->uuidFactory->uuid4(),
            $this->urlSuffixGenerator->createUrlSuffix(),
            $user,
            new \DateTimeImmutable(),
            $passwordReset
        );
    }

    /**
     * @param array $data
     * @return bool
     */
    private function honeypotTricked(array $data)
    {
        return !empty($data['azijnpotje']);
    }

    /**
     * @param string $route
     * @return Response
     */
    private function handleHoneypotField(string $route): Response
    {
        return $this->redirectToRoute($route);
    }

    /**
     * @return RedirectResponse
     */
    private function redirectToLandingPage(): RedirectResponse
    {
        $authChecker = $this->get('security.authorization_checker');
        if ($authChecker->isGranted(['ROLE_VSV', 'ROLE_ADMIN'])) {
            return $this->redirectToRoute('questions_view_index');
        } elseif ($authChecker->isGranted('ROLE_TEST')) {
            return $this->redirectToRoute('documents');
        } else {
            return $this->redirectToRoute('dashboard');
        }
    }
}