+ public function recover(Request $request, ?string $hash, ?string $pass, ?string $mail): Response {
+ //Without mail, pass and hash
+ if (empty($mail) && empty($pass) && empty($hash)) {
+ //Create the LoginType form and give the proper parameters
+ $form = $this->createForm($this->config['recover']['view']['form'], null, [
+ //Set action to recover route name and context
+ 'action' => $this->generateUrl($this->config['route']['recover']['name'], $this->config['route']['recover']['context']),
+ //Without password
+ 'password' => false,
+ //Set method
+ 'method' => 'POST'
+ ]);
+
+ //With post method
+ if ($request->isMethod('POST')) {
+ //Refill the fields in case the form is not valid.
+ $form->handleRequest($request);
+
+ //With form submitted and valid
+ if ($form->isSubmitted() && $form->isValid()) {
+ //Set data
+ $data = $form->getData();
+
+ //Find user by data mail
+ if ($user = $this->doctrine->getRepository($this->config['class']['user'])->findOneByMail($data['mail'])) {
+ //Set mail shortcut
+ $recoverMail =& $this->config['recover']['mail'];
+
+ //Set mail
+ $mail = $this->slugger->short($user->getMail());
+
+ //Set pass
+ $pass = $this->slugger->hash($user->getPassword());
+
+ //Generate each route route
+ foreach($this->config['recover']['route'] as $route => $tag) {
+ //Only process defined routes
+ if (!empty($this->config['route'][$route])) {
+ //Process for recover mail url
+ if ($route == 'recover') {
+ //Set the url in context
+ $recoverMail['context'][$tag] = $this->router->generate(
+ $this->config['route'][$route]['name'],
+ //Prepend recover context with tag
+ [
+ 'mail' => $mail,
+ 'pass' => $pass,
+ 'hash' => $this->slugger->hash($mail.$pass)
+ ]+$this->config['route'][$route]['context'],
+ UrlGeneratorInterface::ABSOLUTE_URL
+ );
+ }
+ }
+ }
+
+ //Set recipient_name
+ $recoverMail['context']['recipient_mail'] = $user->getMail();
+
+ //Set recipient_name
+ $recoverMail['context']['recipient_name'] = $user->getRecipientName();
+
+ //Init subject context
+ $subjectContext = $this->slugger->flatten(array_replace_recursive($this->config['recover']['view']['context'], $recoverMail['context']), null, '.', '%', '%');
+
+ //Translate subject
+ $recoverMail['subject'] = ucfirst($this->translator->trans($recoverMail['subject'], $subjectContext));
+
+ //Create message
+ $message = (new TemplatedEmail())
+ //Set sender
+ ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
+ //Set recipient
+ //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
+ ->to(new Address($recoverMail['context']['recipient_mail'], $recoverMail['context']['recipient_name']))
+ //Set subject
+ ->subject($recoverMail['subject'])
+
+ //Set path to twig templates
+ ->htmlTemplate($recoverMail['html'])
+ ->textTemplate($recoverMail['text'])
+
+ //Set context
+ //XXX: require recursive merge to avoid loosing subkeys
+ //['subject' => $recoverMail['subject']]+$recoverMail['context']+$this->config['recover']['view']['context']
+ ->context(array_replace_recursive($this->config['recover']['view']['context'], $recoverMail['context'], ['subject' => $recoverMail['subject']]));
+
+ //Try sending message
+ //XXX: mail delivery may silently fail
+ try {
+ //Send message
+ $this->mailer->send($message);
+
+ //Redirect on the same route with sent=1 to cleanup form
+ return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
+ //Catch obvious transport exception
+ } catch(TransportExceptionInterface $e) {
+ //Add error message mail unreachable
+ $form->get('mail')->addError(new FormError($this->translator->trans('Account found but unable to contact: %mail%', array('%mail%' => $data['mail']))));
+ }
+ //Accout not found
+ } else {
+ //Add error message to mail field
+ $form->get('mail')->addError(new FormError($this->translator->trans('Unable to find account %mail%', ['%mail%' => $data['mail']])));
+ }
+ }
+ }
+
+ //Render view
+ return $this->render(
+ //Template
+ $this->config['recover']['view']['name'],
+ //Context
+ ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['recover']['view']['context']
+ );
+ }
+
+ //With invalid hash
+ if ($hash != $this->slugger->hash($mail.$pass)) {
+ //Throw bad request
+ throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
+ }
+
+ //Get mail
+ $mail = $this->slugger->unshort($smail = $mail);
+
+ //Without valid mail
+ if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) {
+ //Throw bad request
+ //XXX: prevent slugger reverse engineering by not displaying decoded mail
+ throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
+ }
+
+ //With existing subscriber
+ if (empty($user = $this->doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail))) {
+ //Throw not found
+ //XXX: prevent slugger reverse engineering by not displaying decoded mail
+ throw $this->createNotFoundException($this->translator->trans('Unable to find account %mail%', ['%mail%' => $smail]));
+ }
+
+ //With unmatched pass
+ if ($pass != $this->slugger->hash($user->getPassword())) {
+ //Throw not found
+ //XXX: prevent use of outdated recover link
+ throw $this->createNotFoundException($this->translator->trans('Outdated recover link'));
+ }
+
+ //Create the LoginType form and give the proper parameters
+ $form = $this->createForm($this->config['recover']['view']['form'], $user, [