1 <?php
declare(strict_types
=1);
4 * This file is part of the Rapsys AirBundle package.
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Rapsys\AirBundle\Controller
;
14 use Symfony\Bridge\Twig\Mime\TemplatedEmail
;
15 use Symfony\Component\Form\FormError
;
16 use Symfony\Component\HttpFoundation\Request
;
17 use Symfony\Component\HttpFoundation\Response
;
18 use Symfony\Component\Mailer\Exception\TransportExceptionInterface
;
19 use Symfony\Component\Mailer\MailerInterface
;
20 use Symfony\Component\Mime\Address
;
21 use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken
;
23 use Rapsys\AirBundle\Entity\Civility
;
24 use Rapsys\AirBundle\Entity\Location
;
25 use Rapsys\AirBundle\Entity\Session
;
26 use Rapsys\AirBundle\Entity\Snippet
;
27 use Rapsys\AirBundle\Entity\User
;
28 use Rapsys\AirBundle\Pdf\DisputePdf
;
33 class DefaultController
extends AbstractController
{
37 * @desc Display the about informations
39 * @param Request $request The request instance
40 * @return Response The rendered view
42 public function about(Request
$request): Response
{
44 $this->context
['title'] = $this->translator
->trans('About');
47 $this->context
['description'] = $this->translator
->trans('Libre Air about');
50 $this->context
['keywords'] = [
51 $this->translator
->trans('about'),
52 $this->translator
->trans('Libre Air')
56 $response = $this->render('@RapsysAir/default/about.html.twig', $this->context
);
57 $response->setEtag(md5($response->getContent()));
58 $response->setPublic();
59 $response->isNotModified($request);
68 * @desc Send a contact mail to configured contact
70 * @param Request $request The request instance
71 * @param MailerInterface $mailer The mailer instance
73 * @return Response The rendered view or redirection
75 public function contact(Request
$request, MailerInterface
$mailer): Response
{
77 $this->context
['title'] = $this->translator
->trans('Contact');
80 $this->context
['description'] = $this->translator
->trans('Contact Libre Air');
83 $this->context
['keywords'] = [
84 $this->translator
->trans('contact'),
85 $this->translator
->trans('Libre Air'),
86 $this->translator
->trans('outdoor'),
87 $this->translator
->trans('Argentine Tango'),
88 $this->translator
->trans('calendar')
91 //Create the form according to the FormType created previously.
92 //And give the proper parameters
93 $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', null, [
94 'action' => $this->generateUrl('rapsys_air_contact'),
98 if ($request->isMethod('POST')) {
99 // Refill the fields in case the form is not valid.
100 $form->handleRequest($request);
102 if ($form->isValid()) {
104 $data = $form->getData();
107 $message = (new TemplatedEmail())
109 ->from(new Address($data['mail'], $data['name']))
111 ->to(new Address($this->context
['contact']['mail'], $this->context
['contact']['title']))
113 ->subject($data['subject'])
115 //Set path to twig templates
116 ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
117 ->textTemplate('@RapsysAir/mail/contact.text.twig')
122 'subject' => $data['subject'],
123 'message' => strip_tags($data['message']),
127 //Try sending message
128 //XXX: mail delivery may silently fail
131 $mailer->send($message);
133 //Redirect on the same route with sent=1 to cleanup form
134 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params'));
135 //Catch obvious transport exception
136 } catch(TransportExceptionInterface
$e) {
137 if ($message = $e->getMessage()) {
138 //Add error message mail unreachable
139 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->context
['contact']['mail'], '%message%' => $this->translator
->trans($message)])));
141 //Add error message mail unreachable
142 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to contact: %mail%', ['%mail%' => $this->context
['contact']['mail']])));
149 return $this->render('@RapsysAir/form/contact.html.twig', ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
);
155 * @desc Generate a dispute document
157 * @param Request $request The request instance
158 * @param MailerInterface $mailer The mailer instance
160 * @return Response The rendered view or redirection
162 public function dispute(Request
$request, MailerInterface
$mailer): Response
{
163 //Prevent non-guest to access here
164 $this->denyAccessUnlessGranted('ROLE_USER', null, $this->translator
->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator
->trans('User')]));
167 $this->context
['title'] = $this->translator
->trans('Dispute');
170 $this->context
['description'] = $this->translator
->trans('Libre Air dispute');
173 $this->context
['keywords'] = [
174 $this->translator
->trans('dispute'),
175 $this->translator
->trans('Libre Air'),
176 $this->translator
->trans('outdoor'),
177 $this->translator
->trans('Argentine Tango'),
178 $this->translator
->trans('calendar')
181 //Create the form according to the FormType created previously.
182 //And give the proper parameters
183 $form = $this->createForm('Rapsys\AirBundle\Form\DisputeType', ['court' => 'Paris', 'abstract' => 'Pour constater cette prétendue infraction, les agents verbalisateurs ont pénétré dans un jardin privatif, sans visibilité depuis la voie publique, situé derrière un batiment privé, pour ce faire ils ont franchi au moins un grillage de chantier ou des potteaux métalliques séparant le terrain privé de la voie publique de l\'autre côté du batiment.'], [
184 'action' => $this->generateUrl('rapsys_air_dispute'),
188 if ($request->isMethod('POST')) {
189 // Refill the fields in case the form is not valid.
190 $form->handleRequest($request);
192 if ($form->isValid()) {
194 $data = $form->getData();
197 if (!empty($data['offense']) && $data['offense'] == 'gathering') {
199 $output = DisputePdf
::genGathering($data['court'], $data['notice'], $data['agent'], $data['service'], $data['abstract'], $this->translator
->trans($this->getUser()->getCivility()->getTitle()), $this->getUser()->getForename(), $this->getUser()->getSurname());
201 } elseif (!empty($data['offense'] && $data['offense'] == 'traffic')) {
203 $output = DisputePdf
::genTraffic($data['court'], $data['notice'], $data['agent'], $data['service'], $data['abstract'], $this->translator
->trans($this->getUser()->getCivility()->getTitle()), $this->getUser()->getForename(), $this->getUser()->getSurname());
204 //Unsupported offense
206 header('Content-Type: text/plain');
211 //Send common headers
212 header('Content-Type: application/pdf');
214 //Send remaining headers
215 header('Cache-Control: private, max-age=0, must-revalidate');
216 header('Pragma: public');
218 //Send content-length
219 header('Content-Length: '.strlen($output));
228 # $message = (new TemplatedEmail())
230 # ->from(new Address($data['mail'], $data['name']))
232 # //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
233 # ->to(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
235 # ->subject($data['subject'])
237 # //Set path to twig templates
238 # ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
239 # ->textTemplate('@RapsysAir/mail/contact.text.twig')
244 # 'subject' => $data['subject'],
245 # 'message' => strip_tags($data['message']),
249 # //Try sending message
250 # //XXX: mail delivery may silently fail
253 # $mailer->send($message);
255 # //Redirect on the same route with sent=1 to cleanup form
256 # return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
257 # //Catch obvious transport exception
258 # } catch(TransportExceptionInterface $e) {
259 # if ($message = $e->getMessage()) {
260 # //Add error message mail unreachable
261 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->config['contact']['mail'], '%message%' => $this->translator->trans($message)])));
263 # //Add error message mail unreachable
264 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->config['contact']['mail']])));
271 return $this->render('@RapsysAir/default/dispute.html.twig', ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
);
277 * @desc Display all granted sessions with an application or login form
279 * @param Request $request The request instance
280 * @return Response The rendered view
282 public function index(Request
$request): Response
{
284 $doctrine = $this->getDoctrine();
287 $this->context
['title'] = $this->translator
->trans('Argentine Tango in Paris');
290 $this->context
['description'] = $this->translator
->trans('Outdoor Argentine Tango session calendar in Paris');
293 $this->context
['keywords'] = [
294 $this->translator
->trans('Argentine Tango'),
295 $this->translator
->trans('Paris'),
296 $this->translator
->trans('outdoor'),
297 $this->translator
->trans('calendar'),
298 $this->translator
->trans('Libre Air')
302 //XXX: only valid for home page
303 $this->context
['facebook']['metas']['og:type'] = 'website';
306 $period = new \
DatePeriod(
307 //Start from first monday of week
308 new \
DateTime('Monday this week'),
309 //Iterate on each day
310 new \
DateInterval('P1D'),
311 //End with next sunday and 4 weeks
313 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week'
318 $calendar = $doctrine->getRepository(Session
::class)->fetchCalendarByDatePeriod($this->translator
, $period, null, $request->get('session'), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), $request->getLocale());
321 //XXX: we want to display all active locations anyway
322 $locations = $doctrine->getRepository(Location
::class)->findTranslatedSortedByPeriod($this->translator
, $period);
325 return $this->render('@RapsysAir/default/index.html.twig', ['calendar' => $calendar, 'locations' => $locations]+
$this->context
);
327 //Set Cache-Control must-revalidate directive
328 //TODO: add a javascript forced refresh after 1h ? or header refresh ?
329 #$response->setPublic(true);
330 #$response->setMaxAge(300);
331 #$response->mustRevalidate();
332 ##$response->setCache(['public' => true, 'max_age' => 300]);
334 //Return the response
339 * The organizer regulation page
341 * @desc Display the organizer regulation policy
343 * @param Request $request The request instance
344 * @return Response The rendered view
346 public function organizerRegulation(Request
$request): Response
{
348 $this->context
['title'] = $this->translator
->trans('Organizer regulation');
351 $this->context
['description'] = $this->translator
->trans('Libre Air organizer regulation');
354 $this->context
['keywords'] = [
355 $this->translator
->trans('organizer regulation'),
356 $this->translator
->trans('Libre Air')
360 $response = $this->render('@RapsysAir/default/organizer_regulation.html.twig', $this->context
);
363 $response->setEtag(md5($response->getContent()));
364 $response->setPublic();
365 $response->isNotModified($request);
372 * The terms of service page
374 * @desc Display the terms of service policy
376 * @param Request $request The request instance
377 * @return Response The rendered view
379 public function termsOfService(Request
$request): Response
{
381 $this->context
['title'] = $this->translator
->trans('Terms of service');
384 $this->context
['description'] = $this->translator
->trans('Libre Air terms of service');
387 $this->context
['keywords'] = [
388 $this->translator
->trans('terms of service'),
389 $this->translator
->trans('Libre Air')
393 $response = $this->render('@RapsysAir/default/terms_of_service.html.twig', $this->context
);
396 $response->setEtag(md5($response->getContent()));
397 $response->setPublic();
398 $response->isNotModified($request);
405 * The frequently asked questions page
407 * @desc Display the frequently asked questions
409 * @param Request $request The request instance
410 * @return Response The rendered view
412 public function frequentlyAskedQuestions(Request
$request): Response
{
414 $this->context
['title'] = $this->translator
->trans('Frequently asked questions');
417 $this->context
['description'] = $this->translator
->trans('Libre Air frequently asked questions');
420 $this->context
['keywords'] = [
421 $this->translator
->trans('frequently asked questions'),
422 $this->translator
->trans('faq'),
423 $this->translator
->trans('Libre Air')
427 $response = $this->render('@RapsysAir/default/frequently_asked_questions.html.twig', $this->context
);
430 $response->setEtag(md5($response->getContent()));
431 $response->setPublic();
432 $response->isNotModified($request);
441 * @desc Display all user with a group listed as users
443 * @param Request $request The request instance
445 * @return Response The rendered view
447 public function userIndex(Request
$request): Response
{
449 $doctrine = $this->getDoctrine();
452 if ($this->isGranted('ROLE_ADMIN')) {
454 $section = $this->translator
->trans('Libre Air users');
457 $this->context
['description'] = $this->translator
->trans('Libre Air user list');
461 $section = $this->translator
->trans('Libre Air organizers');
464 $this->context
['description'] = $this->translator
->trans('Libre Air organizers list');
468 $this->context
['keywords'] = [
469 $this->translator
->trans('users'),
470 $this->translator
->trans('user list'),
471 $this->translator
->trans('listing'),
472 $this->translator
->trans('Libre Air')
476 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section;
479 $users = $doctrine->getRepository(User
::class)->findUserGroupedByTranslatedGroup($this->translator
);
482 $period = new \
DatePeriod(
483 //Start from first monday of week
484 new \
DateTime('Monday this week'),
485 //Iterate on each day
486 new \
DateInterval('P1D'),
487 //End with next sunday and 4 weeks
489 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week'
494 if ($this->isGranted('ROLE_ADMIN')) {
496 $this->context
['groups'] = $users;
499 //Only display senior organizers
500 $this->context
['users'] = $users[$this->translator
->trans('Senior')];
504 //XXX: we want to display all active locations anyway
505 $locations = $doctrine->getRepository(Location
::class)->findTranslatedSortedByPeriod($this->translator
, $period);
508 return $this->render('@RapsysAir/user/index.html.twig', ['title' => $title, 'section' => $section, 'locations' => $locations]+
$this->context
);
512 * List all sessions for the user
514 * @desc Display all sessions for the user with an application or login form
516 * @param Request $request The request instance
517 * @param int $id The user id
519 * @return Response The rendered view
521 public function userView(Request
$request, $id): Response
{
523 $doctrine = $this->getDoctrine();
526 if (empty($user = $doctrine->getRepository(User
::class)->findOneById($id))) {
527 throw $this->createNotFoundException($this->translator
->trans('Unable to find user: %id%', ['%id%' => $id]));
531 $token = new UsernamePasswordToken($user, null, 'none', $user->getRoles());
534 $isGuest = $this->get('rapsys_user.access_decision_manager')->decide($token, ['ROLE_GUEST']);
536 //Prevent access when not admin, user is not guest and not currently logged user
537 if (!$this->isGranted('ROLE_ADMIN') && empty($isGuest) && $user != $this->getUser()) {
538 throw $this->createAccessDeniedException($this->translator
->trans('Unable to access user: %id%', ['%id%' => $id]));
542 $section = $user->getPseudonym();
545 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section;
548 $this->context
['description'] = $this->translator
->trans('%pseudonym% outdoor Argentine Tango session calendar', [ '%pseudonym%' => $user->getPseudonym() ]);
551 $this->context
['keywords'] = [
552 $user->getPseudonym(),
553 $this->translator
->trans('outdoor'),
554 $this->translator
->trans('Argentine Tango'),
555 $this->translator
->trans('calendar')
559 $period = new \
DatePeriod(
560 //Start from first monday of week
561 new \
DateTime('Monday this week'),
562 //Iterate on each day
563 new \
DateInterval('P1D'),
564 //End with next sunday and 4 weeks
566 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week'
571 //TODO: highlight with current session route parameter
572 $calendar = $doctrine->getRepository(Session
::class)->fetchUserCalendarByDatePeriod($this->translator
, $period, $isGuest?$id:null, $request->get('session'), $request->getLocale());
575 //XXX: we want to display all active locations anyway
576 $locations = $doctrine->getRepository(Location
::class)->findTranslatedSortedByPeriod($this->translator
, $period, $id);
578 //Create user form for admin or current user
579 if ($this->isGranted('ROLE_ADMIN') || $user == $this->getUser()) {
580 //Create SnippetType form
581 $userForm = $this->createForm('Rapsys\AirBundle\Form\RegisterType', $user, [
583 'action' => $this->generateUrl('rapsys_air_user_view', ['id' => $id]),
584 //Set the form attribute
585 'attr' => [ 'class' => 'col' ],
587 'civility_class' => Civility
::class,
589 'mail' => $this->isGranted('ROLE_ADMIN'),
594 //Init user to context
595 $this->context
['forms']['user'] = $userForm->createView();
598 if ($request->isMethod('POST')) {
599 //Refill the fields in case the form is not valid.
600 $userForm->handleRequest($request);
602 //Handle invalid form
603 if (!$userForm->isSubmitted() || !$userForm->isValid()) {
605 return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id, 'title' => $title, 'section' => $section, 'calendar' => $calendar, 'locations' => $locations]+
$this->context
);
609 $data = $userForm->getData();
612 $manager = $doctrine->getManager();
615 $manager->persist($data);
617 //Flush to get the ids
621 $this->addFlash('notice', $this->translator
->trans('User %id% updated', ['%id%' => $id]));
623 //Extract and process referer
624 if ($referer = $request->headers
->get('referer')) {
625 //Create referer request instance
626 $req = Request
::create($referer);
629 $path = $req->getPathInfo();
631 //Get referer query string
632 $query = $req->getQueryString();
635 $path = str_replace($request->getScriptName(), '', $path);
637 //Try with referer path
640 $oldContext = $this->router
->getContext();
642 //Force clean context
643 //XXX: prevent MethodNotAllowedException because current context method is POST in onevendor/symfony/routing/Matcher/Dumper/CompiledUrlMatcherTrait.php+42
644 $this->router
->setContext(new RequestContext());
646 //Retrieve route matching path
647 $route = $this->router
->match($path);
650 $this->router
->setContext($oldContext);
656 $name = $route['_route'];
658 //Remove route and controller from route defaults
659 unset($route['_route'], $route['_controller']);
661 //Check if user view route
662 if ($name == 'rapsys_air_user_view' && !empty($route['id'])) {
664 $route['id'] = $data->getId();
668 $route['user'] = $data->getId();
672 return $this->redirectToRoute($name, $route);
674 } catch(MethodNotAllowedException
|ResourceNotFoundException
$e) {
675 //Unset referer to fallback to default route
680 //Redirect to cleanup the form
681 return $this->redirectToRoute('rapsys_air', ['user' => $data->getId()]);
685 //Create snippet forms for role_guest
686 if ($this->isGranted('ROLE_ADMIN') || ($this->isGranted('ROLE_GUEST') && $user == $this->getUser())) {
687 //Fetch all user snippet
688 $snippets = $doctrine->getRepository(Snippet
::class)->findByLocaleUserId($request->getLocale(), $id);
690 //Rekey by location id
691 $snippets = array_reduce($snippets, function($carry, $item){$carry
[$item
->getLocation()->getId()] = $item
; return $carry
;}, []);
693 //Init snippets to context
694 $this->context
['forms']['snippets'] = [];
696 //Iterate on locations
697 foreach($locations as $locationId => $location) {
699 $snippet = new Snippet();
702 $snippet->setLocale($request->getLocale());
705 $snippet->setUser($user);
707 //Set default location
708 $snippet->setLocation($doctrine->getRepository(Location
::class)->findOneById($locationId));
710 //With existing snippet
711 if (!empty($snippets[$locationId])) {
712 $snippet = $snippets[$locationId];
713 $action = $this->generateUrl('rapsys_air_snippet_edit', ['id' => $snippet->getId()]);
716 $action = $this->generateUrl('rapsys_air_snippet_add', ['location' => $locationId]);
719 //Create SnippetType form
720 $form = $this->container
->get('form.factory')->createNamed('snipped_'.$request->getLocale().'_'.$locationId, 'Rapsys\AirBundle\Form\SnippetType', $snippet, [
723 //Set the form attribute
727 //Add form to context
728 $this->context
['forms']['snippets'][$locationId] = $form->createView();
733 return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id, 'title' => $title, 'section' => $section, 'calendar' => $calendar, 'locations' => $locations]+
$this->context
);