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_title' => $this->config
['class']['title'],
509 'title' => $doctrine->getRepository($this->config
['class']['title'])->findOneByTitle($this->config
['default']['title']),
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->setTitle($data['title']);
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';