vendor/pimcore/pimcore/models/Asset.php line 263

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;
  15. use Doctrine\DBAL\Exception\DeadlockException;
  16. use Exception;
  17. use function is_array;
  18. use League\Flysystem\FilesystemException;
  19. use League\Flysystem\FilesystemOperator;
  20. use League\Flysystem\UnableToMoveFile;
  21. use League\Flysystem\UnableToRetrieveMetadata;
  22. use Pimcore;
  23. use Pimcore\Cache;
  24. use Pimcore\Cache\RuntimeCache;
  25. use Pimcore\Config;
  26. use Pimcore\Event\AssetEvents;
  27. use Pimcore\Event\FrontendEvents;
  28. use Pimcore\Event\Model\AssetEvent;
  29. use Pimcore\File;
  30. use Pimcore\Helper\TemporaryFileHelperTrait;
  31. use Pimcore\Loader\ImplementationLoader\Exception\UnsupportedException;
  32. use Pimcore\Localization\LocaleServiceInterface;
  33. use Pimcore\Logger;
  34. use Pimcore\Messenger\AssetUpdateTasksMessage;
  35. use Pimcore\Messenger\VersionDeleteMessage;
  36. use Pimcore\Model\Asset\Dao;
  37. use Pimcore\Model\Asset\Folder;
  38. use Pimcore\Model\Asset\Image\Thumbnail\Config as ThumbnailConfig;
  39. use Pimcore\Model\Asset\Listing;
  40. use Pimcore\Model\Asset\MetaData\ClassDefinition\Data\Data;
  41. use Pimcore\Model\Asset\MetaData\ClassDefinition\Data\DataDefinitionInterface;
  42. use Pimcore\Model\Element\DuplicateFullPathException;
  43. use Pimcore\Model\Element\ElementInterface;
  44. use Pimcore\Model\Element\Service;
  45. use Pimcore\Model\Element\Traits\ScheduledTasksTrait;
  46. use Pimcore\Model\Element\ValidationException;
  47. use Pimcore\Model\Exception\NotFoundException;
  48. use Pimcore\Tool;
  49. use Pimcore\Tool\Serialize;
  50. use Pimcore\Tool\Storage;
  51. use stdClass;
  52. use Symfony\Component\EventDispatcher\GenericEvent;
  53. use Symfony\Component\Mime\MimeTypes;
  54. /**
  55.  * @method Dao getDao()
  56.  * @method bool __isBasedOnLatestData()
  57.  * @method int getChildAmount($user = null)
  58.  * @method string|null getCurrentFullPath()
  59.  */
  60. class Asset extends Element\AbstractElement
  61. {
  62.     use ScheduledTasksTrait;
  63.     use TemporaryFileHelperTrait;
  64.     /**
  65.      * all possible types of assets
  66.      *
  67.      * @internal
  68.      *
  69.      * @var array
  70.      */
  71.     public static $types = ['folder''image''text''audio''video''document''archive''unknown'];
  72.     /**
  73.      * @internal
  74.      *
  75.      * @var string
  76.      */
  77.     protected $type '';
  78.     /**
  79.      * @internal
  80.      *
  81.      * @var string|null
  82.      */
  83.     protected $filename;
  84.     /**
  85.      * @internal
  86.      *
  87.      * @var string|null
  88.      */
  89.     protected $mimetype;
  90.     /**
  91.      * @internal
  92.      *
  93.      * @var resource|null
  94.      */
  95.     protected $stream;
  96.     /**
  97.      * @internal
  98.      *
  99.      * @var array|null
  100.      */
  101.     protected $versions null;
  102.     /**
  103.      * @internal
  104.      *
  105.      * @var array
  106.      */
  107.     protected $metadata = [];
  108.     /**
  109.      * List of some custom settings  [key] => value
  110.      * Here there can be stored some data, eg. the video thumbnail files, ...  of the asset, ...
  111.      *
  112.      * @internal
  113.      *
  114.      * @var array
  115.      */
  116.     protected $customSettings = [];
  117.     /**
  118.      * @internal
  119.      *
  120.      * @var bool
  121.      */
  122.     protected $hasMetaData false;
  123.     /**
  124.      * @internal
  125.      *
  126.      * @var array|null
  127.      */
  128.     protected $siblings;
  129.     /**
  130.      * @internal
  131.      *
  132.      * @var bool|null
  133.      */
  134.     protected $hasSiblings;
  135.     /**
  136.      * @internal
  137.      *
  138.      * @var bool
  139.      */
  140.     protected $dataChanged false;
  141.     /**
  142.      * @internal
  143.      *
  144.      * @var int|null
  145.      */
  146.     protected ?int $dataModificationDate null;
  147.     /**
  148.      * @return int|null
  149.      */
  150.     public function getDataModificationDate(): ?int
  151.     {
  152.         return $this->dataModificationDate;
  153.     }
  154.     /**
  155.      * @param int|null $dataModificationDate
  156.      *
  157.      * @return $this
  158.      */
  159.     public function setDataModificationDate(?int $dataModificationDate): static
  160.     {
  161.         $this->dataModificationDate $dataModificationDate;
  162.         return $this;
  163.     }
  164.     /**
  165.      * {@inheritdoc}
  166.      */
  167.     protected function getBlockedVars(): array
  168.     {
  169.         $blockedVars = ['scheduledTasks''hasChildren''versions''parent''stream'];
  170.         if (!$this->isInDumpState()) {
  171.             // for caching asset
  172.             $blockedVars array_merge($blockedVars, ['children''properties']);
  173.         }
  174.         return $blockedVars;
  175.     }
  176.     /**
  177.      *
  178.      * @return array
  179.      */
  180.     public static function getTypes()
  181.     {
  182.         return self::$types;
  183.     }
  184.     /**
  185.      * Static helper to get an asset by the passed path
  186.      *
  187.      * @param string $path
  188.      * @param array|bool $force
  189.      *
  190.      * @return static|null
  191.      */
  192.     public static function getByPath($path$force false)
  193.     {
  194.         if (!$path) {
  195.             return null;
  196.         }
  197.         $path Element\Service::correctPath($path);
  198.         try {
  199.             $asset = new static();
  200.             $asset->getDao()->getByPath($path);
  201.             return static::getById(
  202.                 $asset->getId(),
  203.                 Service::prepareGetByIdParams($force__METHOD__func_num_args() > 1)
  204.             );
  205.         } catch (NotFoundException $e) {
  206.             return null;
  207.         }
  208.     }
  209.     /**
  210.      * @internal
  211.      *
  212.      * @param Asset $asset
  213.      *
  214.      * @return bool
  215.      */
  216.     protected static function typeMatch(Asset $asset)
  217.     {
  218.         $staticType = static::class;
  219.         if ($staticType !== Asset::class) {
  220.             if (!$asset instanceof $staticType) {
  221.                 return false;
  222.             }
  223.         }
  224.         return true;
  225.     }
  226.     /**
  227.      * @param int|string $id
  228.      * @param array|bool $force
  229.      *
  230.      * @return static|null
  231.      */
  232.     public static function getById($id$force false)
  233.     {
  234.         if (!is_numeric($id) || $id 1) {
  235.             return null;
  236.         }
  237.         $id = (int)$id;
  238.         $cacheKey self::getCacheKey($id);
  239.         $params Service::prepareGetByIdParams($force__METHOD__func_num_args() > 1);
  240.         if (!$params['force'] && RuntimeCache::isRegistered($cacheKey)) {
  241.             $asset RuntimeCache::get($cacheKey);
  242.             if ($asset && static::typeMatch($asset)) {
  243.                 return $asset;
  244.             }
  245.         }
  246.         if ($params['force'] || !($asset Cache::load($cacheKey))) {
  247.             $asset = new static();
  248.             try {
  249.                 $asset->getDao()->getById($id);
  250.                 $className 'Pimcore\\Model\\Asset\\' ucfirst($asset->getType());
  251.                 /** @var Asset $newAsset */
  252.                 $newAsset self::getModelFactory()->build($className);
  253.                 if (get_class($asset) !== get_class($newAsset)) {
  254.                     $asset $newAsset;
  255.                     $asset->getDao()->getById($id);
  256.                 }
  257.                 RuntimeCache::set($cacheKey$asset);
  258.                 $asset->__setDataVersionTimestamp($asset->getModificationDate());
  259.                 $asset->resetDirtyMap();
  260.                 Cache::save($asset$cacheKey);
  261.             } catch (NotFoundException $e) {
  262.                 $asset null;
  263.             }
  264.         } else {
  265.             RuntimeCache::set($cacheKey$asset);
  266.         }
  267.         if ($asset && static::typeMatch($asset)) {
  268.             \Pimcore::getEventDispatcher()->dispatch(
  269.                 new AssetEvent($asset, ['params' => $params]),
  270.                 AssetEvents::POST_LOAD
  271.             );
  272.         }
  273.         return $asset;
  274.     }
  275.     /**
  276.      * @param int $parentId
  277.      * @param array $data
  278.      * @param bool $save
  279.      *
  280.      * @return Asset
  281.      */
  282.     public static function create($parentId$data = [], $save true)
  283.     {
  284.         // create already the real class for the asset type, this is especially for images, because a system-thumbnail
  285.         // (tree) is generated immediately after creating an image
  286.         $class Asset::class;
  287.         if (
  288.             array_key_exists('filename'$data) &&
  289.             (
  290.                 array_key_exists('data'$data) ||
  291.                 array_key_exists('sourcePath'$data) ||
  292.                 array_key_exists('stream'$data)
  293.             )
  294.         ) {
  295.             if (array_key_exists('data'$data) || array_key_exists('stream'$data)) {
  296.                 $fileExtension File::getFileExtension($data['filename']);
  297.                 $tmpFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/asset-create-tmp-file-' uniqid() . '.' $fileExtension;
  298.                 if (!str_starts_with($tmpFilePIMCORE_SYSTEM_TEMP_DIRECTORY)) {
  299.                     throw new \InvalidArgumentException('Invalid filename');
  300.                 }
  301.                 if (array_key_exists('data'$data)) {
  302.                     File::put($tmpFile$data['data']);
  303.                     self::checkMaxPixels($tmpFile$data);
  304.                     $mimeType MimeTypes::getDefault()->guessMimeType($tmpFile);
  305.                     unlink($tmpFile);
  306.                 } else {
  307.                     $streamMeta stream_get_meta_data($data['stream']);
  308.                     if (file_exists($streamMeta['uri'])) {
  309.                         // stream is a local file, so we don't have to write a tmp file
  310.                         self::checkMaxPixels($streamMeta['uri'], $data);
  311.                         $mimeType MimeTypes::getDefault()->guessMimeType($streamMeta['uri']);
  312.                     } else {
  313.                         // write a tmp file because the stream isn't a pointer to the local filesystem
  314.                         $isRewindable = @rewind($data['stream']);
  315.                         $dest fopen($tmpFile'w+'falseFile::getContext());
  316.                         stream_copy_to_stream($data['stream'], $dest);
  317.                         self::checkMaxPixels($tmpFile$data);
  318.                         $mimeType MimeTypes::getDefault()->guessMimeType($tmpFile);
  319.                         if (!$isRewindable) {
  320.                             $data['stream'] = $dest;
  321.                         } else {
  322.                             fclose($dest);
  323.                             unlink($tmpFile);
  324.                         }
  325.                     }
  326.                 }
  327.             } else {
  328.                 if (is_dir($data['sourcePath'])) {
  329.                     $mimeType 'directory';
  330.                 } else {
  331.                     self::checkMaxPixels($data['sourcePath'], $data);
  332.                     $mimeType MimeTypes::getDefault()->guessMimeType($data['sourcePath']);
  333.                     if (is_file($data['sourcePath'])) {
  334.                         $data['stream'] = fopen($data['sourcePath'], 'rb'falseFile::getContext());
  335.                     }
  336.                 }
  337.                 unset($data['sourcePath']);
  338.             }
  339.             $type self::getTypeFromMimeMapping($mimeType$data['filename']);
  340.             $class '\\Pimcore\\Model\\Asset\\' ucfirst($type);
  341.             if (array_key_exists('type'$data)) {
  342.                 unset($data['type']);
  343.             }
  344.         }
  345.         /** @var Asset $asset */
  346.         $asset self::getModelFactory()->build($class);
  347.         $asset->setParentId($parentId);
  348.         self::checkCreateData($data);
  349.         $asset->setValues($data);
  350.         if ($save) {
  351.             $asset->save();
  352.         }
  353.         return $asset;
  354.     }
  355.     private static function checkMaxPixels(string $localPath, array $data): void
  356.     {
  357.         // this check is intentionally done in Asset::create() because in Asset::update() it would result
  358.         // in an additional download from remote storage if configured, so in terms of performance
  359.         // this is the more efficient way
  360.         $maxPixels = (int) Pimcore::getContainer()->getParameter('pimcore.config')['assets']['image']['max_pixels'];
  361.         if ($size = @getimagesize($localPath)) {
  362.             $imagePixels = (int) ($size[0] * $size[1]);
  363.             if ($imagePixels $maxPixels) {
  364.                 Logger::error("Image to be created {$localPath} (temp. path) exceeds max pixel size of {$maxPixels}, you can change the value in config pimcore.assets.image.max_pixels");
  365.                 $diff sqrt(+ ($maxPixels $imagePixels));
  366.                 $suggestion_0 = (int) round($size[0] / $diff, -2PHP_ROUND_HALF_DOWN);
  367.                 $suggestion_1 = (int) round($size[1] / $diff, -2PHP_ROUND_HALF_DOWN);
  368.                 $mp $maxPixels 1_000_000;
  369.                 // unlink file before throwing exception
  370.                 unlink($localPath);
  371.                 throw new ValidationException("<p>Image dimensions of <em>{$data['filename']}</em> are too large.</p>
  372. <p>Max size: <code>{$mp}</code> <abbr title='Million pixels'>Megapixels</abbr></p>
  373. <p>Suggestion: resize to <code>{$suggestion_0}&times;{$suggestion_1}</code> pixels or smaller.</p>");
  374.             }
  375.         }
  376.     }
  377.     /**
  378.      * @param array $config
  379.      *
  380.      * @return Listing
  381.      *
  382.      * @throws Exception
  383.      */
  384.     public static function getList($config = [])
  385.     {
  386.         if (!is_array($config)) {
  387.             throw new \RuntimeException('Unable to initiate list class - please provide valid configuration array');
  388.         }
  389.         $listClass Listing::class;
  390.         /** @var Listing $list */
  391.         $list self::getModelFactory()->build($listClass);
  392.         $list->setValues($config);
  393.         return $list;
  394.     }
  395.     /**
  396.      * @deprecated will be removed in Pimcore 11
  397.      *
  398.      * @param array $config
  399.      *
  400.      * @return int total count
  401.      */
  402.     public static function getTotalCount($config = [])
  403.     {
  404.         $list = static::getList($config);
  405.         $count $list->getTotalCount();
  406.         return $count;
  407.     }
  408.     /**
  409.      * @internal
  410.      *
  411.      * @param string $mimeType
  412.      * @param string $filename
  413.      *
  414.      * @return string
  415.      */
  416.     public static function getTypeFromMimeMapping($mimeType$filename)
  417.     {
  418.         if ($mimeType == 'directory') {
  419.             return 'folder';
  420.         }
  421.         $type null;
  422.         $mappings = [
  423.             'unknown' => ["/\.stp$/"],
  424.             'image' => ['/image/'"/\.eps$/""/\.ai$/""/\.svgz$/""/\.pcx$/""/\.iff$/""/\.pct$/",
  425.                 "/\.wmf$/"'/photoshop/'],
  426.             'text' => ['/text\//''/xml$/''/\.json$/'],
  427.             'audio' => ['/audio/'],
  428.             'video' => ['/video/'],
  429.             'document' => ['/msword/''/pdf/''/powerpoint/''/office/''/excel/''/opendocument/'],
  430.             'archive' => ['/zip/''/tar/'],
  431.         ];
  432.         foreach ($mappings as $assetType => $patterns) {
  433.             foreach ($patterns as $pattern) {
  434.                 if (preg_match($pattern$mimeType ' .' File::getFileExtension($filename))) {
  435.                     $type $assetType;
  436.                     break;
  437.                 }
  438.             }
  439.             // break at first match
  440.             if ($type) {
  441.                 break;
  442.             }
  443.         }
  444.         if (!$type) {
  445.             $type 'unknown';
  446.         }
  447.         return $type;
  448.     }
  449.     /**
  450.      * {@inheritdoc}
  451.      */
  452.     public function save()
  453.     {
  454.         // additional parameters (e.g. "versionNote" for the version note)
  455.         $params = [];
  456.         if (func_num_args() && is_array(func_get_arg(0))) {
  457.             $params func_get_arg(0);
  458.         }
  459.         $isUpdate false;
  460.         $differentOldPath null;
  461.         try {
  462.             $preEvent = new AssetEvent($this$params);
  463.             if ($this->getId()) {
  464.                 $isUpdate true;
  465.                 $this->dispatchEvent($preEventAssetEvents::PRE_UPDATE);
  466.             } else {
  467.                 $this->dispatchEvent($preEventAssetEvents::PRE_ADD);
  468.             }
  469.             $params $preEvent->getArguments();
  470.             $this->correctPath();
  471.             $params['isUpdate'] = $isUpdate// need for $this->update() for certain types (image, video, document)
  472.             // we wrap the save actions in a loop here, to restart the database transactions in the case it fails
  473.             // if a transaction fails it gets restarted $maxRetries times, then the exception is thrown out
  474.             // especially useful to avoid problems with deadlocks in multi-threaded environments (forked workers, ...)
  475.             $maxRetries 5;
  476.             for ($retries 0$retries $maxRetries$retries++) {
  477.                 $this->beginTransaction();
  478.                 try {
  479.                     if (!$isUpdate) {
  480.                         $this->getDao()->create();
  481.                     }
  482.                     // get the old path from the database before the update is done
  483.                     $oldPath null;
  484.                     if ($isUpdate) {
  485.                         $oldPath $this->getDao()->getCurrentFullPath();
  486.                     }
  487.                     $this->update($params);
  488.                     $storage Storage::get('asset');
  489.                     // if the old path is different from the new path, update all children
  490.                     $updatedChildren = [];
  491.                     if ($oldPath && $oldPath != $this->getRealFullPath()) {
  492.                         $differentOldPath $oldPath;
  493.                         try {
  494.                             $storage->move($oldPath$this->getRealFullPath());
  495.                         } catch (UnableToMoveFile $e) {
  496.                             //update children, if unable to move parent
  497.                             $this->updateChildPaths($storage$oldPath);
  498.                         }
  499.                         $this->getDao()->updateWorkspaces();
  500.                         $updatedChildren $this->getDao()->updateChildPaths($oldPath);
  501.                         $this->relocateThumbnails($oldPath);
  502.                     }
  503.                     // lastly create a new version if necessary
  504.                     // this has to be after the registry update and the DB update, otherwise this would cause problem in the
  505.                     // $this->__wakeUp() method which is called by $version->save(); (path correction for version restore)
  506.                     if ($this->getType() != 'folder') {
  507.                         $this->saveVersion(falsefalse, isset($params['versionNote']) ? $params['versionNote'] : null);
  508.                     }
  509.                     $this->commit();
  510.                     break; // transaction was successfully completed, so we cancel the loop here -> no restart required
  511.                 } catch (Exception $e) {
  512.                     try {
  513.                         $this->rollBack();
  514.                     } catch (Exception $er) {
  515.                         // PDO adapter throws exceptions if rollback fails
  516.                         Logger::error((string) $er);
  517.                     }
  518.                     // we try to start the transaction $maxRetries times again (deadlocks, ...)
  519.                     if ($e instanceof DeadlockException && $retries < ($maxRetries 1)) {
  520.                         $run $retries 1;
  521.                         $waitTime rand(15) * 100000// microseconds
  522.                         Logger::warn('Unable to finish transaction (' $run ". run) because of the following reason '" $e->getMessage() . "'. --> Retrying in " $waitTime ' microseconds ... (' . ($run 1) . ' of ' $maxRetries ')');
  523.                         usleep($waitTime); // wait specified time until we restart the transaction
  524.                     } else {
  525.                         // if the transaction still fail after $maxRetries retries, we throw out the exception
  526.                         throw $e;
  527.                     }
  528.                 }
  529.             }
  530.             $additionalTags = [];
  531.             if (isset($updatedChildren) && is_array($updatedChildren)) {
  532.                 foreach ($updatedChildren as $assetId) {
  533.                     $tag 'asset_' $assetId;
  534.                     $additionalTags[] = $tag;
  535.                     // remove the child also from registry (internal cache) to avoid path inconsistencies during long running scripts, such as CLI
  536.                     RuntimeCache::set($tagnull);
  537.                 }
  538.             }
  539.             $this->clearDependentCache($additionalTags);
  540.             if ($this->getDataChanged()) {
  541.                 if (in_array($this->getType(), ['image''video''document'])) {
  542.                     $this->addToUpdateTaskQueue();
  543.                 }
  544.             }
  545.             $this->setDataChanged(false);
  546.             $postEvent = new AssetEvent($this$params);
  547.             if ($isUpdate) {
  548.                 if ($differentOldPath) {
  549.                     $postEvent->setArgument('oldPath'$differentOldPath);
  550.                 }
  551.                 $this->dispatchEvent($postEventAssetEvents::POST_UPDATE);
  552.             } else {
  553.                 $this->dispatchEvent($postEventAssetEvents::POST_ADD);
  554.             }
  555.             return $this;
  556.         } catch (Exception $e) {
  557.             $failureEvent = new AssetEvent($this$params);
  558.             $failureEvent->setArgument('exception'$e);
  559.             if ($isUpdate) {
  560.                 $this->dispatchEvent($failureEventAssetEvents::POST_UPDATE_FAILURE);
  561.             } else {
  562.                 $this->dispatchEvent($failureEventAssetEvents::POST_ADD_FAILURE);
  563.             }
  564.             throw $e;
  565.         }
  566.     }
  567.     /**
  568.      * @internal
  569.      *
  570.      * @throws Exception|DuplicateFullPathException
  571.      */
  572.     public function correctPath()
  573.     {
  574.         // set path
  575.         if ($this->getId() != 1) { // not for the root node
  576.             if (!Element\Service::isValidKey($this->getKey(), 'asset')) {
  577.                 throw new Exception("invalid filename '" $this->getKey() . "' for asset with id [ " $this->getId() . ' ]');
  578.             }
  579.             if ($this->getParentId() == $this->getId()) {
  580.                 throw new Exception("ParentID and ID is identical, an element can't be the parent of itself.");
  581.             }
  582.             if ($this->getFilename() === '..' || $this->getFilename() === '.') {
  583.                 throw new Exception('Cannot create asset called ".." or "."');
  584.             }
  585.             $parent Asset::getById($this->getParentId());
  586.             if ($parent) {
  587.                 // use the parent's path from the database here (getCurrentFullPath),
  588.                 //to ensure the path really exists and does not rely on the path
  589.                 // that is currently in the parent asset (in memory),
  590.                 //because this might have changed but wasn't not saved
  591.                 $this->setPath(str_replace('//''/'$parent->getCurrentFullPath() . '/'));
  592.             } else {
  593.                 trigger_deprecation(
  594.                     'pimcore/pimcore',
  595.                     '10.5',
  596.                     'Fallback for parentId will be removed in Pimcore 11.',
  597.                 );
  598.                 // parent document doesn't exist anymore, set the parent to to root
  599.                 $this->setParentId(1);
  600.                 $this->setPath('/');
  601.             }
  602.         } elseif ($this->getId() == 1) {
  603.             // some data in root node should always be the same
  604.             $this->setParentId(0);
  605.             $this->setPath('/');
  606.             $this->setFilename('');
  607.             $this->setType('folder');
  608.         }
  609.         // do not allow PHP and .htaccess files
  610.         if (preg_match("@\.ph(p[\d+]?|t|tml|ps|ar)$@i"$this->getFilename()) || $this->getFilename() == '.htaccess') {
  611.             $this->setFilename($this->getFilename() . '.txt');
  612.         }
  613.         if (mb_strlen($this->getFilename()) > 255) {
  614.             throw new Exception('Filenames longer than 255 characters are not allowed');
  615.         }
  616.         if (Asset\Service::pathExists($this->getRealFullPath())) {
  617.             $duplicate Asset::getByPath($this->getRealFullPath());
  618.             if ($duplicate instanceof Asset && $duplicate->getId() != $this->getId()) {
  619.                 $duplicateFullPathException = new DuplicateFullPathException('Duplicate full path [ ' $this->getRealFullPath() . ' ] - cannot save asset');
  620.                 $duplicateFullPathException->setDuplicateElement($duplicate);
  621.                 throw $duplicateFullPathException;
  622.             }
  623.         }
  624.         $this->validatePathLength();
  625.     }
  626.     /**
  627.      * @internal
  628.      *
  629.      * @param array $params additional parameters (e.g. "versionNote" for the version note)
  630.      *
  631.      * @throws Exception
  632.      */
  633.     protected function update($params = [])
  634.     {
  635.         $storage Storage::get('asset');
  636.         $this->updateModificationInfos();
  637.         $path $this->getRealFullPath();
  638.         $typeChanged false;
  639.         if ($this->getType() != 'folder') {
  640.             if ($this->getDataChanged()) {
  641.                 $src $this->getStream();
  642.                 if (!$storage->fileExists($path) || !stream_is_local($storage->readStream($path))) {
  643.                     // write stream directly if target file doesn't exist or if target is a remote storage
  644.                     // this is because we don't have hardlinks there, so we don't need to consider them (see below)
  645.                     $storage->writeStream($path$src);
  646.                 } else {
  647.                     // We don't open a stream on existing files, because they could be possibly used by versions
  648.                     // using hardlinks, so it's safer to write them to a temp file first, so the inode and therefore
  649.                     // also the versioning information persists. Using the stream on the existing file would overwrite the
  650.                     // contents of the inode and therefore leads to wrong version data
  651.                     $pathInfo pathinfo($this->getFilename());
  652.                     $tempFilePath $this->getRealPath() . uniqid('temp_');
  653.                     if ($pathInfo['extension'] ?? false) {
  654.                         $tempFilePath .= '.' $pathInfo['extension'];
  655.                     }
  656.                     $storage->writeStream($tempFilePath$src);
  657.                     $storage->delete($path);
  658.                     $storage->move($tempFilePath$path);
  659.                 }
  660.                 // delete old legacy file if exists
  661.                 $dbPath $this->getDao()->getCurrentFullPath();
  662.                 if ($dbPath !== $path && $storage->fileExists($dbPath)) {
  663.                     $storage->delete($dbPath);
  664.                 }
  665.                 $this->closeStream(); // set stream to null, so that the source stream isn't used anymore after saving
  666.                 try {
  667.                     $mimeType $storage->mimeType($path);
  668.                 } catch(UnableToRetrieveMetadata $e) {
  669.                     $mimeType 'application/octet-stream';
  670.                 }
  671.                 $this->setMimeType($mimeType);
  672.                 // set type
  673.                 $type self::getTypeFromMimeMapping($mimeType$this->getFilename());
  674.                 if ($type != $this->getType()) {
  675.                     $this->setType($type);
  676.                     $typeChanged true;
  677.                 }
  678.                 // not only check if the type is set but also if the implementation can be found
  679.                 $className 'Pimcore\\Model\\Asset\\' ucfirst($this->getType());
  680.                 if (!self::getModelFactory()->supports($className)) {
  681.                     throw new Exception('unable to resolve asset implementation with type: ' $this->getType());
  682.                 }
  683.             }
  684.         } else {
  685.             $storage->createDirectory($path);
  686.         }
  687.         if (!$this->getType()) {
  688.             $this->setType('unknown');
  689.         }
  690.         $this->postPersistData();
  691.         // save properties
  692.         $this->getProperties();
  693.         $this->getDao()->deleteAllProperties();
  694.         if (is_array($this->getProperties()) && count($this->getProperties()) > 0) {
  695.             foreach ($this->getProperties() as $property) {
  696.                 if (!$property->getInherited()) {
  697.                     $property->setDao(null);
  698.                     $property->setCid($this->getId());
  699.                     $property->setCtype('asset');
  700.                     $property->setCpath($this->getRealFullPath());
  701.                     $property->save();
  702.                 }
  703.             }
  704.         }
  705.         // save dependencies
  706.         $d = new Dependency();
  707.         $d->setSourceType('asset');
  708.         $d->setSourceId($this->getId());
  709.         foreach ($this->resolveDependencies() as $requirement) {
  710.             if ($requirement['id'] == $this->getId() && $requirement['type'] == 'asset') {
  711.                 // dont't add a reference to yourself
  712.                 continue;
  713.             } else {
  714.                 $d->addRequirement($requirement['id'], $requirement['type']);
  715.             }
  716.         }
  717.         $d->save();
  718.         $this->getDao()->update();
  719.         //set asset to registry
  720.         $cacheKey self::getCacheKey($this->getId());
  721.         RuntimeCache::set($cacheKey$this);
  722.         if (static::class === Asset::class || $typeChanged) {
  723.             // get concrete type of asset
  724.             // this is important because at the time of creating an asset it's not clear which type (resp. class) it will have
  725.             // the type (image, document, ...) depends on the mime-type
  726.             RuntimeCache::set($cacheKeynull);
  727.             Asset::getById($this->getId()); // call it to load it to the runtime cache again
  728.         }
  729.         $this->closeStream();
  730.     }
  731.     /**
  732.      * @internal
  733.      */
  734.     protected function postPersistData()
  735.     {
  736.         // hook for the save process, can be overwritten in implementations, such as Image
  737.     }
  738.     /**
  739.      * @param bool $setModificationDate
  740.      * @param bool $saveOnlyVersion
  741.      * @param string $versionNote version note
  742.      *
  743.      * @return null|Version
  744.      *
  745.      * @throws Exception
  746.      */
  747.     public function saveVersion($setModificationDate true$saveOnlyVersion true$versionNote null)
  748.     {
  749.         try {
  750.             // hook should be also called if "save only new version" is selected
  751.             if ($saveOnlyVersion) {
  752.                 $event = new AssetEvent($this, [
  753.                     'saveVersionOnly' => true,
  754.                 ]);
  755.                 $this->dispatchEvent($eventAssetEvents::PRE_UPDATE);
  756.             }
  757.             // set date
  758.             if ($setModificationDate) {
  759.                 $this->setModificationDate(time());
  760.             }
  761.             // scheduled tasks are saved always, they are not versioned!
  762.             $this->saveScheduledTasks();
  763.             // create version
  764.             $version null;
  765.             // only create a new version if there is at least 1 allowed
  766.             // or if saveVersion() was called directly (it's a newer version of the asset)
  767.             $assetsConfig Config::getSystemConfiguration('assets');
  768.             if ((is_null($assetsConfig['versions']['days'] ?? null) && is_null($assetsConfig['versions']['steps'] ?? null))
  769.                 || (!empty($assetsConfig['versions']['steps']))
  770.                 || !empty($assetsConfig['versions']['days'])
  771.                 || $setModificationDate) {
  772.                 $saveStackTrace = !($assetsConfig['versions']['disable_stack_trace'] ?? false);
  773.                 $version $this->doSaveVersion($versionNote$saveOnlyVersion$saveStackTrace);
  774.             }
  775.             // hook should be also called if "save only new version" is selected
  776.             if ($saveOnlyVersion) {
  777.                 $event = new AssetEvent($this, [
  778.                     'saveVersionOnly' => true,
  779.                 ]);
  780.                 $this->dispatchEvent($eventAssetEvents::POST_UPDATE);
  781.             }
  782.             return $version;
  783.         } catch (Exception $e) {
  784.             $event = new AssetEvent($this, [
  785.                 'saveVersionOnly' => true,
  786.                 'exception' => $e,
  787.             ]);
  788.             $this->dispatchEvent($eventAssetEvents::POST_UPDATE_FAILURE);
  789.             throw $e;
  790.         }
  791.     }
  792.     /**
  793.      * {@inheritdoc}
  794.      */
  795.     public function getFullPath()
  796.     {
  797.         $path $this->getPath() . $this->getFilename();
  798.         if (Tool::isFrontend()) {
  799.             return $this->getFrontendFullPath();
  800.         }
  801.         return $path;
  802.     }
  803.     /**
  804.      * Returns the full path of the asset (listener aware)
  805.      *
  806.      * @return string
  807.      *
  808.      * @internal
  809.      */
  810.     public function getFrontendFullPath()
  811.     {
  812.         $path $this->getPath() . $this->getFilename();
  813.         $path urlencode_ignore_slash($path);
  814.         $prefix Pimcore::getContainer()->getParameter('pimcore.config')['assets']['frontend_prefixes']['source'];
  815.         $path $prefix $path;
  816.         $event = new GenericEvent($this, [
  817.             'frontendPath' => $path,
  818.         ]);
  819.         $this->dispatchEvent($eventFrontendEvents::ASSET_PATH);
  820.         return $event->getArgument('frontendPath');
  821.     }
  822.     /**
  823.      * {@inheritdoc}
  824.      */
  825.     public function getRealPath()
  826.     {
  827.         return $this->path;
  828.     }
  829.     /**
  830.      * {@inheritdoc}
  831.      */
  832.     public function getRealFullPath()
  833.     {
  834.         $path $this->getRealPath() . $this->getFilename();
  835.         return $path;
  836.     }
  837.     /**
  838.      * @return array
  839.      */
  840.     public function getSiblings()
  841.     {
  842.         if ($this->siblings === null) {
  843.             if ($this->getParentId()) {
  844.                 $list = new Asset\Listing();
  845.                 $list->addConditionParam('parentId = ?'$this->getParentId());
  846.                 if ($this->getId()) {
  847.                     $list->addConditionParam('id != ?'$this->getId());
  848.                 }
  849.                 $list->setOrderKey('filename');
  850.                 $list->setOrder('asc');
  851.                 $this->siblings $list->getAssets();
  852.             } else {
  853.                 $this->siblings = [];
  854.             }
  855.         }
  856.         return $this->siblings;
  857.     }
  858.     /**
  859.      * @return bool
  860.      */
  861.     public function hasSiblings()
  862.     {
  863.         if (is_bool($this->hasSiblings)) {
  864.             if (($this->hasSiblings && empty($this->siblings)) || (!$this->hasSiblings && !empty($this->siblings))) {
  865.                 return $this->getDao()->hasSiblings();
  866.             } else {
  867.                 return $this->hasSiblings;
  868.             }
  869.         }
  870.         return $this->getDao()->hasSiblings();
  871.     }
  872.     /**
  873.      * @return bool
  874.      */
  875.     public function hasChildren()
  876.     {
  877.         return false;
  878.     }
  879.     /**
  880.      * @return Asset[]
  881.      */
  882.     public function getChildren()
  883.     {
  884.         return [];
  885.     }
  886.     /**
  887.      * @throws FilesystemException
  888.      */
  889.     private function deletePhysicalFile()
  890.     {
  891.         $storage Storage::get('asset');
  892.         if ($this->getType() != 'folder') {
  893.             $storage->delete($this->getRealFullPath());
  894.         } else {
  895.             $storage->deleteDirectory($this->getRealFullPath());
  896.         }
  897.     }
  898.     /**
  899.      * {@inheritdoc}
  900.      */
  901.     public function delete(bool $isNested false)
  902.     {
  903.         if ($this->getId() == 1) {
  904.             throw new Exception('root-node cannot be deleted');
  905.         }
  906.         $this->dispatchEvent(new AssetEvent($this), AssetEvents::PRE_DELETE);
  907.         $this->beginTransaction();
  908.         try {
  909.             $this->closeStream();
  910.             // remove children
  911.             if ($this->hasChildren()) {
  912.                 foreach ($this->getChildren() as $child) {
  913.                     $child->delete(true);
  914.                 }
  915.             }
  916.             // Dispatch Symfony Message Bus to delete versions
  917.             Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  918.                 new VersionDeleteMessage(Service::getElementType($this), $this->getId())
  919.             );
  920.             // remove all properties
  921.             $this->getDao()->deleteAllProperties();
  922.             // remove all tasks
  923.             $this->getDao()->deleteAllTasks();
  924.             // remove dependencies
  925.             $d $this->getDependencies();
  926.             $d->cleanAllForElement($this);
  927.             // remove from resource
  928.             $this->getDao()->delete();
  929.             $this->commit();
  930.             // remove file on filesystem
  931.             if (!$isNested) {
  932.                 $fullPath $this->getRealFullPath();
  933.                 if ($fullPath != '/..' && !strpos($fullPath,
  934.                     '/../') && $this->getKey() !== '.' && $this->getKey() !== '..') {
  935.                     $this->deletePhysicalFile();
  936.                 }
  937.             }
  938.             $this->clearThumbnails(true);
  939.             //remove target parent folder preview thumbnails
  940.             $this->clearFolderThumbnails($this);
  941.         } catch (Exception $e) {
  942.             try {
  943.                 $this->rollBack();
  944.             } catch (Exception $er) {
  945.                 // PDO adapter throws exceptions if rollback fails
  946.                 Logger::info((string) $er);
  947.             }
  948.             $failureEvent = new AssetEvent($this);
  949.             $failureEvent->setArgument('exception'$e);
  950.             $this->dispatchEvent($failureEventAssetEvents::POST_DELETE_FAILURE);
  951.             Logger::crit((string) $e);
  952.             throw $e;
  953.         }
  954.         // empty asset cache
  955.         $this->clearDependentCache();
  956.         // clear asset from registry
  957.         RuntimeCache::set(self::getCacheKey($this->getId()), null);
  958.         $this->dispatchEvent(new AssetEvent($this), AssetEvents::POST_DELETE);
  959.     }
  960.     /**
  961.      * {@inheritdoc}
  962.      */
  963.     public function clearDependentCache($additionalTags = [])
  964.     {
  965.         try {
  966.             $tags = [$this->getCacheTag(), 'asset_properties''output'];
  967.             $tags array_merge($tags$additionalTags);
  968.             Cache::clearTags($tags);
  969.         } catch (Exception $e) {
  970.             Logger::crit((string) $e);
  971.         }
  972.     }
  973.     /**
  974.      * @return string|null
  975.      */
  976.     public function getFilename()
  977.     {
  978.         return $this->filename;
  979.     }
  980.     /**
  981.      * {@inheritdoc}
  982.      */
  983.     public function getKey()
  984.     {
  985.         return $this->getFilename();
  986.     }
  987.     /**
  988.      * {@inheritdoc}
  989.      */
  990.     public function getType()
  991.     {
  992.         return $this->type;
  993.     }
  994.     /**
  995.      * @param string $filename
  996.      *
  997.      * @return $this
  998.      */
  999.     public function setFilename($filename)
  1000.     {
  1001.         $this->filename = (string)$filename;
  1002.         return $this;
  1003.     }
  1004.     /**
  1005.      * {@inheritdoc}
  1006.      */
  1007.     public function setKey($key)
  1008.     {
  1009.         return $this->setFilename($key);
  1010.     }
  1011.     /**
  1012.      * @param string $type
  1013.      *
  1014.      * @return $this
  1015.      */
  1016.     public function setType($type)
  1017.     {
  1018.         $this->type = (string)$type;
  1019.         return $this;
  1020.     }
  1021.     /**
  1022.      * @return string|false
  1023.      */
  1024.     public function getData()
  1025.     {
  1026.         $stream $this->getStream();
  1027.         if ($stream) {
  1028.             return stream_get_contents($stream);
  1029.         }
  1030.         return '';
  1031.     }
  1032.     /**
  1033.      * @param mixed $data
  1034.      *
  1035.      * @return $this
  1036.      */
  1037.     public function setData($data)
  1038.     {
  1039.         $handle tmpfile();
  1040.         fwrite($handle$data);
  1041.         $this->setStream($handle);
  1042.         return $this;
  1043.     }
  1044.     /**
  1045.      * @return resource|null
  1046.      */
  1047.     public function getStream()
  1048.     {
  1049.         if ($this->stream) {
  1050.             if (get_resource_type($this->stream) !== 'stream') {
  1051.                 $this->stream null;
  1052.             } elseif (!@rewind($this->stream)) {
  1053.                 $this->stream null;
  1054.             }
  1055.         }
  1056.         if (!$this->stream && $this->getType() !== 'folder') {
  1057.             try {
  1058.                 $this->stream Storage::get('asset')->readStream($this->getRealFullPath());
  1059.             } catch (Exception $e) {
  1060.                 $this->stream tmpfile();
  1061.             }
  1062.         }
  1063.         return $this->stream;
  1064.     }
  1065.     /**
  1066.      * @param resource|null $stream
  1067.      *
  1068.      * @return $this
  1069.      */
  1070.     public function setStream($stream)
  1071.     {
  1072.         // close existing stream
  1073.         if ($stream !== $this->stream) {
  1074.             $this->closeStream();
  1075.         }
  1076.         if (is_resource($stream)) {
  1077.             $this->setDataChanged();
  1078.             $this->setDataModificationDate(time());
  1079.             $this->stream $stream;
  1080.             $isRewindable = @rewind($this->stream);
  1081.             if (!$isRewindable) {
  1082.                 $tempFile $this->getLocalFileFromStream($this->stream);
  1083.                 $dest fopen($tempFile'rb'falseFile::getContext());
  1084.                 $this->stream $dest;
  1085.             }
  1086.         } elseif (is_null($stream)) {
  1087.             $this->stream null;
  1088.         }
  1089.         return $this;
  1090.     }
  1091.     private function closeStream()
  1092.     {
  1093.         if (is_resource($this->stream)) {
  1094.             @fclose($this->stream);
  1095.             $this->stream null;
  1096.         }
  1097.     }
  1098.     /**
  1099.      * @return bool
  1100.      */
  1101.     public function getDataChanged()
  1102.     {
  1103.         return $this->dataChanged;
  1104.     }
  1105.     /**
  1106.      * @param bool $changed
  1107.      *
  1108.      * @return $this
  1109.      */
  1110.     public function setDataChanged($changed true)
  1111.     {
  1112.         $this->dataChanged $changed;
  1113.         return $this;
  1114.     }
  1115.     /**
  1116.      * {@inheritdoc}
  1117.      */
  1118.     public function getVersions()
  1119.     {
  1120.         if ($this->versions === null) {
  1121.             $this->setVersions($this->getDao()->getVersions());
  1122.         }
  1123.         return $this->versions;
  1124.     }
  1125.     /**
  1126.      * @param Version[] $versions
  1127.      *
  1128.      * @return $this
  1129.      */
  1130.     public function setVersions($versions)
  1131.     {
  1132.         $this->versions $versions;
  1133.         return $this;
  1134.     }
  1135.     /**
  1136.      * @internal
  1137.      *
  1138.      * @param bool $keep whether to delete this file on shutdown or not
  1139.      *
  1140.      * @return string
  1141.      *
  1142.      * @throws Exception
  1143.      */
  1144.     public function getTemporaryFile(bool $keep false)
  1145.     {
  1146.         return self::getTemporaryFileFromStream($this->getStream(), $keep);
  1147.     }
  1148.     /**
  1149.      * @internal
  1150.      *
  1151.      * @return string
  1152.      *
  1153.      * @throws Exception
  1154.      */
  1155.     public function getLocalFile()
  1156.     {
  1157.         return self::getLocalFileFromStream($this->getStream());
  1158.     }
  1159.     /**
  1160.      * @param string $key
  1161.      * @param mixed $value
  1162.      *
  1163.      * @return $this
  1164.      */
  1165.     public function setCustomSetting($key$value)
  1166.     {
  1167.         $this->customSettings[$key] = $value;
  1168.         return $this;
  1169.     }
  1170.     /**
  1171.      * @param string $key
  1172.      *
  1173.      * @return mixed
  1174.      */
  1175.     public function getCustomSetting($key)
  1176.     {
  1177.         if (is_array($this->customSettings) && array_key_exists($key$this->customSettings)) {
  1178.             return $this->customSettings[$key];
  1179.         }
  1180.         return null;
  1181.     }
  1182.     /**
  1183.      * @param string $key
  1184.      */
  1185.     public function removeCustomSetting($key)
  1186.     {
  1187.         if (is_array($this->customSettings) && array_key_exists($key$this->customSettings)) {
  1188.             unset($this->customSettings[$key]);
  1189.         }
  1190.     }
  1191.     /**
  1192.      * @return array
  1193.      */
  1194.     public function getCustomSettings()
  1195.     {
  1196.         return $this->customSettings;
  1197.     }
  1198.     /**
  1199.      * @param mixed $customSettings
  1200.      *
  1201.      * @return $this
  1202.      */
  1203.     public function setCustomSettings($customSettings)
  1204.     {
  1205.         if (is_string($customSettings)) {
  1206.             $customSettings Serialize::unserialize($customSettings);
  1207.         }
  1208.         if ($customSettings instanceof stdClass) {
  1209.             $customSettings = (array)$customSettings;
  1210.         }
  1211.         if (!is_array($customSettings)) {
  1212.             $customSettings = [];
  1213.         }
  1214.         $this->customSettings $customSettings;
  1215.         return $this;
  1216.     }
  1217.     /**
  1218.      * @return string|null
  1219.      */
  1220.     public function getMimeType()
  1221.     {
  1222.         return $this->mimetype;
  1223.     }
  1224.     /**
  1225.      * @param string $mimetype
  1226.      *
  1227.      * @return $this
  1228.      */
  1229.     public function setMimeType($mimetype)
  1230.     {
  1231.         $this->mimetype = (string)$mimetype;
  1232.         return $this;
  1233.     }
  1234.     /**
  1235.      * @param array $metadata for each array item: mandatory keys: name, type - optional keys: data, language
  1236.      *
  1237.      * @return $this
  1238.      *
  1239.      * @internal
  1240.      *
  1241.      */
  1242.     public function setMetadataRaw($metadata)
  1243.     {
  1244.         $this->metadata $metadata;
  1245.         if ($this->metadata) {
  1246.             $this->setHasMetaData(true);
  1247.         }
  1248.         return $this;
  1249.     }
  1250.     /**
  1251.      * @param array[]|stdClass[] $metadata for each array item: mandatory keys: name, type - optional keys: data, language
  1252.      *
  1253.      * @return $this
  1254.      */
  1255.     public function setMetadata($metadata)
  1256.     {
  1257.         $this->metadata = [];
  1258.         $this->setHasMetaData(false);
  1259.         if (!empty($metadata)) {
  1260.             foreach ((array)$metadata as $metaItem) {
  1261.                 $metaItem = (array)$metaItem// also allow object with appropriate keys
  1262.                 $this->addMetadata($metaItem['name'], $metaItem['type'], $metaItem['data'] ?? null$metaItem['language'] ?? null);
  1263.             }
  1264.         }
  1265.         return $this;
  1266.     }
  1267.     /**
  1268.      * @return bool
  1269.      */
  1270.     public function getHasMetaData()
  1271.     {
  1272.         return $this->hasMetaData;
  1273.     }
  1274.     /**
  1275.      * @param bool $hasMetaData
  1276.      *
  1277.      * @return $this
  1278.      */
  1279.     public function setHasMetaData($hasMetaData)
  1280.     {
  1281.         $this->hasMetaData = (bool)$hasMetaData;
  1282.         return $this;
  1283.     }
  1284.     /**
  1285.      * @param string $name
  1286.      * @param string $type can be "asset", "checkbox", "date", "document", "input", "object", "select" or "textarea"
  1287.      * @param mixed $data
  1288.      * @param string|null $language
  1289.      *
  1290.      * @return $this
  1291.      */
  1292.     public function addMetadata($name$type$data null$language null)
  1293.     {
  1294.         if ($name && $type) {
  1295.             $tmp = [];
  1296.             $name str_replace('~''---'$name);
  1297.             if (!is_array($this->metadata)) {
  1298.                 $this->metadata = [];
  1299.             }
  1300.             foreach ($this->metadata as $item) {
  1301.                 if ($item['name'] != $name || $language != $item['language']) {
  1302.                     $tmp[] = $item;
  1303.                 }
  1304.             }
  1305.             $item = [
  1306.                 'name' => $name,
  1307.                 'type' => $type,
  1308.                 'data' => $data,
  1309.                 'language' => $language,
  1310.             ];
  1311.             $loader Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data');
  1312.             try {
  1313.                 /** @var Data $instance */
  1314.                 $instance $loader->build($item['type']);
  1315.                 $transformedData $instance->transformSetterData($data$item);
  1316.                 $item['data'] = $transformedData;
  1317.             } catch (UnsupportedException $e) {
  1318.             }
  1319.             $tmp[] = $item;
  1320.             $this->metadata $tmp;
  1321.             $this->setHasMetaData(true);
  1322.         }
  1323.         return $this;
  1324.     }
  1325.     /**
  1326.      * @param string $name
  1327.      * @param string|null $language
  1328.      *
  1329.      * @return $this
  1330.      */
  1331.     public function removeMetadata(string $name, ?string $language null)
  1332.     {
  1333.         if ($name) {
  1334.             $tmp = [];
  1335.             $name str_replace('~''---'$name);
  1336.             if (!is_array($this->metadata)) {
  1337.                 $this->metadata = [];
  1338.             }
  1339.             foreach ($this->metadata as $item) {
  1340.                 if ($item['name'] === $name && ($language == $item['language'] || $language === '*')) {
  1341.                     continue;
  1342.                 }
  1343.                 $tmp[] = $item;
  1344.             }
  1345.             $this->metadata $tmp;
  1346.             $this->setHasMetaData(!empty($this->metadata));
  1347.         }
  1348.         return $this;
  1349.     }
  1350.     /**
  1351.      * @param string|null $name
  1352.      * @param string|null $language
  1353.      * @param bool $strictMatch
  1354.      * @param bool $raw
  1355.      *
  1356.      * @return array|string|null
  1357.      */
  1358.     public function getMetadata($name null$language null$strictMatch false$raw false)
  1359.     {
  1360.         $preEvent = new AssetEvent($this);
  1361.         $preEvent->setArgument('metadata'$this->metadata);
  1362.         $this->dispatchEvent($preEventAssetEvents::PRE_GET_METADATA);
  1363.         $this->metadata $preEvent->getArgument('metadata');
  1364.         $convert = function ($metaData) {
  1365.             $loader Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data');
  1366.             $transformedData $metaData['data'];
  1367.             try {
  1368.                 /** @var Data $instance */
  1369.                 $instance $loader->build($metaData['type']);
  1370.                 $transformedData $instance->transformGetterData($metaData['data'], $metaData);
  1371.             } catch (UnsupportedException $e) {
  1372.                 Logger::error((string) $e);
  1373.             }
  1374.             return $transformedData;
  1375.         };
  1376.         if ($name) {
  1377.             $result null;
  1378.             if ($language === null) {
  1379.                 $language Pimcore::getContainer()->get(LocaleServiceInterface::class)->findLocale();
  1380.             }
  1381.             $data null;
  1382.             foreach ($this->metadata as $md) {
  1383.                 if ($md['name'] == $name) {
  1384.                     if ($language == $md['language'] || (empty($md['language']) && !$strictMatch)) {
  1385.                         $data $md;
  1386.                         break;
  1387.                     }
  1388.                 }
  1389.             }
  1390.             if ($data) {
  1391.                 $result $raw $data $convert($data);
  1392.             }
  1393.             return $result;
  1394.         }
  1395.         $metaData $this->getObjectVar('metadata');
  1396.         $result = [];
  1397.         if (is_array($metaData)) {
  1398.             foreach ($metaData as $md) {
  1399.                 $md = (array)$md;
  1400.                 if (!$raw) {
  1401.                     $md['data'] = $convert($md);
  1402.                 }
  1403.                 $result[] = $md;
  1404.             }
  1405.         }
  1406.         return $result;
  1407.     }
  1408.     /**
  1409.      * @param bool $formatted
  1410.      * @param int $precision
  1411.      *
  1412.      * @return string|int
  1413.      */
  1414.     public function getFileSize($formatted false$precision 2)
  1415.     {
  1416.         try {
  1417.             $bytes Storage::get('asset')->fileSize($this->getRealFullPath());
  1418.         } catch (Exception $e) {
  1419.             $bytes 0;
  1420.         }
  1421.         if ($formatted) {
  1422.             return formatBytes($bytes$precision);
  1423.         }
  1424.         return $bytes;
  1425.     }
  1426.     /**
  1427.      * @return Asset|null
  1428.      */
  1429.     public function getParent() /** : ?Asset */
  1430.     {
  1431.         $parent parent::getParent();
  1432.         return $parent instanceof Asset $parent null;
  1433.     }
  1434.     /**
  1435.      * @param Asset|null $parent
  1436.      *
  1437.      * @return $this
  1438.      */
  1439.     public function setParent($parent)
  1440.     {
  1441.         $this->parent $parent;
  1442.         if ($parent instanceof Asset) {
  1443.             $this->parentId $parent->getId();
  1444.         }
  1445.         return $this;
  1446.     }
  1447.     public function __wakeup()
  1448.     {
  1449.         if ($this->isInDumpState()) {
  1450.             // set current parent and path, this is necessary because the serialized data can have a different path than the original element (element was moved)
  1451.             $originalElement Asset::getById($this->getId());
  1452.             if ($originalElement) {
  1453.                 $this->setParentId($originalElement->getParentId());
  1454.                 $this->setPath($originalElement->getRealPath());
  1455.             }
  1456.         }
  1457.         if ($this->isInDumpState() && $this->properties !== null) {
  1458.             $this->renewInheritedProperties();
  1459.         }
  1460.         $this->setInDumpState(false);
  1461.     }
  1462.     public function __destruct()
  1463.     {
  1464.         // close open streams
  1465.         $this->closeStream();
  1466.     }
  1467.     /**
  1468.      * {@inheritdoc}
  1469.      */
  1470.     protected function resolveDependencies(): array
  1471.     {
  1472.         $dependencies = [parent::resolveDependencies()];
  1473.         if ($this->hasMetaData) {
  1474.             $loader Pimcore::getContainer()->get('pimcore.implementation_loader.asset.metadata.data');
  1475.             foreach ($this->getMetadata() as $metaData) {
  1476.                 if (!empty($metaData['data'])) {
  1477.                     /** @var ElementInterface $elementData */
  1478.                     $elementData $metaData['data'];
  1479.                     $elementType $metaData['type'];
  1480.                     try {
  1481.                         /** @var DataDefinitionInterface $implementation */
  1482.                         $implementation $loader->build($elementType);
  1483.                         $dependencies[] = $implementation->resolveDependencies($elementData$metaData);
  1484.                     } catch (UnsupportedException $e) {
  1485.                     }
  1486.                 }
  1487.             }
  1488.         }
  1489.         return array_merge(...$dependencies);
  1490.     }
  1491.     public function __clone()
  1492.     {
  1493.         parent::__clone();
  1494.         $this->parent null;
  1495.         $this->versions null;
  1496.         $this->hasSiblings null;
  1497.         $this->siblings null;
  1498.         $this->scheduledTasks null;
  1499.         $this->closeStream();
  1500.     }
  1501.     /**
  1502.      * @param bool $force
  1503.      */
  1504.     public function clearThumbnails($force false)
  1505.     {
  1506.         if ($this->getDataChanged() || $force) {
  1507.             foreach (['thumbnail''asset_cache'] as $storageName) {
  1508.                 $storage Storage::get($storageName);
  1509.                 $storage->deleteDirectory($this->getRealPath() . $this->getId());
  1510.             }
  1511.             $this->getDao()->deleteFromThumbnailCache();
  1512.         }
  1513.     }
  1514.     /**
  1515.      * @param FilesystemOperator $storage
  1516.      * @param string $oldPath
  1517.      * @param string|null $newPath
  1518.      *
  1519.      * @throws FilesystemException
  1520.      */
  1521.     private function updateChildPaths(FilesystemOperator $storagestring $oldPathstring $newPath null)
  1522.     {
  1523.         if ($newPath === null) {
  1524.             $newPath $this->getRealFullPath();
  1525.         }
  1526.         try {
  1527.             $children $storage->listContents($oldPathtrue);
  1528.             foreach ($children as $child) {
  1529.                 if ($child['type'] === 'file') {
  1530.                     $src  $child['path'];
  1531.                     $dest str_replace($oldPath$newPath'/' $src);
  1532.                     $storage->move($src$dest);
  1533.                 }
  1534.             }
  1535.             $storage->deleteDirectory($oldPath);
  1536.         } catch (UnableToMoveFile $e) {
  1537.             // noting to do
  1538.         }
  1539.     }
  1540.     /**
  1541.      * @param string $oldPath
  1542.      *
  1543.      * @throws FilesystemException
  1544.      */
  1545.     private function relocateThumbnails(string $oldPath)
  1546.     {
  1547.         if ($this instanceof Folder) {
  1548.             $oldThumbnailsPath $oldPath;
  1549.             $newThumbnailsPath $this->getRealFullPath();
  1550.         } else {
  1551.             $oldThumbnailsPath dirname($oldPath) . '/' $this->getId();
  1552.             $newThumbnailsPath $this->getRealPath() . $this->getId();
  1553.         }
  1554.         if ($oldThumbnailsPath === $newThumbnailsPath) {
  1555.             //path is equal, probably file name changed - so clear all thumbnails
  1556.             $this->clearThumbnails(true);
  1557.         } else {
  1558.             //remove source parent folder preview thumbnails
  1559.             $sourceFolder Asset::getByPath(dirname($oldPath));
  1560.             if ($sourceFolder) {
  1561.                 $this->clearFolderThumbnails($sourceFolder);
  1562.             }
  1563.             //remove target parent folder preview thumbnails
  1564.             $this->clearFolderThumbnails($this);
  1565.             foreach (['thumbnail''asset_cache'] as $storageName) {
  1566.                 $storage Storage::get($storageName);
  1567.                 try {
  1568.                     $storage->move($oldThumbnailsPath$newThumbnailsPath);
  1569.                 } catch (UnableToMoveFile $e) {
  1570.                     //update children, if unable to move parent
  1571.                     $this->updateChildPaths($storage$oldPath);
  1572.                 }
  1573.             }
  1574.         }
  1575.     }
  1576.     /**
  1577.      * @param Asset $asset
  1578.      */
  1579.     private function clearFolderThumbnails(Asset $asset): void
  1580.     {
  1581.         do {
  1582.             if ($asset instanceof Folder) {
  1583.                 $asset->clearThumbnails(true);
  1584.             }
  1585.             $asset $asset->getParent();
  1586.         } while ($asset !== null);
  1587.     }
  1588.     /**
  1589.      * @param string $name
  1590.      */
  1591.     public function clearThumbnail($name)
  1592.     {
  1593.         try {
  1594.             Storage::get('thumbnail')->deleteDirectory($this->getRealPath().'/'.$this->getId().'/image-thumb__'.$this->getId().'__'.$name);
  1595.             $this->getDao()->deleteFromThumbnailCache($name);
  1596.         } catch (Exception $e) {
  1597.             // noting to do
  1598.         }
  1599.     }
  1600.     /**
  1601.      * @internal
  1602.      */
  1603.     protected function addToUpdateTaskQueue(): void
  1604.     {
  1605.         Pimcore::getContainer()->get('messenger.bus.pimcore-core')->dispatch(
  1606.             new AssetUpdateTasksMessage($this->getId())
  1607.         );
  1608.     }
  1609.     /**
  1610.      * @return string
  1611.      */
  1612.     public function getFrontendPath(): string
  1613.     {
  1614.         $path $this->getFullPath();
  1615.         if (!\preg_match('@^(https?|data):@'$path)) {
  1616.             $path \Pimcore\Tool::getHostUrl() . $path;
  1617.         }
  1618.         return $path;
  1619.     }
  1620.     /**
  1621.      * @internal
  1622.      *
  1623.      * @throws Exception
  1624.      */
  1625.     public function addThumbnailFileToCache(string $localFilestring $filenameThumbnailConfig $config): void
  1626.     {
  1627.         //try to get the dimensions with getimagesize because it is much faster than e.g. the Imagick-Adapter
  1628.         if ($imageSize = @getimagesize($localFile)) {
  1629.             $dimensions = [
  1630.                 'width' => $imageSize[0],
  1631.                 'height' => $imageSize[1],
  1632.             ];
  1633.         } else {
  1634.             //fallback to Default Adapter
  1635.             $image \Pimcore\Image::getInstance();
  1636.             if ($image->load($localFile)) {
  1637.                 $dimensions = [
  1638.                     'width' => $image->getWidth(),
  1639.                     'height' => $image->getHeight(),
  1640.                 ];
  1641.             }
  1642.         }
  1643.         if (!empty($dimensions)) {
  1644.             $this->getDao()->addToThumbnailCache(
  1645.                 $config->getName(),
  1646.                 $filename,
  1647.                 filesize($localFile),
  1648.                 $dimensions['width'],
  1649.                 $dimensions['height']
  1650.             );
  1651.         }
  1652.     }
  1653. }