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

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