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

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