]> Raphaƫl G. Git Repositories - airbundle/blob - Controller/DefaultController.php
Switch to full locales
[airbundle] / Controller / DefaultController.php
1 <?php
2
3 namespace Rapsys\AirBundle\Controller;
4
5 use Symfony\Bridge\Twig\Mime\TemplatedEmail;
6 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
7 use Symfony\Bundle\FrameworkBundle\Controller\ControllerTrait;
8 use Symfony\Component\Asset\Packages;
9 use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
10 use Symfony\Component\Filesystem\Filesystem;
11 use Symfony\Component\DependencyInjection\ContainerAwareTrait;
12 use Symfony\Component\DependencyInjection\ContainerInterface;
13 use Symfony\Component\Form\FormError;
14 use Symfony\Component\HttpFoundation\Request;
15 use Symfony\Component\HttpFoundation\RequestStack;
16 use Symfony\Component\HttpFoundation\Response;
17 use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
18 use Symfony\Component\Mailer\MailerInterface;
19 use Symfony\Component\Mime\Address;
20 use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
21 use Symfony\Component\Routing\RouterInterface;
22 use Symfony\Component\Translation\TranslatorInterface;
23
24 use Rapsys\AirBundle\Entity\Application;
25 use Rapsys\AirBundle\Entity\Location;
26 use Rapsys\AirBundle\Entity\Session;
27 use Rapsys\AirBundle\Entity\Slot;
28 use Rapsys\AirBundle\Entity\User;
29 use Rapsys\AirBundle\Pdf\DisputePdf;
30 use Rapsys\UserBundle\Utils\Slugger;
31
32
33 class DefaultController {
34 use ControllerTrait {
35 //Rename render as _render
36 render as protected _render;
37 }
38
39 ///Config array
40 protected $config;
41
42 ///Context array
43 protected $context;
44
45 ///Router instance
46 protected $router;
47
48 ///Translator instance
49 protected $translator;
50
51 ///Packages instance
52 protected $asset;
53
54 ///RequestStack instance
55 protected $stack;
56
57 /**
58 * @var ContainerInterface
59 */
60 protected $container;
61
62 ///Facebook image array
63 protected $facebookImage = [];
64
65 /**
66 * Inject container and translator interface
67 *
68 * @param ContainerInterface $container The container instance
69 * @param RouterInterface $router The router instance
70 * @param RequestStack $stack The request stack
71 * @param TranslatorInterface $translator The translator instance
72 */
73 public function __construct(ContainerInterface $container, RouterInterface $router, RequestStack $stack, TranslatorInterface $translator, Packages $asset) {
74 //Retrieve config
75 $this->config = $container->getParameter($this->getAlias());
76
77 //Set the container
78 $this->container = $container;
79
80 //Set the router
81 $this->router = $router;
82
83 //Set the translator
84 $this->translator = $translator;
85
86 //Set the asset
87 $this->asset = $asset;
88
89 //Set the request stack
90 $this->stack = $stack;
91
92 //Set the context
93 $this->context = [
94 'copy' => [
95 'by' => $translator->trans($this->config['copy']['by']),
96 'link' => $this->config['copy']['link'],
97 'long' => $translator->trans($this->config['copy']['long']),
98 'short' => $translator->trans($this->config['copy']['short']),
99 'title' => $this->config['copy']['title']
100 ],
101 'page' => [
102 'description' => null,
103 'section' => null,
104 'title' => null
105 ],
106 'site' => [
107 'ico' => $this->config['site']['ico'],
108 'logo' => $this->config['site']['logo'],
109 'png' => $this->config['site']['png'],
110 'svg' => $this->config['site']['svg'],
111 'title' => $translator->trans($this->config['site']['title']),
112 'url' => $router->generate($this->config['site']['url']),
113 ],
114 'canonical' => null,
115 'alternates' => [],
116 'ogps' => [
117 'type' => 'article',
118 'site_name' => $this->translator->trans($this->config['site']['title'])
119 ],
120 'facebooks' => [
121 #'admins' => $this->config['facebook']['admins'],
122 'app_id' => $this->config['facebook']['apps']
123 ],
124 'forms' => []
125 ];
126
127 //Get current request
128 $currentRequest = $stack->getCurrentRequest();
129
130 //Get current locale
131 #$currentLocale = $router->getContext()->getParameters()['_locale'];
132 $currentLocale = $currentRequest->getLocale();
133
134 //Set translator locale
135 //XXX: allow LocaleSubscriber on the fly locale change for first page
136 $this->translator->setLocale($currentLocale);
137
138 //Iterate on locales excluding current one
139 foreach($this->config['locales'] as $locale) {
140 //Set titles
141 $titles = [];
142
143 //Iterate on other locales
144 foreach(array_diff($this->config['locales'], [$locale]) as $other) {
145 $titles[$other] = $translator->trans($this->config['languages'][$locale], [], null, $other);
146 }
147
148 //Get context path
149 $path = $router->getContext()->getPathInfo();
150
151 //Retrieve route matching path
152 $route = $router->match($path);
153
154 //Get route name
155 $name = $route['_route'];
156
157 //Unset route name
158 unset($route['_route']);
159
160 //With current locale
161 if ($locale == $currentLocale) {
162 //Set locale locales context
163 $this->context['canonical'] = $router->generate($name, ['_locale' => $locale]+$route, UrlGeneratorInterface::ABSOLUTE_URL);
164 } else {
165 //Set locale locales context
166 $this->context['alternates'][] = [
167 'lang' => $locale,
168 'absolute' => $router->generate($name, ['_locale' => $locale]+$route, UrlGeneratorInterface::ABSOLUTE_URL),
169 'relative' => $router->generate($name, ['_locale' => $locale]+$route),
170 'title' => implode('/', $titles),
171 'translated' => $translator->trans($this->config['languages'][$locale], [], null, $locale)
172 ];
173 }
174 }
175 }
176
177 /**
178 * The contact page
179 *
180 * @desc Send a contact mail to configured contact
181 *
182 * @param Request $request The request instance
183 * @param MailerInterface $mailer The mailer instance
184 *
185 * @return Response The rendered view or redirection
186 */
187 public function contact(Request $request, MailerInterface $mailer): Response {
188 //Set page
189 $this->context['page']['title'] = $this->translator->trans('Contact');
190
191 //Set description
192 $this->context['page']['description'] = $this->translator->trans('Contact Libre Air');
193
194 //Set keywords
195 $this->context['keywords'] = [
196 $this->translator->trans('contact'),
197 $this->translator->trans('Libre Air'),
198 $this->translator->trans('outdoor'),
199 $this->translator->trans('Argentine Tango'),
200 $this->translator->trans('calendar')
201 ];
202
203 //Create the form according to the FormType created previously.
204 //And give the proper parameters
205 $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', null, [
206 'action' => $this->generateUrl('rapsys_air_contact'),
207 'method' => 'POST'
208 ]);
209
210 if ($request->isMethod('POST')) {
211 // Refill the fields in case the form is not valid.
212 $form->handleRequest($request);
213
214 if ($form->isValid()) {
215 //Get data
216 $data = $form->getData();
217
218 //Create message
219 $message = (new TemplatedEmail())
220 //Set sender
221 ->from(new Address($data['mail'], $data['name']))
222 //Set recipient
223 //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
224 ->to(new Address($this->config['contact']['mail'], $this->config['contact']['name']))
225 //Set subject
226 ->subject($data['subject'])
227
228 //Set path to twig templates
229 ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
230 ->textTemplate('@RapsysAir/mail/contact.text.twig')
231
232 //Set context
233 ->context(
234 [
235 'subject' => $data['subject'],
236 'message' => strip_tags($data['message']),
237 ]+$this->context
238 );
239
240 //Try sending message
241 //XXX: mail delivery may silently fail
242 try {
243 //Send message
244 $mailer->send($message);
245
246 //Redirect on the same route with sent=1 to cleanup form
247 return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
248 //Catch obvious transport exception
249 } catch(TransportExceptionInterface $e) {
250 if ($message = $e->getMessage()) {
251 //Add error message mail unreachable
252 $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->config['contact']['mail'], '%message%' => $this->translator->trans($message)])));
253 } else {
254 //Add error message mail unreachable
255 $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->config['contact']['mail']])));
256 }
257 }
258 }
259 }
260
261 //Render template
262 return $this->render('@RapsysAir/form/contact.html.twig', ['form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->context);
263 }
264
265 /**
266 * The dispute page
267 *
268 * @desc Generate a dispute document
269 *
270 * @param Request $request The request instance
271 * @param MailerInterface $mailer The mailer instance
272 *
273 * @return Response The rendered view or redirection
274 */
275 public function dispute(Request $request, MailerInterface $mailer): Response {
276 //Prevent non-guest to access here
277 $this->denyAccessUnlessGranted('ROLE_USER', null, $this->translator->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator->trans('User')]));
278 //Set section
279 $section = $this->translator->trans('Dispute');
280
281 //Set description
282 $this->context['description'] = $this->translator->trans('Libre Air dispute');
283
284 //Set keywords
285 $this->context['keywords'] = [
286 $this->translator->trans('dispute'),
287 $this->translator->trans('Libre Air'),
288 $this->translator->trans('outdoor'),
289 $this->translator->trans('Argentine Tango'),
290 $this->translator->trans('calendar')
291 ];
292
293 //Set title
294 $title = $this->translator->trans($this->config['site']['title']).' - '.$section;
295
296 //Create the form according to the FormType created previously.
297 //And give the proper parameters
298 $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.'], [
299 'action' => $this->generateUrl('rapsys_air_dispute'),
300 'method' => 'POST'
301 ]);
302
303 if ($request->isMethod('POST')) {
304 // Refill the fields in case the form is not valid.
305 $form->handleRequest($request);
306
307 if ($form->isValid()) {
308 //Get data
309 $data = $form->getData();
310
311 //Gathering offense
312 if (!empty($data['offense']) && $data['offense'] == 'gathering') {
313 //Add gathering
314 $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());
315 //Traffic offense
316 } elseif (!empty($data['offense'] && $data['offense'] == 'traffic')) {
317 //Add traffic
318 $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());
319 //Unsupported offense
320 } else {
321 header('Content-Type: text/plain');
322 die('TODO');
323 exit;
324 }
325
326 //Send common headers
327 header('Content-Type: application/pdf');
328
329 //Send remaining headers
330 header('Cache-Control: private, max-age=0, must-revalidate');
331 header('Pragma: public');
332
333 //Send content-length
334 header('Content-Length: '.strlen($output));
335
336 //Display the pdf
337 echo $output;
338
339 //Die for now
340 exit;
341
342 # //Create message
343 # $message = (new TemplatedEmail())
344 # //Set sender
345 # ->from(new Address($data['mail'], $data['name']))
346 # //Set recipient
347 # //XXX: remove the debug set in vendor/symfony/mime/Address.php +46
348 # ->to(new Address($this->config['contact']['mail'], $this->config['contact']['name']))
349 # //Set subject
350 # ->subject($data['subject'])
351 #
352 # //Set path to twig templates
353 # ->htmlTemplate('@RapsysAir/mail/contact.html.twig')
354 # ->textTemplate('@RapsysAir/mail/contact.text.twig')
355 #
356 # //Set context
357 # ->context(
358 # [
359 # 'subject' => $data['subject'],
360 # 'message' => strip_tags($data['message']),
361 # ]+$this->context
362 # );
363 #
364 # //Try sending message
365 # //XXX: mail delivery may silently fail
366 # try {
367 # //Send message
368 # $mailer->send($message);
369 #
370 # //Redirect on the same route with sent=1 to cleanup form
371 # return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params'));
372 # //Catch obvious transport exception
373 # } catch(TransportExceptionInterface $e) {
374 # if ($message = $e->getMessage()) {
375 # //Add error message mail unreachable
376 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->config['contact']['mail'], '%message%' => $this->translator->trans($message)])));
377 # } else {
378 # //Add error message mail unreachable
379 # $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->config['contact']['mail']])));
380 # }
381 # }
382 }
383 }
384
385 //Render template
386 return $this->render('@RapsysAir/default/dispute.html.twig', ['title' => $title, 'section' => $section, 'form' => $form->createView(), 'sent' => $request->query->get('sent', 0)]+$this->context);
387 }
388
389 /**
390 * The index page
391 *
392 * @desc Display all granted sessions with an application or login form
393 *
394 * @param Request $request The request instance
395 *
396 * @return Response The rendered view
397 */
398 public function index(Request $request): Response {
399 //Fetch doctrine
400 $doctrine = $this->getDoctrine();
401
402 //Set page
403 $this->context['page']['title'] = $this->translator->trans('Argentine Tango in Paris');
404
405 //Set description
406 $this->context['page']['description'] = $this->translator->trans('Outdoor Argentine Tango session calendar in Paris');
407
408 //Set keywords
409 $this->context['keywords'] = [
410 $this->translator->trans('Argentine Tango'),
411 $this->translator->trans('Paris'),
412 $this->translator->trans('outdoor'),
413 $this->translator->trans('calendar'),
414 $this->translator->trans('Libre Air')
415 ];
416
417 //Set type
418 //XXX: only valid for home page
419 $this->context['ogps']['type'] = 'website';
420
421 //Set facebook image
422 $this->facebookImage = [
423 //XXX: format facebook/<controller>/<action>.<locale>.jpeg
424 'destination' => 'facebook/default/index.'.$request->getLocale().'.jpeg',
425 'texts' => [
426 $this->context['site']['title'] => [
427 'font' => 'irishgrover',
428 'size' => 110
429 ],
430 $this->context['page']['title'] => [
431 'align' => 'left'
432 ]/*,
433 $this->context['canonical'] => [
434 'align' => 'right',
435 'font' => 'labelleaurore',
436 'size' => 75
437 ]*/
438 ]
439 ];
440
441 //Compute period
442 $period = new \DatePeriod(
443 //Start from first monday of week
444 new \DateTime('Monday this week'),
445 //Iterate on each day
446 new \DateInterval('P1D'),
447 //End with next sunday and 4 weeks
448 new \DateTime(
449 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week'
450 )
451 );
452
453 //Fetch calendar
454 $calendar = $doctrine->getRepository(Session::class)->fetchCalendarByDatePeriod($this->translator, $period, null, $request->get('session'), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED'));
455
456 //Fetch locations
457 //XXX: we want to display all active locations anyway
458 $locations = $doctrine->getRepository(Location::class)->findTranslatedSortedByPeriod($this->translator, $period);
459
460 //Render the view
461 return $this->render('@RapsysAir/default/index.html.twig', ['calendar' => $calendar, 'locations' => $locations]+$this->context);
462
463 //Set Cache-Control must-revalidate directive
464 //TODO: add a javascript forced refresh after 1h ? or header refresh ?
465 #$response->setPublic(true);
466 #$response->setMaxAge(300);
467 #$response->mustRevalidate();
468 ##$response->setCache(['public' => true, 'max_age' => 300]);
469
470 //Return the response
471 #return $response;
472 }
473
474 /**
475 * The organizer regulation page
476 *
477 * @desc Display the organizer regulation policy
478 *
479 * @return Response The rendered view
480 */
481 public function organizerRegulation(): Response {
482 //Set section
483 $section = $this->translator->trans('Organizer regulation');
484
485 //Set description
486 $this->context['description'] = $this->translator->trans('Libre Air organizer regulation');
487
488 //Set keywords
489 $this->context['keywords'] = [
490 $this->translator->trans('organizer regulation'),
491 $this->translator->trans('Libre Air')
492 ];
493
494 //Set title
495 $title = $this->translator->trans($this->config['site']['title']).' - '.$section;
496
497 //Render template
498 return $this->render('@RapsysAir/default/organizer_regulation.html.twig', ['title' => $title, 'section' => $section]+$this->context);
499 }
500
501 /**
502 * The terms of service page
503 *
504 * @desc Display the terms of service policy
505 *
506 * @return Response The rendered view
507 */
508 public function termsOfService(): Response {
509 //Set section
510 $section = $this->translator->trans('Terms of service');
511
512 //Set description
513 $this->context['description'] = $this->translator->trans('Libre Air terms of service');
514
515 //Set keywords
516 $this->context['keywords'] = [
517 $this->translator->trans('terms of service'),
518 $this->translator->trans('Libre Air')
519 ];
520
521 //Set title
522 $title = $this->translator->trans($this->config['site']['title']).' - '.$section;
523
524 //Render template
525 return $this->render('@RapsysAir/default/terms_of_service.html.twig', ['title' => $title, 'section' => $section]+$this->context);
526 }
527
528 /**
529 * The frequently asked questions page
530 *
531 * @desc Display the frequently asked questions
532 *
533 * @return Response The rendered view
534 */
535 public function frequentlyAskedQuestions(): Response {
536 //Set section
537 $section = $this->translator->trans('Frequently asked questions');
538
539 //Set description
540 $this->context['description'] = $this->translator->trans('Libre Air frequently asked questions');
541
542 //Set keywords
543 $this->context['keywords'] = [
544 $this->translator->trans('frequently asked questions'),
545 $this->translator->trans('faq'),
546 $this->translator->trans('Libre Air')
547 ];
548
549 //Set title
550 $title = $this->translator->trans($this->config['site']['title']).' - '.$section;
551
552 //Render template
553 return $this->render('@RapsysAir/default/frequently_asked_questions.html.twig', ['title' => $title, 'section' => $section]+$this->context);
554 }
555
556 /**
557 * Return the bundle alias
558 *
559 * {@inheritdoc}
560 */
561 public function getAlias(): string {
562 return 'rapsys_air';
563 }
564
565 /**
566 * Return the facebook image
567 *
568 * @desc Generate image in jpeg format or load it from cache
569 *
570 * @return array The image array
571 */
572 protected function getFacebookImage(): array {
573 //Set texts
574 $texts = $this->facebookImage['texts'] ?? [];
575
576 //Set default source
577 $source = $this->facebookImage['source'] ?? 'png/facebook.png';
578
579 //Set default source
580 $updated = $this->facebookImage['updated'] ?? strtotime('last week');
581
582 //Set source path
583 $src = $this->config['path']['public'].'/'.$source;
584
585 //Set cache path
586 //XXX: remove extension and store as png anyway
587 $cache = $this->config['path']['cache'].'/facebook/'.substr($source, 0, strrpos($source, '.')).'.'.$this->config['facebook']['width'].'x'.$this->config['facebook']['height'].'.png';
588
589 //Set destination path
590 $dest = $this->config['path']['public'].'/'.$this->facebookImage['destination'];
591
592 //Set asset
593 $asset = '@RapsysAir/'.$this->facebookImage['destination'];
594
595 //With up to date generated image
596 if (
597 is_file($dest) &&
598 ($stat = stat($dest)) &&
599 $stat['mtime'] >= $updated
600 ) {
601 //Get image size
602 //TODO: see if it works every time
603 list ($width, $height) = getimagesize($dest);
604
605 //Return image data
606 return [
607 //TODO: see if it works every time
608 'image' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'],
609 'image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))),
610 'image:height' => $height,
611 'image:width' => $width
612 ];
613 //With image candidate
614 } elseif (is_file($src)) {
615 //Create image object
616 $image = new \Imagick();
617
618 //With cache image
619 if (is_file($cache)) {
620 //Read image
621 $image->readImage($cache);
622 //Without we generate it
623 } else {
624 //Check target directory
625 if (!is_dir($dir = dirname($cache))) {
626 //Create filesystem object
627 $filesystem = new Filesystem();
628
629 try {
630 //Create dir
631 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
632 $filesystem->mkdir($dir, 0775);
633 } catch (IOExceptionInterface $e) {
634 //Throw error
635 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
636 }
637 }
638
639 //Read image
640 $image->readImage($src);
641
642 //Crop using aspect ratio
643 //XXX: for better result upload image directly in aspect ratio :)
644 $image->cropThumbnailImage($this->config['facebook']['width'], $this->config['facebook']['height']);
645
646 //Strip image exif data and properties
647 $image->stripImage();
648
649 //Save cache image
650 if (!$image->writeImage($cache)) {
651 //Throw error
652 throw new \Exception(sprintf('Unable to write image "%s"', $cache));
653 }
654 }
655 //Check target directory
656 if (!is_dir($dir = dirname($dest))) {
657 //Create filesystem object
658 $filesystem = new Filesystem();
659
660 try {
661 //Create dir
662 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
663 $filesystem->mkdir($dir, 0775);
664 } catch (IOExceptionInterface $e) {
665 //Throw error
666 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
667 }
668 }
669
670 //Get image width
671 $width = $image->getImageWidth();
672
673 //Get image height
674 $height = $image->getImageHeight();
675
676 //Create draw
677 $draw = new \ImagickDraw();
678
679 //Set stroke antialias
680 $draw->setStrokeAntialias(true);
681
682 //Set text antialias
683 $draw->setTextAntialias(true);
684
685 //Set stroke width
686 //TODO: configure that ?
687 $draw->setStrokeWidth(15);
688
689 //Set font aliases
690 $fonts = [
691 'irishgrover' => $this->config['path']['public'].'/ttf/irishgrover.v10.ttf',
692 'droidsans' => $this->config['path']['public'].'/ttf/droidsans.regular.ttf',
693 'dejavusans' => $this->config['path']['public'].'/ttf/dejavusans.2.37.ttf',
694 'labelleaurore' => $this->config['path']['public'].'/ttf/labelleaurore.v10.ttf'
695 ];
696
697 //Set align aliases
698 $aligns = [
699 'left' => \Imagick::ALIGN_LEFT,
700 'center' => \Imagick::ALIGN_CENTER,
701 'right' => \Imagick::ALIGN_RIGHT
702 ];
703
704 //Set default font
705 $defaultFont = 'dejavusans';
706
707 //Set default align
708 $defaultAlign = 'center';
709
710 //Set default size
711 $defaultSize = 60;
712
713 //Set default stroke
714 $defaultStroke = '#00c3f9';
715
716 //Set default fill
717 $defaultFill = 'white';
718
719 //Init counter
720 $i = 1;
721
722 //Set text count
723 $count = count($texts);
724
725 //Draw each text stroke
726 foreach($texts as $text => $data) {
727 //Set font
728 $draw->setFont($fonts[$data['font']??$defaultFont]);
729
730 //Set font size
731 $draw->setFontSize($data['size']??$defaultSize);
732
733 //Set text alignment
734 $draw->setTextAlignment($align = ($aligns[$data['align']??$defaultAlign]));
735
736 //Get font metrics
737 $metrics = $image->queryFontMetrics($draw, $text);
738
739 //Without y
740 if (empty($data['y'])) {
741 //Position verticaly each text evenly
742 $texts[$text]['y'] = $data['y'] = (($height + 100) / (count($texts) + 1) * $i) - 50;
743 }
744
745 //Without x
746 if (empty($data['x'])) {
747 if ($align == \Imagick::ALIGN_CENTER) {
748 $texts[$text]['x'] = $data['x'] = $width/2;
749 } elseif ($align == \Imagick::ALIGN_LEFT) {
750 $texts[$text]['x'] = $data['x'] = 50;
751 } elseif ($align == \Imagick::ALIGN_RIGHT) {
752 $texts[$text]['x'] = $data['x'] = $width - 50;
753 }
754 }
755
756 //Center verticaly
757 //XXX: add ascender part then center it back by half of textHeight
758 //TODO: maybe add a boundingbox ???
759 $texts[$text]['y'] = $data['y'] += $metrics['ascender'] - $metrics['textHeight']/2;
760
761 //Set stroke color
762 $draw->setStrokeColor(new \ImagickPixel($data['stroke']??$defaultStroke));
763
764 //Set fill color
765 $draw->setFillColor(new \ImagickPixel($data['stroke']??$defaultStroke));
766
767 //Add annotation
768 $draw->annotation($data['x'], $data['y'], $text);
769
770 //Increase counter
771 $i++;
772 }
773
774 //Create stroke object
775 $stroke = new \Imagick();
776
777 //Add new image
778 $stroke->newImage($width, $height, new \ImagickPixel('transparent'));
779
780 //Draw on image
781 $stroke->drawImage($draw);
782
783 //Blur image
784 //XXX: blur the stroke canvas only
785 $stroke->blurImage(5,3);
786
787 //Set opacity to 0.5
788 //XXX: see https://www.php.net/manual/en/image.evaluateimage.php
789 $stroke->evaluateImage(\Imagick::EVALUATE_DIVIDE, 1.5, \Imagick::CHANNEL_ALPHA);
790
791 //Compose image
792 $image->compositeImage($stroke, \Imagick::COMPOSITE_OVER, 0, 0);
793
794 //Clear stroke
795 $stroke->clear();
796
797 //Destroy stroke
798 unset($stroke);
799
800 //Clear draw
801 $draw->clear();
802
803 //Set text antialias
804 $draw->setTextAntialias(true);
805
806 //Draw each text
807 foreach($texts as $text => $data) {
808 //Set font
809 $draw->setFont($fonts[$data['font']??$defaultFont]);
810
811 //Set font size
812 $draw->setFontSize($data['size']??$defaultSize);
813
814 //Set text alignment
815 $draw->setTextAlignment($aligns[$data['align']??$defaultAlign]);
816
817 //Set fill color
818 $draw->setFillColor(new \ImagickPixel($data['fill']??$defaultFill));
819
820 //Add annotation
821 $draw->annotation($data['x'], $data['y'], $text);
822 }
823
824 //Draw on image
825 $image->drawImage($draw);
826
827 //Strip image exif data and properties
828 $image->stripImage();
829
830 //Set image format
831 $image->setImageFormat('jpeg');
832
833 //Save image
834 if (!$image->writeImage($dest)) {
835 //Throw error
836 throw new \Exception(sprintf('Unable to write image "%s"', $dest));
837 }
838
839 //Get dest stat
840 //TODO: see if it works every time
841 $stat = stat($dest);
842
843 //Return image data
844 return [
845 //TODO: see if it works every time
846 'image' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'],
847 'image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))),
848 'image:height' => $height,
849 'image:width' => $width
850 ];
851 }
852
853 //Return empty array without image
854 return [];
855 }
856
857 /**
858 * Renders a view
859 *
860 * {@inheritdoc}
861 */
862 protected function render(string $view, array $parameters = [], Response $response = null): Response {
863 //Create application form for role_guest
864 if ($this->isGranted('ROLE_GUEST')) {
865 //Without application form
866 if (empty($parameters['forms']['application'])) {
867 //Fetch doctrine
868 $doctrine = $this->getDoctrine();
869
870 //Create ApplicationType form
871 $application = $this->createForm('Rapsys\AirBundle\Form\ApplicationType', null, [
872 //Set the action
873 'action' => $this->generateUrl('rapsys_air_application_add'),
874 //Set the form attribute
875 'attr' => [ 'class' => 'col' ],
876 //Set admin
877 'admin' => $this->isGranted('ROLE_ADMIN'),
878 //Set default user to current
879 'user' => $this->getUser()->getId(),
880 //Set default slot to evening
881 //XXX: default to Evening (3)
882 'slot' => $doctrine->getRepository(Slot::class)->findOneById(3)
883 ]);
884
885 //Add form to context
886 $parameters['forms']['application'] = $application->createView();
887 }
888 //Create login form for anonymous
889 } elseif (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
890 //Create ApplicationType form
891 $login = $this->createForm('Rapsys\UserBundle\Form\LoginType', null, [
892 //Set the action
893 'action' => $this->generateUrl('rapsys_user_login'),
894 //Set the form attribute
895 'attr' => [ 'class' => 'col' ]
896 ]);
897
898 //Add form to context
899 $parameters['forms']['login'] = $login->createView();
900 }
901
902 /*
903 //TODO: set here or in constructor the controller and action name
904 //XXX: used to autogenerate the facebookimage dest
905 //XXX: with just page title and canonical we may generate miniatures automaticaly
906 if ($_SERVER['REMOTE_ADDR'] == '89.3.147.209') {
907 header('Content-Type: text/plain');
908 #var_dump($this->getModuleName());
909 #var_dump($this->getController());
910 exit;
911 #var_dump(__CLASS__);
912 #var_dump($router);
913 exit;
914 var_dump($currentRequest->attributes->get('_controller'));
915 exit;
916 }*/
917
918
919 //With canonical
920 if (!empty($parameters['canonical'])) {
921 //Set facebook url
922 $parameters['ogps']['url'] = $parameters['canonical'];
923 }
924
925 //With page title
926 if (!empty($parameters['page']['title'])) {
927 //Set facebook title
928 $parameters['ogps']['title'] = $parameters['page']['title'];
929 }
930
931 //With page description
932 if (!empty($parameters['page']['description'])) {
933 //Set facebook description
934 $parameters['ogps']['description'] = $parameters['page']['description'];
935 }
936
937 //With facebook image defined
938 if (!empty($this->facebookImage)) {
939 //Get facebook image
940 $parameters['ogps'] += $this->getFacebookImage();
941 }
942
943 //Call parent method
944 return $this->_render($view, $parameters, $response);
945 }
946 }