]> Raphaël G. Git Repositories - userbundle/blob - Controller/DefaultController.php
Add slug
[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 return $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 //Set 403
618 new Response('', 403)
619 );
620 //With unactivated existing
621 } elseif (!$existing->isActivated()) {
622 //Set mail shortcut
623 //TODO: change for activate ???
624 $activateMail =& $this->config['register']['mail'];
625
626 //Generate each route route
627 foreach($this->config['register']['route'] as $route => $tag) {
628 //Only process defined routes
629 if (!empty($this->config['route'][$route])) {
630 //Process for confirm url
631 if ($route == 'confirm') {
632 //Set the url in context
633 $activateMail['context'][$tag] = $this->get('router')->generate(
634 $this->config['route'][$route]['name'],
635 //Prepend subscribe context with tag
636 [
637 'mail' => $smail = $slugger->short($existing->getMail()),
638 'hash' => $slugger->hash($smail)
639 ]+$this->config['route'][$route]['context'],
640 UrlGeneratorInterface::ABSOLUTE_URL
641 );
642 }
643 }
644 }
645
646 //Set recipient_name
647 $activateMail['context']['recipient_mail'] = $existing->getMail();
648
649 //Set recipient name
650 $activateMail['context']['recipient_name'] = implode(' ', [$existing->getForename(), $existing->getSurname(), $existing->getPseudonym()?'('.$existing->getPseudonym().')':'']);
651
652 //Init subject context
653 $subjectContext = $slugger->flatten(array_replace_recursive($this->config['register']['view']['context'], $activateMail['context']), null, '.', '%', '%');
654
655 //Translate subject
656 $activateMail['subject'] = ucfirst($this->translator->trans($activateMail['subject'], $subjectContext));
657
658 //Create message
659 $message = (new TemplatedEmail())
660 //Set sender
661 ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
662 //Set recipient
663 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
664 ->to(new Address($activateMail['context']['recipient_mail'], $activateMail['context']['recipient_name']))
665 //Set subject
666 ->subject($activateMail['subject'])
667
668 //Set path to twig templates
669 ->htmlTemplate($activateMail['html'])
670 ->textTemplate($activateMail['text'])
671
672 //Set context
673 ->context(['subject' => $activateMail['subject']]+$activateMail['context']);
674
675 //Try sending message
676 //XXX: mail delivery may silently fail
677 try {
678 //Send message
679 $mailer->send($message);
680 //Catch obvious transport exception
681 } catch(TransportExceptionInterface $e) {
682 //Add error message mail unreachable
683 $this->addFlash('error', $this->translator->trans('Account %mail% tried activate but unable to contact', ['%mail%' => $existing->getMail()]));
684 }
685
686 //Get route params
687 $routeParams = $request->get('_route_params');
688
689 //Remove mail, field and hash from route params
690 unset($routeParams['mail'], $routeParams['field'], $routeParams['hash']);
691
692 //Redirect on the same route with sent=1 to cleanup form
693 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$routeParams);
694 }
695
696 //Add error message mail already exists
697 $this->addFlash('warning', $this->translator->trans('Account %mail% already exists', ['%mail%' => $existing->getMail()]));
698
699 //Redirect to user view
700 return $this->redirectToRoute(
701 $this->config['route']['edit']['name'],
702 [
703 'mail' => $smail = $slugger->short($existing->getMail()),
704 'hash' => $slugger->hash($smail)
705 ]+$this->config['route']['edit']['context']
706 );
707 }
708 //Without mail
709 } else {
710 //Set smail
711 $smail = $mail;
712 }
713
714 //Try
715 try {
716 //Unshort then unserialize field
717 $field = $slugger->unserialize($sfield = $field);
718 //Catch type error
719 } catch (\Error|\Exception $e) {
720 //Throw bad request
721 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]), $e);
722 }
723
724 //With non array field
725 if (!is_array($field)) {
726 //Throw bad request
727 throw new BadRequestHttpException($this->translator->trans('Invalid %field% field: %value%', ['%field%' => 'field', '%value%' => $field]));
728 }
729 //Without field and hash
730 } else {
731 //Set smail
732 $smail = $mail;
733
734 //Set smail
735 $sfield = $field;
736
737 //Reset field
738 $field = [
739 //Without slug
740 'slug' => false
741 ];
742 }
743
744 //Init reflection
745 $reflection = new \ReflectionClass($this->config['class']['user']);
746
747 //Create new user
748 $user = $reflection->newInstance(strval($mail));
749
750 //Create the RegisterType form and give the proper parameters
751 $form = $this->createForm($this->config['register']['view']['form'], $user, $field+[
752 //Set action to register route name and context
753 'action' => $this->generateUrl($this->config['route']['register']['name'], ['mail' => $smail, 'field' => $sfield, 'hash' => $hash]+$this->config['route']['register']['context']),
754 //Set civility class
755 'civility_class' => $this->config['class']['civility'],
756 //Set civility default
757 'civility_default' => $doctrine->getRepository($this->config['class']['civility'])->findOneByTitle($this->config['default']['civility']),
758 //With mail
759 'mail' => true,
760 //Set method
761 'method' => 'POST'
762 ]);
763
764 if ($request->isMethod('POST')) {
765 //Refill the fields in case the form is not valid.
766 $form->handleRequest($request);
767
768 if ($form->isValid()) {
769 //Set data
770 $data = $form->getData();
771
772 //With existing registrant
773 if ($doctrine->getRepository($this->config['class']['user'])->findOneByMail($mail = $data->getMail())) {
774 //Add error message mail already exists
775 $this->addFlash('warning', $this->translator->trans('Account %mail% already exists', ['%mail%' => $mail]));
776
777 //Redirect to user view
778 return $this->redirectToRoute(
779 $this->config['route']['edit']['name'],
780 [
781 'mail' => $smail = $slugger->short($mail),
782 'hash' => $slugger->hash($smail)
783 ]+$this->config['route']['edit']['context']
784 );
785 }
786
787 //Set mail shortcut
788 $registerMail =& $this->config['register']['mail'];
789
790 //Extract names and pseudonym from mail
791 $names = explode(' ', $pseudonym = ucwords(trim(preg_replace('/[^a-zA-Z]+/', ' ', current(explode('@', $data->getMail()))))));
792
793 //Set pseudonym
794 $user->setPseudonym($user->getPseudonym()??$pseudonym);
795
796 //Set forename
797 $user->setForename($user->getForename()??$names[0]);
798
799 //Set surname
800 $user->setSurname($user->getSurname()??$names[1]??$names[0]);
801
802 //Set password
803 $user->setPassword($encoder->encodePassword($user, $user->getPassword()??$data->getMail()));
804
805 //Set created
806 $user->setCreated(new \DateTime('now'));
807
808 //Set updated
809 $user->setUpdated(new \DateTime('now'));
810
811 //Persist user
812 $manager->persist($user);
813
814 //Iterate on default group
815 foreach($this->config['default']['group'] as $i => $groupTitle) {
816 //Fetch group
817 if (($group = $doctrine->getRepository($this->config['class']['group'])->findOneByTitle($groupTitle))) {
818 //Set default group
819 //XXX: see vendor/symfony/security-core/Role/Role.php
820 $user->addGroup($group);
821 //Group not found
822 } else {
823 //Throw exception
824 //XXX: consider missing group as fatal
825 throw new \Exception(sprintf('Group from rapsys_user.default.group[%d] not found by title: %s', $i, $groupTitle));
826 }
827 }
828
829 //Generate each route route
830 foreach($this->config['register']['route'] as $route => $tag) {
831 //Only process defined routes
832 if (!empty($this->config['route'][$route])) {
833 //Process for confirm url
834 if ($route == 'confirm') {
835 //Set the url in context
836 $registerMail['context'][$tag] = $this->get('router')->generate(
837 $this->config['route'][$route]['name'],
838 //Prepend subscribe context with tag
839 [
840 'mail' => $smail = $slugger->short($data->getMail()),
841 'hash' => $slugger->hash($smail)
842 ]+$this->config['route'][$route]['context'],
843 UrlGeneratorInterface::ABSOLUTE_URL
844 );
845 }
846 }
847 }
848
849 //XXX: DEBUG: remove me
850 //die($registerMail['context']['confirm_url']);
851
852 //Set recipient_name
853 $registerMail['context']['recipient_mail'] = $data->getMail();
854
855 //Set recipient name
856 $registerMail['context']['recipient_name'] = '';
857
858 //Set recipient name
859 $registerMail['context']['recipient_name'] = implode(' ', [$data->getForename(), $data->getSurname(), $data->getPseudonym()?'('.$data->getPseudonym().')':'']);
860
861 //Init subject context
862 $subjectContext = $slugger->flatten(array_replace_recursive($this->config['register']['view']['context'], $registerMail['context']), null, '.', '%', '%');
863
864 //Translate subject
865 $registerMail['subject'] = ucfirst($this->translator->trans($registerMail['subject'], $subjectContext));
866
867 //Create message
868 $message = (new TemplatedEmail())
869 //Set sender
870 ->from(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
871 //Set recipient
872 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
873 ->to(new Address($registerMail['context']['recipient_mail'], $registerMail['context']['recipient_name']))
874 //Set subject
875 ->subject($registerMail['subject'])
876
877 //Set path to twig templates
878 ->htmlTemplate($registerMail['html'])
879 ->textTemplate($registerMail['text'])
880
881 //Set context
882 ->context(['subject' => $registerMail['subject']]+$registerMail['context']);
883
884 //Try saving in database
885 try {
886 //Send to database
887 $manager->flush();
888
889 //Add error message mail already exists
890 $this->addFlash('notice', $this->translator->trans('Your account has been created'));
891
892 //Try sending message
893 //XXX: mail delivery may silently fail
894 try {
895 //Send message
896 $mailer->send($message);
897
898 //Redirect on the same route with sent=1 to cleanup form
899 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
900 //Catch obvious transport exception
901 } catch(TransportExceptionInterface $e) {
902 //Add error message mail unreachable
903 $form->get('mail')->addError(new FormError($this->translator->trans('Account %mail% tried subscribe but unable to contact', ['%mail%' => $data->getMail()])));
904 }
905 //Catch double subscription
906 } catch (UniqueConstraintViolationException $e) {
907 //Add error message mail already exists
908 $this->addFlash('error', $this->translator->trans('Account %mail% already exists', ['%mail%' => $mail]));
909 }
910 }
911 }
912
913 //Render view
914 return $this->render(
915 //Template
916 $this->config['register']['view']['name'],
917 //Context
918 ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->config['register']['view']['context']
919 );
920 }
921 }