]> Raphaël G. Git Repositories - packbundle/blob - Controller.php
Remove unused size member function
[packbundle] / Controller.php
1 <?php declare(strict_types=1);
2
3 /*
4 * This file is part of the Rapsys PackBundle package.
5 *
6 * (c) Raphaël Gertz <symfony@rapsys.eu>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12 namespace Rapsys\PackBundle;
13
14 use Rapsys\PackBundle\Util\ImageUtil;
15 use Rapsys\PackBundle\Util\SluggerUtil;
16
17 use Psr\Container\ContainerInterface;
18
19 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
20 use Symfony\Component\Filesystem\Exception\IOExceptionInterface;
21 use Symfony\Component\Filesystem\Filesystem;
22 use Symfony\Component\HttpFoundation\BinaryFileResponse;
23 use Symfony\Component\HttpFoundation\HeaderUtils;
24 use Symfony\Component\HttpFoundation\Request;
25 use Symfony\Component\HttpFoundation\Response;
26 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
27 use Symfony\Component\Routing\RequestContext;
28 use Symfony\Contracts\Service\ServiceSubscriberInterface;
29
30 /**
31 * {@inheritdoc}
32 */
33 class Controller extends AbstractController implements ServiceSubscriberInterface {
34 /**
35 * Alias string
36 */
37 protected string $alias;
38
39 /**
40 * Config array
41 */
42 protected array $config;
43
44 /**
45 * Stream context
46 */
47 protected mixed $ctx;
48
49 /**
50 * Version string
51 */
52 protected string $version;
53
54 /**
55 * Creates a new image controller
56 *
57 * @param ContainerInterface $container The ContainerInterface instance
58 * @param ImageUtil $image The MapUtil instance
59 * @param SluggerUtil $slugger The SluggerUtil instance
60 */
61 function __construct(protected ContainerInterface $container, protected ImageUtil $image, protected SluggerUtil $slugger) {
62 //Retrieve config
63 $this->config = $container->getParameter($this->alias = RapsysPackBundle::getAlias());
64
65 //Set ctx
66 $this->ctx = stream_context_create(
67 [
68 'http' => [
69 'max_redirects' => $_ENV['RAPSYSPACK_REDIRECT'] ?? 20,
70 'timeout' => $_ENV['RAPSYSPACK_TIMEOUT'] ?? (($timeout = ini_get('default_socket_timeout')) !== false && $timeout !== '' ? (float)$timeout : 60),
71 'user_agent' => $_ENV['RAPSYSPACK_AGENT'] ?? (($agent = ini_get('user_agent')) !== false && $agent !== '' ? (string)$agent : $this->alias.'/'.($this->version = RapsysPackBundle::getVersion()))
72 ]
73 ]
74 );
75 }
76
77 /**
78 * Return captcha image
79 *
80 * @param Request $request The Request instance
81 * @param string $hash The hash
82 * @param string $equation The shorted equation
83 * @param int $height The height
84 * @param int $width The width
85 * @return Response The rendered image
86 */
87 public function captcha(Request $request, string $hash, string $equation, int $height, int $width, string $_format): Response {
88 //Without matching hash
89 if ($hash !== $this->slugger->serialize([$equation, $height, $width])) {
90 //Throw new exception
91 throw new NotFoundHttpException(sprintf('Unable to match captcha hash: %s', $hash));
92 //Without valid format
93 } elseif ($_format !== 'jpeg' && $_format !== 'png' && $_format !== 'webp') {
94 //Throw new exception
95 throw new NotFoundHttpException('Invalid thumb format');
96 }
97
98 //Unshort equation
99 $equation = $this->slugger->unshort($short = $equation);
100
101 //Set hashed tree
102 $hashed = str_split(strval($equation));
103
104 //Set captcha
105 $captcha = $this->config['cache'].'/'.$this->config['prefixes']['captcha'].'/'.$hashed[0].'/'.$hashed[4].'/'.$hashed[8].'/'.$short.'.'.$_format;
106
107 //Without captcha up to date file
108 if (!is_file($captcha) || !($mtime = stat($captcha)['mtime']) || $mtime < (new \DateTime('-1 hour'))->getTimestamp()) {
109 //Without existing captcha path
110 if (!is_dir($dir = dirname($captcha))) {
111 //Create filesystem object
112 $filesystem = new Filesystem();
113
114 try {
115 //Create path
116 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
117 //XXX: on CoW filesystems execute a chattr +C before filling
118 $filesystem->mkdir($dir, 0775);
119 } catch (IOExceptionInterface $e) {
120 //Throw error
121 throw new \Exception(sprintf('Output path "%s" do not exists and unable to create it', $dir), 0, $e);
122 }
123 }
124
125 //Create image instance
126 $image = new \Imagick();
127
128 //Add imagick draw instance
129 //XXX: see https://www.php.net/manual/fr/imagick.examples-1.php#example-3916
130 $draw = new \ImagickDraw();
131
132 //Set text antialias
133 $draw->setTextAntialias(true);
134
135 //Set stroke antialias
136 $draw->setStrokeAntialias(true);
137
138 //Set text alignment
139 $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
140
141 //Set gravity
142 $draw->setGravity(\Imagick::GRAVITY_CENTER);
143
144 //Set fill color
145 $draw->setFillColor($this->config['captcha']['fill']);
146
147 //Set stroke color
148 $draw->setStrokeColor($this->config['captcha']['border']);
149
150 //Set font size
151 $draw->setFontSize($this->config['captcha']['size'] / 1.5);
152
153 //Set stroke width
154 $draw->setStrokeWidth($this->config['captcha']['thickness'] / 3);
155
156 //Set rotation
157 $draw->rotate($rotate = (rand(25, 75)*(rand(0,1)?-.1:.1)));
158
159 //Get font metrics
160 $metrics2 = $image->queryFontMetrics($draw, strval('stop spam'));
161
162 //Add annotation
163 $draw->annotation($width / 2 - ceil(rand(intval(-$metrics2['textWidth']), intval($metrics2['textWidth'])) / 2) - abs($rotate), ceil($metrics2['textHeight'] + $metrics2['descender'] + $metrics2['ascender']) / 2 - $this->config['captcha']['thickness'] - $rotate, strval('stop spam'));
164
165 //Set rotation
166 $draw->rotate(-$rotate);
167
168 //Set font size
169 $draw->setFontSize($this->config['captcha']['size']);
170
171 //Set stroke width
172 $draw->setStrokeWidth($this->config['captcha']['thickness']);
173
174 //Set rotation
175 $draw->rotate($rotate = (rand(25, 50)*(rand(0,1)?-.1:.1)));
176
177 //Get font metrics
178 $metrics = $image->queryFontMetrics($draw, strval($equation));
179
180 //Add annotation
181 $draw->annotation($width / 2, ceil($metrics['textHeight'] + $metrics['descender'] + $metrics['ascender']) / 2 - $this->config['captcha']['thickness'], strval($equation));
182
183 //Set rotation
184 $draw->rotate(-$rotate);
185
186 //Add new image
187 #$image->newImage(intval(ceil($metrics['textWidth'])), intval(ceil($metrics['textHeight'] + $metrics['descender'])), new \ImagickPixel($this->config['captcha']['background']), 'jpeg');
188 $image->newImage($width, $height, new \ImagickPixel($this->config['captcha']['background']), $_format);
189
190 //Draw on image
191 $image->drawImage($draw);
192
193 //Strip image exif data and properties
194 $image->stripImage();
195
196 //Set compression quality
197 $image->setImageCompressionQuality(70);
198
199 //Save captcha
200 if (!$image->writeImage($captcha)) {
201 //Throw error
202 throw new \Exception(sprintf('Unable to write image "%s"', $captcha));
203 }
204
205 //Set mtime
206 $mtime = stat($captcha)['mtime'];
207 }
208
209 //Read captcha from cache
210 $response = new BinaryFileResponse($captcha);
211
212 //Set file name
213 $response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, 'captcha-stop-spam-'.str_replace([' ', '*', '+'], ['-', 'mul', 'add'], $equation).'.'.$_format);
214
215 //Set etag
216 $response->setEtag(md5($hash));
217
218 //Set last modified
219 $response->setLastModified(\DateTime::createFromFormat('U', strval($mtime)));
220
221 //Set as public
222 $response->setPublic();
223
224 //Return 304 response if not modified
225 $response->isNotModified($request);
226
227 //Return response
228 return $response;
229 }
230
231 /**
232 * Return facebook image
233 *
234 * @param Request $request The Request instance
235 * @param string $hash The hash
236 * @param string $path The image path
237 * @param int $height The height
238 * @param int $width The width
239 * @return Response The rendered image
240 */
241 public function facebook(Request $request, string $hash, string $path, int $height, int $width, string $_format): Response {
242 //Without matching hash
243 if ($hash !== $this->slugger->serialize([$path, $height, $width])) {
244 //Throw new exception
245 throw new NotFoundHttpException(sprintf('Unable to match facebook hash: %s', $hash));
246 //Without matching format
247 } elseif ($_format !== 'jpeg' && $_format !== 'png' && $_format !== 'webp') {
248 //Throw new exception
249 throw new NotFoundHttpException(sprintf('Invalid facebook format: %s', $_format));
250 }
251
252 //Unshort path
253 $path = $this->slugger->unshort($short = $path);
254
255 //Without facebook file
256 if (!is_file($facebook = $this->config['cache'].'/'.$this->config['prefixes']['facebook'].$path.'.'.$_format)) {
257 //Throw new exception
258 throw new NotFoundHttpException('Unable to get facebook file');
259 }
260
261 //Read facebook from cache
262 $response = new BinaryFileResponse($facebook);
263
264 //Set file name
265 $response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, 'facebook-'.$hash.'.'.$_format);
266
267 //Set etag
268 $response->setEtag(md5($hash));
269
270 //Set last modified
271 $response->setLastModified(\DateTime::createFromFormat('U', strval(stat($facebook)['mtime'])));
272
273 //Set as public
274 $response->setPublic();
275
276 //Return 304 response if not modified
277 $response->isNotModified($request);
278
279 //Return response
280 return $response;
281 }
282
283 /**
284 * Return map image
285 *
286 * @param Request $request The Request instance
287 * @param string $hash The hash
288 * @param int $updated The updated timestamp
289 * @param float $latitude The latitude
290 * @param float $longitude The longitude
291 * @param int $zoom The zoom
292 * @param int $width The width
293 * @param int $height The height
294 * @return Response The rendered image
295 */
296 public function map(Request $request, string $hash, float $latitude, float $longitude, int $height, int $width, int $zoom, string $_format): Response {
297 //Without matching hash
298 if ($hash !== $this->slugger->hash([$height, $width, $zoom, $latitude, $longitude])) {
299 //Throw new exception
300 throw new NotFoundHttpException(sprintf('Unable to match map hash: %s', $hash));
301 }
302
303 //Set map
304 $map = $this->config['cache'].'/'.$this->config['prefixes']['map'].'/'.$zoom.'/'.($latitude*1000000%10).'/'.($longitude*1000000%10).'/'.$latitude.','.$longitude.'-'.$zoom.'-'.$width.'x'.$height.'.'.$_format;
305
306 //Without map file
307 //TODO: refresh after config modification ?
308 if (!is_file($map)) {
309 //Without existing map path
310 if (!is_dir($dir = dirname($map))) {
311 //Create filesystem object
312 $filesystem = new Filesystem();
313
314 try {
315 //Create path
316 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
317 //XXX: on CoW filesystems execute a chattr +C before filling
318 $filesystem->mkdir($dir, 0775);
319 } catch (IOExceptionInterface $e) {
320 //Throw error
321 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
322 }
323 }
324
325 //Create image instance
326 $image = new \Imagick();
327
328 //Add new image
329 $image->newImage($width, $height, new \ImagickPixel('transparent'), $_format);
330
331 //Create tile instance
332 $tile = new \Imagick();
333
334 //Get tile xy
335 $centerX = $this->image->longitudeToX($longitude, $zoom);
336 $centerY = $this->image->latitudeToY($latitude, $zoom);
337
338 //Calculate start xy
339 $startX = floor(floor($centerX) - $width / $this->config['map']['tz']);
340 $startY = floor(floor($centerY) - $height / $this->config['map']['tz']);
341
342 //Calculate end xy
343 $endX = ceil(ceil($centerX) + $width / $this->config['map']['tz']);
344 $endY = ceil(ceil($centerY) + $height / $this->config['map']['tz']);
345
346 for($x = $startX; $x <= $endX; $x++) {
347 for($y = $startY; $y <= $endY; $y++) {
348 //Set cache path
349 $cache = $this->config['cache'].'/'.$this->config['prefixes']['map'].'/'.$zoom.'/'.($x%10).'/'.($y%10).'/'.$x.','.$y.'.png';
350
351 //Without cache image
352 if (!is_file($cache)) {
353 //Set tile url
354 $tileUri = str_replace(['{Z}', '{X}', '{Y}'], [$zoom, $x, $y], $this->config['servers'][$this->config['map']['server']]);
355
356 //Without cache path
357 if (!is_dir($dir = dirname($cache))) {
358 //Create filesystem object
359 $filesystem = new Filesystem();
360
361 try {
362 //Create path
363 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
364 $filesystem->mkdir($dir, 0775);
365 } catch (IOExceptionInterface $e) {
366 //Throw error
367 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
368 }
369 }
370
371 //Store tile in cache
372 file_put_contents($cache, file_get_contents($tileUri, false, $this->ctx));
373 }
374
375 //Set dest x
376 $destX = intval(floor($width / 2 - $this->config['map']['tz'] * ($centerX - $x)));
377
378 //Set dest y
379 $destY = intval(floor($height / 2 - $this->config['map']['tz'] * ($centerY - $y)));
380
381 //Read tile from cache
382 $tile->readImage($cache);
383
384 //Compose image
385 $image->compositeImage($tile, \Imagick::COMPOSITE_OVER, $destX, $destY);
386
387 //Clear tile
388 $tile->clear();
389 }
390 }
391
392 //Add imagick draw instance
393 //XXX: see https://www.php.net/manual/fr/imagick.examples-1.php#example-3916
394 $draw = new \ImagickDraw();
395
396 //Set text antialias
397 $draw->setTextAntialias(true);
398
399 //Set stroke antialias
400 $draw->setStrokeAntialias(true);
401
402 //Set text alignment
403 $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
404
405 //Set gravity
406 $draw->setGravity(\Imagick::GRAVITY_CENTER);
407
408 //Set fill color
409 $draw->setFillColor($this->config['map']['fill']);
410
411 //Set stroke color
412 $draw->setStrokeColor($this->config['map']['border']);
413
414 //Set stroke width
415 $draw->setStrokeWidth($this->config['map']['thickness']);
416
417 //Draw circle
418 $draw->circle($width/2 - $this->config['map']['radius'], $height/2 - $this->config['map']['radius'], $width/2 + $this->config['map']['radius'], $height/2 + $this->config['map']['radius']);
419
420 //Draw on image
421 $image->drawImage($draw);
422
423 //Strip image exif data and properties
424 $image->stripImage();
425
426 //Add latitude
427 //XXX: not supported by imagick :'(
428 $image->setImageProperty('exif:GPSLatitude', $this->image->latitudeToSexagesimal($latitude));
429
430 //Add longitude
431 //XXX: not supported by imagick :'(
432 $image->setImageProperty('exif:GPSLongitude', $this->image->longitudeToSexagesimal($longitude));
433
434 //Set progressive jpeg
435 $image->setInterlaceScheme(\Imagick::INTERLACE_PLANE);
436
437 //Set compression quality
438 $image->setImageCompressionQuality($this->config['map']['quality']);
439
440 //Save image
441 if (!$image->writeImage($map)) {
442 //Throw error
443 throw new \Exception(sprintf('Unable to write image "%s"', $map));
444 }
445 }
446
447 //Read map from cache
448 $response = new BinaryFileResponse($map);
449
450 //Set file name
451 $response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, basename($map));
452
453 //Set etag
454 $response->setEtag(md5(serialize([$height, $width, $zoom, $latitude, $longitude])));
455
456 //Set last modified
457 $response->setLastModified(\DateTime::createFromFormat('U', strval(stat($map)['mtime'])));
458
459 //Disable robot index
460 $response->headers->set('X-Robots-Tag', 'noindex');
461
462 //Set as public
463 $response->setPublic();
464
465 //Return 304 response if not modified
466 $response->isNotModified($request);
467
468 //Return response
469 return $response;
470 }
471
472 /**
473 * Return multi map image
474 *
475 * @param Request $request The Request instance
476 * @param string $hash The hash
477 * @param int $updated The updated timestamp
478 * @param float $latitude The latitude
479 * @param float $longitude The longitude
480 * @param string $coordinates The coordinates
481 * @param int $zoom The zoom
482 * @param int $width The width
483 * @param int $height The height
484 * @return Response The rendered image
485 */
486 public function multi(Request $request, string $hash, string $coordinate, int $height, int $width, int $zoom, string $_format): Response {
487 //Without matching hash
488 if ($hash !== $this->slugger->hash([$height, $width, $zoom, $coordinate])) {
489 //Throw new exception
490 throw new NotFoundHttpException(sprintf('Unable to match multi map hash: %s', $hash));
491 }
492
493 //Set latitudes and longitudes array
494 $latitudes = $longitudes = [];
495
496 //Set coordinates
497 $coordinates = array_map(
498 function ($v) use (&$latitudes, &$longitudes) {
499 list($latitude, $longitude) = explode(',', $v);
500 $latitudes[] = $latitude;
501 $longitudes[] = $longitude;
502 return [ $latitude, $longitude ];
503 },
504 explode('-', $coordinate)
505 );
506
507 //Set latitude
508 $latitude = round((min($latitudes)+max($latitudes))/2, 6);
509
510 //Set longitude
511 $longitude = round((min($longitudes)+max($longitudes))/2, 6);
512
513 //Set map
514 $map = $this->config['cache'].'/'.$this->config['prefixes']['multi'].'/'.$zoom.'/'.($latitude*1000000%10).'/'.($longitude*1000000%10).'/'.$coordinate.'-'.$zoom.'-'.$width.'x'.$height.'.'.$_format;
515
516 //Without map file
517 if (!is_file($map)) {
518 //Without existing multi path
519 if (!is_dir($dir = dirname($map))) {
520 //Create filesystem object
521 $filesystem = new Filesystem();
522
523 try {
524 //Create path
525 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
526 //XXX: on CoW filesystems execute a chattr +C before filling
527 $filesystem->mkdir($dir, 0775);
528 } catch (IOExceptionInterface $e) {
529 //Throw error
530 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
531 }
532 }
533
534 //Create image instance
535 $image = new \Imagick();
536
537 //Add new image
538 $image->newImage($width, $height, new \ImagickPixel('transparent'), $_format);
539
540 //Create tile instance
541 $tile = new \Imagick();
542
543 //Get tile xy
544 $centerX = $this->image->longitudeToX($longitude, $zoom);
545 $centerY = $this->image->latitudeToY($latitude, $zoom);
546
547 //Calculate start xy
548 $startX = floor(floor($centerX) - $width / $this->config['multi']['tz']);
549 $startY = floor(floor($centerY) - $height / $this->config['multi']['tz']);
550
551 //Calculate end xy
552 $endX = ceil(ceil($centerX) + $width / $this->config['multi']['tz']);
553 $endY = ceil(ceil($centerY) + $height / $this->config['multi']['tz']);
554
555 for($x = $startX; $x <= $endX; $x++) {
556 for($y = $startY; $y <= $endY; $y++) {
557 //Set cache path
558 $cache = $this->config['cache'].'/'.$this->config['prefixes']['multi'].'/'.$zoom.'/'.($x%10).'/'.($y%10).'/'.$x.','.$y.'.png';
559
560 //Without cache image
561 if (!is_file($cache)) {
562 //Set tile url
563 $tileUri = str_replace(['{Z}', '{X}', '{Y}'], [$zoom, $x, $y], $this->config['servers'][$this->config['multi']['server']]);
564
565 //Without cache path
566 if (!is_dir($dir = dirname($cache))) {
567 //Create filesystem object
568 $filesystem = new Filesystem();
569
570 try {
571 //Create path
572 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
573 $filesystem->mkdir($dir, 0775);
574 } catch (IOExceptionInterface $e) {
575 //Throw error
576 throw new \Exception(sprintf('Output directory "%s" do not exists and unable to create it', $dir), 0, $e);
577 }
578 }
579
580 //Store tile in cache
581 file_put_contents($cache, file_get_contents($tileUri, false, $this->ctx));
582 }
583
584 //Set dest x
585 $destX = intval(floor($width / 2 - $this->config['multi']['tz'] * ($centerX - $x)));
586
587 //Set dest y
588 $destY = intval(floor($height / 2 - $this->config['multi']['tz'] * ($centerY - $y)));
589
590 //Read tile from cache
591 $tile->readImage($cache);
592
593 //Compose image
594 $image->compositeImage($tile, \Imagick::COMPOSITE_OVER, $destX, $destY);
595
596 //Clear tile
597 $tile->clear();
598 }
599 }
600
601 //Add imagick draw instance
602 //XXX: see https://www.php.net/manual/fr/imagick.examples-1.php#example-3916
603 $draw = new \ImagickDraw();
604
605 //Set text antialias
606 $draw->setTextAntialias(true);
607
608 //Set stroke antialias
609 $draw->setStrokeAntialias(true);
610
611 //Set text alignment
612 $draw->setTextAlignment(\Imagick::ALIGN_CENTER);
613
614 //Set gravity
615 $draw->setGravity(\Imagick::GRAVITY_CENTER);
616
617 //Iterate on locations
618 foreach($coordinates as $id => $coordinate) {
619 //Get coordinates
620 list($clatitude, $clongitude) = $coordinate;
621
622 //Set dest x
623 $destX = intval(floor($width / 2 - $this->config['multi']['tz'] * ($centerX - $this->image->longitudeToX(floatval($clongitude), $zoom))));
624
625 //Set dest y
626 $destY = intval(floor($height / 2 - $this->config['multi']['tz'] * ($centerY - $this->image->latitudeToY(floatval($clatitude), $zoom))));
627
628 //Set fill color
629 $draw->setFillColor($this->config['multi']['fill']);
630
631 //Set font size
632 $draw->setFontSize($this->config['multi']['size']);
633
634 //Set stroke color
635 $draw->setStrokeColor($this->config['multi']['border']);
636
637 //Set circle radius
638 $radius = $this->config['multi']['radius'];
639
640 //Set stroke width
641 $stroke = $this->config['multi']['thickness'];
642
643 //With matching position
644 if ($clatitude === $latitude && $clongitude == $longitude) {
645 //Set fill color
646 $draw->setFillColor($this->config['multi']['highfill']);
647
648 //Set font size
649 $draw->setFontSize($this->config['multi']['highsize']);
650
651 //Set stroke color
652 $draw->setStrokeColor($this->config['multi']['highborder']);
653
654 //Set circle radius
655 $radius = $this->config['multi']['highradius'];
656
657 //Set stroke width
658 $stroke = $this->config['multi']['highthickness'];
659 }
660
661 //Set stroke width
662 $draw->setStrokeWidth($stroke);
663
664 //Draw circle
665 $draw->circle($destX - $radius, $destY - $radius, $destX + $radius, $destY + $radius);
666
667 //Set fill color
668 $draw->setFillColor($draw->getStrokeColor());
669
670 //Set stroke width
671 $draw->setStrokeWidth($stroke / 4);
672
673 //Get font metrics
674 #$metrics = $image->queryFontMetrics($draw, strval($id));
675
676 //Add annotation
677 $draw->annotation($destX - $radius, $destY + $stroke, strval($id));
678 }
679
680 //Draw on image
681 $image->drawImage($draw);
682
683 //Strip image exif data and properties
684 $image->stripImage();
685
686 //Add latitude
687 //XXX: not supported by imagick :'(
688 $image->setImageProperty('exif:GPSLatitude', $this->image->latitudeToSexagesimal($latitude));
689
690 //Add longitude
691 //XXX: not supported by imagick :'(
692 $image->setImageProperty('exif:GPSLongitude', $this->image->longitudeToSexagesimal($longitude));
693
694 //Add description
695 //XXX: not supported by imagick :'(
696 #$image->setImageProperty('exif:Description', $caption);
697
698 //Set progressive jpeg
699 $image->setInterlaceScheme(\Imagick::INTERLACE_PLANE);
700
701 //Set compression quality
702 $image->setImageCompressionQuality($this->config['multi']['quality']);
703
704 //Save image
705 if (!$image->writeImage($map)) {
706 //Throw error
707 throw new \Exception(sprintf('Unable to write image "%s"', $path));
708 }
709 }
710
711 //Read map from cache
712 $response = new BinaryFileResponse($map);
713
714 //Set file name
715 #$response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, 'multimap-'.$latitude.','.$longitude.'-'.$zoom.'-'.$width.'x'.$height.'.jpeg');
716 $response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, basename($map));
717
718 //Set etag
719 $response->setEtag(md5(serialize([$height, $width, $zoom, $coordinate])));
720
721 //Set last modified
722 $response->setLastModified(\DateTime::createFromFormat('U', strval(stat($map)['mtime'])));
723
724 //Disable robot index
725 $response->headers->set('X-Robots-Tag', 'noindex');
726
727 //Set as public
728 $response->setPublic();
729
730 //Return 304 response if not modified
731 $response->isNotModified($request);
732
733 //Return response
734 return $response;
735 }
736
737 /**
738 * Return thumb image
739 *
740 * @param Request $request The Request instance
741 * @param string $hash The hash
742 * @param string $path The image path
743 * @param int $height The height
744 * @param int $width The width
745 * @return Response The rendered image
746 */
747 public function thumb(Request $request, string $hash, string $path, int $height, int $width, string $_format): Response {
748 //Without matching hash
749 if ($hash !== $this->slugger->serialize([$path, $height, $width])) {
750 //Throw new exception
751 throw new NotFoundHttpException('Invalid thumb hash');
752 //Without valid format
753 } elseif ($_format !== 'jpeg' && $_format !== 'png' && $_format !== 'webp') {
754 //Throw new exception
755 throw new NotFoundHttpException('Invalid thumb format');
756 }
757
758 //Unshort path
759 $path = $this->slugger->unshort($short = $path);
760
761 //Set thumb
762 $thumb = $this->config['cache'].'/'.$this->config['prefixes']['thumb'].$path.'.'.$_format;
763
764 //Without file
765 if (!is_file($path) || !($updated = stat($path)['mtime'])) {
766 //Throw new exception
767 throw new NotFoundHttpException('Unable to get thumb file');
768 }
769
770 //Without thumb up to date file
771 if (!is_file($thumb) || !($mtime = stat($thumb)['mtime']) || $mtime < $updated) {
772 //Without existing thumb path
773 if (!is_dir($dir = dirname($thumb))) {
774 //Create filesystem object
775 $filesystem = new Filesystem();
776
777 try {
778 //Create path
779 //XXX: set as 0775, symfony umask (0022) will reduce rights (0755)
780 //XXX: on CoW filesystems execute a chattr +C before filling
781 $filesystem->mkdir($dir, 0775);
782 } catch (IOExceptionInterface $e) {
783 //Throw error
784 throw new \Exception(sprintf('Output path "%s" do not exists and unable to create it', $dir), 0, $e);
785 }
786 }
787
788 //Create image instance
789 $image = new \Imagick();
790
791 //Read image
792 $image->readImage(realpath($path));
793
794 //Crop using aspect ratio
795 //XXX: for better result upload image directly in aspect ratio :)
796 $image->cropThumbnailImage($width, $height);
797
798 //Strip image exif data and properties
799 $image->stripImage();
800
801 //Set compression quality
802 //TODO: ajust that
803 $image->setImageCompressionQuality(70);
804
805 //Set image format
806 #$image->setImageFormat($_format);
807
808 //Save thumb
809 if (!$image->writeImage($thumb)) {
810 //Throw error
811 throw new \Exception(sprintf('Unable to write image "%s"', $thumb));
812 }
813
814 //Set mtime
815 $mtime = stat($thumb)['mtime'];
816 }
817
818 //Read thumb from cache
819 $response = new BinaryFileResponse($thumb);
820
821 //Set file name
822 $response->setContentDisposition(HeaderUtils::DISPOSITION_INLINE, 'thumb-'.$hash.'.'.$_format);
823
824 //Set etag
825 $response->setEtag(md5($hash));
826
827 //Set last modified
828 $response->setLastModified(\DateTime::createFromFormat('U', strval($mtime)));
829
830 //Set as public
831 $response->setPublic();
832
833 //Return 304 response if not modified
834 $response->isNotModified($request);
835
836 //Return response
837 return $response;
838 }
839 }