vendor/pimcore/pimcore/models/Document/Editable/Image.php line 26

  1. <?php
  2. declare(strict_types=1);
  3. /**
  4.  * Pimcore
  5.  *
  6.  * This source file is available under two different licenses:
  7.  * - GNU General Public License version 3 (GPLv3)
  8.  * - Pimcore Commercial License (PCL)
  9.  * Full copyright and license information is available in
  10.  * LICENSE.md which is distributed with this source code.
  11.  *
  12.  *  @copyright  Copyright (c) Pimcore GmbH (http://www.pimcore.org)
  13.  *  @license    http://www.pimcore.org/license     GPLv3 and PCL
  14.  */
  15. namespace Pimcore\Model\Document\Editable;
  16. use Pimcore\Model;
  17. use Pimcore\Model\Asset;
  18. use Pimcore\Model\Element;
  19. use Pimcore\Model\Element\ElementDescriptor;
  20. use Pimcore\Tool\Serialize;
  21. /**
  22.  * @method \Pimcore\Model\Document\Editable\Dao getDao()
  23.  */
  24. class Image extends Model\Document\Editable implements IdRewriterInterfaceEditmodeDataInterface
  25. {
  26.     /**
  27.      * ID of the referenced image
  28.      *
  29.      * @internal
  30.      */
  31.     protected ?int $id null;
  32.     /**
  33.      * The ALT text of the image
  34.      *
  35.      * @internal
  36.      */
  37.     protected string $alt '';
  38.     /**
  39.      * Contains the imageobject itself
  40.      *
  41.      * @internal
  42.      *
  43.      */
  44.     protected Asset\Image|Element\ElementDescriptor|null $image null;
  45.     /**
  46.      * @internal
  47.      *
  48.      */
  49.     protected bool $cropPercent false;
  50.     /**
  51.      * @internal
  52.      *
  53.      */
  54.     protected float $cropWidth 0.0;
  55.     /**
  56.      * @internal
  57.      *
  58.      */
  59.     protected float $cropHeight 0.0;
  60.     /**
  61.      * @internal
  62.      *
  63.      */
  64.     protected float $cropTop 0.0;
  65.     /**
  66.      * @internal
  67.      *
  68.      */
  69.     protected float $cropLeft 0.0;
  70.     /**
  71.      * @internal
  72.      *
  73.      */
  74.     protected array $hotspots = [];
  75.     /**
  76.      * @internal
  77.      *
  78.      */
  79.     protected array $marker = [];
  80.     /**
  81.      * The Thumbnail config of the image
  82.      *
  83.      * @internal
  84.      */
  85.     protected array|string|null $thumbnail null;
  86.     public function getType(): string
  87.     {
  88.         return 'image';
  89.     }
  90.     public function getData(): mixed
  91.     {
  92.         return [
  93.             'id' => $this->id,
  94.             'alt' => $this->alt,
  95.             'cropPercent' => $this->cropPercent,
  96.             'cropWidth' => $this->cropWidth,
  97.             'cropHeight' => $this->cropHeight,
  98.             'cropTop' => $this->cropTop,
  99.             'cropLeft' => $this->cropLeft,
  100.             'hotspots' => $this->hotspots,
  101.             'marker' => $this->marker,
  102.             'thumbnail' => $this->thumbnail,
  103.         ];
  104.     }
  105.     public function getDataForResource(): array
  106.     {
  107.         return [
  108.             'id' => $this->id,
  109.             'alt' => $this->alt,
  110.             'cropPercent' => $this->cropPercent,
  111.             'cropWidth' => $this->cropWidth,
  112.             'cropHeight' => $this->cropHeight,
  113.             'cropTop' => $this->cropTop,
  114.             'cropLeft' => $this->cropLeft,
  115.             'hotspots' => $this->hotspots,
  116.             'marker' => $this->marker,
  117.             'thumbnail' => $this->thumbnail,
  118.         ];
  119.     }
  120.     public function getDataEditmode(): ?array
  121.     {
  122.         $image $this->getImage();
  123.         if ($image instanceof Asset\Image) {
  124.             $rewritePath = function ($data) {
  125.                 if (!is_array($data)) {
  126.                     return [];
  127.                 }
  128.                 foreach ($data as &$element) {
  129.                     if (array_key_exists('data'$element) && is_array($element['data']) && count($element['data']) > 0) {
  130.                         foreach ($element['data'] as &$metaData) {
  131.                             if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  132.                                 $metaData get_object_vars($metaData);
  133.                             }
  134.                             if (in_array($metaData['type'], ['object''asset''document'])
  135.                             && $el Element\Service::getElementById($metaData['type'], $metaData['value'])) {
  136.                                 $metaData['value'] = $el;
  137.                             }
  138.                             if ($metaData['value'] instanceof Element\ElementInterface) {
  139.                                 $metaData['value'] = $metaData['value']->getRealFullPath();
  140.                             }
  141.                         }
  142.                     }
  143.                 }
  144.                 return $data;
  145.             };
  146.             $marker $rewritePath($this->marker);
  147.             $hotspots $rewritePath($this->hotspots);
  148.             return [
  149.                 'id' => $this->id,
  150.                 'path' => $image->getRealFullPath(),
  151.                 'alt' => $this->alt,
  152.                 'cropPercent' => $this->cropPercent,
  153.                 'cropWidth' => $this->cropWidth,
  154.                 'cropHeight' => $this->cropHeight,
  155.                 'cropTop' => $this->cropTop,
  156.                 'cropLeft' => $this->cropLeft,
  157.                 'hotspots' => $hotspots,
  158.                 'marker' => $marker,
  159.                 'thumbnail' => $this->thumbnail,
  160.                 'predefinedDataTemplates' => $this->getConfig()['predefinedDataTemplates'] ?? null,
  161.             ];
  162.         }
  163.         return null;
  164.     }
  165.     public function getConfig(): array
  166.     {
  167.         $config parent::getConfig();
  168.         if (isset($config['thumbnail']) && !isset($config['focal_point_context_menu_item'])) {
  169.             $thumbConfig Asset\Image\Thumbnail\Config::getByAutoDetect($config['thumbnail']);
  170.             if ($thumbConfig) {
  171.                 foreach ($thumbConfig->getItems() as $item) {
  172.                     if ($item['method'] == 'cover') {
  173.                         $config['focal_point_context_menu_item'] = true;
  174.                         $this->config['focal_point_context_menu_item'] = true;
  175.                         break;
  176.                     }
  177.                 }
  178.             }
  179.         }
  180.         return $config;
  181.     }
  182.     public function frontend()
  183.     {
  184.         $image $this->getImage();
  185.         if ($image instanceof Asset\Image) {
  186.             $thumbnailName $this->config['thumbnail'] ?? null;
  187.             if ($thumbnailName || $this->cropPercent) {
  188.                 // create a thumbnail first
  189.                 $autoName false;
  190.                 $thumbConfig $image->getThumbnail($thumbnailName)->getConfig();
  191.                 if (!$thumbConfig && $this->cropPercent) {
  192.                     $thumbConfig = new Asset\Image\Thumbnail\Config();
  193.                 }
  194.                 if ($this->cropPercent) {
  195.                     $this->applyCustomCropping($thumbConfig);
  196.                     $autoName true;
  197.                 }
  198.                 if (isset($this->config['highResolution']) && $this->config['highResolution'] > 1) {
  199.                     $thumbConfig->setHighResolution($this->config['highResolution']);
  200.                 }
  201.                 // autogenerate a name for the thumbnail because it's different from the original
  202.                 if ($autoName) {
  203.                     $thumbConfig->generateAutoName();
  204.                 }
  205.                 $deferred true;
  206.                 if (isset($this->config['deferred'])) {
  207.                     $deferred $this->config['deferred'];
  208.                 }
  209.                 $thumbnail $image->getThumbnail($thumbConfig$deferred);
  210.             } else {
  211.                 // we're using the thumbnail class only to generate the HTML
  212.                 $thumbnail $image->getThumbnail();
  213.             }
  214.             $attributes array_merge($this->config, [
  215.                 'alt' => $this->alt,
  216.                 'title' => $this->alt,
  217.             ]);
  218.             $removeAttributes = [];
  219.             if (isset($this->config['removeAttributes']) && is_array($this->config['removeAttributes'])) {
  220.                 $removeAttributes $this->config['removeAttributes'];
  221.             }
  222.             // thumbnail's HTML is always generated by the thumbnail itself
  223.             return $thumbnail->getHtml($attributes);
  224.         }
  225.         return '';
  226.     }
  227.     public function setDataFromResource(mixed $data): static
  228.     {
  229.         if (strlen($data) > 2) {
  230.             $data Serialize::unserialize($data);
  231.         }
  232.         $rewritePath = function ($data) {
  233.             if (!is_array($data)) {
  234.                 return [];
  235.             }
  236.             foreach ($data as &$element) {
  237.                 if (array_key_exists('data'$element) && is_array($element['data']) && count($element['data']) > 0) {
  238.                     foreach ($element['data'] as &$metaData) {
  239.                         // this is for backward compatibility (Array vs. MarkerHotspotItem)
  240.                         if (is_array($metaData)) {
  241.                             $metaData = new Element\Data\MarkerHotspotItem($metaData);
  242.                         }
  243.                     }
  244.                 }
  245.             }
  246.             return $data;
  247.         };
  248.         if (array_key_exists('marker'$data) && is_array($data['marker']) && count($data['marker']) > 0) {
  249.             $data['marker'] = $rewritePath($data['marker']);
  250.         }
  251.         if (array_key_exists('hotspots'$data) && is_array($data['hotspots']) && count($data['hotspots']) > 0) {
  252.             $data['hotspots'] = $rewritePath($data['hotspots']);
  253.         }
  254.         $this->setData($data);
  255.         return $this;
  256.     }
  257.     public function setDataFromEditmode(mixed $data): static
  258.     {
  259.         $rewritePath = function ($data) {
  260.             if (!is_array($data)) {
  261.                 return [];
  262.             }
  263.             foreach ($data as &$element) {
  264.                 if (array_key_exists('data'$element) && is_array($element['data']) && count($element['data']) > 0) {
  265.                     foreach ($element['data'] as &$metaData) {
  266.                         $metaData = new Element\Data\MarkerHotspotItem($metaData);
  267.                         if (in_array($metaData['type'], ['object''asset''document'])) {
  268.                             $el Element\Service::getElementByPath($metaData['type'], $metaData->getValue());
  269.                             $metaData['value'] = $el;
  270.                         }
  271.                     }
  272.                 }
  273.             }
  274.             return $data;
  275.         };
  276.         if (is_array($data)) {
  277.             if (array_key_exists('marker'$data) && is_array($data['marker']) && count($data['marker']) > 0) {
  278.                 $data['marker'] = $rewritePath($data['marker']);
  279.             }
  280.             if (array_key_exists('hotspots'$data) && is_array($data['hotspots']) && count($data['hotspots']) > 0) {
  281.                 $data['hotspots'] = $rewritePath($data['hotspots']);
  282.             }
  283.             $this->setData($data);
  284.         }
  285.         return $this;
  286.     }
  287.     private function setData(array $data): void
  288.     {
  289.         $this->id $data['id'] ?? null;
  290.         $this->alt = (string)($data['alt'] ?? '');
  291.         $this->cropPercent $data['cropPercent'] ?? false;
  292.         $this->cropWidth $data['cropWidth'] ?? 0;
  293.         $this->cropHeight $data['cropHeight'] ?? 0;
  294.         $this->cropTop $data['cropTop'] ?? 0;
  295.         $this->cropLeft $data['cropLeft'] ?? 0;
  296.         $this->marker $data['marker'] ?? [];
  297.         $this->hotspots $data['hotspots'] ?? [];
  298.         $this->thumbnail $data['thumbnail'] ?? null;
  299.     }
  300.     public function getText(): string
  301.     {
  302.         return $this->alt;
  303.     }
  304.     public function setText(string $text): void
  305.     {
  306.         $this->alt $text;
  307.     }
  308.     public function getAlt(): string
  309.     {
  310.         return $this->getText();
  311.     }
  312.     public function getThumbnailConfig(): array|string|null
  313.     {
  314.         return $this->thumbnail;
  315.     }
  316.     public function getSrc(): string
  317.     {
  318.         $image $this->getImage();
  319.         if ($image instanceof Asset) {
  320.             return $image->getFullPath();
  321.         }
  322.         return '';
  323.     }
  324.     public function getImage(): Asset\Image|null
  325.     {
  326.         if (!$this->image instanceof Asset\Image) {
  327.             $this->image $this->getId() ? Asset\Image::getById($this->getId()) : null;
  328.         }
  329.         return $this->image;
  330.     }
  331.     /**
  332.      * @return $this
  333.      */
  334.     public function setImage(Asset\Image|ElementDescriptor|null $image): static
  335.     {
  336.         $this->image $image;
  337.         if ($image instanceof Asset\Image) {
  338.             $this->setId($image->getId());
  339.         }
  340.         return $this;
  341.     }
  342.     /**
  343.      *
  344.      * @return $this
  345.      */
  346.     public function setId(?int $id): static
  347.     {
  348.         $this->id $id;
  349.         return $this;
  350.     }
  351.     public function getId(): ?int
  352.     {
  353.         return $this->id;
  354.     }
  355.     public function getThumbnail(array|string|Asset\Image\Thumbnail\Config $confbool $deferred true): Asset\Image\ThumbnailInterface|string
  356.     {
  357.         $image $this->getImage();
  358.         if ($image instanceof Asset\Image) {
  359.             $thumbConfig $image->getThumbnail($conf)->getConfig();
  360.             if ($thumbConfig && $this->cropPercent) {
  361.                 $this->applyCustomCropping($thumbConfig);
  362.                 $thumbConfig->generateAutoName();
  363.             }
  364.             return $image->getThumbnail($thumbConfig$deferred);
  365.         }
  366.         return '';
  367.     }
  368.     private function applyCustomCropping(Asset\Image\Thumbnail\Config $thumbConfig): void
  369.     {
  370.         $cropConfig = [
  371.             'width' => $this->cropWidth,
  372.             'height' => $this->cropHeight,
  373.             'y' => $this->cropTop,
  374.             'x' => $this->cropLeft,
  375.         ];
  376.         $thumbConfig->addItemAt(0'cropPercent'$cropConfig);
  377.         // also crop media query specific configs
  378.         if ($thumbConfig->hasMedias()) {
  379.             foreach ($thumbConfig->getMedias() as $mediaName => $mediaItems) {
  380.                 $thumbConfig->addItemAt(0'cropPercent'$cropConfig$mediaName);
  381.             }
  382.         }
  383.     }
  384.     public function isEmpty(): bool
  385.     {
  386.         $image $this->getImage();
  387.         if ($image instanceof Asset\Image) {
  388.             return false;
  389.         }
  390.         return true;
  391.     }
  392.     public function getCacheTags(Model\Document\PageSnippet $ownerDocument, array $tags = []): array
  393.     {
  394.         $image $this->getImage();
  395.         if ($image instanceof Asset) {
  396.             if (!array_key_exists($image->getCacheTag(), $tags)) {
  397.                 $tags $image->getCacheTags($tags);
  398.             }
  399.         }
  400.         $getMetaDataCacheTags = function ($data$tags) {
  401.             if (!is_array($data)) {
  402.                 return $tags;
  403.             }
  404.             foreach ($data as $element) {
  405.                 if (array_key_exists('data'$element) && is_array($element['data']) && count($element['data']) > 0) {
  406.                     foreach ($element['data'] as $metaData) {
  407.                         if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  408.                             $metaData get_object_vars($metaData);
  409.                         }
  410.                         if ($metaData['value'] instanceof Element\ElementInterface) {
  411.                             if (!array_key_exists($metaData['value']->getCacheTag(), $tags)) {
  412.                                 $tags $metaData['value']->getCacheTags($tags);
  413.                             }
  414.                         }
  415.                     }
  416.                 }
  417.             }
  418.             return $tags;
  419.         };
  420.         $tags $getMetaDataCacheTags($this->marker$tags);
  421.         $tags $getMetaDataCacheTags($this->hotspots$tags);
  422.         return $tags;
  423.     }
  424.     public function resolveDependencies(): array
  425.     {
  426.         $dependencies = [];
  427.         $image $this->getImage();
  428.         if ($image instanceof Asset\Image) {
  429.             $key 'asset_' $image->getId();
  430.             $dependencies[$key] = [
  431.                 'id' => $image->getId(),
  432.                 'type' => 'asset',
  433.             ];
  434.         }
  435.         $getMetaDataDependencies = function ($data$dependencies) {
  436.             if (!is_array($data)) {
  437.                 return $dependencies;
  438.             }
  439.             foreach ($data as $element) {
  440.                 if (array_key_exists('data'$element) && is_array($element['data']) && count($element['data']) > 0) {
  441.                     foreach ($element['data'] as $metaData) {
  442.                         if ($metaData instanceof Element\Data\MarkerHotspotItem) {
  443.                             $metaData get_object_vars($metaData);
  444.                         }
  445.                         if ($metaData['value'] instanceof Element\ElementInterface) {
  446.                             $dependencies[$metaData['type'] . '_' $metaData['value']->getId()] = [
  447.                                 'id' => $metaData['value']->getId(),
  448.                                 'type' => $metaData['type'],
  449.                             ];
  450.                         }
  451.                     }
  452.                 }
  453.             }
  454.             return $dependencies;
  455.         };
  456.         $dependencies $getMetaDataDependencies($this->marker$dependencies);
  457.         $dependencies $getMetaDataDependencies($this->hotspots$dependencies);
  458.         return $dependencies;
  459.     }
  460.     /**
  461.      * @return $this
  462.      */
  463.     public function setCropHeight(float $cropHeight): static
  464.     {
  465.         $this->cropHeight $cropHeight;
  466.         return $this;
  467.     }
  468.     public function getCropHeight(): float
  469.     {
  470.         return $this->cropHeight;
  471.     }
  472.     /**
  473.      * @return $this
  474.      */
  475.     public function setCropLeft(float $cropLeft): static
  476.     {
  477.         $this->cropLeft $cropLeft;
  478.         return $this;
  479.     }
  480.     public function getCropLeft(): float
  481.     {
  482.         return $this->cropLeft;
  483.     }
  484.     /**
  485.      * @return $this
  486.      */
  487.     public function setCropPercent(bool $cropPercent): static
  488.     {
  489.         $this->cropPercent $cropPercent;
  490.         return $this;
  491.     }
  492.     public function getCropPercent(): bool
  493.     {
  494.         return $this->cropPercent;
  495.     }
  496.     /**
  497.      * @return $this
  498.      */
  499.     public function setCropTop(float $cropTop): static
  500.     {
  501.         $this->cropTop $cropTop;
  502.         return $this;
  503.     }
  504.     public function getCropTop(): float
  505.     {
  506.         return $this->cropTop;
  507.     }
  508.     /**
  509.      * @return $this
  510.      */
  511.     public function setCropWidth(float $cropWidth): static
  512.     {
  513.         $this->cropWidth $cropWidth;
  514.         return $this;
  515.     }
  516.     public function getCropWidth(): float
  517.     {
  518.         return $this->cropWidth;
  519.     }
  520.     /**
  521.      * @return $this
  522.      */
  523.     public function setHotspots(array $hotspots): static
  524.     {
  525.         $this->hotspots $hotspots;
  526.         return $this;
  527.     }
  528.     public function getHotspots(): array
  529.     {
  530.         return $this->hotspots;
  531.     }
  532.     /**
  533.      * @return $this
  534.      */
  535.     public function setMarker(array $marker): static
  536.     {
  537.         $this->marker $marker;
  538.         return $this;
  539.     }
  540.     public function getMarker(): array
  541.     {
  542.         return $this->marker;
  543.     }
  544.     public function rewriteIds(array $idMapping): void
  545.     {
  546.         if (array_key_exists('asset'$idMapping) && array_key_exists($this->getId(), $idMapping['asset'])) {
  547.             $this->setId($idMapping['asset'][$this->getId()]);
  548.             // reset marker & hotspot information
  549.             $this->setHotspots([]);
  550.             $this->setMarker([]);
  551.             $this->setCropPercent(false);
  552.             $this->setImage(null);
  553.         }
  554.     }
  555.     public function __sleep(): array
  556.     {
  557.         $finalVars = [];
  558.         $parentVars parent::__sleep();
  559.         $blockedVars = ['image'];
  560.         foreach ($parentVars as $key) {
  561.             if (!in_array($key$blockedVars)) {
  562.                 $finalVars[] = $key;
  563.             }
  564.         }
  565.         return $finalVars;
  566.     }
  567.     /**
  568.      * @internal
  569.      *
  570.      * https://github.com/pimcore/pimcore/issues/15932
  571.      * used for non-nullable properties stored with null
  572.      *
  573.      * @TODO: Remove in Pimcore 12
  574.      *
  575.      */
  576.     public function __unserialize(array $data): void
  577.     {
  578.         foreach (get_object_vars($this) as $property => $value) {
  579.             $this->$property $data["\0*\0".$property] ?? $value;
  580.         }
  581.     }
  582. }