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