3 namespace Rapsys\AirBundle\Controller
; 
   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
; 
  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
; 
  33 class DefaultController 
{ 
  35                 //Rename render as _render 
  36                 render 
as protected _render
; 
  48         ///Translator instance 
  49         protected $translator; 
  54         ///RequestStack instance 
  64          * @var ContainerInterface 
  68         ///Facebook image array 
  69         protected $facebookImage = []; 
  72          * Inject container and translator interface 
  74          * @param ContainerInterface $container The container instance 
  75          * @param RouterInterface $router The router instance 
  76          * @param RequestStack $stack The request stack 
  77          * @param TranslatorInterface $translator The translator instance 
  79         public function __construct(ContainerInterface 
$container, RouterInterface 
$router, RequestStack 
$stack, TranslatorInterface 
$translator, Packages 
$asset) { 
  81                 $this->config 
= $container->getParameter($this->getAlias()); 
  84                 $this->container 
= $container; 
  87                 $this->router 
= $router; 
  90                 $this->translator 
= $translator; 
  93                 $this->asset 
= $asset; 
  95                 //Set the request stack 
  96                 $this->stack 
= $stack; 
 101                                 'title' => $translator->trans($this->config
['contact']['title']), 
 102                                 'mail' => $this->config
['contact']['mail'] 
 105                                 'by' => $translator->trans($this->config
['copy']['by']), 
 106                                 'link' => $this->config
['copy']['link'], 
 107                                 'long' => $translator->trans($this->config
['copy']['long']), 
 108                                 'short' => $translator->trans($this->config
['copy']['short']), 
 109                                 'title' => $this->config
['copy']['title'] 
 112                                 'description' => null, 
 117                                 'donate' => $this->config
['site']['donate'], 
 118                                 'ico' => $this->config
['site']['ico'], 
 119                                 'logo' => $this->config
['site']['logo'], 
 120                                 'png' => $this->config
['site']['png'], 
 121                                 'svg' => $this->config
['site']['svg'], 
 122                                 'title' => $translator->trans($this->config
['site']['title']), 
 123                                 'url' => $router->generate($this->config
['site']['url']) 
 129                                 'site_name' => $this->translator
->trans($this->config
['site']['title']) 
 132                                 #'admins' => $this->config['facebook']['admins'], 
 133                                 'app_id' => $this->config
['facebook']['apps'] 
 138                 //Get current request 
 139                 $this->request 
= $stack->getCurrentRequest(); 
 142                 #$this->locale = $router->getContext()->getParameters()['_locale']; 
 143                 $this->locale 
= $this->request
->getLocale(); 
 145                 //Set translator locale 
 146                 //XXX: allow LocaleSubscriber on the fly locale change for first page 
 147                 $this->translator
->setLocale($this->locale
); 
 149                 //Iterate on locales excluding current one 
 150                 foreach($this->config
['locales'] as $locale) { 
 154                         //Iterate on other locales 
 155                         foreach(array_diff($this->config
['locales'], [$locale]) as $other) { 
 156                                 $titles[$other] = $translator->trans($this->config
['languages'][$locale], [], null, $other); 
 160                         $path = $router->getContext()->getPathInfo(); 
 162                         //Retrieve route matching path 
 163                         $route = $router->match($path); 
 166                         $name = $route['_route']; 
 169                         unset($route['_route']); 
 171                         //With current locale 
 172                         if ($locale == $this->locale
) { 
 173                                 //Set locale locales context 
 174                                 $this->context
['canonical'] = $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
); 
 176                                 //Set locale locales context 
 177                                 $this->context
['alternates'][$locale] = [ 
 178                                         'absolute' => $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
), 
 179                                         'relative' => $router->generate($name, ['_locale' => $locale]+
$route), 
 180                                         'title' => implode('/', $titles), 
 181                                         'translated' => $translator->trans($this->config
['languages'][$locale], [], null, $locale) 
 186                         if (empty($this->context
['alternates'][$shortLocale = substr($locale, 0, 2)])) { 
 187                                 //Set locale locales context 
 188                                 $this->context
['alternates'][$shortLocale] = [ 
 189                                         'absolute' => $router->generate($name, ['_locale' => $locale]+
$route, UrlGeneratorInterface
::ABSOLUTE_URL
), 
 190                                         'relative' => $router->generate($name, ['_locale' => $locale]+
$route), 
 191                                         'title' => implode('/', $titles), 
 192                                         'translated' => $translator->trans($this->config
['languages'][$locale], [], null, $locale) 
 201          * @desc Display the about informations 
 203          * @return Response The rendered view 
 205         public function about(): Response 
{ 
 207                 $this->context
['page']['title'] = $this->translator
->trans('About'); 
 210                 $this->context
['page']['description'] = $this->translator
->trans('Libre Air about'); 
 213                 $this->context
['keywords'] = [ 
 214                         $this->translator
->trans('about'), 
 215                         $this->translator
->trans('Libre Air') 
 219                 $response = $this->render('@RapsysAir/default/about.html.twig', $this->context
); 
 220                 $response->setEtag(md5($response->getContent())); 
 221                 $response->setPublic(); 
 222                 $response->isNotModified($this->request
); 
 231          * @desc Send a contact mail to configured contact 
 233          * @param Request $request The request instance 
 234          * @param MailerInterface $mailer The mailer instance 
 236          * @return Response The rendered view or redirection 
 238         public function contact(Request 
$request, MailerInterface 
$mailer): Response 
{ 
 240                 $this->context
['page']['title'] = $this->translator
->trans('Contact'); 
 243                 $this->context
['page']['description'] = $this->translator
->trans('Contact Libre Air'); 
 246                 $this->context
['keywords'] = [ 
 247                         $this->translator
->trans('contact'), 
 248                         $this->translator
->trans('Libre Air'), 
 249                         $this->translator
->trans('outdoor'), 
 250                         $this->translator
->trans('Argentine Tango'), 
 251                         $this->translator
->trans('calendar') 
 254                 //Create the form according to the FormType created previously. 
 255                 //And give the proper parameters 
 256                 $form = $this->createForm('Rapsys\AirBundle\Form\ContactType', null, [ 
 257                         'action' => $this->generateUrl('rapsys_air_contact'), 
 261                 if ($request->isMethod('POST')) { 
 262                         // Refill the fields in case the form is not valid. 
 263                         $form->handleRequest($request); 
 265                         if ($form->isValid()) { 
 267                                 $data = $form->getData(); 
 270                                 $message = (new TemplatedEmail()) 
 272                                         ->from(new Address($data['mail'], $data['name'])) 
 274                                         ->to(new Address($this->context
['contact']['mail'], $this->context
['contact']['title'])) 
 276                                         ->subject($data['subject']) 
 278                                         //Set path to twig templates 
 279                                         ->htmlTemplate('@RapsysAir/mail/contact.html.twig') 
 280                                         ->textTemplate('@RapsysAir/mail/contact.text.twig') 
 285                                                         'subject' => $data['subject'], 
 286                                                         'message' => strip_tags($data['message']), 
 290                                 //Try sending message 
 291                                 //XXX: mail delivery may silently fail 
 294                                         $mailer->send($message); 
 296                                         //Redirect on the same route with sent=1 to cleanup form 
 297                                         return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+
$request->get('_route_params')); 
 298                                 //Catch obvious transport exception 
 299                                 } catch(TransportExceptionInterface 
$e) { 
 300                                         if ($message = $e->getMessage()) { 
 301                                                 //Add error message mail unreachable 
 302                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->context
['contact']['mail'], '%message%' => $this->translator
->trans($message)]))); 
 304                                                 //Add error message mail unreachable 
 305                                                 $form->get('mail')->addError(new FormError($this->translator
->trans('Unable to contact: %mail%', ['%mail%' => $this->context
['contact']['mail']]))); 
 312                 return $this->render('@RapsysAir/form/contact.html.twig', ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
); 
 318          * @desc Generate a dispute document 
 320          * @param Request $request The request instance 
 321          * @param MailerInterface $mailer The mailer instance 
 323          * @return Response The rendered view or redirection 
 325         public function dispute(Request 
$request, MailerInterface 
$mailer): Response 
{ 
 326                 //Prevent non-guest to access here 
 327                 $this->denyAccessUnlessGranted('ROLE_USER', null, $this->translator
->trans('Unable to access this page without role %role%!', ['%role%' => $this->translator
->trans('User')])); 
 330                 $this->context
['page']['title'] = $this->translator
->trans('Dispute'); 
 333                 $this->context
['page']['description'] = $this->translator
->trans('Libre Air dispute'); 
 336                 $this->context
['keywords'] = [ 
 337                         $this->translator
->trans('dispute'), 
 338                         $this->translator
->trans('Libre Air'), 
 339                         $this->translator
->trans('outdoor'), 
 340                         $this->translator
->trans('Argentine Tango'), 
 341                         $this->translator
->trans('calendar') 
 344                 //Create the form according to the FormType created previously. 
 345                 //And give the proper parameters 
 346                 $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.'], [ 
 347                         'action' => $this->generateUrl('rapsys_air_dispute'), 
 351                 if ($request->isMethod('POST')) { 
 352                         // Refill the fields in case the form is not valid. 
 353                         $form->handleRequest($request); 
 355                         if ($form->isValid()) { 
 357                                 $data = $form->getData(); 
 360                                 if (!empty($data['offense']) && $data['offense'] == 'gathering') { 
 362                                         $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()); 
 364                                 } elseif (!empty($data['offense'] && $data['offense'] == 'traffic')) { 
 366                                         $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()); 
 367                                 //Unsupported offense 
 369                                         header('Content-Type: text/plain'); 
 374                                 //Send common headers 
 375                                 header('Content-Type: application/pdf'); 
 377                                 //Send remaining headers 
 378                                 header('Cache-Control: private, max-age=0, must-revalidate'); 
 379                                 header('Pragma: public'); 
 381                                 //Send content-length 
 382                                 header('Content-Length: '.strlen($output)); 
 391 #                               $message = (new TemplatedEmail()) 
 393 #                                       ->from(new Address($data['mail'], $data['name'])) 
 395 #                                       //XXX: remove the debug set in vendor/symfony/mime/Address.php +46 
 396 #                                       ->to(new Address($this->config['contact']['mail'], $this->config['contact']['title'])) 
 398 #                                       ->subject($data['subject']) 
 400 #                                       //Set path to twig templates 
 401 #                                       ->htmlTemplate('@RapsysAir/mail/contact.html.twig') 
 402 #                                       ->textTemplate('@RapsysAir/mail/contact.text.twig') 
 407 #                                                       'subject' => $data['subject'], 
 408 #                                                       'message' => strip_tags($data['message']), 
 412 #                               //Try sending message 
 413 #                               //XXX: mail delivery may silently fail 
 416 #                                       $mailer->send($message); 
 418 #                                       //Redirect on the same route with sent=1 to cleanup form 
 419 #                                       return $this->redirectToRoute($request->get('_route'), ['sent' => 1]+$request->get('_route_params')); 
 420 #                               //Catch obvious transport exception 
 421 #                               } catch(TransportExceptionInterface $e) { 
 422 #                                       if ($message = $e->getMessage()) { 
 423 #                                               //Add error message mail unreachable 
 424 #                                               $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%: %message%', ['%mail%' => $this->config['contact']['mail'], '%message%' => $this->translator->trans($message)]))); 
 426 #                                               //Add error message mail unreachable 
 427 #                                               $form->get('mail')->addError(new FormError($this->translator->trans('Unable to contact: %mail%', ['%mail%' => $this->config['contact']['mail']]))); 
 434                 return $this->render('@RapsysAir/default/dispute.html.twig', ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
); 
 440          * @desc Display all granted sessions with an application or login form 
 442          * @param Request $request The request instance 
 444          * @return Response The rendered view 
 446         public function index(Request 
$request): Response 
{ 
 448                 $doctrine = $this->getDoctrine(); 
 451                 $this->context
['page']['title'] = $this->translator
->trans('Argentine Tango in Paris'); 
 454                 $this->context
['page']['description'] = $this->translator
->trans('Outdoor Argentine Tango session calendar in Paris'); 
 457                 $this->context
['keywords'] = [ 
 458                         $this->translator
->trans('Argentine Tango'), 
 459                         $this->translator
->trans('Paris'), 
 460                         $this->translator
->trans('outdoor'), 
 461                         $this->translator
->trans('calendar'), 
 462                         $this->translator
->trans('Libre Air') 
 466                 //XXX: only valid for home page 
 467                 $this->context
['ogps']['type'] = 'website'; 
 470                 $period = new \
DatePeriod( 
 471                         //Start from first monday of week 
 472                         new \
DateTime('Monday this week'), 
 473                         //Iterate on each day 
 474                         new \
DateInterval('P1D'), 
 475                         //End with next sunday and 4 weeks 
 477                                 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week' 
 482                 $calendar = $doctrine->getRepository(Session
::class)->fetchCalendarByDatePeriod($this->translator
, $period, null, $request->get('session'), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED')); 
 485                 //XXX: we want to display all active locations anyway 
 486                 $locations = $doctrine->getRepository(Location
::class)->findTranslatedSortedByPeriod($this->translator
, $period); 
 489                 return $this->render('@RapsysAir/default/index.html.twig', ['calendar' => $calendar, 'locations' => $locations]+
$this->context
); 
 491                 //Set Cache-Control must-revalidate directive 
 492                 //TODO: add a javascript forced refresh after 1h ? or header refresh ? 
 493                 #$response->setPublic(true); 
 494                 #$response->setMaxAge(300); 
 495                 #$response->mustRevalidate(); 
 496                 ##$response->setCache(['public' => true, 'max_age' => 300]); 
 498                 //Return the response 
 503          * The organizer regulation page 
 505          * @desc Display the organizer regulation policy 
 507          * @return Response The rendered view 
 509         public function organizerRegulation(): Response 
{ 
 511                 $this->context
['page']['title'] = $this->translator
->trans('Organizer regulation'); 
 514                 $this->context
['page']['description'] = $this->translator
->trans('Libre Air organizer regulation'); 
 517                 $this->context
['keywords'] = [ 
 518                         $this->translator
->trans('organizer regulation'), 
 519                         $this->translator
->trans('Libre Air') 
 523                 $response = $this->render('@RapsysAir/default/organizer_regulation.html.twig', $this->context
); 
 526                 $response->setEtag(md5($response->getContent())); 
 527                 $response->setPublic(); 
 528                 $response->isNotModified($this->request
); 
 535          * The terms of service page 
 537          * @desc Display the terms of service policy 
 539          * @return Response The rendered view 
 541         public function termsOfService(): Response 
{ 
 543                 $this->context
['page']['title'] = $this->translator
->trans('Terms of service'); 
 546                 $this->context
['page']['description'] = $this->translator
->trans('Libre Air terms of service'); 
 549                 $this->context
['keywords'] = [ 
 550                         $this->translator
->trans('terms of service'), 
 551                         $this->translator
->trans('Libre Air') 
 555                 $response = $this->render('@RapsysAir/default/terms_of_service.html.twig', $this->context
); 
 558                 $response->setEtag(md5($response->getContent())); 
 559                 $response->setPublic(); 
 560                 $response->isNotModified($this->request
); 
 567          * The frequently asked questions page 
 569          * @desc Display the frequently asked questions 
 571          * @return Response The rendered view 
 573         public function frequentlyAskedQuestions(): Response 
{ 
 575                 $this->context
['page']['title'] = $this->translator
->trans('Frequently asked questions'); 
 578                 $this->context
['page']['description'] = $this->translator
->trans('Libre Air frequently asked questions'); 
 581                 $this->context
['keywords'] = [ 
 582                         $this->translator
->trans('frequently asked questions'), 
 583                         $this->translator
->trans('faq'), 
 584                         $this->translator
->trans('Libre Air') 
 588                 $response = $this->render('@RapsysAir/default/frequently_asked_questions.html.twig', $this->context
); 
 591                 $response->setEtag(md5($response->getContent())); 
 592                 $response->setPublic(); 
 593                 $response->isNotModified($this->request
); 
 600          * Return the bundle alias 
 604         public function getAlias(): string { 
 609          * Return the facebook image 
 611          * @desc Generate image in jpeg format or load it from cache 
 613          * @return array The image array 
 615         protected function getFacebookImage(): array { 
 617                 $texts = $this->facebookImage
['texts'] ?? []; 
 620                 $source = $this->facebookImage
['source'] ?? 'png/facebook.png'; 
 623                 $updated = $this->facebookImage
['updated'] ?? strtotime('last week'); 
 625                 //Set default destination 
 626                 //XXX: format facebook<pathinfo>.jpeg 
 627                 //XXX: was facebook/<controller>/<action>.<locale>.jpeg 
 628                 $destination = $this->facebookImage
['destination'] ?? 'facebook'.$this->request
->getPathInfo().'.jpeg'; 
 631                 $src = $this->config
['path']['public'].'/'.$source; 
 634                 //XXX: remove extension and store as png anyway 
 635                 $cache = $this->config
['path']['cache'].'/facebook/'.substr($source, 0, strrpos($source, '.')).'.'.$this->config
['facebook']['width'].'x'.$this->config
['facebook']['height'].'.png'; 
 637                 //Set destination path 
 638                 $dest = $this->config
['path']['public'].'/'.$destination; 
 641                 $asset = '@RapsysAir/'.$destination; 
 643                 //With up to date generated image 
 646                         ($stat = stat($dest)) && 
 647                         $stat['mtime'] >= $updated 
 650                         list ($width, $height) = getimagesize($dest); 
 652                         //With canonical in texts 
 653                         if (!empty($texts[$this->context
['canonical']])) { 
 654                                 //Prevent canonical to finish in alt 
 655                                 unset($texts[$this->context
['canonical']]); 
 660                                 #'image' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 661                                 'image:url' => $this->stack
->getCurrentRequest()->getUriForPath($this->asset
->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 662                                 #'image:secure_url' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 663                                 'image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))), 
 664                                 'image:height' => $height, 
 665                                 'image:width' => $width 
 667                 //With image candidate 
 668                 } elseif (is_file($src)) { 
 669                         //Create image object 
 670                         $image = new \
Imagick(); 
 673                         if (is_file($cache)) { 
 675                                 $image->readImage($cache); 
 676                         //Without we generate it 
 678                                 //Check target directory 
 679                                 if (!is_dir($dir = dirname($cache))) { 
 680                                         //Create filesystem object 
 681                                         $filesystem = new Filesystem(); 
 685                                                 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 686                                                 $filesystem->mkdir($dir, 0775); 
 687                                         } catch (IOExceptionInterface 
$e) { 
 689                                                 throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 694                                 $image->readImage($src); 
 696                                 //Crop using aspect ratio 
 697                                 //XXX: for better result upload image directly in aspect ratio :) 
 698                                 $image->cropThumbnailImage($this->config
['facebook']['width'], $this->config
['facebook']['height']); 
 700                                 //Strip image exif data and properties 
 701                                 $image->stripImage(); 
 704                                 if (!$image->writeImage($cache)) { 
 706                                         throw new \
Exception(sprintf('Unable to write image "%s"', $cache)); 
 709                         //Check target directory 
 710                         if (!is_dir($dir = dirname($dest))) { 
 711                                 //Create filesystem object 
 712                                 $filesystem = new Filesystem(); 
 716                                         //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 717                                         $filesystem->mkdir($dir, 0775); 
 718                                 } catch (IOExceptionInterface 
$e) { 
 720                                         throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 725                         $width = $image->getImageWidth(); 
 728                         $height = $image->getImageHeight(); 
 731                         $draw = new \
ImagickDraw(); 
 733                         //Set stroke antialias 
 734                         $draw->setStrokeAntialias(true); 
 737                         $draw->setTextAntialias(true); 
 740                         $draw->setStrokeWidth($this->facebookImage
['stroke']??15); 
 744                                 'irishgrover' => $this->config
['path']['public'].'/ttf/irishgrover.v10.ttf', 
 745                                 'droidsans' => $this->config
['path']['public'].'/ttf/droidsans.regular.ttf', 
 746                                 'dejavusans' => $this->config
['path']['public'].'/ttf/dejavusans.2.37.ttf', 
 747                                 'labelleaurore' => $this->config
['path']['public'].'/ttf/labelleaurore.v10.ttf' 
 752                                 'left' => \Imagick
::ALIGN_LEFT
, 
 753                                 'center' => \Imagick
::ALIGN_CENTER
, 
 754                                 'right' => \Imagick
::ALIGN_RIGHT
 
 758                         $defaultFont = 'dejavusans'; 
 761                         $defaultAlign = 'center'; 
 767                         $defaultStroke = '#00c3f9'; 
 770                         $defaultFill = 'white'; 
 776                         $count = count($texts); 
 778                         //Draw each text stroke 
 779                         foreach($texts as $text => $data) { 
 781                                 $draw->setFont($fonts[$data['font']??$defaultFont]); 
 784                                 $draw->setFontSize($data['size']??$defaultSize); 
 787                                 $draw->setTextAlignment($align = ($aligns[$data['align']??$defaultAlign])); 
 790                                 $metrics = $image->queryFontMetrics($draw, $text); 
 793                                 if (empty($data['y'])) { 
 794                                         //Position verticaly each text evenly 
 795                                         $texts[$text]['y'] = $data['y'] = (($height + 
100) / (count($texts) + 
1) * $i) - 50; 
 799                                 if (empty($data['x'])) { 
 800                                         if ($align == \Imagick
::ALIGN_CENTER
) { 
 801                                                 $texts[$text]['x'] = $data['x'] = $width/2; 
 802                                         } elseif ($align == \Imagick
::ALIGN_LEFT
) { 
 803                                                 $texts[$text]['x'] = $data['x'] = 50; 
 804                                         } elseif ($align == \Imagick
::ALIGN_RIGHT
) { 
 805                                                 $texts[$text]['x'] = $data['x'] = $width - 50; 
 810                                 //XXX: add ascender part then center it back by half of textHeight 
 811                                 //TODO: maybe add a boundingbox ??? 
 812                                 $texts[$text]['y'] = $data['y'] +
= $metrics['ascender'] - $metrics['textHeight']/2; 
 815                                 $draw->setStrokeColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 818                                 $draw->setFillColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 821                                 $draw->annotation($data['x'], $data['y'], $text); 
 827                         //Create stroke object 
 828                         $stroke = new \
Imagick(); 
 831                         $stroke->newImage($width, $height, new \
ImagickPixel('transparent')); 
 834                         $stroke->drawImage($draw); 
 837                         //XXX: blur the stroke canvas only 
 838                         $stroke->blurImage(5,3); 
 841                         //XXX: see https://www.php.net/manual/en/image.evaluateimage.php 
 842                         $stroke->evaluateImage(\Imagick
::EVALUATE_DIVIDE
, 1.5, \Imagick
::CHANNEL_ALPHA
); 
 845                         $image->compositeImage($stroke, \Imagick
::COMPOSITE_OVER
, 0, 0); 
 857                         $draw->setTextAntialias(true); 
 860                         foreach($texts as $text => $data) { 
 862                                 $draw->setFont($fonts[$data['font']??$defaultFont]); 
 865                                 $draw->setFontSize($data['size']??$defaultSize); 
 868                                 $draw->setTextAlignment($aligns[$data['align']??$defaultAlign]); 
 871                                 $draw->setFillColor(new \
ImagickPixel($data['fill']??$defaultFill)); 
 874                                 $draw->annotation($data['x'], $data['y'], $text); 
 878                         $image->drawImage($draw); 
 880                         //Strip image exif data and properties 
 881                         $image->stripImage(); 
 884                         $image->setImageFormat('jpeg'); 
 887                         if (!$image->writeImage($dest)) { 
 889                                 throw new \
Exception(sprintf('Unable to write image "%s"', $dest)); 
 893                         //TODO: see if it works every time 
 896                         //With canonical in texts 
 897                         if (!empty($texts[$this->context
['canonical']])) { 
 898                                 //Prevent canonical to finish in alt 
 899                                 unset($texts[$this->context
['canonical']]); 
 904                                 //TODO: see if it works every time 
 905                                 #'image' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 906                                 'image:url' => $this->stack
->getCurrentRequest()->getUriForPath($this->asset
->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 907                                 #'image:secure_url' => $this->stack->getCurrentRequest()->getUriForPath($this->asset->getUrl($asset), true),#.'?fbrefresh='.$stat['mtime'], 
 908                                 'image:alt' => str_replace("\n", ' ', implode(' - ', array_keys($texts))), 
 909                                 'image:height' => $height, 
 910                                 'image:width' => $width 
 914                 //Return empty array without image 
 923         protected function render(string $view, array $parameters = [], Response 
$response = null): Response 
{ 
 924                 //Create application form for role_guest 
 925                 if ($this->isGranted('ROLE_GUEST')) { 
 926                         //Without application form 
 927                         if (empty($parameters['forms']['application'])) { 
 929                                 $doctrine = $this->getDoctrine(); 
 931                                 //Create ApplicationType form 
 932                                 $application = $this->createForm('Rapsys\AirBundle\Form\ApplicationType', null, [ 
 934                                         'action' => $this->generateUrl('rapsys_air_application_add'), 
 935                                         //Set the form attribute 
 936                                         'attr' => [ 'class' => 'col' ], 
 938                                         'admin' => $this->isGranted('ROLE_ADMIN'), 
 939                                         //Set default user to current 
 940                                         'user' => $this->getUser()->getId(), 
 941                                         //Set default slot to evening 
 942                                         //XXX: default to Evening (3) 
 943                                         'slot' => $doctrine->getRepository(Slot
::class)->findOneById(3) 
 946                                 //Add form to context 
 947                                 $parameters['forms']['application'] = $application->createView(); 
 949                 //Create login form for anonymous 
 950                 } elseif (!$this->isGranted('IS_AUTHENTICATED_REMEMBERED')) { 
 951                         //Create ApplicationType form 
 952                         $login = $this->createForm('Rapsys\UserBundle\Form\LoginType', null, [ 
 954                                 'action' => $this->generateUrl('rapsys_user_login'), 
 955                                 //Set the form attribute 
 956                                 'attr' => [ 'class' => 'col' ] 
 959                         //Add form to context 
 960                         $parameters['forms']['login'] = $login->createView(); 
 963                 //With page infos and without facebook image 
 964                 if (empty($this->facebookImage
) && !empty($parameters['site']['title']) && !empty($parameters['page']['title']) && !empty($parameters['canonical'])) { 
 966                         $this->facebookImage 
= [ 
 968                                         $parameters['site']['title'] => [ 
 969                                                 'font' => 'irishgrover', 
 972                                         $parameters['page']['title'] => [ 
 975                                         $parameters['canonical'] => [ 
 977                                                 'font' => 'labelleaurore', 
 985                 if (!empty($parameters['canonical'])) { 
 987                         $parameters['ogps']['url'] = $parameters['canonical']; 
 991                 if (!empty($parameters['page']['title'])) { 
 993                         $parameters['ogps']['title'] = $parameters['page']['title']; 
 996                 //With page description 
 997                 if (!empty($parameters['page']['description'])) { 
 998                         //Set facebook description 
 999                         $parameters['ogps']['description'] = $parameters['page']['description']; 
1003                 if (!empty($this->locale
)) { 
1004                         //Set facebook locale 
1005                         $parameters['ogps']['locale'] = str_replace('-', '_', $this->locale
); 
1008                         //XXX: disabled as we don't support fb_locale=xx_xx 
1009                         //XXX: see https://stackoverflow.com/questions/20827882/in-open-graph-markup-whats-the-use-of-oglocalealternate-without-the-locati 
1010                         #if (!empty($parameters['alternates'])) { 
1011                         #       //Iterate on alternates 
1012                         #       foreach($parameters['alternates'] as $lang => $alternate) { 
1013                         #               if (strlen($lang) == 5) { 
1014                         #                       //Set facebook locale alternate 
1015                         #                       $parameters['ogps']['locale:alternate'] = str_replace('-', '_', $lang); 
1021                 //With facebook image defined 
1022                 if (!empty($this->facebookImage
)) { 
1023                         //Get facebook image 
1024                         $parameters['ogps'] +
= $this->getFacebookImage(); 
1027                 //Call parent method 
1028                 return $this->_render($view, $parameters, $response);