]> Raphaël G. Git Repositories - airbundle/blob - Controller/DefaultController.php
Fix token refresh, token cache save and cache path
[airbundle] / Controller / DefaultController.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys AirBundle 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\AirBundle\Controller;
13
14 use Symfony\Bridge\Twig\Mime\TemplatedEmail;
15 use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
16 use Symfony\Component\Filesystem\Filesystem;
17 use Symfony\Component\Form\FormError;
18 use Symfony\Component\HttpFoundation\Request;
19 use Symfony\Component\HttpFoundation\Response;
20 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
21 use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
22 use Symfony\Component\Mime\Address;
23 use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
24 use Symfony\Component\Security\Core\Exception\AccessDeniedException;
25
26 use Rapsys\AirBundle\Entity\Dance;
27 use Rapsys\AirBundle\Entity\Location;
28 use Rapsys\AirBundle\Entity\Session;
29 use Rapsys\AirBundle\Entity\Snippet;
30 use Rapsys\AirBundle\Entity\User;
31 use Rapsys\AirBundle\Pdf\DisputePdf;
32
33 /**
34 * {@inheritdoc}
35 */
36 class DefaultController extends AbstractController {
37 /**
38 * The about page
39 *
40 * @desc Display the about informations
41 *
42 * @param Request $request The request instance
43 * @return Response The rendered view
44 */
45 public function about(Request $request): Response {
46 //Set page
47 $this->context['title'] = $this->translator->trans('About');
48
49 //Set description
50 $this->context['description'] = $this->translator->trans('Libre Air about');
51
52 //Set keywords
53 $this->context['keywords'] = [
54 $this->translator->trans('about'),
55 $this->translator->trans('Libre Air')
56 ];
57
58 //Render template
59 $response = $this->render('@RapsysAir/default/about.html.twig', $this->context);
60 $response->setEtag(md5($response->getContent()));
61 $response->setPublic();
62 $response->isNotModified($request);
63
64 //Return response
65 return $response;
66 }
67
68 /**
69 * The contact page
70 *
71 * @desc Send a contact mail to configured contact
72 *
73 * @param Request $request The request instance
74 *
75 * @return Response The rendered view or redirection
76 */
77 public function contact(Request $request): Response {
78 //Set page
79 $this->context['title'] = $this->translator->trans('Contact');
80
81 //Set description
82 $this->context['description'] = $this->translator->trans('Contact Libre Air');
83
84 //Set keywords
85 $this->context['keywords'] = [
86 $this->translator->trans('contact'),
87 $this->translator->trans('Libre Air'),
88 $this->translator->trans('outdoor'),
89 $this->translator->trans('Argentine Tango'),
90 $this->translator->trans('calendar')
91 ];
92
93 //Set data
94 $data = [];
95
96 //With user
97 if ($user = $this->getUser()) {
98 //Set data
99 $data = [
100 'name' => $user->getRecipientName(),
101 'mail' => $user->getMail()
102 ];
103 }
104
105 //Create the form according to the FormType created previously.
106 //And give the proper parameters
107 $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', $data, [
108 'action' => $this->generateUrl('rapsys_air_contact'),
109 'method' => 'POST'
110 ]);
111
112 if ($request->isMethod('POST')) {
113 // Refill the fields in case the form is not valid.
114 $form->handleRequest($request);
115
116 if ($form->isSubmitted() && $form->isValid()) {
117 //Get data
118 $data = $form->getData();
119
120 //Create message
121 $message = (new TemplatedEmail())
122 //Set sender
123 ->from(new Address($data['mail'], $data['name']))
124 //Set recipient
125 ->to(new Address($this->context['contact']['mail'], $this->context['contact']['title']))
126 //Set subject
127 ->subject($data['subject'])
128
129 //Set path to twig templates
130 ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
131 ->textTemplate('@RapsysAir/mail/contact.text.twig')
132
133 //Set context
134 ->context(
135 [
136 'subject' => $data['subject'],
137 'message' => strip_tags($data['message']),
138 ]+$this->context
139 );
140
141 //Try sending message
142 //XXX: mail delivery may silently fail
143 try {
144 //Send message
145 $this->mailer->send($message);
146
147 //Redirect on the same route with sent=1 to cleanup form
148 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
149 //Catch obvious transport exception
150 } catch(TransportExceptionInterface $e) {
151 if ($message = $e->getMessage()) {
152 //Add error message mail unreachable
153 $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->context['contact']['mail'], '%message%' => $this->translator->trans($message)])));
154 } else {
155 //Add error message mail unreachable
156 $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->context['contact']['mail']])));
157 }
158 }
159 }
160 }
161
162 //Render template
163 return $this->render('@RapsysAir/form/contact.html.twig', ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->context);
164 }
165
166 /**
167 * The dispute page
168 *
169 * @desc Generate a dispute document
170 *
171 * @param Request $request The request instance
172 *
173 * @return Response The rendered view or redirection
174 */
175 public function dispute(Request $request): Response {
176 //Prevent non-guest to access here
177 $this->denyAccessUnlessGranted('ROLE_USER', null, $this->translator->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator->trans('User')]));
178
179 //Set page
180 $this->context['title'] = $this->translator->trans('Dispute');
181
182 //Set description
183 $this->context['description'] = $this->translator->trans('Libre Air dispute');
184
185 //Set keywords
186 $this->context['keywords'] = [
187 $this->translator->trans('dispute'),
188 $this->translator->trans('Libre Air'),
189 $this->translator->trans('outdoor'),
190 $this->translator->trans('Argentine Tango'),
191 $this->translator->trans('calendar')
192 ];
193
194 //Create the form according to the FormType created previously.
195 //And give the proper parameters
196 $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.'], [
197 'action' => $this->generateUrl('rapsys_air_dispute'),
198 'method' => 'POST'
199 ]);
200
201 if ($request->isMethod('POST')) {
202 // Refill the fields in case the form is not valid.
203 $form->handleRequest($request);
204
205 if ($form->isValid()) {
206 //Get data
207 $data = $form->getData();
208
209 //Gathering offense
210 if (!empty($data['offense']) && $data['offense'] == 'gathering') {
211 //Add gathering
212 $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());
213 //Traffic offense
214 } elseif (!empty($data['offense'] && $data['offense'] == 'traffic')) {
215 //Add traffic
216 $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());
217 //Unsupported offense
218 } else {
219 header('Content-Type: text/plain');
220 die('TODO');
221 exit;
222 }
223
224 //Send common headers
225 header('Content-Type: application/pdf');
226
227 //Send remaining headers
228 header('Cache-Control: private, max-age=0, must-revalidate');
229 header('Pragma: public');
230
231 //Send content-length
232 header('Content-Length: '.strlen($output));
233
234 //Display the pdf
235 echo $output;
236
237 //Die for now
238 exit;
239
240 # //Create message
241 # $message = (new TemplatedEmail())
242 # //Set sender
243 # ->from(new Address($data['mail'], $data['name']))
244 # //Set recipient
245 # //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
246 # ->to(new Address($this->config['contact']['mail'], $this->config['contact']['title']))
247 # //Set subject
248 # ->subject($data['subject'])
249 #
250 # //Set path to twig templates
251 # ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
252 # ->textTemplate('@RapsysAir/mail/contact.text.twig')
253 #
254 # //Set context
255 # ->context(
256 # [
257 # 'subject' => $data['subject'],
258 # 'message' => strip_tags($data['message']),
259 # ]+$this->context
260 # );
261 #
262 # //Try sending message
263 # //XXX: mail delivery may silently fail
264 # try {
265 # //Send message
266 # $this->mailer->send($message);
267 #
268 # //Redirect on the same route with sent=1 to cleanup form
269 # return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
270 # //Catch obvious transport exception
271 # } catch(TransportExceptionInterface $e) {
272 # if ($message = $e->getMessage()) {
273 # //Add error message mail unreachable
274 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->config['contact']['mail'], '%message%' => $this->translator->trans($message)])));
275 # } else {
276 # //Add error message mail unreachable
277 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->config['contact']['mail']])));
278 # }
279 # }
280 }
281 }
282
283 //Render template
284 return $this->render('@RapsysAir/default/dispute.html.twig', ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->context);
285 }
286
287 /**
288 * The index page
289 *
290 * Display session calendar
291 *
292 * @param Request $request The request instance
293 * @return Response The rendered view
294 */
295 public function index(Request $request): Response {
296 //Add cities
297 $this->context['cities'] = $this->doctrine->getRepository(Location::class)->findCitiesAsArray($this->period);
298
299 //Add calendar
300 $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'));
301
302 //Add dances
303 $this->context['dances'] = $this->doctrine->getRepository(Dance::class)->findNamesAsArray();
304
305 //Set modified
306 $this->modified = max(array_map(function ($v) { return $v['modified']; }, array_merge($this->context['calendar'], $this->context['cities'], $this->context['dances'])));
307
308 //Create response
309 $response = new Response();
310
311 //With logged user
312 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
313 //Set last modified
314 $response->setLastModified(new \DateTime('-1 year'));
315
316 //Set as private
317 $response->setPrivate();
318 //Without logged user
319 } else {
320 //Set etag
321 //XXX: only for public to force revalidation by last modified
322 $response->setEtag(md5(serialize(array_merge($this->context['calendar'], $this->context['cities'], $this->context['dances']))));
323
324 //Set last modified
325 $response->setLastModified($this->modified);
326
327 //Set as public
328 $response->setPublic();
329
330 //Without role and modification
331 if ($response->isNotModified($request)) {
332 //Return 304 response
333 return $response;
334 }
335 }
336
337 //With cities
338 if (!empty($this->context['cities'])) {
339 //Set locations
340 $locations = [];
341
342 //Iterate on each cities
343 foreach($this->context['cities'] as $city) {
344 //Iterate on each locations
345 foreach($city['locations'] as $location) {
346 //Add location
347 $locations[$location['id']] = $location;
348 }
349 }
350
351 //Add multi
352 $this->context['multimap'] = $this->map->getMultiMap($this->translator->trans('Libre Air cities sector map'), $this->modified->getTimestamp(), $locations);
353
354 //Set cities
355 $cities = array_map(function ($v) { return $v['in']; }, $this->context['cities']);
356
357 //Set dances
358 $dances = array_map(function ($v) { return $v['name']; }, $this->context['dances']);
359 } else {
360 //Set cities
361 $cities = [];
362
363 //Set dances
364 $dances = [];
365 }
366
367 //Set keywords
368 //TODO: use splice instead of that shit !!!
369 //TODO: handle smartly indoor and outdoor !!!
370 $this->context['keywords'] = array_values(
371 array_merge(
372 $dances,
373 $cities,
374 [
375 $this->translator->trans('indoor'),
376 $this->translator->trans('outdoor'),
377 $this->translator->trans('calendar'),
378 $this->translator->trans('Libre Air')
379 ]
380 )
381 );
382
383 //Get textual cities
384 $cities = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($cities, 0, -1))], array_slice($cities, -1)), 'strlen'));
385
386 //Get textual dances
387 $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
388
389 //Set title
390 $this->context['title'] = $this->translator->trans('%dances% %cities%', ['%dances%' => $dances, '%cities%' => $cities]);
391
392 //Set description
393 //TODO: handle french translation when city start with a A, change à in en !
394 $this->context['description'] = $this->translator->trans('%dances% indoor and outdoor calendar %cities%', ['%dances%' => $dances, '%cities%' => $cities]);
395
396 //Set facebook type
397 //XXX: only valid for home page
398 $this->context['facebook']['metas']['og:type'] = 'website';
399
400 //Render the view
401 return $this->render('@RapsysAir/default/index.html.twig', $this->context, $response);
402 }
403
404 /**
405 * The organizer regulation page
406 *
407 * @desc Display the organizer regulation policy
408 *
409 * @param Request $request The request instance
410 * @return Response The rendered view
411 */
412 public function organizerRegulation(Request $request): Response {
413 //Set page
414 $this->context['title'] = $this->translator->trans('Organizer regulation');
415
416 //Set description
417 $this->context['description'] = $this->translator->trans('Libre Air organizer regulation');
418
419 //Set keywords
420 $this->context['keywords'] = [
421 $this->translator->trans('organizer regulation'),
422 $this->translator->trans('Libre Air')
423 ];
424
425 //Render template
426 $response = $this->render('@RapsysAir/default/organizer_regulation.html.twig', $this->context);
427
428 //Set as cachable
429 $response->setEtag(md5($response->getContent()));
430 $response->setPublic();
431 $response->isNotModified($request);
432
433 //Return response
434 return $response;
435 }
436
437 /**
438 * The terms of service page
439 *
440 * @desc Display the terms of service policy
441 *
442 * @param Request $request The request instance
443 * @return Response The rendered view
444 */
445 public function termsOfService(Request $request): Response {
446 //Set page
447 $this->context['title'] = $this->translator->trans('Terms of service');
448
449 //Set description
450 $this->context['description'] = $this->translator->trans('Libre Air terms of service');
451
452 //Set keywords
453 $this->context['keywords'] = [
454 $this->translator->trans('terms of service'),
455 $this->translator->trans('Libre Air')
456 ];
457
458 //Render template
459 $response = $this->render('@RapsysAir/default/terms_of_service.html.twig', $this->context);
460
461 //Set as cachable
462 $response->setEtag(md5($response->getContent()));
463 $response->setPublic();
464 $response->isNotModified($request);
465
466 //Return response
467 return $response;
468 }
469
470 /**
471 * The frequently asked questions page
472 *
473 * @desc Display the frequently asked questions
474 *
475 * @param Request $request The request instance
476 * @return Response The rendered view
477 */
478 public function frequentlyAskedQuestions(Request $request): Response {
479 //Set page
480 $this->context['title'] = $this->translator->trans('Frequently asked questions');
481
482 //Set description
483 $this->context['description'] = $this->translator->trans('Libre Air frequently asked questions');
484
485 //Set keywords
486 $this->context['keywords'] = [
487 $this->translator->trans('frequently asked questions'),
488 $this->translator->trans('faq'),
489 $this->translator->trans('Libre Air')
490 ];
491
492 //Render template
493 $response = $this->render('@RapsysAir/default/frequently_asked_questions.html.twig', $this->context);
494
495 //Set as cachable
496 $response->setEtag(md5($response->getContent()));
497 $response->setPublic();
498 $response->isNotModified($request);
499
500 //Return response
501 return $response;
502 }
503
504 /**
505 * List all users
506 *
507 * @desc Display all user with a group listed as users
508 *
509 * @param Request $request The request instance
510 *
511 * @return Response The rendered view
512 */
513 public function userIndex(Request $request): Response {
514 //With admin role
515 if ($this->isGranted('ROLE_ADMIN')) {
516 //Set section
517 $section = $this->translator->trans('Libre Air users');
518
519 //Set description
520 $this->context['description'] = $this->translator->trans('Libre Air user list');
521 //Without admin role
522 } else {
523 //Set section
524 $section = $this->translator->trans('Libre Air organizers');
525
526 //Set description
527 $this->context['description'] = $this->translator->trans('Libre Air organizers list');
528 }
529
530 //Set keywords
531 $this->context['keywords'] = [
532 $this->translator->trans('users'),
533 $this->translator->trans('user list'),
534 $this->translator->trans('listing'),
535 $this->translator->trans('Libre Air')
536 ];
537
538 //Set title
539 $title = $this->translator->trans($this->config['site']['title']).' - '.$section;
540
541 //Fetch users
542 $users = $this->doctrine->getRepository(User::class)->findIndexByGroupId();
543
544 //With admin role
545 if ($this->isGranted('ROLE_ADMIN')) {
546 //Display all users
547 $this->context['groups'] = $users;
548 //Without admin role
549 } else {
550 //Only display senior organizers
551 $this->context['users'] = $users[$this->translator->trans('Senior')];
552 }
553
554 //Render the view
555 return $this->render('@RapsysAir/user/index.html.twig', ['title' => $title, 'section' => $section]+$this->context);
556 }
557
558 /**
559 * List all sessions for the user
560 *
561 * @desc Display all sessions for the user with an application or login form
562 *
563 * @param Request $request The request instance
564 * @param int $id The user id
565 *
566 * @return Response The rendered view
567 */
568 public function userView(Request $request, int $id, ?string $user): Response {
569 //Get user
570 if (empty($this->context['user'] = $this->doctrine->getRepository(User::class)->findOneByIdAsArray($id, $this->locale))) {
571 //Throw not found
572 throw new NotFoundHttpException($this->translator->trans('Unable to find user: %id%', ['%id%' => $id]));
573 }
574
575 //Create token
576 $token = new AnonymousToken('', $this->context['user']['mail'], $this->context['user']['roles']);
577
578 //Prevent access when not admin, user is not guest and not currently logged user
579 if (!($isAdmin = $this->isGranted('ROLE_ADMIN')) && !($isGuest = $this->decision->decide($token, ['ROLE_GUEST']))) {
580 //Throw access denied
581 throw new AccessDeniedException($this->translator->trans('Unable to access user: %id%', ['%id%' => $id]));
582 }
583
584 //With invalid user slug
585 if ($this->context['user']['slug'] !== $user) {
586 //Redirect to cleaned url
587 return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]);
588 }
589
590 //Fetch calendar
591 $this->context['calendar'] = $this->doctrine->getRepository(Session::class)->findAllByPeriodAsCalendarArray($this->period, !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'), null, null, $id);
592
593 //Get locations at less than 2 km
594 $this->context['locations'] = $this->doctrine->getRepository(Location::class)->findAllByUserIdAsArray($id, $this->period, 2);
595
596 //Set ats
597 $ats = [];
598
599 //Set dances
600 $dances = [];
601
602 //Set indoors
603 $indoors = [];
604
605 //Set ins
606 $ins = [];
607
608 //Set insides
609 $insides = [];
610
611 //Set locations
612 $locations = [];
613
614 //Set types
615 $types = [];
616
617 //Iterate on each calendar
618 foreach($this->context['calendar'] as $date => $calendar) {
619 //Iterate on each session
620 foreach($calendar['sessions'] as $sessionId => $session) {
621 //Add dance
622 $dances[$session['application']['dance']['name']] = $session['application']['dance']['name'];
623
624 //Add types
625 $types[$session['application']['dance']['type']] = lcfirst($session['application']['dance']['type']);
626
627 //Add indoors
628 $indoors[$session['location']['indoor']?'indoor':'outdoor'] = $this->translator->trans($session['location']['indoor']?'indoor':'outdoor');
629
630 //Add insides
631 $insides[$session['location']['indoor']?'inside':'outside'] = $this->translator->trans($session['location']['indoor']?'inside':'outside');
632
633 //Add ats
634 $ats[$session['location']['id']] = $session['location']['at'];
635
636 //Add ins
637 $ins[$session['location']['id']] = $session['location']['in'];
638
639 //Session with application user id
640 if (!empty($session['application']['user']['id']) && $session['application']['user']['id'] == $id) {
641 //Add location
642 $locations[$session['location']['id']] = $session['location'];
643 }
644 }
645 }
646
647 //Set modified
648 //XXX: dance modified is already computed inside calendar modified
649 $this->modified = max(array_merge([$this->context['user']['modified']], array_map(function ($v) { return $v['modified']; }, array_merge($this->context['calendar'], $this->context['locations']))));
650
651 //Create response
652 $response = new Response();
653
654 //With logged user
655 if ($this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
656 //Set last modified
657 $response->setLastModified(new \DateTime('-1 year'));
658
659 //Set as private
660 $response->setPrivate();
661 //Without logged user
662 } else {
663 //Set etag
664 //XXX: only for public to force revalidation by last modified
665 $response->setEtag(md5(serialize(array_merge($this->context['user'], $this->context['calendar'], $this->context['locations']))));
666
667 //Set last modified
668 $response->setLastModified($this->modified);
669
670 //Set as public
671 $response->setPublic();
672
673 //Without role and modification
674 if ($response->isNotModified($request)) {
675 //Return 304 response
676 return $response;
677 }
678 }
679
680 //Add multi map
681 $this->context['multimap'] = $this->map->getMultiMap($this->context['user']['multimap'], $this->modified->getTimestamp(), $this->context['locations']);
682
683 //Set keywords
684 $this->context['keywords'] = [
685 $this->context['user']['pseudonym'],
686 $this->translator->trans('calendar'),
687 $this->translator->trans('Libre Air')
688 ];
689
690 //Set cities
691 $cities = array_unique(array_map(function ($v) { return $v['city']; }, $locations));
692
693 //Set titles
694 $titles = array_map(function ($v) { return $v['title']; }, $locations);
695
696 //Insert dances in keywords
697 array_splice($this->context['keywords'], 1, 0, array_merge($types, $dances, $indoors, $insides, $titles, $cities));
698
699 //Deduplicate ins
700 $ins = array_unique($ins);
701
702 //Get textual dances
703 $dances = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($dances, 0, -1))], array_slice($dances, -1)), 'strlen'));
704
705 //Get textual types
706 $types = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($types, 0, -1))], array_slice($types, -1)), 'strlen'));
707
708 //Get textual indoors
709 $indoors = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($indoors, 0, -1))], array_slice($indoors, -1)), 'strlen'));
710
711 //Get textual ats
712 $ats = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($ats, 0, -1))], array_slice($ats, -1)), 'strlen'));
713
714 //Get textual ins
715 $ins = implode($this->translator->trans(' and '), array_filter(array_merge([implode(', ', array_slice($ins, 0, -1))], array_slice($ins, -1)), 'strlen'));
716
717 //Set title
718 $this->context['title'] = $this->translator->trans('%pseudonym% organizer', ['%pseudonym%' => $this->context['user']['pseudonym']]);
719
720 //With locations
721 if (!empty($locations)) {
722 //Set description
723 $this->context['description'] = ucfirst($this->translator->trans('%dances% %types% %indoors% calendar %ats% %ins% %pseudonym%', ['%dances%' => $dances, '%types%' => $types, '%indoors%' => $indoors, '%ats%' => $ats, '%ins%' => $ins, '%pseudonym%' => $this->translator->trans('by %pseudonym%', ['%pseudonym%' => $this->context['user']['pseudonym']])]));
724 //Without locations
725 } else {
726 //Set description
727 $this->context['description'] = $this->translator->trans('%pseudonym% calendar', ['%pseudonym%' => $this->context['user']['pseudonym']]);
728 }
729
730 //Set user description
731 $this->context['locations_description'] = $this->translator->trans('Libre Air %pseudonym% location list', ['%pseudonym%' => $this->translator->trans('by %pseudonym%', ['%pseudonym%' => $this->context['user']['pseudonym']])]);
732
733 //Set alternates
734 $this->context['alternates'] += $this->context['user']['alternates'];
735
736 //Create snippet forms for role_guest
737 //TODO: optimize this call
738 if ($isAdmin || $isGuest && $this->getUser() && $this->context['user']['id'] == $this->getUser()->getId()) {
739 //Fetch all user snippet
740 $snippets = $this->doctrine->getRepository(Snippet::class)->findByUserIdLocaleIndexByLocationId($id, $this->locale);
741
742 //Get user
743 $user = $this->doctrine->getRepository(User::class)->findOneById($id);
744
745 //Iterate on locations
746 foreach($this->context['locations'] as $locationId => $location) {
747 //With existing snippet
748 if (isset($snippets[$location['id']])) {
749 //Set existing in current
750 $current = $snippets[$location['id']];
751 //Without existing snippet
752 } else {
753 //Init snippet
754 $current = new Snippet();
755
756 //Set default locale
757 $current->setLocale($this->locale);
758
759 //Set default user
760 $current->setUser($user);
761
762 //Set default location
763 $current->setLocation($this->doctrine->getRepository(Location::class)->findOneById($location['id']));
764 }
765
766 //Create SnippetType form
767 $form = $this->factory->createNamed(
768 //Set form id
769 'snippet_'.$locationId.'_'.$id.'_'.$this->locale,
770 //Set form type
771 'Rapsys\AirBundle\Form\SnippetType',
772 //Set form data
773 $current
774 );
775
776 //Refill the fields in case of invalid form
777 $form->handleRequest($request);
778
779 //Handle submitted and valid form
780 //TODO: add a delete snippet ?
781 if ($form->isSubmitted() && $form->isValid()) {
782 //Get snippet
783 $snippet = $form->getData();
784
785 //Queue snippet save
786 $this->manager->persist($snippet);
787
788 //Flush to get the ids
789 $this->manager->flush();
790
791 //Add notice
792 $this->addFlash('notice', $this->translator->trans('Snippet for %user% %location% updated', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']]));
793
794 //Redirect to cleaned url
795 return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]);
796 }
797
798 //Add form to context
799 $this->context['forms']['snippets'][$locationId] = $form->createView();
800
801 //With location user source image
802 if (($isFile = is_file($source = $this->config['path'].'/location/'.$location['id'].'/'.$id.'.png')) && ($mtime = stat($source)['mtime'])) {
803 //Set location image
804 $this->context['locations'][$locationId]['image'] = $this->image->getThumb($location['miniature'], $mtime, $source);
805 }
806
807 //Create ImageType form
808 $form = $this->factory->createNamed(
809 //Set form id
810 'image_'.$locationId.'_'.$id,
811 //Set form type
812 'Rapsys\AirBundle\Form\ImageType',
813 //Set form data
814 [
815 //Set location
816 'location' => $location['id'],
817 //Set user
818 'user' => $id
819 ],
820 //Set form attributes
821 [
822 //Enable delete with image
823 'delete' => isset($this->context['locations'][$locationId]['image'])
824 ]
825 );
826
827 //Refill the fields in case of invalid form
828 $form->handleRequest($request);
829
830 //Handle submitted and valid form
831 if ($form->isSubmitted() && $form->isValid()) {
832 //With delete
833 if ($form->has('delete') && $form->get('delete')->isClicked()) {
834 //With source and mtime
835 if ($isFile && !empty($source) && !empty($mtime)) {
836 //Clear thumb
837 $this->image->remove($mtime, $source);
838
839 //Unlink file
840 unlink($this->config['path'].'/location/'.$location['id'].'/'.$id.'.png');
841
842 //Add notice
843 $this->addFlash('notice', $this->translator->trans('Image for %user% %location% deleted', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']]));
844
845 //Redirect to cleaned url
846 return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]);
847 }
848 }
849
850 //With image
851 if ($image = $form->get('image')->getData()) {
852 //Check source path
853 if (!is_dir($dir = dirname($source))) {
854 //Create filesystem object
855 $filesystem = new Filesystem();
856
857 try {
858 //Create dir
859 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
860 $filesystem->mkdir($dir, 0775);
861 } catch (IOExceptionInterface $e) {
862 //Throw error
863 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
864 }
865 }
866
867 //Set source
868 $source = realpath($dir).'/'.basename($source);
869
870 //Create imagick object
871 $imagick = new \Imagick();
872
873 //Read image
874 $imagick->readImage($image->getRealPath());
875
876 //Save image
877 if (!$imagick->writeImage($source)) {
878 //Throw error
879 throw new \Exception(sprintf('Unable to write image "%s"', $source));
880 }
881
882 //Add notice
883 $this->addFlash('notice', $this->translator->trans('Image for %user% %location% updated', ['%location%' => $location['at'], '%user%' => $this->context['user']['pseudonym']]));
884
885 //Redirect to cleaned url
886 return $this->redirectToRoute('rapsys_air_user_view', ['id' => $id, 'user' => $this->context['user']['slug']]);
887 }
888 }
889
890 //Add form to context
891 $this->context['forms']['images'][$locationId] = $form->createView();
892 }
893 }
894
895 //Render the view
896 return $this->render('@RapsysAir/user/view.html.twig', ['id' => $id]+$this->context);
897 }
898 }