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                 $editForm = $this->createForm($this->config
['register']['view']['form'], $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'), 
 341                 //Create the RegisterType form and give the proper parameters 
 342                 $edit = $this->createForm($this->config
['edit']['view']['edit'], $user, [ 
 343                         //Set action to register route name and context 
 344                         'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']), 
 346                         'civility_class' => $this->config
['class']['civility'], 
 347                         //Set civility default 
 348                         'civility_default' => $doctrine->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']), 
 350                         'mail' => $this->isGranted('ROLE_ADMIN'), 
 358                 if ($this->isGranted('ROLE_ADMIN')) { 
 359                         //Create the LoginType form and give the proper parameters 
 360                         $reset = $this->createForm($this->config
['edit']['view']['reset'], $user, [ 
 361                                 //Set action to register route name and context 
 362                                 'action' => $this->generateUrl($this->config
['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']), 
 370                         if ($request->isMethod('POST')) { 
 371                                 //Refill the fields in case the form is not valid. 
 372                                 $reset->handleRequest($request); 
 374                                 //With reset submitted and valid 
 375                                 if ($reset->isSubmitted() && $reset->isValid()) { 
 377                                         $data = $reset->getData(); 
 380                                         $data->setPassword($encoder->encodePassword($data, $data->getPassword())); 
 383                                         $manager->persist($data); 
 385                                         //Flush to get the ids 
 389                                         $this->addFlash('notice', $this->translator
->trans('Account %mail% password updated', ['%mail%' => $mail = $data->getMail()])); 
 391                                         //Redirect to cleanup the form 
 392                                         return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']); 
 397                         $this->config
['edit']['view']['context']['reset'] = $reset->createView(); 
 399                 //XXX: prefer a reset on login to force user unspam action 
 402                         $this->addFlash('notice', $this->translator
->trans('To change your password login with your mail and any password then follow the procedure')); 
 406                 if ($request->isMethod('POST')) { 
 407                         //Refill the fields in case the form is not valid. 
 408                         $edit->handleRequest($request); 
 410                         //With edit submitted and valid 
 411                         if ($edit->isSubmitted() && $edit->isValid()) { 
 413                                 $data = $edit->getData(); 
 416                                 $manager->persist($data); 
 418                                 //Flush to get the ids 
 422                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% updated', ['%mail%' => $mail = $data->getMail()])); 
 424                                 //Redirect to cleanup the form 
 425                                 return $this->redirectToRoute($this->config
['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+
$this->config
['route']['edit']['context']); 
 430                 return $this->render( 
 432                         $this->config
['edit']['view']['name'], 
 434                         ['edit' => $edit->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['edit']['view']['context'] 
 441          * @param Request $request The request 
 442          * @param AuthenticationUtils $authenticationUtils The authentication utils 
 443          * @param RouterInterface $router The router instance 
 444          * @param SluggerUtil $slugger The slugger 
 445          * @param string $mail The shorted mail address 
 446          * @param string $hash The hashed password 
 447          * @return Response The response 
 449         public function login(Request 
$request, AuthenticationUtils 
$authenticationUtils, RouterInterface 
$router, SluggerUtil 
$slugger, $mail, $hash): Response 
{ 
 450                 //Create the LoginType form and give the proper parameters 
 451                 $login = $this->createForm($this->config
['login']['view']['form'], null, [ 
 452                         //Set action to login route name and context 
 453                         'action' => $this->generateUrl($this->config
['route']['login']['name'], $this->config
['route']['login']['context']), 
 454                         //Disable repeated password 
 455                         'password_repeated' => false, 
 464                 if (!empty($mail) && !empty($hash)) { 
 466                         if ($hash != $slugger->hash($mail)) { 
 468                                 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash])); 
 472                         $mail = $slugger->unshort($smail = $mail); 
 475                         if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) { 
 477                                 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail])); 
 481                         $login->get('mail')->setData($mail); 
 482                 //Last username entered by the user 
 483                 } elseif ($lastUsername = $authenticationUtils->getLastUsername()) { 
 484                         $login->get('mail')->setData($lastUsername); 
 487                 //Get the login error if there is one 
 488                 if ($error = $authenticationUtils->getLastAuthenticationError()) { 
 489                         //Get translated error 
 490                         $error = $this->translator
->trans($error->getMessageKey()); 
 492                         //Add error message to mail field 
 493                         $login->get('mail')->addError(new FormError($error)); 
 495                         //Create the LoginType form and give the proper parameters 
 496                         $recover = $this->createForm($this->config
['recover']['view']['form'], null, [ 
 497                                 //Set action to recover route name and context 
 498                                 'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']), 
 505                         //Get recover mail entity 
 506                         $recover->get('mail') 
 507                                 //Set mail from login form 
 508                                 ->setData($login->get('mail')->getData()) 
 510                                 ->addError(new FormError($this->translator
->trans('Use this form to recover your account'))); 
 512                         //Add recover form to context 
 513                         $context['recover'] = $recover->createView(); 
 516                         $this->addFlash('notice', $this->translator
->trans('To change your password login with your mail and any password then follow the procedure')); 
 520                 return $this->render( 
 522                         $this->config
['login']['view']['name'], 
 524                         ['login' => $login->createView()]+
$context+
$this->config
['login']['view']['context'] 
 531          * @param Request $request The request 
 532          * @param Registry $manager The doctrine registry 
 533          * @param UserPasswordEncoderInterface $encoder The password encoder 
 534          * @param EntityManagerInterface $manager The doctrine entity manager 
 535          * @param SluggerUtil $slugger The slugger 
 536          * @param MailerInterface $mailer The mailer 
 537          * @param string $mail The shorted mail address 
 538          * @param string $pass The shorted password 
 539          * @param string $hash The hashed password 
 540          * @return Response The response 
 542         public function recover(Request 
$request, Registry 
$doctrine, UserPasswordEncoderInterface 
$encoder, EntityManagerInterface 
$manager, SluggerUtil 
$slugger, MailerInterface 
$mailer, $mail, $pass, $hash): Response 
{ 
 543                 //Without mail, pass and hash 
 544                 if (empty($mail) && empty($pass) && empty($hash)) { 
 545                         //Create the LoginType form and give the proper parameters 
 546                         $form = $this->createForm($this->config
['recover']['view']['form'], null, [ 
 547                                 //Set action to recover route name and context 
 548                                 'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']), 
 555                         if ($request->isMethod('POST')) { 
 556                                 //Refill the fields in case the form is not valid. 
 557                                 $form->handleRequest($request); 
 559                                 if ($form->isValid()) { 
 561                                         $data = $form->getData(); 
 563                                         //Find user by data mail 
 564                                         if ($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($data['mail'])) { 
 566                                                 $recoverMail =& $this->config
['recover']['mail']; 
 569                                                 $mail = $slugger->short($user->getMail()); 
 572                                                 $pass = $slugger->hash($user->getPassword()); 
 574                                                 //Generate each route route 
 575                                                 foreach($this->config
['recover']['route'] as $route => $tag) { 
 576                                                         //Only process defined routes 
 577                                                         if (!empty($this->config
['route'][$route])) { 
 578                                                                 //Process for recover mail url 
 579                                                                 if ($route == 'recover') { 
 580                                                                         //Set the url in context 
 581                                                                         $recoverMail['context'][$tag] = $this->get('router')->generate( 
 582                                                                                 $this->config
['route'][$route]['name'], 
 583                                                                                 //Prepend recover context with tag 
 587                                                                                         'hash' => $slugger->hash($mail.$pass) 
 588                                                                                 ]+
$this->config
['route'][$route]['context'], 
 589                                                                                 UrlGeneratorInterface
::ABSOLUTE_URL
 
 596                                                 $recoverMail['context']['recipient_mail'] = $user->getMail(); 
 599                                                 $recoverMail['context']['recipient_name'] = trim($user->getForename().' '.$user->getSurname().($user->getPseudonym()?' ('.$user->getPseudonym().')':'')); 
 601                                                 //Init subject context 
 602                                                 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['recover']['view']['context'], $recoverMail['context']), null, '.', '%', '%'); 
 605                                                 $recoverMail['subject'] = ucfirst($this->translator
->trans($recoverMail['subject'], $subjectContext)); 
 608                                                 $message = (new TemplatedEmail()) 
 610                                                         ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title'])) 
 612                                                         //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 613                                                         ->to(new Address($recoverMail['context']['recipient_mail'], $recoverMail['context']['recipient_name'])) 
 615                                                         ->subject($recoverMail['subject']) 
 617                                                         //Set path to twig templates 
 618                                                         ->htmlTemplate($recoverMail['html']) 
 619                                                         ->textTemplate($recoverMail['text']) 
 622                                                         //XXX: require recursive merge to avoid loosing subkeys 
 623                                                         //['subject' => $recoverMail['subject']]+$recoverMail['context']+$this->config['recover']['view']['context'] 
 624                                                         ->context(array_replace_recursive($this->config
['recover']['view']['context'], $recoverMail['context'], ['subject' => $recoverMail['subject']])); 
 626                                                 //Try sending message 
 627                                                 //XXX: mail delivery may silently fail 
 630                                                         $mailer->send($message); 
 632                                                         //Redirect on the same route with sent=1 to cleanup form 
 633                                                         return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params')); 
 634                                                 //Catch obvious transport exception 
 635                                                 } catch(TransportExceptionInterface 
$e) { 
 636                                                         //Add error message mail unreachable 
 637                                                         $form->get('mail')->addError(new FormError($this->translator
->trans('Account found but unable to contact: %mail%', array('%mail%' => $data['mail'])))); 
 641                                                 //Add error message to mail field 
 642                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $data['mail']]))); 
 648                         return $this->render( 
 650                                 $this->config
['recover']['view']['name'], 
 652                                 ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['recover']['view']['context'] 
 657                 if ($hash != $slugger->hash($mail.$pass)) { 
 659                         throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash])); 
 663                 $mail = $slugger->unshort($smail = $mail); 
 666                 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) { 
 668                         //XXX: prevent slugger reverse engineering by not displaying decoded mail 
 669                         throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail])); 
 672                 //With existing subscriber 
 673                 if (empty($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail))) { 
 675                         //XXX: prevent slugger reverse engineering by not displaying decoded mail 
 676                         throw $this->createNotFoundException($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $smail])); 
 679                 //With unmatched pass 
 680                 if ($pass != $slugger->hash($user->getPassword())) { 
 682                         //XXX: prevent use of outdated recover link 
 683                         throw $this->createNotFoundException($this->translator
->trans('Outdated recover link')); 
 686                 //Create the LoginType form and give the proper parameters 
 687                 $form = $this->createForm($this->config
['recover']['view']['form'], $user, [ 
 688                         //Set action to recover route name and context 
 689                         'action' => $this->generateUrl($this->config
['route']['recover']['name'], ['mail' => $smail, 'pass' => $pass, 'hash' => $hash]+
$this->config
['route']['recover']['context']), 
 696                 if ($request->isMethod('POST')) { 
 697                         //Refill the fields in case the form is not valid. 
 698                         $form->handleRequest($request); 
 700                         if ($form->isValid()) { 
 702                                 $data = $form->getData(); 
 704                                 //Set encoded password 
 705                                 $encoded = $encoder->encodePassword($user, $user->getPassword()); 
 708                                 $pass = $slugger->hash($encoded); 
 711                                 $user->setPassword($encoded); 
 714                                 $manager->persist($user); 
 720                                 $this->addFlash('notice', $this->translator
->trans('Account %mail% password updated', ['%mail%' => $mail])); 
 722                                 //Redirect to user login 
 723                                 return $this->redirectToRoute($this->config
['route']['login']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+
$this->config
['route']['login']['context']); 
 728                 return $this->render( 
 730                         $this->config
['recover']['view']['name'], 
 732                         ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['recover']['view']['context'] 
 737          * Register an account 
 739          * @param Request $request The request 
 740          * @param Registry $manager The doctrine registry 
 741          * @param UserPasswordEncoderInterface $encoder The password encoder 
 742          * @param EntityManagerInterface $manager The doctrine entity manager 
 743          * @param SluggerUtil $slugger The slugger 
 744          * @param MailerInterface $mailer The mailer 
 745          * @param LoggerInterface $logger The logger 
 746          * @param string $mail The shorted mail address 
 747          * @param string $field The serialized then shorted form field array 
 748          * @param string $hash The hashed serialized field array 
 749          * @return Response The response 
 751         public function register(Request 
$request, Registry 
$doctrine, UserPasswordEncoderInterface 
$encoder, EntityManagerInterface 
$manager, SluggerUtil 
$slugger, MailerInterface 
$mailer, LoggerInterface 
$logger, $mail, $field, $hash): Response 
{ 
 753                 $reflection = new \
ReflectionClass($this->config
['class']['user']); 
 756                 $user = $reflection->newInstance(); 
 758                 //With mail and field 
 759                 if (!empty($field) && !empty($hash)) { 
 761                         if ($hash != $slugger->hash($mail.$field)) { 
 763                                 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash])); 
 769                                 $mail = $slugger->unshort($smail = $mail); 
 772                                 if (filter_var($mail, FILTER_VALIDATE_EMAIL
) === false) { 
 774                                         //XXX: prevent slugger reverse engineering by not displaying decoded mail 
 775                                         throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail])); 
 779                                 $user->setMail($mail); 
 781                                 //With existing registrant 
 782                                 if ($existing = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail)) { 
 783                                         //With disabled existing 
 784                                         if ($existing->isDisabled()) { 
 786                                                 return $this->render( 
 788                                                         $this->config
['register']['view']['name'], 
 790                                                         ['title' => $this->translator
->trans('Access denied'), 'disabled' => 1]+
$this->config
['register']['view']['context'], 
 792                                                         new Response('', 403) 
 794                                         //With unactivated existing 
 795                                         } elseif (!$existing->isActivated()) { 
 797                                                 //TODO: change for activate ??? 
 798                                                 $activateMail =& $this->config
['register']['mail']; 
 800                                                 //Generate each route route 
 801                                                 foreach($this->config
['register']['route'] as $route => $tag) { 
 802                                                         //Only process defined routes 
 803                                                         if (!empty($this->config
['route'][$route])) { 
 804                                                                 //Process for confirm url 
 805                                                                 if ($route == 'confirm') { 
 806                                                                         //Set the url in context 
 807                                                                         $activateMail['context'][$tag] = $this->get('router')->generate( 
 808                                                                                 $this->config
['route'][$route]['name'], 
 809                                                                                 //Prepend subscribe context with tag 
 811                                                                                         'mail' => $smail = $slugger->short($existing->getMail()), 
 812                                                                                         'hash' => $slugger->hash($smail) 
 813                                                                                 ]+
$this->config
['route'][$route]['context'], 
 814                                                                                 UrlGeneratorInterface
::ABSOLUTE_URL
 
 821                                                 $activateMail['context']['recipient_mail'] = $existing->getMail(); 
 824                                                 $activateMail['context']['recipient_name'] = implode(' ', [$existing->getForename(), $existing->getSurname(), $existing->getPseudonym()?'('.$existing->getPseudonym().')':'']); 
 826                                                 //Init subject context 
 827                                                 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['register']['view']['context'], $activateMail['context']), null, '.', '%', '%'); 
 830                                                 $activateMail['subject'] = ucfirst($this->translator
->trans($activateMail['subject'], $subjectContext)); 
 833                                                 $message = (new TemplatedEmail()) 
 835                                                         ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title'])) 
 837                                                         //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 838                                                         ->to(new Address($activateMail['context']['recipient_mail'], $activateMail['context']['recipient_name'])) 
 840                                                         ->subject($activateMail['subject']) 
 842                                                         //Set path to twig templates 
 843                                                         ->htmlTemplate($activateMail['html']) 
 844                                                         ->textTemplate($activateMail['text']) 
 847                                                         ->context(['subject' => $activateMail['subject']]+
$activateMail['context']); 
 849                                                 //Try sending message 
 850                                                 //XXX: mail delivery may silently fail 
 853                                                         $mailer->send($message); 
 854                                                 //Catch obvious transport exception 
 855                                                 } catch(TransportExceptionInterface 
$e) { 
 856                                                         //Add error message mail unreachable 
 857                                                         $this->addFlash('error', $this->translator
->trans('Account %mail% tried activate but unable to contact', ['%mail%' => $existing->getMail()])); 
 861                                                 $routeParams = $request->get('_route_params'); 
 863                                                 //Remove mail, field and hash from route params 
 864                                                 unset($routeParams['mail'], $routeParams['field'], $routeParams['hash']); 
 866                                                 //Redirect on the same route with sent=1 to cleanup form 
 867                                                 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$routeParams); 
 870                                         //Add error message mail already exists 
 871                                         $this->addFlash('warning', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $existing->getMail()])); 
 873                                         //Redirect to user view 
 874                                         return $this->redirectToRoute( 
 875                                                 $this->config
['route']['edit']['name'], 
 877                                                         'mail' => $smail = $slugger->short($existing->getMail()), 
 878                                                         'hash' => $slugger->hash($smail) 
 879                                                 ]+
$this->config
['route']['edit']['context'] 
 890                                 //Unshort then unserialize field 
 891                                 $field = $slugger->unserialize($sfield = $field); 
 893                         } catch (\Error
|\Exception 
$e) { 
 895                                 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]), $e); 
 898                         //With non array field 
 899                         if (!is_array($field)) { 
 901                                 throw new BadRequestHttpException($this->translator
->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field])); 
 903                 //Without field and hash 
 915                 //Create the RegisterType form and give the proper parameters 
 916                 $form = $this->createForm($this->config
['register']['view']['form'], $user, $field+
[ 
 917                         //Set action to register route name and context 
 918                         'action' => $this->generateUrl($this->config
['route']['register']['name'], ['mail' => $smail, 'field' => $sfield, 'hash' => $hash]+
$this->config
['route']['register']['context']), 
 920                         'civility_class' => $this->config
['class']['civility'], 
 921                         //Set civility default 
 922                         'civility_default' => $doctrine->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']), 
 929                 if ($request->isMethod('POST')) { 
 930                         //Refill the fields in case the form is not valid. 
 931                         $form->handleRequest($request); 
 933                         if ($form->isValid()) { 
 935                                 $data = $form->getData(); 
 937                                 //With existing registrant 
 938                                 if ($doctrine->getRepository($this->config
['class']['user'])->findOneByMail($mail = $data->getMail())) { 
 939                                         //Add error message mail already exists 
 940                                         $this->addFlash('warning', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $mail])); 
 942                                         //Redirect to user view 
 943                                         return $this->redirectToRoute( 
 944                                                 $this->config
['route']['edit']['name'], 
 946                                                         'mail' => $smail = $slugger->short($mail), 
 947                                                         'hash' => $slugger->hash($smail) 
 948                                                 ]+
$this->config
['route']['edit']['context'] 
 953                                 $registerMail =& $this->config
['register']['mail']; 
 955                                 //Extract names and pseudonym from mail 
 956                                 $names = explode(' ', $pseudonym = ucwords(trim(preg_replace('/[^a-zA-Z]+/', ' ', current(explode('@', $data->getMail())))))); 
 959                                 $user->setPseudonym($user->getPseudonym()??$pseudonym); 
 962                                 $user->setForename($user->getForename()??$names[0]); 
 965                                 $user->setSurname($user->getSurname()??$names[1]??$names[0]); 
 968                                 $user->setPassword($encoder->encodePassword($user, $user->getPassword()??$data->getMail())); 
 971                                 $user->setCreated(new \
DateTime('now')); 
 974                                 $user->setUpdated(new \
DateTime('now')); 
 977                                 $manager->persist($user); 
 979                                 //Iterate on default group 
 980                                 foreach($this->config
['default']['group'] as $i => $groupTitle) { 
 982                                         if (($group = $doctrine->getRepository($this->config
['class']['group'])->findOneByTitle($groupTitle))) { 
 984                                                 //XXX: see vendor/symfony/security-core/Role/Role.php 
 985                                                 $user->addGroup($group); 
 989                                                 //XXX: consider missing group as fatal 
 990                                                 throw new \
Exception(sprintf('Group from rapsys_user.default.group[%d] not found by title: %s', $i, $groupTitle)); 
 994                                 //Generate each route route 
 995                                 foreach($this->config
['register']['route'] as $route => $tag) { 
 996                                         //Only process defined routes 
 997                                         if (!empty($this->config
['route'][$route])) { 
 998                                                 //Process for confirm url 
 999                                                 if ($route == 'confirm') { 
1000                                                         //Set the url in context 
1001                                                         $registerMail['context'][$tag] = $this->get('router')->generate( 
1002                                                                 $this->config
['route'][$route]['name'], 
1003                                                                 //Prepend subscribe context with tag 
1005                                                                         'mail' => $smail = $slugger->short($data->getMail()), 
1006                                                                         'hash' => $slugger->hash($smail) 
1007                                                                 ]+
$this->config
['route'][$route]['context'], 
1008                                                                 UrlGeneratorInterface
::ABSOLUTE_URL
 
1014                                 //XXX: DEBUG: remove me 
1015                                 //die($registerMail['context']['confirm_url']); 
1017                                 //Log new user infos 
1020                                         $this->translator
->trans( 
1021                                                 'newuser:mail=%mail%|locale=%locale%|confirm=%confirm%', 
1023                                                         '%mail%' => $data->getMail(), 
1024                                                         '%locale%' => $request->getLocale(), 
1025                                                         '%confirm%' => $registerMail['context'][$this->config
['register']['route']['confirm']] 
1030                                 //Set recipient_name 
1031                                 $registerMail['context']['recipient_mail'] = $data->getMail(); 
1033                                 //Set recipient name 
1034                                 $registerMail['context']['recipient_name'] = ''; 
1036                                 //Set recipient name 
1037                                 $registerMail['context']['recipient_name'] = implode(' ', [$data->getForename(), $data->getSurname(), $data->getPseudonym()?'('.$data->getPseudonym().')':'']); 
1039                                 //Init subject context 
1040                                 $subjectContext = $slugger->flatten(array_replace_recursive($this->config
['register']['view']['context'], $registerMail['context']), null, '.', '%', '%'); 
1043                                 $registerMail['subject'] = ucfirst($this->translator
->trans($registerMail['subject'], $subjectContext)); 
1046                                 $message = (new TemplatedEmail()) 
1048                                         ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['title'])) 
1050                                         //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
1051                                         ->to(new Address($registerMail['context']['recipient_mail'], $registerMail['context']['recipient_name'])) 
1053                                         ->subject($registerMail['subject']) 
1055                                         //Set path to twig templates 
1056                                         ->htmlTemplate($registerMail['html']) 
1057                                         ->textTemplate($registerMail['text']) 
1060                                         ->context(['subject' => $registerMail['subject']]+
$registerMail['context']); 
1062                                 //Try saving in database 
1067                                         //Add error message mail already exists 
1068                                         $this->addFlash('notice', $this->translator
->trans('Your account has been created')); 
1070                                         //Try sending message 
1071                                         //XXX: mail delivery may silently fail 
1074                                                 $mailer->send($message); 
1076                                                 //Redirect on the same route with sent=1 to cleanup form 
1077                                                 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params')); 
1078                                         //Catch obvious transport exception 
1079                                         } catch(TransportExceptionInterface 
$e) { 
1080                                                 //Add error message mail unreachable 
1081                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Account %mail% tried subscribe but unable to contact', ['%mail%' => $data->getMail()]))); 
1083                                 //Catch double subscription 
1084                                 } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException 
$e) { 
1085                                         //Add error message mail already exists 
1086                                         $this->addFlash('error', $this->translator
->trans('Account %mail% already exists', ['%mail%' => $mail])); 
1092                 return $this->render( 
1094                         $this->config
['register']['view']['name'], 
1096                         ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['register']['view']['context'] 
1103         public function getAlias(): string { 
1104                 return RapsysUserBundle
::getAlias();