src/Controller/UserController.php line 339

  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Controller;
  4. use App\Entity\Company;
  5. use App\Entity\Offer;
  6. use App\Entity\Team;
  7. use App\Entity\User;
  8. use App\Form\ProfileType;
  9. use App\Form\RegistrationType;
  10. use App\Form\ResetType;
  11. use App\Service\CompanyService;
  12. use App\Service\DropboxService;
  13. use App\Service\MailjetService;
  14. use App\Service\UserService;
  15. use App\Service\WidgetService;
  16. use Doctrine\ORM\EntityManagerInterface;
  17. use Exception;
  18. use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
  19. use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
  20. use Symfony\Component\HttpFoundation\JsonResponse;
  21. use Symfony\Component\HttpFoundation\RedirectResponse;
  22. use Symfony\Component\HttpFoundation\Request;
  23. use Symfony\Component\HttpFoundation\Response;
  24. use Symfony\Component\HttpFoundation\Session\Session;
  25. use Symfony\Component\Routing\Annotation\Route;
  26. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  27. use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
  28. use Symfony\Contracts\Translation\TranslatorInterface;
  29. final class UserController extends AbstractController
  30. {
  31.     public function __construct(
  32.         private readonly TranslatorInterface $translator,
  33.         private readonly EntityManagerInterface $em,
  34.         private readonly UserService $userService,
  35.         private readonly WidgetService $widgetService,
  36.         private readonly DropboxService $dropboxService
  37.     ) {
  38.     }
  39.     /**
  40.      * This will never be executed: Symfony will intercept this first and handle the logout automatically.
  41.      * See logout in config/packages/security.yaml
  42.      */
  43.     #[Route(path'/logout'name'logout')]
  44.     public function logout(): void
  45.     {
  46.         // @codeCoverageIgnoreStart
  47.         throw new Exception('This should never be reached!');
  48.         // @codeCoverageIgnoreEnd
  49.     }
  50.     /**
  51.      * Display login page
  52.      * The login action is automatically executed by Symfony
  53.      */
  54.     #[Route(path'/login'name'login')]
  55.     public function login(AuthenticationUtils $helper): Response
  56.     {
  57.         // If already connected, redirect
  58.         if ($this->isGranted('ROLE_USER')) {
  59.             return $this->redirectToRoute('index');
  60.         }
  61.         return $this->render('user/login.html.twig', [
  62.             'last_username' => $helper->getLastUsername(),
  63.             'error' => $helper->getLastAuthenticationError(),
  64.         ]);
  65.     }
  66.     /**
  67.      * Register a new user
  68.      */
  69.     #[Route(path'/register'name'register')]
  70.     public function register(
  71.         Request $request,
  72.         Session $session,
  73.         CompanyService $companyService
  74.     ): RedirectResponse|Response {
  75.         // If already connected, redirect
  76.         if ($this->isGranted('ROLE_USER')) {
  77.             return $this->redirectToRoute('index');
  78.         }
  79.         $user = new User();
  80.         $form $this->createForm(RegistrationType::class, $user);
  81.         $form->handleRequest($request);
  82.         if ($form->isSubmitted() && $form->isValid()) {
  83.             // Then, create user
  84.             $this->userService->setPasswordForUser($user);
  85.             $user->setCompany($companyService->createCompany($request->request->get('registration_company')));
  86.             $user->setIsCompanyAdmin(true);
  87.             $user->setLocale($request->getLocale());
  88.             $this->em->persist($user);
  89.             $this->em->flush();
  90.             // Check for subscription redirect
  91.             $stripeId $request->request->get('stripeId');
  92.             if (isset($stripeId) && $this->em->getRepository(Offer::class)->findOneBy(['stripeId' => $stripeId])) {
  93.                 // @codeCoverageIgnoreStart
  94.                 return $this->redirectToRoute('subscription_new', ['stripeId' => $stripeId]);
  95.                 // @codeCoverageIgnoreEnd
  96.             }
  97.             $session->getFlashBag()->add('info'$this->translator->trans('flash.account.register_confirmed.info', ['%email%' => $user->getEmail()]));
  98.             return $this->redirectToRoute('login');
  99.         }
  100.         return $this->render('user/register.html.twig', [
  101.             'form' => $form->createView(),
  102.             'stripeId' => $request->query->get('stripeId')
  103.         ]);
  104.     }
  105.     /**
  106.      * Resend confirmation email
  107.      */
  108.     #[Route(path'/resend_register/{email}'name'user_resend_register_confirmation')]
  109.     public function resendRegisterConfirmation(string $emailSession $session): RedirectResponse
  110.     {
  111.         $user $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
  112.         // If user exist and not already enabled
  113.         if (!$user || $user->getIsEnabled()) {
  114.             $session->getFlashBag()->add('warning'$this->translator->trans('flash.account.register_confirmed.warning', ['%email%' => $email]));
  115.         } else {
  116.             $this->userService->sendRegisterConfirmationEmail($user);
  117.             $session->getFlashBag()->add('info'$this->translator->trans('flash.account.register_confirmed.info', ['%email%' => $user->getEmail()]));
  118.         }
  119.         return $this->redirectToRoute('login');
  120.     }
  121.     /**
  122.      * Enable user account
  123.      */
  124.     #[Route(path'/register/{token}'name'user_register_confirmed')]
  125.     public function registerConfirmed(string $tokenSession $session): RedirectResponse
  126.     {
  127.         $user $this->em->getRepository(User::class)->findOneBy(['confirmationToken' => $token]);
  128.         // Invalid token
  129.         if (!$user) {
  130.             $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.resetting.reset.token_danger'));
  131.             return $this->redirectToRoute('index');
  132.         }
  133.         // Reset token, enable account
  134.         $user->setConfirmationToken(null);
  135.         $user->setIsEnabled(true);
  136.         $this->em->flush();
  137.         $session->getFlashBag()->add('success'$this->translator->trans('flash.account.register_confirmed.success'));
  138.         // Log the user
  139.         $this->userService->registerUserSession($user);
  140.         return $this->redirectToRoute('thank_you');
  141.     }
  142.     /**
  143.      * Invite a new user to company
  144.      */
  145.     #[Route(path'/invite'name'user_invite')]
  146.     #[IsGranted('ROLE_USER')]
  147.     public function invite(
  148.         Request $request,
  149.         Session $session,
  150.         UrlGeneratorInterface $router
  151.     ): RedirectResponse|Response {
  152.         $formDatas $request->request->all();
  153.         // If no email, render form
  154.         if (empty($email $formDatas['invitation_email'] ?? null)) {
  155.             return $this->render('user/invite.html.twig', [
  156.                 'teams' => $this->em->getRepository(Team::class)->findLike()
  157.             ]);
  158.         }
  159.         // If user already in a company
  160.         $user $this->em->getRepository(User::class)->findOneBy(['email' => $email]);
  161.         if ($user && !empty($user->getCompany())) {
  162.             $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.invite.danger'));
  163.             return $this->render('user/invite.html.twig', [
  164.                 'teams' => $this->em->getRepository(Team::class)->findLike()
  165.             ]);
  166.         }
  167.         // If user does not exist, create it
  168.         if (!$user) {
  169.             $user = new User();
  170.             $user->setEmail($email);
  171.             $user->setCompany($this->getUser()->getCompany());
  172.             $user->setIsCompanyAdmin($formDatas['invitation_isAdmin'] ?? false);
  173.             $user->setLocale($this->getUser()->getLocale());
  174.             $user->setIsEnabled(true);
  175.             /** @var Team $team */
  176.             foreach ($teams $this->em->getRepository(Team::class)->findByIds($formDatas['invitation_teams'] ?? []) as $team) {
  177.                 $user->addTeam($team);
  178.             }
  179.             $this->em->persist($user);
  180.             $this->em->flush();
  181.             $url $router->generate('resetting_reset', ['token' => $user->getConfirmationToken(), 'type' => 'invite'], UrlGeneratorInterface::ABSOLUTE_URL);
  182.         } else {
  183.             // If user already exist but do not have a company yet
  184.             $this->userService->setConfirmationTokenForUser($user);
  185.             $this->em->flush();
  186.             $url $router->generate('user_invite_confirmed', ['token' => $this->getUser()->getCompany()->getUniqueHash() . '-' $user->getConfirmationToken()], UrlGeneratorInterface::ABSOLUTE_URL);
  187.         }
  188.         $this->userService->sendInviteEmail($user$url);
  189.         $session->getFlashBag()->add('success'$this->translator->trans('flash.account.invite.success', ['%email%' => $user->getEmail()]));
  190.         return $this->redirectToRoute('company_index');
  191.     }
  192.     /**
  193.      * Confirm invitation link
  194.      */
  195.     #[Route(path'/invite/{token}'name'user_invite_confirmed')]
  196.     public function inviteConfirmed(string $tokenSession $session): RedirectResponse
  197.     {
  198.         $token explode('-'$token); // Get user token and company token
  199.         // If it seems to a valid token, go on
  200.         if (count($token) == 2) {
  201.             $user $this->em->getRepository(User::class)->findOneBy(['confirmationToken' => $token[1]]);
  202.             // Invalid user token
  203.             if (!$user) {
  204.                 $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.resetting.reset.token_danger'));
  205.             } else {
  206.                 $company $this->em->getRepository(Company::class)->findOneBy(['uniqueHash' => $token[0]]);
  207.                 // If valid company token, add link to user, reset user token
  208.                 if ($company) {
  209.                     $user->setCompany($company);
  210.                     $user->setConfirmationToken(null);
  211.                     $this->em->flush();
  212.                     $session->getFlashBag()->add('success'$this->translator->trans('flash.account.invite_confirmed.success', ['%company%' => $company->getName()]));
  213.                     // Log user
  214.                     $this->userService->registerUserSession($user);
  215.                 }
  216.             }
  217.         }
  218.         return $this->redirectToRoute('index');
  219.     }
  220.     /**
  221.      * Update user profile
  222.      */
  223.     #[Route(path'/profile/edit'name'user_edit')]
  224.     #[IsGranted('ROLE_USER')]
  225.     public function edit(Request $requestSession $session): RedirectResponse|Response
  226.     {
  227.         $form $this->createForm(ProfileType::class, $this->getUser());
  228.         $form->handleRequest($request);
  229.         if ($form->isSubmitted() && $form->isValid()) {
  230.             // @codeCoverageIgnoreStart
  231.             // If "empty" Dropbox token
  232.             if (substr_count($form->getData()->getDropboxToken(), 'X') == 64) {
  233.                 $this->getUser()->setDropboxToken(null);
  234.             }
  235.             // @codeCoverageIgnoreEnd
  236.             $currentPassword $request->request->get('current_password');
  237.             $isPasswordValid $this->userService->isPasswordValidForUser($this->getUser(), $currentPassword);
  238.             // If all passwords checker are valid, update password
  239.             if ($currentPassword != '' && $isPasswordValid) {
  240.                 $this->userService->setPasswordForUser($this->getUser());
  241.                 $session->getFlashBag()->add('success'$this->translator->trans('flash.account.update.success'));
  242.             } elseif ($currentPassword != '' && !$isPasswordValid) {
  243.                 // If all passwords checker are not valid, display an error
  244.                 $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.update.danger'));
  245.             } else {
  246.                 // Display a success message for other information than passwords
  247.                 $session->getFlashBag()->add('success'$this->translator->trans('flash.account.update.success'));
  248.             }
  249.             $this->em->flush();
  250.             return $this->redirectToRoute('user_edit');
  251.         }
  252.         // If form is not valid (wrong passwords repeat for eg), display an error
  253.         if ($form->isSubmitted() && !$form->isValid()) {
  254.             $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.update.danger'));
  255.         }
  256.         return $this->render('user/profile/edit.html.twig', [
  257.             'form' => $form->createView()
  258.         ]);
  259.     }
  260.     /**
  261.      * Update user mailer subscription
  262.      */
  263.     #[Route(path'/profile/mailer'name'user_mailer_send')]
  264.     #[IsGranted('ROLE_USER')]
  265.     public function mailerSend(MailjetService $mailjetRequest $request): Response
  266.     {
  267.         $save $request->request->get('save');  // Get form sent
  268.         // If form sent
  269.         if (isset($save)) {
  270.             $this->userService->updateMailerSettings($this->getUser(), $request->request->all('send'));
  271.         }
  272.         // Render form
  273.         return $this->render('user/profile/send.html.twig', [
  274.             'templates' => $mailjet->getAllNotRequired(),
  275.             'mailerSubscription' => json_decode($this->getUser()->getMailerSubscription(), true)
  276.         ]);
  277.     }
  278.     /**
  279.      * Ask for reset password
  280.      */
  281.     #[Route(path'/resetting/request'name'resetting_request')]
  282.     public function resetRequest(Request $requestSession $session): RedirectResponse|Response
  283.     {
  284.         // If already connected, redirect
  285.         if ($this->isGranted('ROLE_USER')) {
  286.             return $this->redirectToRoute('index');
  287.         }
  288.         // If form sent
  289.         $email $request->request->get('email');
  290.         if (isset($email)) {
  291.             // Check for existing user
  292.             $user $this->em->getRepository(User::class)->findOneBy(['email' => $request->request->get('email')]);
  293.             // If user exist, create a reset request
  294.             if ($user) {
  295.                 $this->userService->sendResettingResetEmail($user);
  296.                 $session->getFlashBag()->add('success'$this->translator->trans('flash.account.resetting.request.success'));
  297.                 return $this->redirectToRoute('login');
  298.             } else {
  299.                 // If user not exist, display an error
  300.                 $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.resetting.request.danger'));
  301.             }
  302.         }
  303.         return $this->render('user/resetting/request.html.twig');
  304.     }
  305.     /**
  306.      * Reset password
  307.      */
  308.     #[Route(path'/resetting/reset/{token}/{type}'name'resetting_reset')]
  309.     public function resetReset(
  310.         string $token,
  311.         string $type,
  312.         Request $request,
  313.         Session $session
  314.     ): RedirectResponse|Response {
  315.         $user $this->em->getRepository(User::class)->findOneBy(['confirmationToken' => $token]);
  316.         // Invalid token
  317.         if (!$user) {
  318.             $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.resetting.reset.token_danger'));
  319.             return $this->redirectToRoute('login');
  320.         }
  321.         $form $this->createForm(ResetType::class);
  322.         $form->handleRequest($request);
  323.         if ($form->isSubmitted() && $form->isValid()) {
  324.             // Set new password
  325.             $user->setPlainPassword($form->get('plainPassword')->getData());
  326.             $this->userService->setPasswordForUser($user);
  327.             $user->setConfirmationToken(null); // Reset token
  328.             $this->em->flush();
  329.             // If token is for invite and enable account
  330.             if ($type == 'invite') {
  331.                 // Log user
  332.                 $this->userService->registerUserSession($user);
  333.                 $session->getFlashBag()->add('success'$this->translator->trans('flash.account.invite_confirmed.success', ['%company%' => $user->getCompany()->getName()]));
  334.                 return $this->redirectToRoute('index');
  335.             } else {
  336.                 // If token is for reset
  337.                 $session->getFlashBag()->add('success'$this->translator->trans('flash.account.update.success'));
  338.             }
  339.             return $this->redirectToRoute('login');
  340.         }
  341.         // If form is not valid (wrong passwords repeat), display an error
  342.         if ($form->isSubmitted() && !$form->isValid()) {
  343.             $session->getFlashBag()->add('danger'$this->translator->trans('flash.account.update.danger'));
  344.         }
  345.         // Render form
  346.         return $this->render('user/resetting/reset.html.twig', [
  347.             'form' => $form->createView(),
  348.             'token' => $token,
  349.             'type' => $type
  350.         ]);
  351.     }
  352.     #[Route(path'/thank_you'name'thank_you'methods: ['GET'])]
  353.     #[IsGranted('ROLE_USER')]
  354.     public function thankYou(): Response
  355.     {
  356.         // If user try to access this page w/ more than one widget
  357.         if (count($this->getUser()->getWidgets()) != 1) {
  358.             return $this->redirectToRoute('index');
  359.         }
  360.         return $this->render('user/thank_you.html.twig', [
  361.             'widget' => $this->getUser()->getWidgets()->first(), // Get first user widget
  362.             'snippet_path' => $this->widgetService->getSnippetPrefixRoute(),
  363.             'snippet_filename' => WidgetService::SNIPPET_FILENAME
  364.         ]);
  365.     }
  366.     /**
  367.      * Get Dropbox token validity
  368.      */
  369.     #[Route(path'/dropbox_connect'name'dropbox_connect')]
  370.     #[IsGranted('ROLE_USER')]
  371.     public function dropboxConnectAjax(Request $request): JsonResponse
  372.     {
  373.         return new JsonResponse($this->dropboxService->dropboxConnect($request->query->get('token')));
  374.     }
  375.     /**
  376.      * Search active user LIKE email (ajax request for Select2EntityType)
  377.      */
  378.     #[Route(path'/search_active_ajax'name'user_searchActive_ajax'methods: ['GET'])]
  379.     #[IsGranted('ROLE_USER')]
  380.     public function searchActiveAjax(Request $request): JsonResponse
  381.     {
  382.         return new JsonResponse($this->em->getRepository(User::class)->getActiveUser($request->get('q')));
  383.     }
  384. }