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