]> Raphaƫl G. Git Repositories - airbundle/blob - Controller/DefaultController.php
Add snippet's rate field
[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 }