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

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