3 namespace Rapsys\UserBundle\Controller
; 
   5 use Rapsys\UserBundle\Utils\Slugger
; 
   6 use Symfony\Bridge\Twig\Mime\TemplatedEmail
; 
   7 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController
; 
   8 use Symfony\Component\DependencyInjection\ContainerInterface
; 
   9 use Symfony\Component\Form\FormError
; 
  10 use Symfony\Component\HttpFoundation\Request
; 
  11 use Symfony\Component\Mailer\Exception\TransportExceptionInterface
; 
  12 use Symfony\Component\Mailer\MailerInterface
; 
  13 use Symfony\Component\Mime\Address
; 
  14 use Symfony\Component\Routing\Generator\UrlGeneratorInterface
; 
  15 use Symfony\Component\Routing\RouterInterface
; 
  16 use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface
; 
  17 use Symfony\Component\Security\Http\Authentication\AuthenticationUtils
; 
  18 use Symfony\Component\Translation\TranslatorInterface
; 
  20 class DefaultController 
extends AbstractController 
{ 
  25         protected $translator; 
  30          * @param ContainerInterface $container The containter instance 
  31          * @param RouterInterface $router The router instance 
  32          * @param TranslatorInterface $translator The translator instance 
  34         public function __construct(ContainerInterface 
$container, RouterInterface 
$router, TranslatorInterface 
$translator) { 
  36                 $this->config 
= $container->getParameter($this->getAlias()); 
  39                 $this->translator 
= $translator; 
  42                 //XXX: we don't use this as it would be too slow, maybe ??? 
  43                 #$action = str_replace(self::getAlias().'_', '', $container->get('request_stack')->getCurrentRequest()->get('_route')); 
  48                 //Look for keys to translate 
  49                 if (!empty($this->config
['translate'])) { 
  50                         //Iterate on keys to translate 
  51                         foreach($this->config
['translate'] as $translate) { 
  55                                 foreach(array_reverse(explode('.', $translate)) as $curkey) { 
  56                                         $tmp = array_combine([$curkey], [$tmp]); 
  59                                 $translates = array_replace_recursive($translates, $tmp); 
  63                 //Inject every requested route in view and mail context 
  64                 foreach($this->config 
as $tag => $current) { 
  65                         //Look for entry with route subkey 
  66                         if (!empty($current['route'])) { 
  67                                 //Generate url for both view and mail 
  68                                 foreach(['view', 'mail'] as $view) { 
  69                                         //Check that context key is usable 
  70                                         if (isset($current[$view]['context']) && is_array($current[$view]['context'])) { 
  71                                                 //Process every routes 
  72                                                 foreach($current['route'] as $route => $key) { 
  73                                                         //Skip recover_mail route as it requires some parameters 
  74                                                         if ($route == 'recover_mail') { 
  79                                                         $value = $router->generate( 
  80                                                                 $this->config
['route'][$route]['name'], 
  81                                                                 $this->config
['route'][$route]['context'], 
  82                                                                 //Generate absolute url for mails 
  83                                                                 $view=='mail'?UrlGeneratorInterface
::ABSOLUTE_URL
:UrlGeneratorInterface
::ABSOLUTE_PATH
 
  87                                                         if (strpos($key, '.') !== false) { 
  92                                                                 foreach(array_reverse(explode('.', $key)) as $curkey) { 
  93                                                                         $tmp = array_combine([$curkey], [$tmp]); 
  97                                                                 $this->config
[$tag][$view]['context'] = array_replace_recursive($this->config
[$tag][$view]['context'], $tmp); 
 101                                                                 $this->config
[$tag][$view]['context'][$key] = $value; 
 105                                                 //Look for successful intersections 
 106                                                 if (!empty(array_intersect_key($translates, $current[$view]['context']))) { 
 107                                                         //Iterate on keys to translate 
 108                                                         foreach($this->config
['translate'] as $translate) { 
 110                                                                 $keys = explode('.', $translate); 
 113                                                                 $tmp = $current[$view]['context']; 
 116                                                                 foreach($keys as $curkey) { 
 118                                                                         $tmp = $tmp[$curkey]; 
 121                                                                 //Translate tmp value 
 122                                                                 $tmp = $translator->trans($tmp); 
 125                                                                 foreach(array_reverse($keys) as $curkey) { 
 127                                                                         $tmp = array_combine([$curkey], [$tmp]); 
 131                                                                 $this->config
[$tag][$view]['context'] = array_replace_recursive($this->config
[$tag][$view]['context'], $tmp); 
 136                                                 $currentLocale = $router->getContext()->getParameters()['_locale']; 
 138                                                 //Iterate on locales excluding current one 
 139                                                 foreach($this->config
['locales'] as $locale) { 
 143                                                         //Iterate on other locales 
 144                                                         foreach(array_diff($this->config
['locales'], [$locale]) as $other) { 
 145                                                                 $titles[$other] = $translator->trans($this->config
['languages'][$locale], [], null, $other); 
 149                                                         $path = $router->getContext()->getPathInfo(); 
 151                                                         //Retrieve route matching path 
 152                                                         $route = $router->match($path); 
 155                                                         $name = $route['_route']; 
 158                                                         unset($route['_route']); 
 160                                                         //With current locale 
 161                                                         if ($locale == $currentLocale) { 
 162                                                                 //Set locale locales context 
 163                                                                 $this->config
[$tag][$view]['context']['canonical'] = $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
); 
 165                                                                 //Set locale locales context 
 166                                                                 $this->config
[$tag][$view]['context']['alternates'][] = [ 
 168                                                                         'absolute' => $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
), 
 169                                                                         'relative' => $router->generate($name, ['_locale' => $locale]+
$route), 
 170                                                                         'title' => implode('/', $titles), 
 171                                                                         'translated' => $translator->trans($this->config
['languages'][$locale], [], null, $locale) 
 184          * @param Request $request The request 
 185          * @param AuthenticationUtils $authenticationUtils The authentication utils 
 186          * @return Response The response 
 188         public function login(Request 
$request, AuthenticationUtils 
$authenticationUtils) { 
 189                 //Create the LoginType form and give the proper parameters 
 190                 $login = $this->createForm($this->config
['login']['view']['form'], null, [ 
 191                         //Set action to login route name and context 
 192                         'action' => $this->generateUrl($this->config
['route']['login']['name'], $this->config
['route']['login']['context']), 
 199                 //Last username entered by the user 
 200                 if ($lastUsername = $authenticationUtils->getLastUsername()) { 
 201                         $login->get('mail')->setData($lastUsername); 
 204                 //Get the login error if there is one 
 205                 if ($error = $authenticationUtils->getLastAuthenticationError()) { 
 206                         //Get translated error 
 207                         $error = $this->translator
->trans($error->getMessageKey()); 
 209                         //Add error message to mail field 
 210                         $login->get('mail')->addError(new FormError($error)); 
 212                         //Create the RecoverType form and give the proper parameters 
 213                         $recover = $this->createForm($this->config
['recover']['view']['form'], null, [ 
 214                                 //Set action to recover route name and context 
 215                                 'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']), 
 219                         //Get recover mail entity 
 220                         $recover->get('mail') 
 221                                 //Set mail from login form 
 222                                 ->setData($login->get('mail')->getData()) 
 224                                 ->addError(new FormError($this->translator
->trans('Use this form to recover your account'))); 
 226                         //Add recover form to context 
 227                         $context['recover'] = $recover->createView(); 
 231                 return $this->render( 
 233                         $this->config
['login']['view']['name'], 
 235                         ['login' => $login->createView()]+
$context+
$this->config
['login']['view']['context'] 
 242          * @param Request $request The request 
 243          * @param Slugger $slugger The slugger 
 244          * @param MailerInterface $mailer The mailer 
 245          * @return Response The response 
 247         public function recover(Request 
$request, Slugger 
$slugger, MailerInterface 
$mailer) { 
 248                 //Create the RecoverType form and give the proper parameters 
 249                 $form = $this->createForm($this->config
['recover']['view']['form'], null, array( 
 250                         //Set action to recover route name and context 
 251                         'action' => $this->generateUrl($this->config
['route']['recover']['name'], $this->config
['route']['recover']['context']), 
 255                 if ($request->isMethod('POST')) { 
 256                         //Refill the fields in case the form is not valid. 
 257                         $form->handleRequest($request); 
 259                         if ($form->isValid()) { 
 261                                 $doctrine = $this->getDoctrine(); 
 264                                 $data = $form->getData(); 
 267                                 if ($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($data['mail'])) { 
 269                                         $mail =& $this->config
['recover']['mail']; 
 271                                         //Generate each route route 
 272                                         foreach($this->config
['recover']['route'] as $route => $tag) { 
 273                                                 //Only process defined routes 
 274                                                 if (empty($mail['context'][$tag]) && !empty($this->config
['route'][$route])) { 
 275                                                         //Process for recover mail url 
 276                                                         if ($route == 'recover_mail') { 
 277                                                                 //Set the url in context 
 278                                                                 $mail['context'][$tag] = $this->get('router')->generate( 
 279                                                                         $this->config
['route'][$route]['name'], 
 280                                                                         //Prepend recover context with tag 
 282                                                                                 'recipient' => $slugger->short($user->getMail()), 
 283                                                                                 'hash' => $slugger->hash($user->getPassword()) 
 284                                                                         ]+
$this->config
['route'][$route]['context'], 
 285                                                                         UrlGeneratorInterface
::ABSOLUTE_URL
 
 292                                         $mail['context']['recipient_mail'] = $data['mail']; 
 295                                         $mail['context']['recipient_name'] = trim($user->getForename().' '.$user->getSurname().($user->getPseudonym()?' ('.$user->getPseudonym().')':'')); 
 297                                         //Init subject context 
 298                                         $subjectContext = $this->flatten(array_replace_recursive($this->config
['recover']['view']['context'], $mail['context']), null, '.', '%', '%'); 
 301                                         $mail['subject'] = ucfirst($this->translator
->trans($mail['subject'], $subjectContext)); 
 304                                         $message = (new TemplatedEmail()) 
 306                                                 ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['name'])) 
 308                                                 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 309                                                 ->to(new Address($mail['context']['recipient_mail'], $mail['context']['recipient_name'])) 
 311                                                 ->subject($mail['subject']) 
 313                                                 //Set path to twig templates 
 314                                                 ->htmlTemplate($mail['html']) 
 315                                                 ->textTemplate($mail['text']) 
 318                                                 //XXX: require recursive merge to avoid loosing subkeys 
 319                                                 //['subject' => $mail['subject']]+$mail['context']+$this->config['recover']['view']['context'] 
 320                                                 ->context(array_replace_recursive($this->config
['recover']['view']['context'], $mail['context'], ['subject' => $mail['subject']])); 
 322                                         //Try sending message 
 323                                         //XXX: mail delivery may silently fail 
 326                                                 $mailer->send($message); 
 328                                                 //Redirect on the same route with sent=1 to cleanup form 
 329                                                 #return $this->redirectToRoute('rapsys_user_register', array('sent' => 1)); 
 330                                                 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params')); 
 331                                         //Catch obvious transport exception 
 332                                         } catch(TransportExceptionInterface 
$e) { 
 333                                                 //Add error message mail unreachable 
 334                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Account found but unable to contact: %mail%', array('%mail%' => $data['mail'])))); 
 338                                         //Add error message to mail field 
 339                                         $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to find account %mail%', ['%mail%' => $data['mail']]))); 
 345                 return $this->render( 
 347                         $this->config
['recover']['view']['name'], 
 349                         ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['recover']['view']['context'] 
 354          * Recover account with mail link 
 356          * @param Request $request The request 
 357          * @param UserPasswordEncoderInterface $encoder The password encoder 
 358          * @param Slugger $slugger The slugger 
 359          * @param MailerInterface $mailer The mailer 
 360          * @param string $recipient The shorted recipient mail address 
 361          * @param string $hash The hashed password 
 362          * @return Response The response 
 364         public function recoverMail(Request 
$request, UserPasswordEncoderInterface 
$encoder, Slugger 
$slugger, MailerInterface 
$mailer, $recipient, $hash) { 
 365                 //Create the RecoverType form and give the proper parameters 
 366                 $form = $this->createForm($this->config
['recover_mail']['view']['form'], null, array( 
 367                         //Set action to recover route name and context 
 368                         'action' => $this->generateUrl($this->config
['route']['recover_mail']['name'], ['recipient' => $recipient, 'hash' => $hash]+
$this->config
['route']['recover_mail']['context']), 
 373                 $doctrine = $this->getDoctrine(); 
 379                 if (($user = $doctrine->getRepository($this->config
['class']['user'])->findOneByMail($slugger->unshort($recipient))) && $found = ($hash == $slugger->hash($user->getPassword()))) { 
 380                         if ($request->isMethod('POST')) { 
 381                                 //Refill the fields in case the form is not valid. 
 382                                 $form->handleRequest($request); 
 384                                 if ($form->isValid()) { 
 386                                         $data = $form->getData(); 
 388                                         //set encoded password 
 389                                         $encoded = $encoder->encodePassword($user, $data['password']); 
 392                                         $user->setPassword($encoded); 
 395                                         $manager = $doctrine->getManager(); 
 398                                         $manager->persist($user); 
 404                                         $mail =& $this->config
['recover_mail']['mail']; 
 407                                         $hash = $slugger->hash($encoded); 
 409                                         //Generate each route route 
 410                                         foreach($this->config
['recover_mail']['route'] as $route => $tag) { 
 411                                                 //Only process defined routes 
 412                                                 if (empty($mail['context'][$tag]) && !empty($this->config
['route'][$route])) { 
 413                                                         //Process for recover mail url 
 414                                                         if ($route == 'recover_mail') { 
 415                                                                 //Prepend recover context with tag 
 416                                                                 $this->config
['route'][$route]['context'] = [ 
 417                                                                         'recipient' => $recipient, 
 419                                                                 ]+
$this->config
['route'][$route]['context']; 
 421                                                         //Set the url in context 
 422                                                         $mail['context'][$tag] = $this->get('router')->generate( 
 423                                                                 $this->config
['route'][$route]['name'], 
 424                                                                 $this->config
['route'][$route]['context'], 
 425                                                                 UrlGeneratorInterface
::ABSOLUTE_URL
 
 431                                         $mail['context']['recipient_mail'] = $user->getMail(); 
 434                                         $mail['context']['recipient_name'] = trim($user->getForename().' '.$user->getSurname().($user->getPseudonym()?' ('.$user->getPseudonym().')':'')); 
 436                                         //Init subject context 
 437                                         $subjectContext = $this->flatten(array_replace_recursive($this->config
['recover_mail']['view']['context'], $mail['context']), null, '.', '%', '%'); 
 440                                         $mail['subject'] = ucfirst($this->translator
->trans($mail['subject'], $subjectContext)); 
 443                                         $message = (new TemplatedEmail()) 
 445                                                 ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['name'])) 
 447                                                 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 448                                                 ->to(new Address($mail['context']['recipient_mail'], $mail['context']['recipient_name'])) 
 450                                                 ->subject($mail['subject']) 
 452                                                 //Set path to twig templates 
 453                                                 ->htmlTemplate($mail['html']) 
 454                                                 ->textTemplate($mail['text']) 
 457                                                 //XXX: require recursive merge to avoid loosing subkeys 
 458                                                 //['subject' => $mail['subject']]+$mail['context']+$this->config['recover_mail']['view']['context'] 
 459                                                 ->context(array_replace_recursive($this->config
['recover_mail']['view']['context'], $mail['context'], ['subject' => $mail['subject']])); 
 461                                         //Try sending message 
 462                                         //XXX: mail delivery may silently fail 
 465                                                 $mailer->send($message); 
 467                                                 //Redirect on the same route with sent=1 to cleanup form 
 468                                                 return $this->redirectToRoute($request->get('_route'), ['recipient' => $recipient, 'hash' => $hash, 'sent' => 1]+
$request->get('_route_params')); 
 469                                         //Catch obvious transport exception 
 470                                         } catch(TransportExceptionInterface 
$e) { 
 471                                                 //Add error message mail unreachable 
 472                                                 $form->get('password')->get('first')->addError(new FormError($this->translator
->trans('Account %mail% updated but unable to contact', array('%mail%' => $mail['context']['recipient_mail'])))); 
 478                         //Add error in flash message 
 479                         //XXX: prevent slugger reverse engineering by not displaying decoded recipient 
 480                         #$this->addFlash('error', $this->translator->trans('Unable to find account %mail%', ['%mail%' => $slugger->unshort($recipient)])); 
 484                 return $this->render( 
 486                         $this->config
['recover_mail']['view']['name'], 
 488                         ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0), 'found' => $found]+
$this->config
['recover_mail']['view']['context'] 
 493          * Register an account 
 495          * @todo: activation link 
 497          * @param Request $request The request 
 498          * @param UserPasswordEncoderInterface $encoder The password encoder 
 499          * @param MailerInterface $mailer The mailer 
 500          * @return Response The response 
 502         public function register(Request 
$request, UserPasswordEncoderInterface 
$encoder, MailerInterface 
$mailer) { 
 504                 $doctrine = $this->getDoctrine(); 
 506                 //Create the RegisterType form and give the proper parameters 
 507                 $form = $this->createForm($this->config
['register']['view']['form'], null, array( 
 508                         'class_civility' => $this->config
['class']['civility'], 
 509                         'civility' => $doctrine->getRepository($this->config
['class']['civility'])->findOneByTitle($this->config
['default']['civility']), 
 510                         //Set action to register route name and context 
 511                         'action' => $this->generateUrl($this->config
['route']['register']['name'], $this->config
['route']['register']['context']), 
 515                 if ($request->isMethod('POST')) { 
 516                         //Refill the fields in case the form is not valid. 
 517                         $form->handleRequest($request); 
 519                         if ($form->isValid()) { 
 521                                 $data = $form->getData(); 
 524                                 $mail =& $this->config
['register']['mail']; 
 526                                 //Generate each route route 
 527                                 foreach($this->config
['register']['route'] as $route => $tag) { 
 528                                         if (empty($mail['context'][$tag]) && !empty($this->config
['route'][$route])) { 
 529                                                 $mail['context'][$tag] = $this->get('router')->generate( 
 530                                                         $this->config
['route'][$route]['name'], 
 531                                                         $this->config
['route'][$route]['context'], 
 532                                                         UrlGeneratorInterface
::ABSOLUTE_URL
 
 538                                 $mail['context']['recipient_mail'] = $data['mail']; 
 541                                 $mail['context']['recipient_name'] = trim($data['forename'].' '.$data['surname'].($data['pseudonym']?' ('.$data['pseudonym'].')':'')); 
 543                                 //Init subject context 
 544                                 $subjectContext = $this->flatten(array_replace_recursive($this->config
['register']['view']['context'], $mail['context']), null, '.', '%', '%'); 
 547                                 $mail['subject'] = ucfirst($this->translator
->trans($mail['subject'], $subjectContext)); 
 550                                 $message = (new TemplatedEmail()) 
 552                                         ->from(new Address($this->config
['contact']['mail'], $this->config
['contact']['name'])) 
 554                                         //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 555                                         ->to(new Address($mail['context']['recipient_mail'], $mail['context']['recipient_name'])) 
 557                                         ->subject($mail['subject']) 
 559                                         //Set path to twig templates 
 560                                         ->htmlTemplate($mail['html']) 
 561                                         ->textTemplate($mail['text']) 
 564                                         //XXX: require recursive merge to avoid loosing subkeys 
 565                                         //['subject' => $mail['subject']]+$mail['context']+$this->config['register']['view']['context'] 
 566                                         ->context(array_replace_recursive($this->config
['register']['view']['context'], $mail['context'], ['subject' => $mail['subject']])); 
 569                                 $manager = $doctrine->getManager(); 
 572                                 $reflection = new \
ReflectionClass($this->config
['class']['user']); 
 575                                 $user = $reflection->newInstance(); 
 577                                 $user->setMail($data['mail']); 
 578                                 $user->setPseudonym($data['pseudonym']); 
 579                                 $user->setForename($data['forename']); 
 580                                 $user->setSurname($data['surname']); 
 581                                 $user->setPhone($data['phone']); 
 582                                 $user->setPassword($encoder->encodePassword($user, $data['password'])); 
 583                                 $user->setActive(true); 
 584                                 $user->setCivility($data['civility']); 
 586                                 //Iterate on default group 
 587                                 foreach($this->config
['default']['group'] as $i => $groupTitle) { 
 589                                         if (($group = $doctrine->getRepository($this->config
['class']['group'])->findOneByTitle($groupTitle))) { 
 591                                                 //XXX: see vendor/symfony/security-core/Role/Role.php 
 592                                                 $user->addGroup($group); 
 596                                                 //XXX: consider missing group as fatal 
 597                                                 throw new \
Exception(sprintf('Group from rapsys_user.default.group[%d] not found by title: %s', $i, $groupTitle)); 
 601                                 $user->setCreated(new \
DateTime('now')); 
 602                                 $user->setUpdated(new \
DateTime('now')); 
 605                                 $manager->persist($user); 
 607                                 //Try saving in database 
 612                                         //Try sending message 
 613                                         //XXX: mail delivery may silently fail 
 616                                                 $mailer->send($message); 
 618                                                 //Redirect on the same route with sent=1 to cleanup form 
 619                                                 #return $this->redirectToRoute('rapsys_user_register', array('sent' => 1)); 
 620                                                 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params')); 
 621                                         //Catch obvious transport exception 
 622                                         } catch(TransportExceptionInterface 
$e) { 
 623                                                 //Add error message mail unreachable 
 624                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Account %mail% created but unable to contact', array('%mail%' => $data['mail'])))); 
 626                                 //Catch double subscription 
 627                                 } catch (\Doctrine\DBAL\Exception\UniqueConstraintViolationException 
$e) { 
 628                                         //Add error message mail already exists 
 629                                         $form->get('mail')->addError(new FormError($this->translator
->trans('Account %mail% already exists', ['%mail%' => $data['mail']]))); 
 635                 return $this->render( 
 637                         $this->config
['register']['view']['name'], 
 639                         ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->config
['register']['view']['context'] 
 644          * Recursively flatten an array 
 646          * @param array $data The data tree 
 647          * @param string|null $current The current prefix 
 648          * @param string $sep The key separator 
 649          * @param string $prefix The key prefix 
 650          * @param string $suffix The key suffix 
 651          * @return array The flattened data 
 653         protected function flatten($data, $current = null, $sep = '.', $prefix = '', $suffix = '') { 
 657                 //Look for data array 
 658                 if (is_array($data)) { 
 659                         //Iteare on each pair 
 660                         foreach($data as $k => $v) { 
 661                                 //Merge flattened value in return array 
 662                                 $ret +
= $this->flatten($v, empty($current) ? $k : $current.$sep.$k, $sep, $prefix, $suffix); 
 666                         //Store data in flattened key 
 667                         $ret[$prefix.$current.$suffix] = $data; 
 677         public function getAlias() { 
 678                 return 'rapsys_user';