vendor/pimcore/pimcore/models/Asset/Image/Thumbnail.php line 380

Open in your IDE?
  1. <?php
  2. /**
  3.  * Pimcore
  4.  *
  5.  * This source file is available under two different licenses:
  6.  * - GNU General Public License version 3 (GPLv3)
  7.  * - Pimcore Commercial License (PCL)
  8.  * Full copyright and license information is available in
  9.  * LICENSE.md which is distributed with this source code.
  10.  *
  11.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  12.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  13.  */
  14. namespace Pimcore\Model\Asset\Image;
  15. use Pimcore\Event\AssetEvents;
  16. use Pimcore\Event\FrontendEvents;
  17. use Pimcore\Logger;
  18. use Pimcore\Model\Asset;
  19. use Pimcore\Model\Asset\Image;
  20. use Pimcore\Model\Asset\Thumbnail\ImageThumbnailTrait;
  21. use Pimcore\Model\Exception\NotFoundException;
  22. use Pimcore\Tool;
  23. use Symfony\Component\EventDispatcher\GenericEvent;
  24. final class Thumbnail
  25. {
  26.     use ImageThumbnailTrait;
  27.     /**
  28.      * @internal
  29.      *
  30.      * @var bool[]
  31.      */
  32.     protected static $hasListenersCache = [];
  33.     /**
  34.      * @param Image $asset
  35.      * @param string|array|Thumbnail\Config|null $config
  36.      * @param bool $deferred
  37.      */
  38.     public function __construct($asset$config null$deferred true)
  39.     {
  40.         $this->asset $asset;
  41.         $this->deferred $deferred;
  42.         $this->config $this->createConfig($config);
  43.     }
  44.     /**
  45.      * @param bool $deferredAllowed
  46.      * @param bool $cacheBuster
  47.      *
  48.      * @return string
  49.      */
  50.     public function getPath($deferredAllowed true$cacheBuster false)
  51.     {
  52.         $pathReference null;
  53.         if ($this->getConfig()) {
  54.             if ($this->useOriginalFile($this->asset->getFilename()) && $this->getConfig()->isSvgTargetFormatPossible()) {
  55.                 // we still generate the raster image, to get the final size of the thumbnail
  56.                 // we use getRealFullPath() here, to avoid double encoding (getFullPath() returns already encoded path)
  57.                 $pathReference = [
  58.                     'src' => $this->asset->getRealFullPath(),
  59.                     'type' => 'asset',
  60.                 ];
  61.             }
  62.         }
  63.         if (!$pathReference) {
  64.             $pathReference $this->getPathReference($deferredAllowed);
  65.         }
  66.         $path $this->convertToWebPath($pathReference);
  67.         if ($cacheBuster) {
  68.             $path $this->addCacheBuster($path, ['cacheBuster' => true], $this->getAsset());
  69.         }
  70.         if ($this->hasListeners(FrontendEvents::ASSET_IMAGE_THUMBNAIL)) {
  71.             $event = new GenericEvent($this, [
  72.                 'pathReference' => $pathReference,
  73.                 'frontendPath' => $path,
  74.             ]);
  75.             \Pimcore::getEventDispatcher()->dispatch($eventFrontendEvents::ASSET_IMAGE_THUMBNAIL);
  76.             $path $event->getArgument('frontendPath');
  77.         }
  78.         return $path;
  79.     }
  80.     public function getFileSize(): ?int
  81.     {
  82.         $pathReference $this->getPathReference(false);
  83.         if ($pathReference['type'] === 'asset') {
  84.             return $this->asset->getFileSize();
  85.         } elseif (isset($pathReference['storagePath'])) {
  86.             return Tool\Storage::get('thumbnail')->fileSize($pathReference['storagePath']);
  87.         }
  88.         return null;
  89.     }
  90.     /**
  91.      * @return null|resource
  92.      */
  93.     public function getStream()
  94.     {
  95.         $pathReference $this->getPathReference(false);
  96.         if ($pathReference['type'] === 'asset') {
  97.             return $this->asset->getStream();
  98.         } elseif (isset($pathReference['storagePath'])) {
  99.             return Tool\Storage::get('thumbnail')->readStream($pathReference['storagePath']);
  100.         }
  101.         return null;
  102.     }
  103.     /**
  104.      * @param string $eventName
  105.      *
  106.      * @return bool
  107.      */
  108.     protected function hasListeners(string $eventName): bool
  109.     {
  110.         if (!isset(self::$hasListenersCache[$eventName])) {
  111.             self::$hasListenersCache[$eventName] = \Pimcore::getEventDispatcher()->hasListeners($eventName);
  112.         }
  113.         return self::$hasListenersCache[$eventName];
  114.     }
  115.     /**
  116.      * @param string $filename
  117.      *
  118.      * @return bool
  119.      */
  120.     protected function useOriginalFile($filename)
  121.     {
  122.         if ($this->getConfig()) {
  123.             if (!$this->getConfig()->isRasterizeSVG() && preg_match("@\.svgz?$@"$filename)) {
  124.                 return true;
  125.             }
  126.         }
  127.         return false;
  128.     }
  129.     /**
  130.      * @internal
  131.      *
  132.      * @param bool $deferredAllowed
  133.      */
  134.     public function generate($deferredAllowed true)
  135.     {
  136.         $deferred false;
  137.         $generated false;
  138.         if ($this->asset && empty($this->pathReference)) {
  139.             // if no correct thumbnail config is given use the original image as thumbnail
  140.             if (!$this->config) {
  141.                 $this->pathReference = [
  142.                     'type' => 'asset',
  143.                     'src' => $this->asset->getRealFullPath(),
  144.                 ];
  145.             } else {
  146.                 try {
  147.                     $deferred $deferredAllowed && $this->deferred;
  148.                     $this->pathReference Thumbnail\Processor::process($this->asset$this->confignull$deferred$generated);
  149.                 } catch (\Exception $e) {
  150.                     Logger::error("Couldn't create thumbnail of image " $this->asset->getRealFullPath());
  151.                     Logger::error($e);
  152.                 }
  153.             }
  154.         }
  155.         if (empty($this->pathReference)) {
  156.             $this->pathReference = [
  157.                 'type' => 'error',
  158.                 'src' => '/bundles/pimcoreadmin/img/filetype-not-supported.svg',
  159.             ];
  160.         }
  161.         if ($this->hasListeners(AssetEvents::IMAGE_THUMBNAIL)) {
  162.             $event = new GenericEvent($this, [
  163.                 'deferred' => $deferred,
  164.                 'generated' => $generated,
  165.             ]);
  166.             \Pimcore::getEventDispatcher()->dispatch($eventAssetEvents::IMAGE_THUMBNAIL);
  167.         }
  168.     }
  169.     /**
  170.      * @return string Public path to thumbnail image.
  171.      */
  172.     public function __toString()
  173.     {
  174.         return $this->getPath(true);
  175.     }
  176.     /**
  177.      * @param string $path
  178.      * @param array $options
  179.      * @param Asset $asset
  180.      *
  181.      * @return string
  182.      */
  183.     private function addCacheBuster(string $path, array $optionsAsset $asset): string
  184.     {
  185.         if (isset($options['cacheBuster']) && $options['cacheBuster']) {
  186.             if (!str_starts_with($path'http')) {
  187.                 $path '/cache-buster-' $asset->getVersionCount() . $path;
  188.             }
  189.         }
  190.         return $path;
  191.     }
  192.     private function getSourceTagHtml(Image\Thumbnail\Config $thumbConfigstring $mediaQueryImage $image, array $options): string
  193.     {
  194.         $srcSetValues = [];
  195.         $sourceTagAttributes = [];
  196.         foreach ([12] as $highRes) {
  197.             $thumbConfigRes = clone $thumbConfig;
  198.             $thumbConfigRes->selectMedia($mediaQuery);
  199.             $thumbConfigRes->setHighResolution($highRes);
  200.             $thumb $image->getThumbnail($thumbConfigRestrue);
  201.             $descriptor $highRes 'x';
  202.             $srcSetValues[] = $this->addCacheBuster($thumb ' ' $descriptor$options$image);
  203.             if ($this->useOriginalFile($this->asset->getFilename()) && $this->getConfig()->isSvgTargetFormatPossible()) {
  204.                 break;
  205.             }
  206.         }
  207.         $sourceTagAttributes['srcset'] = implode(', '$srcSetValues);
  208.         if ($mediaQuery) {
  209.             $sourceTagAttributes['media'] = $mediaQuery;
  210.             $thumb->reset();
  211.         }
  212.         if (isset($options['previewDataUri'])) {
  213.             $sourceTagAttributes['data-srcset'] = $sourceTagAttributes['srcset'];
  214.             unset($sourceTagAttributes['srcset']);
  215.         }
  216.         $sourceTagAttributes['type'] = $thumb->getMimeType();
  217.         $sourceCallback $options['sourceCallback'] ?? null;
  218.         if ($sourceCallback) {
  219.             $sourceTagAttributes $sourceCallback($sourceTagAttributes);
  220.         }
  221.         return '<source ' array_to_html_attribute_string($sourceTagAttributes) . ' />';
  222.     }
  223.     /**
  224.      * Get generated HTML for displaying the thumbnail image in a HTML document.
  225.      *
  226.      * @param array $options Custom configuration
  227.      *
  228.      * @return string
  229.      */
  230.     public function getHtml($options = [])
  231.     {
  232.         /** @var Image $image */
  233.         $image $this->getAsset();
  234.         $thumbConfig $this->getConfig();
  235.         $pictureTagAttributes $options['pictureAttributes'] ?? []; // this is used for the html5 <picture> element
  236.         if ((isset($options['lowQualityPlaceholder']) && $options['lowQualityPlaceholder']) && !Tool::isFrontendRequestByAdmin()) {
  237.             $previewDataUri $image->getLowQualityPreviewDataUri();
  238.             if (!$previewDataUri) {
  239.                 // use a 1x1 transparent GIF as a fallback if no LQIP exists
  240.                 $previewDataUri 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
  241.             }
  242.             // this gets used in getImagTag() later
  243.             $options['previewDataUri'] = $previewDataUri;
  244.         }
  245.         $isAutoFormat $thumbConfig instanceof Image\Thumbnail\Config strtolower($thumbConfig->getFormat()) === 'source' false;
  246.         if ($isAutoFormat) {
  247.             // ensure the default image is not WebP
  248.             $this->pathReference = [];
  249.         }
  250.         $pictureCallback $options['pictureCallback'] ?? null;
  251.         if ($pictureCallback) {
  252.             $pictureTagAttributes $pictureCallback($pictureTagAttributes);
  253.         }
  254.         $html '<picture ' array_to_html_attribute_string($pictureTagAttributes) . '>' "\n";
  255.         if ($thumbConfig instanceof Image\Thumbnail\Config) {
  256.             $mediaConfigs $thumbConfig->getMedias();
  257.             // currently only max-width is supported, the key of the media is WIDTHw (eg. 400w) according to the srcset specification
  258.             ksort($mediaConfigsSORT_NUMERIC);
  259.             array_push($mediaConfigs$thumbConfig->getItems()); //add the default config at the end - picturePolyfill v4
  260.             foreach ($mediaConfigs as $mediaQuery => $config) {
  261.                 $sourceHtml $this->getSourceTagHtml($thumbConfig$mediaQuery$image$options);
  262.                 if (!empty($sourceHtml)) {
  263.                     if ($isAutoFormat) {
  264.                         $autoFormats \Pimcore::getContainer()->getParameter('pimcore.config')['assets']['image']['thumbnails']['auto_formats'];
  265.                         foreach ($autoFormats as $autoFormat => $autoFormatConfig) {
  266.                             if (self::supportsFormat($autoFormat) && $autoFormatConfig['enabled']) {
  267.                                 $thumbConfigAutoFormat = clone $thumbConfig;
  268.                                 $thumbConfigAutoFormat->setFormat($autoFormat);
  269.                                 if (!empty($autoFormatConfig['quality'])) {
  270.                                     $thumbConfigAutoFormat->setQuality($autoFormatConfig['quality']);
  271.                                 }
  272.                                 $sourceWebP $this->getSourceTagHtml($thumbConfigAutoFormat$mediaQuery$image$options);
  273.                                 if (!empty($sourceWebP)) {
  274.                                     $html .= "\t" $sourceWebP "\n";
  275.                                 }
  276.                             }
  277.                         }
  278.                     }
  279.                     $html .= "\t" $sourceHtml "\n";
  280.                 }
  281.             }
  282.         }
  283.         if (!($options['disableImgTag'] ?? null)) {
  284.             $html .= "\t" $this->getImageTag($options) . "\n";
  285.         }
  286.         $html .= '</picture>' "\n";
  287.         if (isset($options['useDataSrc']) && $options['useDataSrc']) {
  288.             $html preg_replace('/ src(set)?=/i'' data-src$1='$html);
  289.         }
  290.         return $html;
  291.     }
  292.     /**
  293.      * @param array $options
  294.      * @param array $removeAttributes
  295.      *
  296.      * @return string
  297.      */
  298.     public function getImageTag(array $options = [], array $removeAttributes = []): string
  299.     {
  300.         /** @var Image $image */
  301.         $image $this->getAsset();
  302.         $attributes $options['imgAttributes'] ?? [];
  303.         $callback $options['imgCallback'] ?? null;
  304.         if (isset($options['previewDataUri'])) {
  305.             $attributes['src'] = $options['previewDataUri'];
  306.         } else {
  307.             $path $this->getPath(true);
  308.             $attributes['src'] = $this->addCacheBuster($path$options$image);
  309.         }
  310.         if (!isset($options['disableWidthHeightAttributes'])) {
  311.             if ($this->getWidth()) {
  312.                 $attributes['width'] = $this->getWidth();
  313.             }
  314.             if ($this->getHeight()) {
  315.                 $attributes['height'] = $this->getHeight();
  316.             }
  317.         }
  318.         $altText = !empty($options['alt']) ? $options['alt'] : (!empty($attributes['alt']) ? $attributes['alt'] : '');
  319.         $titleText = !empty($options['title']) ? $options['title'] : (!empty($attributes['title']) ? $attributes['title'] : '');
  320.         if (empty($titleText) && (!isset($options['disableAutoTitle']) || !$options['disableAutoTitle'])) {
  321.             if ($image->getMetadata('title')) {
  322.                 $titleText $image->getMetadata('title');
  323.             }
  324.         }
  325.         if (empty($altText) && (!isset($options['disableAutoAlt']) || !$options['disableAutoAlt'])) {
  326.             if ($image->getMetadata('alt')) {
  327.                 $altText $image->getMetadata('alt');
  328.             } elseif (isset($options['defaultalt'])) {
  329.                 $altText $options['defaultalt'];
  330.             } else {
  331.                 $altText $titleText;
  332.             }
  333.         }
  334.         // get copyright from asset
  335.         if ($image->getMetadata('copyright') && (!isset($options['disableAutoCopyright']) || !$options['disableAutoCopyright'])) {
  336.             if (!empty($altText)) {
  337.                 $altText .= ' | ';
  338.             }
  339.             if (!empty($titleText)) {
  340.                 $titleText .= ' | ';
  341.             }
  342.             $altText .= ('© ' $image->getMetadata('copyright'));
  343.             $titleText .= ('© ' $image->getMetadata('copyright'));
  344.         }
  345.         $attributes['alt'] = $altText;
  346.         if (!empty($titleText)) {
  347.             $attributes['title'] = $titleText;
  348.         }
  349.         if (!isset($attributes['loading'])) {
  350.             $attributes['loading'] = 'lazy';
  351.         }
  352.         foreach ($removeAttributes as $attribute) {
  353.             unset($attributes[$attribute]);
  354.         }
  355.         if ($callback) {
  356.             $attributes $callback($attributes);
  357.         }
  358.         $htmlImgTag '';
  359.         if (!empty($attributes)) {
  360.             $htmlImgTag '<img ' array_to_html_attribute_string($attributes) . ' />';
  361.         }
  362.         return $htmlImgTag;
  363.     }
  364.     /**
  365.      * @param string $name
  366.      * @param int $highRes
  367.      *
  368.      * @return Thumbnail
  369.      *
  370.      * @throws \Exception
  371.      */
  372.     public function getMedia($name$highRes 1)
  373.     {
  374.         $thumbConfig $this->getConfig();
  375.         $mediaConfigs $thumbConfig->getMedias();
  376.         if (isset($mediaConfigs[$name])) {
  377.             $thumbConfigRes = clone $thumbConfig;
  378.             $thumbConfigRes->selectMedia($name);
  379.             $thumbConfigRes->setHighResolution($highRes);
  380.             $thumbConfigRes->setMedias([]);
  381.             /** @var Image $asset */
  382.             $asset $this->getAsset();
  383.             $thumb $asset->getThumbnail($thumbConfigRes);
  384.             return $thumb;
  385.         } else {
  386.             throw new \Exception("Media query '" $name "' doesn't exist in thumbnail configuration: " $thumbConfig->getName());
  387.         }
  388.     }
  389.     /**
  390.      * Get a thumbnail image configuration.
  391.      *
  392.      * @param string|array|Thumbnail\Config $selector Name, array or object describing a thumbnail configuration.
  393.      *
  394.      * @return Thumbnail\Config
  395.      *
  396.      * @throws NotFoundException
  397.      */
  398.     private function createConfig($selector)
  399.     {
  400.         $thumbnailConfig Thumbnail\Config::getByAutoDetect($selector);
  401.         if (!empty($selector) && $thumbnailConfig === null) {
  402.             throw new NotFoundException('Thumbnail definition "' . (is_string($selector) ? $selector '') . '" does not exist');
  403.         }
  404.         return $thumbnailConfig;
  405.     }
  406. }