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

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