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

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