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 
  58          * @var ContainerInterface 
  62         ///Facebook image array 
  63         protected $facebookImage = []; 
  66          * Inject container and translator interface 
  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 
  73         public function __construct(ContainerInterface 
$container, RouterInterface 
$router, RequestStack 
$stack, TranslatorInterface 
$translator, Packages 
$asset) { 
  75                 $this->config 
= $container->getParameter($this->getAlias()); 
  78                 $this->container 
= $container; 
  81                 $this->router 
= $router; 
  84                 $this->translator 
= $translator; 
  87                 $this->asset 
= $asset; 
  89                 //Set the request stack 
  90                 $this->stack 
= $stack; 
  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'] 
 102                                 'description' => null, 
 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']), 
 118                                 'site_name' => $this->translator
->trans($this->config
['site']['title']) 
 121                                 #'admins' => $this->config['facebook']['admins'], 
 122                                 'app_id' => $this->config
['facebook']['apps'] 
 127                 //Get current request 
 128                 $currentRequest = $stack->getCurrentRequest(); 
 131                 #$currentLocale = $router->getContext()->getParameters()['_locale']; 
 132                 $currentLocale = $currentRequest->getLocale(); 
 134                 //Set translator locale 
 135                 //XXX: allow LocaleSubscriber on the fly locale change for first page 
 136                 $this->translator
->setLocale($currentLocale); 
 138                 //Iterate on locales excluding current one 
 139                 foreach($this->config
['locales'] as $locale) { 
 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); 
 149                         $path = $router->getContext()->getPathInfo(); 
 151                         //Retrieve route matching path 
 152                         $route = $router->match($path); 
 155                         $name = $route['_route']; 
 158                         unset($route['_route']); 
 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
); 
 165                                 //Set locale locales context 
 166                                 $this->context
['alternates'][] = [ 
 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) 
 180          * @desc Send a contact mail to configured contact 
 182          * @param Request $request The request instance 
 183          * @param MailerInterface $mailer The mailer instance 
 185          * @return Response The rendered view or redirection 
 187         public function contact(Request 
$request, MailerInterface 
$mailer): Response 
{ 
 189                 $this->context
['page']['title'] = $this->translator
->trans('Contact'); 
 192                 $this->context
['page']['description'] = $this->translator
->trans('Contact Libre Air'); 
 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') 
 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'), 
 210                 if ($request->isMethod('POST')) { 
 211                         // Refill the fields in case the form is not valid. 
 212                         $form->handleRequest($request); 
 214                         if ($form->isValid()) { 
 216                                 $data = $form->getData(); 
 219                                 $message = (new TemplatedEmail()) 
 221                                         ->from(new Address($data['mail'], $data['name'])) 
 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'])) 
 226                                         ->subject($data['subject']) 
 228                                         //Set path to twig templates 
 229                                         ->htmlTemplate('@RapsysAir/mail/contact.html.twig') 
 230                                         ->textTemplate('@RapsysAir/mail/contact.text.twig') 
 235                                                         'subject' => $data['subject'], 
 236                                                         'message' => strip_tags($data['message']), 
 240                                 //Try sending message 
 241                                 //XXX: mail delivery may silently fail 
 244                                         $mailer->send($message); 
 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)]))); 
 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']]))); 
 262                 return $this->render('@RapsysAir/form/contact.html.twig', ['form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
); 
 268          * @desc Generate a dispute document 
 270          * @param Request $request The request instance 
 271          * @param MailerInterface $mailer The mailer instance 
 273          * @return Response The rendered view or redirection 
 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')])); 
 279                 $section = $this->translator
->trans('Dispute'); 
 282                 $this->context
['description'] = $this->translator
->trans('Libre Air dispute'); 
 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') 
 294                 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section; 
 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'), 
 303                 if ($request->isMethod('POST')) { 
 304                         // Refill the fields in case the form is not valid. 
 305                         $form->handleRequest($request); 
 307                         if ($form->isValid()) { 
 309                                 $data = $form->getData(); 
 312                                 if (!empty($data['offense']) && $data['offense'] == '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()); 
 316                                 } elseif (!empty($data['offense'] && $data['offense'] == '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 
 321                                         header('Content-Type: text/plain'); 
 326                                 //Send common headers 
 327                                 header('Content-Type: application/pdf'); 
 329                                 //Send remaining headers 
 330                                 header('Cache-Control: private, max-age=0, must-revalidate'); 
 331                                 header('Pragma: public'); 
 333                                 //Send content-length 
 334                                 header('Content-Length: '.strlen($output)); 
 343 #                               $message = (new TemplatedEmail()) 
 345 #                                       ->from(new Address($data['mail'], $data['name'])) 
 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'])) 
 350 #                                       ->subject($data['subject']) 
 352 #                                       //Set path to twig templates 
 353 #                                       ->htmlTemplate('@RapsysAir/mail/contact.html.twig') 
 354 #                                       ->textTemplate('@RapsysAir/mail/contact.text.twig') 
 359 #                                                       'subject' => $data['subject'], 
 360 #                                                       'message' => strip_tags($data['message']), 
 364 #                               //Try sending message 
 365 #                               //XXX: mail delivery may silently fail 
 368 #                                       $mailer->send($message); 
 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)]))); 
 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']]))); 
 386                 return $this->render('@RapsysAir/default/dispute.html.twig', ['title' => $title, 'section' => $section, 'form' => $form->createView(), 'sent' => $request->query
->get('sent', 0)]+
$this->context
); 
 392          * @desc Display all granted sessions with an application or login form 
 394          * @param Request $request The request instance 
 396          * @return Response The rendered view 
 398         public function index(Request 
$request): Response 
{ 
 400                 $doctrine = $this->getDoctrine(); 
 403                 $this->context
['page']['title'] = $this->translator
->trans('Argentine Tango in Paris'); 
 406                 $this->context
['page']['description'] = $this->translator
->trans('Outdoor Argentine Tango session calendar in Paris'); 
 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') 
 418                 //XXX: only valid for home page 
 419                 $this->context
['ogps']['type'] = 'website'; 
 422                 $this->facebookImage 
= [ 
 423                         //XXX: format facebook/<controller>/<action>.<locale>.jpeg 
 424                         'destination' => 'facebook/default/index.'.$request->getLocale().'.jpeg', 
 426                                 $this->context
['site']['title'] => [ 
 427                                         'font' => 'irishgrover', 
 430                                 $this->context
['page']['title'] => [ 
 433                                 $this->context['canonical'] => [ 
 435                                         'font' => 'labelleaurore', 
 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 
 449                                 $this->isGranted('IS_AUTHENTICATED_REMEMBERED')?'Monday this week + 3 week':'Monday this week + 2 week' 
 454                 $calendar = $doctrine->getRepository(Session
::class)->fetchCalendarByDatePeriod($this->translator
, $period, null, $request->get('session'), !$this->isGranted('IS_AUTHENTICATED_REMEMBERED')); 
 457                 //XXX: we want to display all active locations anyway 
 458                 $locations = $doctrine->getRepository(Location
::class)->findTranslatedSortedByPeriod($this->translator
, $period); 
 461                 return $this->render('@RapsysAir/default/index.html.twig', ['calendar' => $calendar, 'locations' => $locations]+
$this->context
); 
 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]); 
 470                 //Return the response 
 475          * The organizer regulation page 
 477          * @desc Display the organizer regulation policy 
 479          * @return Response The rendered view 
 481         public function organizerRegulation(): Response 
{ 
 483                 $section = $this->translator
->trans('Organizer regulation'); 
 486                 $this->context
['description'] = $this->translator
->trans('Libre Air organizer regulation'); 
 489                 $this->context
['keywords'] = [ 
 490                         $this->translator
->trans('organizer regulation'), 
 491                         $this->translator
->trans('Libre Air') 
 495                 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section; 
 498                 return $this->render('@RapsysAir/default/organizer_regulation.html.twig', ['title' => $title, 'section' => $section]+
$this->context
); 
 502          * The terms of service page 
 504          * @desc Display the terms of service policy 
 506          * @return Response The rendered view 
 508         public function termsOfService(): Response 
{ 
 510                 $section = $this->translator
->trans('Terms of service'); 
 513                 $this->context
['description'] = $this->translator
->trans('Libre Air terms of service'); 
 516                 $this->context
['keywords'] = [ 
 517                         $this->translator
->trans('terms of service'), 
 518                         $this->translator
->trans('Libre Air') 
 522                 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section; 
 525                 return $this->render('@RapsysAir/default/terms_of_service.html.twig', ['title' => $title, 'section' => $section]+
$this->context
); 
 529          * The frequently asked questions page 
 531          * @desc Display the frequently asked questions 
 533          * @return Response The rendered view 
 535         public function frequentlyAskedQuestions(): Response 
{ 
 537                 $section = $this->translator
->trans('Frequently asked questions'); 
 540                 $this->context
['description'] = $this->translator
->trans('Libre Air frequently asked questions'); 
 543                 $this->context
['keywords'] = [ 
 544                         $this->translator
->trans('frequently asked questions'), 
 545                         $this->translator
->trans('faq'), 
 546                         $this->translator
->trans('Libre Air') 
 550                 $title = $this->translator
->trans($this->config
['site']['title']).' - '.$section; 
 553                 return $this->render('@RapsysAir/default/frequently_asked_questions.html.twig', ['title' => $title, 'section' => $section]+
$this->context
); 
 557          * Return the bundle alias 
 561         public function getAlias(): string { 
 566          * Return the facebook image 
 568          * @desc Generate image in jpeg format or load it from cache 
 570          * @return array The image array 
 572         protected function getFacebookImage(): array { 
 574                 $texts = $this->facebookImage
['texts'] ?? []; 
 577                 $source = $this->facebookImage
['source'] ?? 'png/facebook.png'; 
 580                 $updated = $this->facebookImage
['updated'] ?? strtotime('last week'); 
 583                 $src = $this->config
['path']['public'].'/'.$source; 
 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'; 
 589                 //Set destination path 
 590                 $dest = $this->config
['path']['public'].'/'.$this->facebookImage
['destination']; 
 593                 $asset = '@RapsysAir/'.$this->facebookImage
['destination']; 
 595                 //With up to date generated image 
 598                         ($stat = stat($dest)) && 
 599                         $stat['mtime'] >= $updated 
 602                         //TODO: see if it works every time 
 603                         list ($width, $height) = getimagesize($dest); 
 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 
 613                 //With image candidate 
 614                 } elseif (is_file($src)) { 
 615                         //Create image object 
 616                         $image = new \
Imagick(); 
 619                         if (is_file($cache)) { 
 621                                 $image->readImage($cache); 
 622                         //Without we generate it 
 624                                 //Check target directory 
 625                                 if (!is_dir($dir = dirname($cache))) { 
 626                                         //Create filesystem object 
 627                                         $filesystem = new Filesystem(); 
 631                                                 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 632                                                 $filesystem->mkdir($dir, 0775); 
 633                                         } catch (IOExceptionInterface 
$e) { 
 635                                                 throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 640                                 $image->readImage($src); 
 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']); 
 646                                 //Strip image exif data and properties 
 647                                 $image->stripImage(); 
 650                                 if (!$image->writeImage($cache)) { 
 652                                         throw new \
Exception(sprintf('Unable to write image "%s"', $cache)); 
 655                         //Check target directory 
 656                         if (!is_dir($dir = dirname($dest))) { 
 657                                 //Create filesystem object 
 658                                 $filesystem = new Filesystem(); 
 662                                         //XXX: set as 0775, symfony umask (0022) will reduce rights (0755) 
 663                                         $filesystem->mkdir($dir, 0775); 
 664                                 } catch (IOExceptionInterface 
$e) { 
 666                                         throw new \
Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e); 
 671                         $width = $image->getImageWidth(); 
 674                         $height = $image->getImageHeight(); 
 677                         $draw = new \
ImagickDraw(); 
 679                         //Set stroke antialias 
 680                         $draw->setStrokeAntialias(true); 
 683                         $draw->setTextAntialias(true); 
 686                         //TODO: configure that ? 
 687                         $draw->setStrokeWidth(15); 
 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' 
 699                                 'left' => \Imagick
::ALIGN_LEFT
, 
 700                                 'center' => \Imagick
::ALIGN_CENTER
, 
 701                                 'right' => \Imagick
::ALIGN_RIGHT
 
 705                         $defaultFont = 'dejavusans'; 
 708                         $defaultAlign = 'center'; 
 714                         $defaultStroke = '#00c3f9'; 
 717                         $defaultFill = 'white'; 
 723                         $count = count($texts); 
 725                         //Draw each text stroke 
 726                         foreach($texts as $text => $data) { 
 728                                 $draw->setFont($fonts[$data['font']??$defaultFont]); 
 731                                 $draw->setFontSize($data['size']??$defaultSize); 
 734                                 $draw->setTextAlignment($align = ($aligns[$data['align']??$defaultAlign])); 
 737                                 $metrics = $image->queryFontMetrics($draw, $text); 
 740                                 if (empty($data['y'])) { 
 741                                         //Position verticaly each text evenly 
 742                                         $texts[$text]['y'] = $data['y'] = (($height + 
100) / (count($texts) + 
1) * $i) - 50; 
 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; 
 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; 
 762                                 $draw->setStrokeColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 765                                 $draw->setFillColor(new \
ImagickPixel($data['stroke']??$defaultStroke)); 
 768                                 $draw->annotation($data['x'], $data['y'], $text); 
 774                         //Create stroke object 
 775                         $stroke = new \
Imagick(); 
 778                         $stroke->newImage($width, $height, new \
ImagickPixel('transparent')); 
 781                         $stroke->drawImage($draw); 
 784                         //XXX: blur the stroke canvas only 
 785                         $stroke->blurImage(5,3); 
 788                         //XXX: see https://www.php.net/manual/en/image.evaluateimage.php 
 789                         $stroke->evaluateImage(\Imagick
::EVALUATE_DIVIDE
, 1.5, \Imagick
::CHANNEL_ALPHA
); 
 792                         $image->compositeImage($stroke, \Imagick
::COMPOSITE_OVER
, 0, 0); 
 804                         $draw->setTextAntialias(true); 
 807                         foreach($texts as $text => $data) { 
 809                                 $draw->setFont($fonts[$data['font']??$defaultFont]); 
 812                                 $draw->setFontSize($data['size']??$defaultSize); 
 815                                 $draw->setTextAlignment($aligns[$data['align']??$defaultAlign]); 
 818                                 $draw->setFillColor(new \
ImagickPixel($data['fill']??$defaultFill)); 
 821                                 $draw->annotation($data['x'], $data['y'], $text); 
 825                         $image->drawImage($draw); 
 827                         //Strip image exif data and properties 
 828                         $image->stripImage(); 
 831                         $image->setImageFormat('jpeg'); 
 834                         if (!$image->writeImage($dest)) { 
 836                                 throw new \
Exception(sprintf('Unable to write image "%s"', $dest)); 
 840                         //TODO: see if it works every time 
 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 
 853                 //Return empty array without image 
 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'])) { 
 868                                 $doctrine = $this->getDoctrine(); 
 870                                 //Create ApplicationType form 
 871                                 $application = $this->createForm('Rapsys\AirBundle\Form\ApplicationType', null, [ 
 873                                         'action' => $this->generateUrl('rapsys_air_application_add'), 
 874                                         //Set the form attribute 
 875                                         'attr' => [ 'class' => 'col' ], 
 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) 
 885                                 //Add form to context 
 886                                 $parameters['forms']['application'] = $application->createView(); 
 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, [ 
 893                                 'action' => $this->generateUrl('rapsys_user_login'), 
 894                                 //Set the form attribute 
 895                                 'attr' => [ 'class' => 'col' ] 
 898                         //Add form to context 
 899                         $parameters['forms']['login'] = $login->createView(); 
 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()); 
 911                         #var_dump(__CLASS__); 
 914                         var_dump($currentRequest->attributes->get('_controller')); 
 920                 if (!empty($parameters['canonical'])) { 
 922                         $parameters['ogps']['url'] = $parameters['canonical']; 
 926                 if (!empty($parameters['page']['title'])) { 
 928                         $parameters['ogps']['title'] = $parameters['page']['title']; 
 931                 //With page description 
 932                 if (!empty($parameters['page']['description'])) { 
 933                         //Set facebook description 
 934                         $parameters['ogps']['description'] = $parameters['page']['description']; 
 937                 //With facebook image defined 
 938                 if (!empty($this->facebookImage
)) { 
 940                         $parameters['ogps'] +
= $this->getFacebookImage(); 
 944                 return $this->_render($view, $parameters, $response);