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

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