1 <?php
declare(strict_types
=1);
4 * This file is part of the Rapsys UserBundle package.
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Rapsys\UserBundle\Controller
;
14 use Doctrine\Bundle\DoctrineBundle\Registry
;
15 use Doctrine\ORM\EntityManagerInterface
;
16 use Psr\Log\LoggerInterface
;
17 use Symfony\Bridge\Twig\Mime\TemplatedEmail
;
18 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController
;
19 use Symfony\Component\DependencyInjection\ContainerInterface
;
20 use Symfony\Component\Form\FormError
;
21 use Symfony\Component\HttpFoundation\Request
;
22 use Symfony\Component\HttpFoundation\Response
;
23 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException
;
24 use Symfony\Component\Mailer\Exception\TransportExceptionInterface
;
25 use Symfony\Component\Mailer\MailerInterface
;
26 use Symfony\Component\Mime\Address
;
27 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
;
28 use Symfony\Component\Routing\RouterInterface
;
29 use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface
;
30 use Symfony\Component\Security\Http\Authentication\AuthenticationUtils
;
31 use Symfony\Component\Translation\TranslatorInterface
;
33 use Rapsys\PackBundle\Util\SluggerUtil
;
34 use Rapsys\UserBundle\RapsysUserBundle
;
36 class DefaultController
extends AbstractController
{
41 protected $translator;
46 * @TODO: move all canonical and other view related stuff in an user AbstractController like in RapsysAir render feature !!!!
48 * @param ContainerInterface $container The containter instance
49 * @param RouterInterface $router The router instance
50 * @param TranslatorInterface $translator The translator instance
52 public function __construct(ContainerInterface
$container, RouterInterface
$router, TranslatorInterface
$translator) {
54 $this->config
= $container->getParameter(self
::getAlias());
57 $this->translator
= $translator;
60 $stack = $container->get('request_stack');
63 $request = $stack->getCurrentRequest();
66 $currentLocale = $request->getLocale();
69 $this->config
['context']['locale'] = str_replace('_', '-', $currentLocale);
74 //Look for keys to translate
75 if (!empty($this->config
['translate'])) {
76 //Iterate on keys to translate
77 foreach($this->config
['translate'] as $translate) {
81 foreach(array_reverse(explode('.', $translate)) as $curkey) {
82 $tmp = array_combine([$curkey], [$tmp]);
85 $translates = array_replace_recursive($translates, $tmp);
89 //Inject every requested route in view and mail context
90 foreach($this->config
as $tag => $current) {
91 //Look for entry with title subkey
92 if (!empty($current['title'])) {
93 //Translate title value
94 $this->config
[$tag]['title'] = $translator->trans($current['title']);
97 //Look for entry with route subkey
98 if (!empty($current['route'])) {
99 //Generate url for both view and mail
100 foreach(['view', 'mail'] as $view) {
101 //Check that context key is usable
102 if (isset($current[$view]['context']) && is_array($current[$view]['context'])) {
103 //Merge with global context
104 $this->config
[$tag][$view]['context'] = array_replace_recursive($this->config
['context'], $this->config
[$tag][$view]['context']);
106 //Process every routes
107 foreach($current['route'] as $route => $key) {
109 if ($route == 'confirm') {
110 //Skip route as it requires some parameters
115 $value = $router->generate(
116 $this->config
['route'][$route]['name'],
117 $this->config
['route'][$route]['context'],
118 //Generate absolute url for mails
119 $view=='mail'?UrlGeneratorInterface
::ABSOLUTE_URL
:UrlGeneratorInterface
::ABSOLUTE_PATH
123 if (strpos($key, '.') !== false) {
128 foreach(array_reverse(explode('.', $key)) as $curkey) {
129 $tmp = array_combine([$curkey], [$tmp]);
133 $this->config
[$tag][$view]['context'] = array_replace_recursive($this->config
[$tag][$view]['context'], $tmp);
137 $this->config
[$tag][$view]['context'][$key] = $value;
141 //Look for successful intersections
142 if (!empty(array_intersect_key($translates, $this->config
[$tag][$view]['context']))) {
143 //Iterate on keys to translate
144 foreach($this->config
['translate'] as $translate) {
146 $keys = explode('.', $translate);
149 $tmp = $this->config
[$tag][$view]['context'];
152 foreach($keys as $curkey) {
154 if (!isset($tmp[$curkey])) {
160 $tmp = $tmp[$curkey];
163 //Translate tmp value
164 $tmp = $translator->trans($tmp);
167 foreach(array_reverse($keys) as $curkey) {
169 $tmp = array_combine([$curkey], [$tmp]);
173 $this->config
[$tag][$view]['context'] = array_replace_recursive($this->config
[$tag][$view]['context'], $tmp);
178 if ($view == 'view') {
180 $pathInfo = $router->getContext()->getPathInfo();
182 //Iterate on locales excluding current one
183 foreach($this->config
['locales'] as $locale) {
187 //Iterate on other locales
188 foreach(array_diff($this->config
['locales'], [$locale]) as $other) {
189 $titles[$other] = $translator->trans($this->config
['languages'][$locale], [], null, $other);
192 //Retrieve route matching path
193 $route = $router->match($pathInfo);
196 $name = $route['_route'];
199 unset($route['_route']);
201 //With current locale
202 if ($locale == $currentLocale) {
203 //Set locale locales context
204 $this->config
[$tag][$view]['context']['canonical'] = $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
);
206 //Set locale locales context
207 $this->config
[$tag][$view]['context']['alternates'][$locale] = [
208 'absolute' => $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
),
209 'relative' => $router->generate($name, ['_locale' => $locale]+
$route),
210 'title' => implode('/', $titles),
211 'translated' => $translator->trans($this->config
['languages'][$locale], [], null, $locale)
216 if (empty($this->config
[$tag][$view]['context']['alternates'][$slocale = substr($locale, 0, 2)])) {
218 $this->config
[$tag][$view]['context']['alternates'][$slocale] = [
219 'absolute' => $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
),
220 'relative' => $router->generate($name, ['_locale' => $locale]+
$route),
221 'title' => implode('/', $titles),
222 'translated' => $translator->trans($this->config
['languages'][$locale], [], null, $locale)
234 * Confirm account from mail link
236 * @param Request $request The request
237 * @param Registry $manager The doctrine registry
238 * @param UserPasswordEncoderInterface $encoder The password encoder
239 * @param EntityManagerInterface $manager The doctrine entity manager
240 * @param SluggerUtil $slugger The slugger
241 * @param MailerInterface $mailer The mailer
242 * @param string $mail The shorted mail address
243 * @param string $hash The hashed password
244 * @return Response The response
246 public function confirm(Request
$request, Registry
$doctrine, UserPasswordEncoderInterface
$encoder, EntityManagerInterface
$manager, SluggerUtil
$slugger, MailerInterface
$mailer, $mail, $hash): Response
{
248 if ($hash != $slugger->hash($mail)) {
250 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
254 $mail = $slugger->unshort($smail = $mail);
257 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) {
259 //XXX: prevent slugger reverse engineering by not displaying decoded mail
260 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
263 //Without existing registrant
264 if (!($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail))) {
265 //Add error message mail already exists
266 //XXX: prevent slugger reverse engineering by not displaying decoded mail
267 $this->addFlash('error', $this->translator
->trans('Account %mail% do not exists', ['%mail%' => $smail]));
269 //Redirect to register view
270 return $this->redirectToRoute($this->config
['route']['register']['name'], ['mail' => $smail, 'field' => $sfield = $slugger->serialize([]), 'hash' => $slugger->hash($smail.$sfield)]+
$this->config
['route']['register']['context']);
274 $user->setActive(true);
277 $manager->persist($user);
282 //Add error message mail already exists
283 $this->addFlash('notice', $this->translator
->trans('Your account has been activated'));
285 //Redirect to user view
286 return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']);
290 * Edit account by shorted mail
292 * @param Request $request The request
293 * @param Registry $manager The doctrine registry
294 * @param UserPasswordEncoderInterface $encoder The password encoder
295 * @param EntityManagerInterface $manager The doctrine entity manager
296 * @param SluggerUtil $slugger The slugger
297 * @param string $mail The shorted mail address
298 * @param string $hash The hashed password
299 * @return Response The response
301 public function edit(Request
$request, Registry
$doctrine, UserPasswordEncoderInterface
$encoder, EntityManagerInterface
$manager, SluggerUtil
$slugger, $mail, $hash): Response
{
303 if ($hash != $slugger->hash($mail)) {
305 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
309 $mail = $slugger->unshort($smail = $mail);
311 //With existing subscriber
312 if (empty($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail))) {
314 //XXX: prevent slugger reverse engineering by not displaying decoded mail
315 throw $this->createNotFoundException($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $smail]));
318 //Prevent access when not admin, user is not guest and not currently logged user
319 if (!$this->isGranted('ROLE_ADMIN') && $user != $this->getUser() || !$this->isGranted('IS_AUTHENTICATED_FULLY')) {
320 //Throw access denied
321 //XXX: prevent slugger reverse engineering by not displaying decoded mail
322 throw $this->createAccessDeniedException($this->translator
->trans('Unable to access user: %mail%', ['%mail%' => $smail]));
325 //Create the RegisterType form and give the proper parameters
326 $edit = $this->createForm($this->config
['edit']['view']['edit'], $user, [
327 //Set action to register route name and context
328 'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']),
330 'civility_class' => $this->config
['class']['civility'],
331 //Set civility default
332 'civility_default' => $doctrine->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']),
334 'mail' => $this->isGranted('ROLE_ADMIN'),
342 if ($this->isGranted('ROLE_ADMIN')) {
343 //Create the LoginType form and give the proper parameters
344 $reset = $this->createForm($this->config
['edit']['view']['reset'], $user, [
345 //Set action to register route name and context
346 'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']),
354 if ($request->isMethod('POST')) {
355 //Refill the fields in case the form is not valid.
356 $reset->handleRequest($request);
358 //With reset submitted and valid
359 if ($reset->isSubmitted() && $reset->isValid()) {
361 $data = $reset->getData();
364 $data->setPassword($encoder->encodePassword($data, $data->getPassword()));
367 $manager->persist($data);
369 //Flush to get the ids
373 $this->addFlash('notice', $this->translator
->trans('Account %mail% password updated', ['%mail%' => $mail = $data->getMail()]));
375 //Redirect to cleanup the form
376 return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']);
381 $this->config
['edit']['view']['context']['reset'] = $reset->createView();
383 //XXX: prefer a reset on login to force user unspam action
386 $this->addFlash('notice', $this->translator
->trans('To change your password login with your mail and any password then follow the procedure'));
390 if ($request->isMethod('POST')) {
391 //Refill the fields in case the form is not valid.
392 $edit->handleRequest($request);
394 //With edit submitted and valid
395 if ($edit->isSubmitted() && $edit->isValid()) {
397 $data = $edit->getData();
400 $manager->persist($data);
402 //Flush to get the ids
406 $this->addFlash('notice', $this->translator
->trans('Account %mail% updated', ['%mail%' => $mail = $data->getMail()]));
408 //Redirect to cleanup the form
409 return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']);
414 return $this->render(
416 $this->config
['edit']['view']['name'],
418 ['edit' => $edit->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['edit']['view']['context']
425 * @param Request $request The request
426 * @param AuthenticationUtils $authenticationUtils The authentication utils
427 * @param RouterInterface $router The router instance
428 * @param SluggerUtil $slugger The slugger
429 * @param string $mail The shorted mail address
430 * @param string $hash The hashed password
431 * @return Response The response
433 public function login(Request
$request, AuthenticationUtils
$authenticationUtils, RouterInterface
$router, SluggerUtil
$slugger, $mail, $hash): Response
{
434 //Create the LoginType form and give the proper parameters
435 $login = $this->createForm($this->config
['login']['view']['form'], null, [
436 //Set action to login route name and context
437 'action' => $this->generateUrl($this->config
['route']['login']['name'], $this->config
['route']['login']['context']),
438 //Disable repeated password
439 'password_repeated' => false,
448 if (!empty($mail) && !empty($hash)) {
450 if ($hash != $slugger->hash($mail)) {
452 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
456 $mail = $slugger->unshort($smail = $mail);
459 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) {
461 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
465 $login->get('mail')->setData($mail);
466 //Last username entered by the user
467 } elseif ($lastUsername = $authenticationUtils->getLastUsername()) {
468 $login->get('mail')->setData($lastUsername);
471 //Get the login error if there is one
472 if ($error = $authenticationUtils->getLastAuthenticationError()) {
473 //Get translated error
474 $error = $this->translator
->trans($error->getMessageKey());
476 //Add error message to mail field
477 $login->get('mail')->addError(new FormError($error));
479 //Create the LoginType form and give the proper parameters
480 $recover = $this->createForm($this->config
['recover']['view']['form'], null, [
481 //Set action to recover route name and context
482 'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']),
489 //Get recover mail entity
490 $recover->get('mail')
491 //Set mail from login form
492 ->setData($login->get('mail')->getData())
494 ->addError(new FormError($this->translator
->trans('Use this form to recover your account')));
496 //Add recover form to context
497 $context['recover'] = $recover->createView();
500 $this->addFlash('notice', $this->translator
->trans('To change your password login with your mail and any password then follow the procedure'));
504 return $this->render(
506 $this->config
['login']['view']['name'],
508 ['login' => $login->createView()]+
$context+
$this->config
['login']['view']['context']
515 * @param Request $request The request
516 * @param Registry $manager The doctrine registry
517 * @param UserPasswordEncoderInterface $encoder The password encoder
518 * @param EntityManagerInterface $manager The doctrine entity manager
519 * @param SluggerUtil $slugger The slugger
520 * @param MailerInterface $mailer The mailer
521 * @param string $mail The shorted mail address
522 * @param string $pass The shorted password
523 * @param string $hash The hashed password
524 * @return Response The response
526 public function recover(Request
$request, Registry
$doctrine, UserPasswordEncoderInterface
$encoder, EntityManagerInterface
$manager, SluggerUtil
$slugger, MailerInterface
$mailer, $mail, $pass, $hash): Response
{
527 //Without mail, pass and hash
528 if (empty($mail) && empty($pass) && empty($hash)) {
529 //Create the LoginType form and give the proper parameters
530 $form = $this->createForm($this->config
['recover']['view']['form'], null, [
531 //Set action to recover route name and context
532 'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']),
539 if ($request->isMethod('POST')) {
540 //Refill the fields in case the form is not valid.
541 $form->handleRequest($request);
543 if ($form->isValid()) {
545 $data = $form->getData();
547 //Find user by data mail
548 if ($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($data['mail'])) {
550 $recoverMail =& $this->config
['recover']['mail'];
553 $mail = $slugger->short($user->getMail());
556 $pass = $slugger->hash($user->getPassword());
558 //Generate each route route
559 foreach($this->config
['recover']['route'] as $route => $tag) {
560 //Only process defined routes
561 if (!empty($this->config
['route'][$route])) {
562 //Process for recover mail url
563 if ($route == 'recover') {
564 //Set the url in context
565 $recoverMail['context'][$tag] = $this->get('router')->generate(
566 $this->config
['route'][$route]['name'],
567 //Prepend recover context with tag
571 'hash' => $slugger->hash($mail.$pass)
572 ]+
$this->config
['route'][$route]['context'],
573 UrlGeneratorInterface
::ABSOLUTE_URL
580 $recoverMail['context']['recipient_mail'] = $user->getMail();
583 $recoverMail['context']['recipient_name'] = trim($user->getForename().' '.$user->getSurname().($user->getPseudonym()?' ('.$user->getPseudonym().')':''));
585 //Init subject context
586 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['recover']['view']['context'], $recoverMail['context']), null, '.', '%', '%');
589 $recoverMail['subject'] = ucfirst($this->translator
->trans($recoverMail['subject'], $subjectContext));
592 $message = (new TemplatedEmail())
594 ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title']))
596 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
597 ->to(new Address($recoverMail['context']['recipient_mail'], $recoverMail['context']['recipient_name']))
599 ->subject($recoverMail['subject'])
601 //Set path to twig templates
602 ->htmlTemplate($recoverMail['html'])
603 ->textTemplate($recoverMail['text'])
606 //XXX: require recursive merge to avoid loosing subkeys
607 //['subject' => $recoverMail['subject']]+$recoverMail['context']+$this->config['recover']['view']['context']
608 ->context(array_replace_recursive($this->config
['recover']['view']['context'], $recoverMail['context'], ['subject' => $recoverMail['subject']]));
610 //Try sending message
611 //XXX: mail delivery may silently fail
614 $mailer->send($message);
616 //Redirect on the same route with sent=1 to cleanup form
617 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params'));
618 //Catch obvious transport exception
619 } catch(TransportExceptionInterface
$e) {
620 //Add error message mail unreachable
621 $form->get('mail')->addError(new FormError($this->translator
->trans('Account found but unable to contact: %mail%', array('%mail%' => $data['mail']))));
625 //Add error message to mail field
626 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $data['mail']])));
632 return $this->render(
634 $this->config
['recover']['view']['name'],
636 ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['recover']['view']['context']
641 if ($hash != $slugger->hash($mail.$pass)) {
643 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
647 $mail = $slugger->unshort($smail = $mail);
650 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) {
652 //XXX: prevent slugger reverse engineering by not displaying decoded mail
653 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
656 //With existing subscriber
657 if (empty($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail))) {
659 //XXX: prevent slugger reverse engineering by not displaying decoded mail
660 throw $this->createNotFoundException($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $smail]));
663 //With unmatched pass
664 if ($pass != $slugger->hash($user->getPassword())) {
666 //XXX: prevent use of outdated recover link
667 throw $this->createNotFoundException($this->translator
->trans('Outdated recover link'));
670 //Create the LoginType form and give the proper parameters
671 $form = $this->createForm($this->config
['recover']['view']['form'], $user, [
672 //Set action to recover route name and context
673 'action' => $this->generateUrl($this->config
['route']['recover']['name'], ['mail' => $smail, 'pass' => $pass, 'hash' => $hash]+
$this->config
['route']['recover']['context']),
680 if ($request->isMethod('POST')) {
681 //Refill the fields in case the form is not valid.
682 $form->handleRequest($request);
684 if ($form->isValid()) {
686 $data = $form->getData();
688 //Set encoded password
689 $encoded = $encoder->encodePassword($user, $user->getPassword());
692 $pass = $slugger->hash($encoded);
695 $user->setPassword($encoded);
698 $manager->persist($user);
704 $this->addFlash('notice', $this->translator
->trans('Account %mail% password updated', ['%mail%' => $mail]));
706 //Redirect to user login
707 return $this->redirectToRoute($this->config
['route']['login']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['login']['context']);
712 return $this->render(
714 $this->config
['recover']['view']['name'],
716 ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['recover']['view']['context']
721 * Register an account
723 * @param Request $request The request
724 * @param Registry $manager The doctrine registry
725 * @param UserPasswordEncoderInterface $encoder The password encoder
726 * @param EntityManagerInterface $manager The doctrine entity manager
727 * @param SluggerUtil $slugger The slugger
728 * @param MailerInterface $mailer The mailer
729 * @param LoggerInterface $logger The logger
730 * @param string $mail The shorted mail address
731 * @param string $field The serialized then shorted form field array
732 * @param string $hash The hashed serialized field array
733 * @return Response The response
735 public function register(Request
$request, Registry
$doctrine, UserPasswordEncoderInterface
$encoder, EntityManagerInterface
$manager, SluggerUtil
$slugger, MailerInterface
$mailer, LoggerInterface
$logger, $mail, $field, $hash): Response
{
737 if (!empty($_POST['register']['mail'])) {
740 $this->translator
->trans(
741 'register: mail=%mail% locale=%locale% confirm=%confirm%',
743 '%mail%' => $postMail = $_POST['register']['mail'],
744 '%locale%' => $request->getLocale(),
745 '%confirm%' => $this->get('router')->generate(
746 $this->config
['route']['confirm']['name'],
747 //Prepend subscribe context with tag
749 'mail' => $postSmail = $slugger->short($postMail),
750 'hash' => $slugger->hash($postSmail)
751 ]+
$this->config
['route']['confirm']['context'],
752 UrlGeneratorInterface
::ABSOLUTE_URL
759 //With mail and field
760 if (!empty($field) && !empty($hash)) {
762 if ($hash != $slugger->hash($mail.$field)) {
764 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
770 $mail = $slugger->unshort($smail = $mail);
773 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) {
775 //XXX: prevent slugger reverse engineering by not displaying decoded mail
776 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
779 //With existing registrant
780 if ($existing = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail)) {
781 //With disabled existing
782 if ($existing->isDisabled()) {
784 return $this->render(
786 $this->config
['register']['view']['name'],
788 ['title' => $this->translator
->trans('Access denied'), 'disabled' => 1]+
$this->config
['register']['view']['context'],
790 new Response('', 403)
792 //With unactivated existing
793 } elseif (!$existing->isActivated()) {
795 //TODO: change for activate ???
796 $activateMail =& $this->config
['register']['mail'];
798 //Generate each route route
799 foreach($this->config
['register']['route'] as $route => $tag) {
800 //Only process defined routes
801 if (!empty($this->config
['route'][$route])) {
802 //Process for confirm url
803 if ($route == 'confirm') {
804 //Set the url in context
805 $activateMail['context'][$tag] = $this->get('router')->generate(
806 $this->config
['route'][$route]['name'],
807 //Prepend subscribe context with tag
809 'mail' => $smail = $slugger->short($existing->getMail()),
810 'hash' => $slugger->hash($smail)
811 ]+
$this->config
['route'][$route]['context'],
812 UrlGeneratorInterface
::ABSOLUTE_URL
819 $activateMail['context']['recipient_mail'] = $existing->getMail();
822 $activateMail['context']['recipient_name'] = implode(' ', [$existing->getForename(), $existing->getSurname(), $existing->getPseudonym()?'('.$existing->getPseudonym().')':'']);
824 //Init subject context
825 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['register']['view']['context'], $activateMail['context']), null, '.', '%', '%');
828 $activateMail['subject'] = ucfirst($this->translator
->trans($activateMail['subject'], $subjectContext));
831 $message = (new TemplatedEmail())
833 ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title']))
835 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
836 ->to(new Address($activateMail['context']['recipient_mail'], $activateMail['context']['recipient_name']))
838 ->subject($activateMail['subject'])
840 //Set path to twig templates
841 ->htmlTemplate($activateMail['html'])
842 ->textTemplate($activateMail['text'])
845 ->context(['subject' => $activateMail['subject']]+
$activateMail['context']);
847 //Try sending message
848 //XXX: mail delivery may silently fail
851 $mailer->send($message);
852 //Catch obvious transport exception
853 } catch(TransportExceptionInterface
$e) {
854 //Add error message mail unreachable
855 $this->addFlash('error', $this->translator
->trans('Account %mail% tried activate but unable to contact', ['%mail%' => $existing->getMail()]));
859 $routeParams = $request->get('_route_params');
861 //Remove mail, field and hash from route params
862 unset($routeParams['mail'], $routeParams['field'], $routeParams['hash']);
864 //Redirect on the same route with sent=1 to cleanup form
865 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$routeParams);
868 //Add error message mail already exists
869 $this->addFlash('warning', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $existing->getMail()]));
871 //Redirect to user view
872 return $this->redirectToRoute(
873 $this->config
['route']['edit']['name'],
875 'mail' => $smail = $slugger->short($existing->getMail()),
876 'hash' => $slugger->hash($smail)
877 ]+
$this->config
['route']['edit']['context']
888 //Unshort then unserialize field
889 $field = $slugger->unserialize($sfield = $field);
891 } catch (\Error
|\Exception
$e) {
893 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]), $e);
896 //With non array field
897 if (!is_array($field)) {
899 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]));
901 //Without field and hash
914 $reflection = new \
ReflectionClass($this->config
['class']['user']);
917 $user = $reflection->newInstance(strval($mail));
919 //Create the RegisterType form and give the proper parameters
920 $form = $this->createForm($this->config
['register']['view']['form'], $user, $field+
[
921 //Set action to register route name and context
922 'action' => $this->generateUrl($this->config
['route']['register']['name'], ['mail' => $smail, 'field' => $sfield, 'hash' => $hash]+
$this->config
['route']['register']['context']),
924 'civility_class' => $this->config
['class']['civility'],
925 //Set civility default
926 'civility_default' => $doctrine->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']),
933 if ($request->isMethod('POST')) {
934 //Refill the fields in case the form is not valid.
935 $form->handleRequest($request);
937 if ($form->isValid()) {
939 $data = $form->getData();
941 //With existing registrant
942 if ($doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail = $data->getMail())) {
943 //Add error message mail already exists
944 $this->addFlash('warning', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $mail]));
946 //Redirect to user view
947 return $this->redirectToRoute(
948 $this->config
['route']['edit']['name'],
950 'mail' => $smail = $slugger->short($mail),
951 'hash' => $slugger->hash($smail)
952 ]+
$this->config
['route']['edit']['context']
957 $registerMail =& $this->config
['register']['mail'];
959 //Extract names and pseudonym from mail
960 $names = explode(' ', $pseudonym = ucwords(trim(preg_replace('/[^a-zA-Z]+/', ' ', current(explode('@', $data->getMail()))))));
963 $user->setPseudonym($user->getPseudonym()??$pseudonym);
966 $user->setForename($user->getForename()??$names[0]);
969 $user->setSurname($user->getSurname()??$names[1]??$names[0]);
972 $user->setPassword($encoder->encodePassword($user, $user->getPassword()??$data->getMail()));
975 $user->setCreated(new \
DateTime('now'));
978 $user->setUpdated(new \
DateTime('now'));
981 $manager->persist($user);
983 //Iterate on default group
984 foreach($this->config
['default']['group'] as $i => $groupTitle) {
986 if (($group = $doctrine->getRepository($this->config
['class']['group'])->findOneByTitle($groupTitle))) {
988 //XXX: see vendor/symfony/security-core/Role/Role.php
989 $user->addGroup($group);
993 //XXX: consider missing group as fatal
994 throw new \
Exception(sprintf('Group from rapsys_user.default.group[%d] not found by title: %s', $i, $groupTitle));
998 //Generate each route route
999 foreach($this->config
['register']['route'] as $route => $tag) {
1000 //Only process defined routes
1001 if (!empty($this->config
['route'][$route])) {
1002 //Process for confirm url
1003 if ($route == 'confirm') {
1004 //Set the url in context
1005 $registerMail['context'][$tag] = $this->get('router')->generate(
1006 $this->config
['route'][$route]['name'],
1007 //Prepend subscribe context with tag
1009 'mail' => $smail = $slugger->short($data->getMail()),
1010 'hash' => $slugger->hash($smail)
1011 ]+
$this->config
['route'][$route]['context'],
1012 UrlGeneratorInterface
::ABSOLUTE_URL
1018 //XXX: DEBUG: remove me
1019 //die($registerMail['context']['confirm_url']);
1021 //Set recipient_name
1022 $registerMail['context']['recipient_mail'] = $data->getMail();
1024 //Set recipient name
1025 $registerMail['context']['recipient_name'] = '';
1027 //Set recipient name
1028 $registerMail['context']['recipient_name'] = implode(' ', [$data->getForename(), $data->getSurname(), $data->getPseudonym()?'('.$data->getPseudonym().')':'']);
1030 //Init subject context
1031 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['register']['view']['context'], $registerMail['context']), null, '.', '%', '%');
1034 $registerMail['subject'] = ucfirst($this->translator
->trans($registerMail['subject'], $subjectContext));
1037 $message = (new TemplatedEmail())
1039 ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title']))
1041 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
1042 ->to(new Address($registerMail['context']['recipient_mail'], $registerMail['context']['recipient_name']))
1044 ->subject($registerMail['subject'])
1046 //Set path to twig templates
1047 ->htmlTemplate($registerMail['html'])
1048 ->textTemplate($registerMail['text'])
1051 ->context(['subject' => $registerMail['subject']]+
$registerMail['context']);
1053 //Try saving in database
1058 //Add error message mail already exists
1059 $this->addFlash('notice', $this->translator
->trans('Your account has been created'));
1061 //Try sending message
1062 //XXX: mail delivery may silently fail
1065 $mailer->send($message);
1067 //Redirect on the same route with sent=1 to cleanup form
1068 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params'));
1069 //Catch obvious transport exception
1070 } catch(TransportExceptionInterface
$e) {
1071 //Add error message mail unreachable
1072 $form->get('mail')->addError(new FormError($this->translator
->trans('Account %mail% tried subscribe but unable to contact', ['%mail%' => $data->getMail()])));
1074 //Catch double subscription
1075 } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException
$e) {
1076 //Add error message mail already exists
1077 $this->addFlash('error', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $mail]));
1083 return $this->render(
1085 $this->config
['register']['view']['name'],
1087 ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['register']['view']['context']
1094 public function getAlias(): string {
1095 return RapsysUserBundle
::getAlias();