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

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