]> Raphaël G. Git Repositories - userbundle/blob - Controller/DefaultController.php
Improve disabled 403 status code response
[userbundle] / Controller / DefaultController.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys UserBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\UserBundle\Controller;
13
14 use Doctrine\Bundle\DoctrineBundle\Registry;
15 use Doctrine\ORM\EntityManagerInterface;
16 use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
17 use Psr\Log\LoggerInterface;
18 use Symfony\Bridge\Twig\Mime\TemplatedEmail;
19 use Symfony\Component\Form\FormError;
20 use Symfony\Component\HttpFoundation\Request;
21 use Symfony\Component\HttpFoundation\Response;
22 use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
23 use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
24 use Symfony\Component\Mailer\MailerInterface;
25 use Symfony\Component\Mime\Address;
26 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
27 use Symfony\Component\Routing\RouterInterface;
28 use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
29 use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
30
31 use Rapsys\PackBundle\Util\SluggerUtil;
32
33 /**
34 * {@inheritdoc}
35 */
36 class DefaultController extends AbstractController {
37 /**
38 * Confirm account from mail link
39 *
40 * @param Request $request The request
41 * @param Registry $manager The doctrine registry
42 * @param UserPasswordEncoderInterface $encoder The password encoder
43 * @param EntityManagerInterface $manager The doctrine entity manager
44 * @param SluggerUtil $slugger The slugger
45 * @param MailerInterface $mailer The mailer
46 * @param string $mail The shorted mail address
47 * @param string $hash The hashed password
48 * @return Response The response
49 */
50 public function confirm(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $hash): Response {
51 //With invalid hash
52 if ($hash != $slugger->hash($mail)) {
53 //Throw bad request
54 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
55 }
56
57 //Get mail
58 $mail = $slugger->unshort($smail = $mail);
59
60 //Without valid mail
61 if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) {
62 //Throw bad request
63 //XXX: prevent slugger reverse engineering by not displaying decoded mail
64 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
65 }
66
67 //Without existing registrant
68 if (!($user = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail))) {
69 //Add error message mail already exists
70 //XXX: prevent slugger reverse engineering by not displaying decoded mail
71 $this->addFlash('error', $this->translator->trans('Account %mail% do not exists', ['%mail%' => $smail]));
72
73 //Redirect to register view
74 return $this->redirectToRoute($this->config['route']['register']['name'], ['mail' => $smail, 'field' => $sfield = $slugger->serialize([]), 'hash' => $slugger->hash($smail.$sfield)]+$this->config['route']['register']['context']);
75 }
76
77 //Set active
78 $user->setActive(true);
79
80 //Persist user
81 $manager->persist($user);
82
83 //Send to database
84 $manager->flush();
85
86 //Add error message mail already exists
87 $this->addFlash('notice', $this->translator->trans('Your account has been activated'));
88
89 //Redirect to user view
90 return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']);
91 }
92
93 /**
94 * Edit account by shorted mail
95 *
96 * @param Request $request The request
97 * @param Registry $manager The doctrine registry
98 * @param UserPasswordEncoderInterface $encoder The password encoder
99 * @param EntityManagerInterface $manager The doctrine entity manager
100 * @param SluggerUtil $slugger The slugger
101 * @param string $mail The shorted mail address
102 * @param string $hash The hashed password
103 * @return Response The response
104 */
105 public function edit(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, $mail, $hash): Response {
106 //With invalid hash
107 if ($hash != $slugger->hash($mail)) {
108 //Throw bad request
109 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
110 }
111
112 //Get mail
113 $mail = $slugger->unshort($smail = $mail);
114
115 //With existing subscriber
116 if (empty($user = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail))) {
117 //Throw not found
118 //XXX: prevent slugger reverse engineering by not displaying decoded mail
119 throw $this->createNotFoundException($this->translator->trans('Unable to find account %mail%', ['%mail%' => $smail]));
120 }
121
122 //Prevent access when not admin, user is not guest and not currently logged user
123 if (!$this->isGranted('ROLE_ADMIN') && $user != $this->getUser() || !$this->isGranted('IS_AUTHENTICATED_FULLY')) {
124 //Throw access denied
125 //XXX: prevent slugger reverse engineering by not displaying decoded mail
126 throw $this->createAccessDeniedException($this->translator->trans('Unable to access user: %mail%', ['%mail%' => $smail]));
127 }
128
129 //Create the RegisterType form and give the proper parameters
130 $edit = $this->createForm($this->config['edit']['view']['edit'], $user, [
131 //Set action to register route name and context
132 'action' => $this->generateUrl($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']),
133 //Set civility class
134 'civility_class' => $this->config['class']['civility'],
135 //Set civility default
136 'civility_default' => $doctrine->getRepository($this->config['class']['civility'])->findOneByTitle($this->config['default']['civility']),
137 //Disable mail
138 'mail' => $this->isGranted('ROLE_ADMIN'),
139 //Disable slug
140 'slug' => $this->isGranted('ROLE_ADMIN'),
141 //Disable password
142 'password' => false,
143 //Set method
144 'method' => 'POST'
145 ]);
146
147 //With admin role
148 if ($this->isGranted('ROLE_ADMIN')) {
149 //Create the LoginType form and give the proper parameters
150 $reset = $this->createForm($this->config['edit']['view']['reset'], $user, [
151 //Set action to register route name and context
152 'action' => $this->generateUrl($this->config['route']['edit']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']),
153 //Disable mail
154 'mail' => false,
155 //Set method
156 'method' => 'POST'
157 ]);
158
159 //With post method
160 if ($request->isMethod('POST')) {
161 //Refill the fields in case the form is not valid.
162 $reset->handleRequest($request);
163
164 //With reset submitted and valid
165 if ($reset->isSubmitted() && $reset->isValid()) {
166 //Set data
167 $data = $reset->getData();
168
169 //Set password
170 $data->setPassword($encoder->encodePassword($data, $data->getPassword()));
171
172 //Queue snippet save
173 $manager->persist($data);
174
175 //Flush to get the ids
176 $manager->flush();
177
178 //Add notice
179 $this->addFlash('notice', $this->translator->trans('Account %mail% password updated', ['%mail%' => $mail = $data->getMail()]));
180
181 //Redirect to cleanup the form
182 return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']);
183 }
184 }
185
186 //Add reset view
187 $this->config['edit']['view']['context']['reset'] = $reset->createView();
188 //Without admin role
189 //XXX: prefer a reset on login to force user unspam action
190 } else {
191 //Add notice
192 $this->addFlash('notice', $this->translator->trans('To change your password login with your mail and any password then follow the procedure'));
193 }
194
195 //With post method
196 if ($request->isMethod('POST')) {
197 //Refill the fields in case the form is not valid.
198 $edit->handleRequest($request);
199
200 //With edit submitted and valid
201 if ($edit->isSubmitted() && $edit->isValid()) {
202 //Set data
203 $data = $edit->getData();
204
205 //Set slug
206 $slug = null;
207
208 //With admin
209 if ($this->isGranted('ROLE_ADMIN')) {
210 //With slug
211 if (!empty($data->getSlug())) {
212 //Set slug
213 $slug = $slugger->slug($data->getPseudonym());
214 }
215
216 //Update slug
217 $data->setSlug($slug);
218 }
219
220 //Queue snippet save
221 $manager->persist($data);
222
223 //Try saving in database
224 try {
225 //Flush to get the ids
226 $manager->flush();
227
228 //Add notice
229 $this->addFlash('notice', $this->translator->trans('Account %mail% updated', ['%mail%' => $mail = $data->getMail()]));
230
231 //Redirect to cleanup the form
232 return $this->redirectToRoute($this->config['route']['edit']['name'], ['mail' => $smail = $slugger->short($mail), 'hash' => $slugger->hash($smail)]+$this->config['route']['edit']['context']);
233 //Catch double slug or mail
234 } catch (UniqueConstraintViolationException $e) {
235 //Add error message mail already exists
236 $this->addFlash('error', $this->translator->trans('Account %mail% or with slug %slug% already exists', ['%mail%' => $data->getMail(), '%slug%' => $slug]));
237 }
238 }
239 }
240
241 //Render view
242 return $this->render(
243 //Template
244 $this->config['edit']['view']['name'],
245 //Context
246 ['edit' => $edit->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['edit']['view']['context']
247 );
248 }
249
250 /**
251 * Login
252 *
253 * @param Request $request The request
254 * @param AuthenticationUtils $authenticationUtils The authentication utils
255 * @param RouterInterface $router The router instance
256 * @param SluggerUtil $slugger The slugger
257 * @param string $mail The shorted mail address
258 * @param string $hash The hashed password
259 * @return Response The response
260 */
261 public function login(Request $request, AuthenticationUtils $authenticationUtils, RouterInterface $router, SluggerUtil $slugger, $mail, $hash): Response {
262 //Create the LoginType form and give the proper parameters
263 $login = $this->createForm($this->config['login']['view']['form'], null, [
264 //Set action to login route name and context
265 'action' => $this->generateUrl($this->config['route']['login']['name'], $this->config['route']['login']['context']),
266 //Disable repeated password
267 'password_repeated' => false,
268 //Set method
269 'method' => 'POST'
270 ]);
271
272 //Init context
273 $context = [];
274
275 //With mail
276 if (!empty($mail) && !empty($hash)) {
277 //With invalid hash
278 if ($hash != $slugger->hash($mail)) {
279 //Throw bad request
280 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
281 }
282
283 //Get mail
284 $mail = $slugger->unshort($smail = $mail);
285
286 //Without valid mail
287 if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) {
288 //Throw bad request
289 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
290 }
291
292 //Prefilled mail
293 $login->get('mail')->setData($mail);
294 //Last username entered by the user
295 } elseif ($lastUsername = $authenticationUtils->getLastUsername()) {
296 $login->get('mail')->setData($lastUsername);
297 }
298
299 //Get the login error if there is one
300 if ($error = $authenticationUtils->getLastAuthenticationError()) {
301 //Get translated error
302 $error = $this->translator->trans($error->getMessageKey());
303
304 //Add error message to mail field
305 $login->get('mail')->addError(new FormError($error));
306
307 //Create the LoginType form and give the proper parameters
308 $recover = $this->createForm($this->config['recover']['view']['form'], null, [
309 //Set action to recover route name and context
310 'action' => $this->generateUrl($this->config['route']['recover']['name'], $this->config['route']['recover']['context']),
311 //Without password
312 'password' => false,
313 //Set method
314 'method' => 'POST'
315 ]);
316
317 //Get recover mail entity
318 $recover->get('mail')
319 //Set mail from login form
320 ->setData($login->get('mail')->getData())
321 //Add recover error
322 ->addError(new FormError($this->translator->trans('Use this form to recover your account')));
323
324 //Add recover form to context
325 $context['recover'] = $recover->createView();
326 } else {
327 //Add notice
328 $this->addFlash('notice', $this->translator->trans('To change your password login with your mail and any password then follow the procedure'));
329 }
330
331 //Render view
332 return $this->render(
333 //Template
334 $this->config['login']['view']['name'],
335 //Context
336 ['login' => $login->createView()]+$context+$this->config['login']['view']['context']
337 );
338 }
339
340 /**
341 * Recover account
342 *
343 * @param Request $request The request
344 * @param Registry $manager The doctrine registry
345 * @param UserPasswordEncoderInterface $encoder The password encoder
346 * @param EntityManagerInterface $manager The doctrine entity manager
347 * @param SluggerUtil $slugger The slugger
348 * @param MailerInterface $mailer The mailer
349 * @param string $mail The shorted mail address
350 * @param string $pass The shorted password
351 * @param string $hash The hashed password
352 * @return Response The response
353 */
354 public function recover(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, $mail, $pass, $hash): Response {
355 //Without mail, pass and hash
356 if (empty($mail) && empty($pass) && empty($hash)) {
357 //Create the LoginType form and give the proper parameters
358 $form = $this->createForm($this->config['recover']['view']['form'], null, [
359 //Set action to recover route name and context
360 'action' => $this->generateUrl($this->config['route']['recover']['name'], $this->config['route']['recover']['context']),
361 //Without password
362 'password' => false,
363 //Set method
364 'method' => 'POST'
365 ]);
366
367 if ($request->isMethod('POST')) {
368 //Refill the fields in case the form is not valid.
369 $form->handleRequest($request);
370
371 if ($form->isValid()) {
372 //Set data
373 $data = $form->getData();
374
375 //Find user by data mail
376 if ($user = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($data['mail'])) {
377 //Set mail shortcut
378 $recoverMail =& $this->config['recover']['mail'];
379
380 //Set mail
381 $mail = $slugger->short($user->getMail());
382
383 //Set pass
384 $pass = $slugger->hash($user->getPassword());
385
386 //Generate each route route
387 foreach($this->config['recover']['route'] as $route => $tag) {
388 //Only process defined routes
389 if (!empty($this->config['route'][$route])) {
390 //Process for recover mail url
391 if ($route == 'recover') {
392 //Set the url in context
393 $recoverMail['context'][$tag] = $this->get('router')->generate(
394 $this->config['route'][$route]['name'],
395 //Prepend recover context with tag
396 [
397 'mail' => $mail,
398 'pass' => $pass,
399 'hash' => $slugger->hash($mail.$pass)
400 ]+$this->config['route'][$route]['context'],
401 UrlGeneratorInterface::ABSOLUTE_URL
402 );
403 }
404 }
405 }
406
407 //Set recipient_name
408 $recoverMail['context']['recipient_mail'] = $user->getMail();
409
410 //Set recipient_name
411 $recoverMail['context']['recipient_name'] = trim($user->getForename().' '.$user->getSurname().($user->getPseudonym()?' ('.$user->getPseudonym().')':''));
412
413 //Init subject context
414 $subjectContext = $slugger->flatten(array_replace_recursive($this->config['recover']['view']['context'], $recoverMail['context']), null, '.', '%', '%');
415
416 //Translate subject
417 $recoverMail['subject'] = ucfirst($this->translator->trans($recoverMail['subject'], $subjectContext));
418
419 //Create message
420 $message = (new TemplatedEmail())
421 //Set sender
422 ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
423 //Set recipient
424 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
425 ->to(new Address($recoverMail['context']['recipient_mail'], $recoverMail['context']['recipient_name']))
426 //Set subject
427 ->subject($recoverMail['subject'])
428
429 //Set path to twig templates
430 ->htmlTemplate($recoverMail['html'])
431 ->textTemplate($recoverMail['text'])
432
433 //Set context
434 //XXX: require recursive merge to avoid loosing subkeys
435 //['subject' => $recoverMail['subject']]+$recoverMail['context']+$this->config['recover']['view']['context']
436 ->context(array_replace_recursive($this->config['recover']['view']['context'], $recoverMail['context'], ['subject' => $recoverMail['subject']]));
437
438 //Try sending message
439 //XXX: mail delivery may silently fail
440 try {
441 //Send message
442 $mailer->send($message);
443
444 //Redirect on the same route with sent=1 to cleanup form
445 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
446 //Catch obvious transport exception
447 } catch(TransportExceptionInterface $e) {
448 //Add error message mail unreachable
449 $form->get('mail')->addError(new FormError($this->translator->trans('Account found but unable to contact: %mail%', array('%mail%' => $data['mail']))));
450 }
451 //Accout not found
452 } else {
453 //Add error message to mail field
454 $form->get('mail')->addError(new FormError($this->translator->trans('Unable to find account %mail%', ['%mail%' => $data['mail']])));
455 }
456 }
457 }
458
459 //Render view
460 return $this->render(
461 //Template
462 $this->config['recover']['view']['name'],
463 //Context
464 ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['recover']['view']['context']
465 );
466 }
467
468 //With invalid hash
469 if ($hash != $slugger->hash($mail.$pass)) {
470 //Throw bad request
471 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
472 }
473
474 //Get mail
475 $mail = $slugger->unshort($smail = $mail);
476
477 //Without valid mail
478 if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) {
479 //Throw bad request
480 //XXX: prevent slugger reverse engineering by not displaying decoded mail
481 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
482 }
483
484 //With existing subscriber
485 if (empty($user = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail))) {
486 //Throw not found
487 //XXX: prevent slugger reverse engineering by not displaying decoded mail
488 throw $this->createNotFoundException($this->translator->trans('Unable to find account %mail%', ['%mail%' => $smail]));
489 }
490
491 //With unmatched pass
492 if ($pass != $slugger->hash($user->getPassword())) {
493 //Throw not found
494 //XXX: prevent use of outdated recover link
495 throw $this->createNotFoundException($this->translator->trans('Outdated recover link'));
496 }
497
498 //Create the LoginType form and give the proper parameters
499 $form = $this->createForm($this->config['recover']['view']['form'], $user, [
500 //Set action to recover route name and context
501 'action' => $this->generateUrl($this->config['route']['recover']['name'], ['mail' => $smail, 'pass' => $pass, 'hash' => $hash]+$this->config['route']['recover']['context']),
502 //Without mail
503 'mail' => false,
504 //Set method
505 'method' => 'POST'
506 ]);
507
508 if ($request->isMethod('POST')) {
509 //Refill the fields in case the form is not valid.
510 $form->handleRequest($request);
511
512 if ($form->isValid()) {
513 //Set data
514 $data = $form->getData();
515
516 //Set encoded password
517 $encoded = $encoder->encodePassword($user, $user->getPassword());
518
519 //Update pass
520 $pass = $slugger->hash($encoded);
521
522 //Set user password
523 $user->setPassword($encoded);
524
525 //Persist user
526 $manager->persist($user);
527
528 //Send to database
529 $manager->flush();
530
531 //Add notice
532 $this->addFlash('notice', $this->translator->trans('Account %mail% password updated', ['%mail%' => $mail]));
533
534 //Redirect to user login
535 return $this->redirectToRoute($this->config['route']['login']['name'], ['mail' => $smail, 'hash' => $slugger->hash($smail)]+$this->config['route']['login']['context']);
536 }
537 }
538
539 //Render view
540 return $this->render(
541 //Template
542 $this->config['recover']['view']['name'],
543 //Context
544 ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['recover']['view']['context']
545 );
546 }
547
548 /**
549 * Register an account
550 *
551 * @param Request $request The request
552 * @param Registry $manager The doctrine registry
553 * @param UserPasswordEncoderInterface $encoder The password encoder
554 * @param EntityManagerInterface $manager The doctrine entity manager
555 * @param SluggerUtil $slugger The slugger
556 * @param MailerInterface $mailer The mailer
557 * @param LoggerInterface $logger The logger
558 * @param string $mail The shorted mail address
559 * @param string $field The serialized then shorted form field array
560 * @param string $hash The hashed serialized field array
561 * @return Response The response
562 */
563 public function register(Request $request, Registry $doctrine, UserPasswordEncoderInterface $encoder, EntityManagerInterface $manager, SluggerUtil $slugger, MailerInterface $mailer, LoggerInterface $logger, $mail, $field, $hash): Response {
564 //With mail
565 if (!empty($_POST['register']['mail'])) {
566 //Log new user infos
567 $logger->emergency(
568 $this->translator->trans(
569 'register: mail=%mail% locale=%locale% confirm=%confirm%',
570 [
571 '%mail%' => $postMail = $_POST['register']['mail'],
572 '%locale%' => $request->getLocale(),
573 '%confirm%' => $this->get('router')->generate(
574 $this->config['route']['confirm']['name'],
575 //Prepend subscribe context with tag
576 [
577 'mail' => $postSmail = $slugger->short($postMail),
578 'hash' => $slugger->hash($postSmail)
579 ]+$this->config['route']['confirm']['context'],
580 UrlGeneratorInterface::ABSOLUTE_URL
581 )
582 ]
583 )
584 );
585 }
586
587 //With mail and field
588 if (!empty($field) && !empty($hash)) {
589 //With invalid hash
590 if ($hash != $slugger->hash($mail.$field)) {
591 //Throw bad request
592 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'hash', '%value%' => $hash]));
593 }
594
595 //With mail
596 if (!empty($mail)) {
597 //Get mail
598 $mail = $slugger->unshort($smail = $mail);
599
600 //Without valid mail
601 if (filter_var($mail, FILTER_VALIDATE_EMAIL) === false) {
602 //Throw bad request
603 //XXX: prevent slugger reverse engineering by not displaying decoded mail
604 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'mail', '%value%' => $smail]));
605 }
606
607 //With existing registrant
608 if ($existing = $doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail)) {
609 //With disabled existing
610 if ($existing->isDisabled()) {
611 //Render view
612 $response = $this->render(
613 //Template
614 $this->config['register']['view']['name'],
615 //Context
616 ['title' => $this->translator->trans('Access denied'), 'disabled' => 1]+$this->config['register']['view']['context']
617 );
618
619 //Set 403
620 $response->setStatusCode(403);
621
622 //Return response
623 return $response;
624 //With unactivated existing
625 } elseif (!$existing->isActivated()) {
626 //Set mail shortcut
627 $activateMail =& $this->config['register']['mail'];
628
629 //Generate each route route
630 foreach($this->config['register']['route'] as $route => $tag) {
631 //Only process defined routes
632 if (!empty($this->config['route'][$route])) {
633 //Process for confirm url
634 if ($route == 'confirm') {
635 //Set the url in context
636 $activateMail['context'][$tag] = $this->get('router')->generate(
637 $this->config['route'][$route]['name'],
638 //Prepend subscribe context with tag
639 [
640 'mail' => $smail = $slugger->short($existing->getMail()),
641 'hash' => $slugger->hash($smail)
642 ]+$this->config['route'][$route]['context'],
643 UrlGeneratorInterface::ABSOLUTE_URL
644 );
645 }
646 }
647 }
648
649 //Set recipient_name
650 $activateMail['context']['recipient_mail'] = $existing->getMail();
651
652 //Set recipient name
653 $activateMail['context']['recipient_name'] = implode(' ', [$existing->getForename(), $existing->getSurname(), $existing->getPseudonym()?'('.$existing->getPseudonym().')':'']);
654
655 //Init subject context
656 $subjectContext = $slugger->flatten(array_replace_recursive($this->config['register']['view']['context'], $activateMail['context']), null, '.', '%', '%');
657
658 //Translate subject
659 $activateMail['subject'] = ucfirst($this->translator->trans($activateMail['subject'], $subjectContext));
660
661 //Create message
662 $message = (new TemplatedEmail())
663 //Set sender
664 ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
665 //Set recipient
666 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
667 ->to(new Address($activateMail['context']['recipient_mail'], $activateMail['context']['recipient_name']))
668 //Set subject
669 ->subject($activateMail['subject'])
670
671 //Set path to twig templates
672 ->htmlTemplate($activateMail['html'])
673 ->textTemplate($activateMail['text'])
674
675 //Set context
676 ->context(['subject' => $activateMail['subject']]+$activateMail['context']);
677
678 //Try sending message
679 //XXX: mail delivery may silently fail
680 try {
681 //Send message
682 $mailer->send($message);
683 //Catch obvious transport exception
684 } catch(TransportExceptionInterface $e) {
685 //Add error message mail unreachable
686 $this->addFlash('error', $this->translator->trans('Account %mail% tried activate but unable to contact', ['%mail%' => $existing->getMail()]));
687 }
688
689 //Get route params
690 $routeParams = $request->get('_route_params');
691
692 //Remove mail, field and hash from route params
693 unset($routeParams['mail'], $routeParams['field'], $routeParams['hash']);
694
695 //Redirect on the same route with sent=1 to cleanup form
696 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$routeParams);
697 }
698
699 //Add error message mail already exists
700 $this->addFlash('warning', $this->translator->trans('Account %mail% already exists', ['%mail%' => $existing->getMail()]));
701
702 //Redirect to user view
703 return $this->redirectToRoute(
704 $this->config['route']['edit']['name'],
705 [
706 'mail' => $smail = $slugger->short($existing->getMail()),
707 'hash' => $slugger->hash($smail)
708 ]+$this->config['route']['edit']['context']
709 );
710 }
711 //Without mail
712 } else {
713 //Set smail
714 $smail = $mail;
715 }
716
717 //Try
718 try {
719 //Unshort then unserialize field
720 $field = $slugger->unserialize($sfield = $field);
721 //Catch type error
722 } catch (\Error|\Exception $e) {
723 //Throw bad request
724 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]), $e);
725 }
726
727 //With non array field
728 if (!is_array($field)) {
729 //Throw bad request
730 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]));
731 }
732 //Without field and hash
733 } else {
734 //Set smail
735 $smail = $mail;
736
737 //Set smail
738 $sfield = $field;
739
740 //Reset field
741 $field = [
742 //Without slug
743 'slug' => false
744 ];
745 }
746
747 //Init reflection
748 $reflection = new \ReflectionClass($this->config['class']['user']);
749
750 //Create new user
751 $user = $reflection->newInstance(strval($mail));
752
753 //Create the RegisterType form and give the proper parameters
754 $form = $this->createForm($this->config['register']['view']['form'], $user, $field+[
755 //Set action to register route name and context
756 'action' => $this->generateUrl($this->config['route']['register']['name'], ['mail' => $smail, 'field' => $sfield, 'hash' => $hash]+$this->config['route']['register']['context']),
757 //Set civility class
758 'civility_class' => $this->config['class']['civility'],
759 //Set civility default
760 'civility_default' => $doctrine->getRepository($this->config['class']['civility'])->findOneByTitle($this->config['default']['civility']),
761 //With mail
762 'mail' => true,
763 //Set method
764 'method' => 'POST'
765 ]);
766
767 if ($request->isMethod('POST')) {
768 //Refill the fields in case the form is not valid.
769 $form->handleRequest($request);
770
771 if ($form->isValid()) {
772 //Set data
773 $data = $form->getData();
774
775 //With existing registrant
776 if ($doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail = $data->getMail())) {
777 //Add error message mail already exists
778 $this->addFlash('warning', $this->translator->trans('Account %mail% already exists', ['%mail%' => $mail]));
779
780 //Redirect to user view
781 return $this->redirectToRoute(
782 $this->config['route']['edit']['name'],
783 [
784 'mail' => $smail = $slugger->short($mail),
785 'hash' => $slugger->hash($smail)
786 ]+$this->config['route']['edit']['context']
787 );
788 }
789
790 //Set mail shortcut
791 $registerMail =& $this->config['register']['mail'];
792
793 //Extract names and pseudonym from mail
794 $names = explode(' ', $pseudonym = ucwords(trim(preg_replace('/[^a-zA-Z]+/', ' ', current(explode('@', $data->getMail()))))));
795
796 //Set pseudonym
797 $user->setPseudonym($user->getPseudonym()??$pseudonym);
798
799 //Set forename
800 $user->setForename($user->getForename()??$names[0]);
801
802 //Set surname
803 $user->setSurname($user->getSurname()??$names[1]??$names[0]);
804
805 //Set password
806 $user->setPassword($encoder->encodePassword($user, $user->getPassword()??$data->getMail()));
807
808 //Set created
809 $user->setCreated(new \DateTime('now'));
810
811 //Set updated
812 $user->setUpdated(new \DateTime('now'));
813
814 //Persist user
815 $manager->persist($user);
816
817 //Iterate on default group
818 foreach($this->config['default']['group'] as $i => $groupTitle) {
819 //Fetch group
820 if (($group = $doctrine->getRepository($this->config['class']['group'])->findOneByTitle($groupTitle))) {
821 //Set default group
822 //XXX: see vendor/symfony/security-core/Role/Role.php
823 $user->addGroup($group);
824 //Group not found
825 } else {
826 //Throw exception
827 //XXX: consider missing group as fatal
828 throw new \Exception(sprintf('Group from rapsys_user.default.group[%d] not found by title: %s', $i, $groupTitle));
829 }
830 }
831
832 //Generate each route route
833 foreach($this->config['register']['route'] as $route => $tag) {
834 //Only process defined routes
835 if (!empty($this->config['route'][$route])) {
836 //Process for confirm url
837 if ($route == 'confirm') {
838 //Set the url in context
839 $registerMail['context'][$tag] = $this->get('router')->generate(
840 $this->config['route'][$route]['name'],
841 //Prepend subscribe context with tag
842 [
843 'mail' => $smail = $slugger->short($data->getMail()),
844 'hash' => $slugger->hash($smail)
845 ]+$this->config['route'][$route]['context'],
846 UrlGeneratorInterface::ABSOLUTE_URL
847 );
848 }
849 }
850 }
851
852 //XXX: DEBUG: remove me
853 //die($registerMail['context']['confirm_url']);
854
855 //Set recipient_name
856 $registerMail['context']['recipient_mail'] = $data->getMail();
857
858 //Set recipient name
859 $registerMail['context']['recipient_name'] = '';
860
861 //Set recipient name
862 $registerMail['context']['recipient_name'] = implode(' ', [$data->getForename(), $data->getSurname(), $data->getPseudonym()?'('.$data->getPseudonym().')':'']);
863
864 //Init subject context
865 $subjectContext = $slugger->flatten(array_replace_recursive($this->config['register']['view']['context'], $registerMail['context']), null, '.', '%', '%');
866
867 //Translate subject
868 $registerMail['subject'] = ucfirst($this->translator->trans($registerMail['subject'], $subjectContext));
869
870 //Create message
871 $message = (new TemplatedEmail())
872 //Set sender
873 ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
874 //Set recipient
875 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
876 ->to(new Address($registerMail['context']['recipient_mail'], $registerMail['context']['recipient_name']))
877 //Set subject
878 ->subject($registerMail['subject'])
879
880 //Set path to twig templates
881 ->htmlTemplate($registerMail['html'])
882 ->textTemplate($registerMail['text'])
883
884 //Set context
885 ->context(['subject' => $registerMail['subject']]+$registerMail['context']);
886
887 //Try saving in database
888 try {
889 //Send to database
890 $manager->flush();
891
892 //Add error message mail already exists
893 $this->addFlash('notice', $this->translator->trans('Your account has been created'));
894
895 //Try sending message
896 //XXX: mail delivery may silently fail
897 try {
898 //Send message
899 $mailer->send($message);
900
901 //Redirect on the same route with sent=1 to cleanup form
902 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
903 //Catch obvious transport exception
904 } catch(TransportExceptionInterface $e) {
905 //Add error message mail unreachable
906 $form->get('mail')->addError(new FormError($this->translator->trans('Account %mail% tried subscribe but unable to contact', ['%mail%' => $data->getMail()])));
907 }
908 //Catch double subscription
909 } catch (UniqueConstraintViolationException $e) {
910 //Add error message mail already exists
911 $this->addFlash('error', $this->translator->trans('Account %mail% already exists', ['%mail%' => $mail]));
912 }
913 }
914 }
915
916 //Render view
917 return $this->render(
918 //Template
919 $this->config['register']['view']['name'],
920 //Context
921 ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['register']['view']['context']
922 );
923 }
924 }