vendor/pimcore/pimcore/models/Element/Service.php line 959

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\Element;
  15. use DeepCopy\DeepCopy;
  16. use DeepCopy\Filter\Doctrine\DoctrineCollectionFilter;
  17. use DeepCopy\Filter\SetNullFilter;
  18. use DeepCopy\Matcher\PropertyNameMatcher;
  19. use DeepCopy\Matcher\PropertyTypeMatcher;
  20. use Doctrine\Common\Collections\Collection;
  21. use Doctrine\DBAL\Query\QueryBuilder as DoctrineQueryBuilder;
  22. use League\Csv\EscapeFormula;
  23. use Pimcore;
  24. use Pimcore\Db;
  25. use Pimcore\Event\SystemEvents;
  26. use Pimcore\File;
  27. use Pimcore\Logger;
  28. use Pimcore\Model;
  29. use Pimcore\Model\Asset;
  30. use Pimcore\Model\DataObject;
  31. use Pimcore\Model\DataObject\AbstractObject;
  32. use Pimcore\Model\DataObject\ClassDefinition\Data;
  33. use Pimcore\Model\DataObject\Concrete;
  34. use Pimcore\Model\Dependency;
  35. use Pimcore\Model\Document;
  36. use Pimcore\Model\Element\DeepCopy\MarshalMatcher;
  37. use Pimcore\Model\Element\DeepCopy\PimcoreClassDefinitionMatcher;
  38. use Pimcore\Model\Element\DeepCopy\PimcoreClassDefinitionReplaceFilter;
  39. use Pimcore\Model\Element\DeepCopy\UnmarshalMatcher;
  40. use Pimcore\Model\Tool\TmpStore;
  41. use Pimcore\Tool\Serialize;
  42. use Pimcore\Tool\Session;
  43. use Symfony\Component\EventDispatcher\GenericEvent;
  44. use Symfony\Component\OptionsResolver\OptionsResolver;
  45. use Symfony\Contracts\Translation\TranslatorInterface;
  46. /**
  47. * @method \Pimcore\Model\Element\Dao getDao()
  48. */
  49. class Service extends Model\AbstractModel
  50. {
  51. /**
  52. * @var EscapeFormula|null
  53. */
  54. private static ?EscapeFormula $formatter = null;
  55. /**
  56. * @internal
  57. *
  58. * @param ElementInterface $element
  59. *
  60. * @return string
  61. */
  62. public static function getIdPath(ElementInterface $element): string
  63. {
  64. $path = '';
  65. $elementType = self::getElementType($element);
  66. $parentId = $element->getParentId();
  67. $parentElement = self::getElementById($elementType, $parentId);
  68. if ($parentElement) {
  69. $path = self::getIdPath($parentElement);
  70. }
  71. $path .= '/' . $element->getId();
  72. return $path;
  73. }
  74. /**
  75. * @internal
  76. *
  77. * @param ElementInterface $element
  78. *
  79. * @return string
  80. *
  81. * @throws \Exception
  82. */
  83. public static function getTypePath(ElementInterface $element): string
  84. {
  85. $path = '';
  86. $elementType = self::getElementType($element);
  87. $parentId = $element->getParentId();
  88. $parentElement = self::getElementById($elementType, $parentId);
  89. if ($parentElement) {
  90. $path = self::getTypePath($parentElement);
  91. }
  92. $type = $element->getType();
  93. if ($type !== DataObject::OBJECT_TYPE_FOLDER) {
  94. if ($element instanceof Document) {
  95. $type = 'document';
  96. } elseif ($element instanceof DataObject\AbstractObject) {
  97. $type = 'object';
  98. } elseif ($element instanceof Asset) {
  99. $type = 'asset';
  100. } else {
  101. throw new \Exception('unknown type');
  102. }
  103. }
  104. $path .= '/' . $type;
  105. return $path;
  106. }
  107. /**
  108. * @internal
  109. *
  110. * @param ElementInterface $element
  111. *
  112. * @return string
  113. *
  114. * @throws \Exception
  115. */
  116. public static function getSortIndexPath(ElementInterface $element): string
  117. {
  118. $path = '';
  119. $elementType = self::getElementType($element);
  120. $parentId = $element->getParentId();
  121. $parentElement = self::getElementById($elementType, $parentId);
  122. if ($parentElement) {
  123. $path = self::getSortIndexPath($parentElement);
  124. }
  125. $sortIndex = method_exists($element, 'getIndex') ? (int) $element->getIndex() : 0;
  126. $path .= '/' . $sortIndex;
  127. return $path;
  128. }
  129. /**
  130. * @internal
  131. *
  132. * @param array|Model\Listing\AbstractListing $list
  133. * @param string $idGetter
  134. *
  135. * @return int[]
  136. */
  137. public static function getIdList($list, $idGetter = 'getId')
  138. {
  139. $ids = [];
  140. if (is_array($list)) {
  141. foreach ($list as $entry) {
  142. if (is_object($entry) && method_exists($entry, $idGetter)) {
  143. $ids[] = $entry->$idGetter();
  144. } elseif (is_scalar($entry)) {
  145. $ids[] = $entry;
  146. }
  147. }
  148. }
  149. if ($list instanceof Model\Listing\AbstractListing && method_exists($list, 'loadIdList')) {
  150. $ids = $list->loadIdList();
  151. }
  152. $ids = array_unique($ids);
  153. return $ids;
  154. }
  155. /**
  156. * @param Dependency $d
  157. * @param int|null $offset
  158. * @param int|null $limit
  159. *
  160. * @return array
  161. *
  162. * @internal
  163. *
  164. */
  165. public static function getRequiredByDependenciesForFrontend(Dependency $d, $offset, $limit)
  166. {
  167. $dependencies['hasHidden'] = false;
  168. $dependencies['requiredBy'] = [];
  169. // requiredBy
  170. foreach ($d->getRequiredBy($offset, $limit) as $r) {
  171. if ($e = self::getDependedElement($r)) {
  172. if ($e->isAllowed('list')) {
  173. $dependencies['requiredBy'][] = self::getDependencyForFrontend($e);
  174. } else {
  175. $dependencies['hasHidden'] = true;
  176. }
  177. }
  178. }
  179. return $dependencies;
  180. }
  181. /**
  182. * @param Dependency $d
  183. * @param int|null $offset
  184. * @param int|null $limit
  185. *
  186. * @return array
  187. *
  188. * @internal
  189. *
  190. */
  191. public static function getRequiresDependenciesForFrontend(Dependency $d, $offset, $limit)
  192. {
  193. $dependencies['hasHidden'] = false;
  194. $dependencies['requires'] = [];
  195. // requires
  196. foreach ($d->getRequires($offset, $limit) as $r) {
  197. if ($e = self::getDependedElement($r)) {
  198. if ($e->isAllowed('list')) {
  199. $dependencies['requires'][] = self::getDependencyForFrontend($e);
  200. } else {
  201. $dependencies['hasHidden'] = true;
  202. }
  203. }
  204. }
  205. return $dependencies;
  206. }
  207. /**
  208. * @param ElementInterface $element
  209. *
  210. * @return array
  211. */
  212. private static function getDependencyForFrontend($element)
  213. {
  214. return [
  215. 'id' => $element->getId(),
  216. 'path' => $element->getRealFullPath(),
  217. 'type' => self::getElementType($element),
  218. 'subtype' => $element->getType(),
  219. 'published' => self::isPublished($element),
  220. ];
  221. }
  222. /**
  223. * @param array $config
  224. *
  225. * @return DataObject\AbstractObject|Document|Asset|null
  226. */
  227. private static function getDependedElement($config)
  228. {
  229. if ($config['type'] == 'object') {
  230. return DataObject::getById($config['id']);
  231. } elseif ($config['type'] == 'asset') {
  232. return Asset::getById($config['id']);
  233. } elseif ($config['type'] == 'document') {
  234. return Document::getById($config['id']);
  235. }
  236. return null;
  237. }
  238. /**
  239. * @static
  240. *
  241. * @return bool
  242. */
  243. public static function doHideUnpublished($element)
  244. {
  245. return ($element instanceof AbstractObject && DataObject::doHideUnpublished())
  246. || ($element instanceof Document && Document::doHideUnpublished());
  247. }
  248. /**
  249. * determines whether an element is published
  250. *
  251. * @internal
  252. *
  253. * @param ElementInterface $element
  254. *
  255. * @return bool
  256. */
  257. public static function isPublished($element = null)
  258. {
  259. if ($element instanceof ElementInterface) {
  260. if (method_exists($element, 'isPublished')) {
  261. return $element->isPublished();
  262. } else {
  263. return true;
  264. }
  265. }
  266. return false;
  267. }
  268. /**
  269. * @internal
  270. *
  271. * @param array|null $data
  272. *
  273. * @return array
  274. *
  275. * @throws \Exception
  276. */
  277. public static function filterUnpublishedAdvancedElements($data): array
  278. {
  279. if (DataObject::doHideUnpublished() && is_array($data)) {
  280. $publishedList = [];
  281. $mapping = [];
  282. foreach ($data as $advancedElement) {
  283. if (!$advancedElement instanceof DataObject\Data\ObjectMetadata
  284. && !$advancedElement instanceof DataObject\Data\ElementMetadata) {
  285. throw new \Exception('only supported for advanced many-to-many (+object) relations');
  286. }
  287. $elementId = null;
  288. if ($advancedElement instanceof DataObject\Data\ObjectMetadata) {
  289. $elementId = $advancedElement->getObjectId();
  290. $elementType = 'object';
  291. } else {
  292. $elementId = $advancedElement->getElementId();
  293. $elementType = $advancedElement->getElementType();
  294. }
  295. if (!$elementId) {
  296. continue;
  297. }
  298. if ($elementType == 'asset') {
  299. // there is no published flag for assets
  300. continue;
  301. }
  302. $mapping[$elementType][$elementId] = true;
  303. }
  304. $db = Db::get();
  305. $publishedMapping = [];
  306. // now do the query;
  307. foreach ($mapping as $elementType => $idList) {
  308. $idList = array_keys($mapping[$elementType]);
  309. switch ($elementType) {
  310. case 'document':
  311. $idColumn = 'id';
  312. $publishedColumn = 'published';
  313. break;
  314. case 'object':
  315. $idColumn = 'o_id';
  316. $publishedColumn = 'o_published';
  317. break;
  318. default:
  319. throw new \Exception('unknown type');
  320. }
  321. $query = 'SELECT ' . $idColumn . ' FROM ' . $elementType . 's WHERE ' . $publishedColumn . '=1 AND ' . $idColumn . ' IN (' . implode(',', $idList) . ');';
  322. $publishedIds = $db->fetchFirstColumn($query);
  323. $publishedMapping[$elementType] = $publishedIds;
  324. }
  325. foreach ($data as $advancedElement) {
  326. $elementId = null;
  327. if ($advancedElement instanceof DataObject\Data\ObjectMetadata) {
  328. $elementId = $advancedElement->getObjectId();
  329. $elementType = 'object';
  330. } else {
  331. $elementId = $advancedElement->getElementId();
  332. $elementType = $advancedElement->getElementType();
  333. }
  334. if ($elementType == 'asset') {
  335. $publishedList[] = $advancedElement;
  336. }
  337. if (isset($publishedMapping[$elementType]) && in_array($elementId, $publishedMapping[$elementType])) {
  338. $publishedList[] = $advancedElement;
  339. }
  340. }
  341. return $publishedList;
  342. }
  343. return is_array($data) ? $data : [];
  344. }
  345. /**
  346. * @param string $type
  347. * @param string $path
  348. *
  349. * @return ElementInterface|null
  350. */
  351. public static function getElementByPath($type, $path)
  352. {
  353. $element = null;
  354. if ($type == 'asset') {
  355. $element = Asset::getByPath($path);
  356. } elseif ($type == 'object') {
  357. $element = DataObject::getByPath($path);
  358. } elseif ($type == 'document') {
  359. $element = Document::getByPath($path);
  360. }
  361. return $element;
  362. }
  363. /**
  364. * @internal
  365. *
  366. * @param string|ElementInterface $element
  367. *
  368. * @return string
  369. *
  370. * @throws \Exception
  371. */
  372. public static function getBaseClassNameForElement($element)
  373. {
  374. if ($element instanceof ElementInterface) {
  375. $elementType = self::getElementType($element);
  376. } elseif (is_string($element)) {
  377. $elementType = $element;
  378. } else {
  379. throw new \Exception('Wrong type given for getBaseClassNameForElement(), ElementInterface and string are allowed');
  380. }
  381. $baseClass = ucfirst($elementType);
  382. if ($elementType == 'object') {
  383. $baseClass = 'DataObject';
  384. }
  385. return $baseClass;
  386. }
  387. /**
  388. * @deprecated will be removed in Pimcore 11, use getSafeCopyName() instead
  389. *
  390. * @param string $type
  391. * @param string $sourceKey
  392. * @param ElementInterface $target
  393. *
  394. * @return string
  395. */
  396. public static function getSaveCopyName($type, $sourceKey, $target)
  397. {
  398. return self::getSafeCopyName($sourceKey, $target);
  399. }
  400. /**
  401. * Returns a uniqe key for the element in the $target-Path (recursive)
  402. *
  403. * @return string
  404. *
  405. * @param string $sourceKey
  406. * @param ElementInterface $target
  407. */
  408. public static function getSafeCopyName(string $sourceKey, ElementInterface $target)
  409. {
  410. $type = self::getElementType($target);
  411. if (self::pathExists($target->getRealFullPath() . '/' . $sourceKey, $type)) {
  412. // only for assets: add the prefix _copy before the file extension (if exist) not after to that source.jpg will be source_copy.jpg and not source.jpg_copy
  413. if ($type == 'asset' && $fileExtension = File::getFileExtension($sourceKey)) {
  414. $sourceKey = preg_replace('/\.' . $fileExtension . '$/i', '_copy.' . $fileExtension, $sourceKey);
  415. } elseif (preg_match("/_copy(|_\d*)$/", $sourceKey) === 1) {
  416. // If key already ends with _copy or copy_N, append a digit to avoid _copy_copy_copy naming
  417. $keyParts = explode('_', $sourceKey);
  418. $counterKey = array_key_last($keyParts);
  419. if ((int)$keyParts[$counterKey] > 0) {
  420. $keyParts[$counterKey] = (int)$keyParts[$counterKey] + 1;
  421. } else {
  422. $keyParts[] = 1;
  423. }
  424. $sourceKey = implode('_', $keyParts);
  425. } else {
  426. $sourceKey .= '_copy';
  427. }
  428. return self::getSafeCopyName($sourceKey, $target);
  429. }
  430. return $sourceKey;
  431. }
  432. /**
  433. * @param string $path
  434. * @param string|null $type
  435. *
  436. * @return bool
  437. */
  438. public static function pathExists($path, $type = null)
  439. {
  440. if ($type == 'asset') {
  441. return Asset\Service::pathExists($path);
  442. } elseif ($type == 'document') {
  443. return Document\Service::pathExists($path);
  444. } elseif ($type == 'object') {
  445. return DataObject\Service::pathExists($path);
  446. }
  447. return false;
  448. }
  449. /**
  450. * @param string $type
  451. * @param int $id
  452. * @param array|bool $force
  453. *
  454. * @return Asset|AbstractObject|Document|null
  455. */
  456. public static function getElementById($type, $id, $force = false)
  457. {
  458. $element = null;
  459. $params = self::prepareGetByIdParams($force, __METHOD__, func_num_args() > 2);
  460. if ($type === 'asset') {
  461. $element = Asset::getById($id, $params);
  462. } elseif ($type === 'object') {
  463. $element = DataObject::getById($id, $params);
  464. } elseif ($type === 'document') {
  465. $element = Document::getById($id, $params);
  466. }
  467. return $element;
  468. }
  469. /**
  470. * @internal
  471. *
  472. * @param bool|array $params
  473. *
  474. * @return array
  475. */
  476. public static function prepareGetByIdParams(/*array */$params, string $method, bool $paramsGiven): array
  477. {
  478. if (is_bool($params) && $paramsGiven) {
  479. trigger_deprecation('pimcore/pimcore', '10.5', 'Using $force=%s on %s is deprecated, please use array-syntax [force=>true] instead.', $params ? 'true' : 'false', $method);
  480. $params = ['force' => $params];
  481. } elseif ($params === false) {
  482. $params = [];
  483. }
  484. $resolver = new OptionsResolver();
  485. $resolver->setDefaults([
  486. 'force' => false,
  487. ]);
  488. $resolver->setAllowedTypes('force', 'bool');
  489. return $resolver->resolve($params);
  490. }
  491. /**
  492. * @static
  493. *
  494. * @param ElementInterface $element
  495. *
  496. * @return string|null
  497. */
  498. public static function getElementType($element): ?string
  499. {
  500. if ($element instanceof DataObject\AbstractObject) {
  501. return 'object';
  502. }
  503. if ($element instanceof Document) {
  504. return 'document';
  505. }
  506. if ($element instanceof Asset) {
  507. return 'asset';
  508. }
  509. return null;
  510. }
  511. /**
  512. * @internal
  513. *
  514. * @param string $className
  515. *
  516. * @return string|null
  517. */
  518. public static function getElementTypeByClassName(string $className): ?string
  519. {
  520. $className = trim($className, '\\');
  521. if (is_a($className, AbstractObject::class, true)) {
  522. return 'object';
  523. }
  524. if (is_a($className, Asset::class, true)) {
  525. return 'asset';
  526. }
  527. if (is_a($className, Document::class, true)) {
  528. return 'document';
  529. }
  530. return null;
  531. }
  532. /**
  533. * @internal
  534. *
  535. * @param ElementInterface $element
  536. *
  537. * @return string|null
  538. */
  539. public static function getElementHash(ElementInterface $element): ?string
  540. {
  541. $elementType = self::getElementType($element);
  542. if ($elementType === null) {
  543. return null;
  544. }
  545. return $elementType . '-' . $element->getId();
  546. }
  547. /**
  548. * determines the type of an element (object,asset,document)
  549. *
  550. * @deprecated use getElementType() instead, will be removed in Pimcore 11
  551. *
  552. * @param ElementInterface $element
  553. *
  554. * @return string
  555. */
  556. public static function getType($element)
  557. {
  558. trigger_deprecation(
  559. 'pimcore/pimcore',
  560. '10.0',
  561. 'The Service::getType() method is deprecated, use Service::getElementType() instead.'
  562. );
  563. return self::getElementType($element);
  564. }
  565. /**
  566. * @internal
  567. *
  568. * @param array $props
  569. *
  570. * @return array
  571. */
  572. public static function minimizePropertiesForEditmode($props)
  573. {
  574. $properties = [];
  575. foreach ($props as $key => $p) {
  576. //$p = object2array($p);
  577. $allowedProperties = [
  578. 'key',
  579. 'o_key',
  580. 'filename',
  581. 'path',
  582. 'o_path',
  583. 'id',
  584. 'o_id',
  585. 'o_type',
  586. 'type',
  587. ];
  588. if ($p->getData() instanceof Document || $p->getData() instanceof Asset || $p->getData() instanceof DataObject\AbstractObject) {
  589. $pa = [];
  590. $vars = $p->getData()->getObjectVars();
  591. foreach ($vars as $k => $value) {
  592. if (in_array($k, $allowedProperties)) {
  593. $pa[$k] = $value;
  594. }
  595. }
  596. // clone it because of caching
  597. $tmp = clone $p;
  598. $tmp->setData($pa);
  599. $properties[$key] = $tmp->getObjectVars();
  600. } else {
  601. $properties[$key] = $p->getObjectVars();
  602. }
  603. // add config from predefined properties
  604. if ($p->getName() && $p->getType()) {
  605. $predefined = Model\Property\Predefined::getByKey($p->getName());
  606. if ($predefined && $predefined->getType() == $p->getType()) {
  607. $properties[$key]['config'] = $predefined->getConfig();
  608. $properties[$key]['predefinedName'] = $predefined->getName();
  609. $properties[$key]['description'] = $predefined->getDescription();
  610. }
  611. }
  612. }
  613. return $properties;
  614. }
  615. /**
  616. * @internal
  617. *
  618. * @param DataObject|Document|Asset\Folder $target the parent element
  619. * @param ElementInterface $new the newly inserted child
  620. */
  621. protected function updateChildren($target, $new)
  622. {
  623. //check in case of recursion
  624. $found = false;
  625. foreach ($target->getChildren() as $child) {
  626. if ($child->getId() == $new->getId()) {
  627. $found = true;
  628. break;
  629. }
  630. }
  631. if (!$found) {
  632. $target->setChildren(array_merge($target->getChildren(), [$new]));
  633. }
  634. }
  635. /**
  636. * @internal
  637. *
  638. * @param ElementInterface $element
  639. *
  640. * @return array
  641. */
  642. public static function gridElementData(ElementInterface $element)
  643. {
  644. $data = [
  645. 'id' => $element->getId(),
  646. 'fullpath' => $element->getRealFullPath(),
  647. 'type' => self::getElementType($element),
  648. 'subtype' => $element->getType(),
  649. 'filename' => $element->getKey(),
  650. 'creationDate' => $element->getCreationDate(),
  651. 'modificationDate' => $element->getModificationDate(),
  652. ];
  653. if (method_exists($element, 'isPublished')) {
  654. $data['published'] = $element->isPublished();
  655. } else {
  656. $data['published'] = true;
  657. }
  658. return $data;
  659. }
  660. /**
  661. * find all elements which the user may not list and therefore may never be shown to the user.
  662. * A user may have custom workspaces and/or may inherit those from their role(s), if any.
  663. *
  664. * @internal
  665. *
  666. * @param string $type asset|object|document
  667. * @param Model\User $user
  668. *
  669. * @return array{forbidden: array, allowed: array}
  670. */
  671. public static function findForbiddenPaths($type, $user)
  672. {
  673. $db = Db::get();
  674. if ($user->isAdmin()) {
  675. return ['forbidden' => [], 'allowed' => ['/']];
  676. }
  677. $workspaceCids = [];
  678. $userWorkspaces = $db->fetchAllAssociative('SELECT cpath, cid, list FROM users_workspaces_' . $type . ' WHERE userId = ?', [$user->getId()]);
  679. if ($userWorkspaces) {
  680. // this collects the array that are on user-level, which have top priority
  681. foreach ($userWorkspaces as $userWorkspace) {
  682. $workspaceCids[] = $userWorkspace['cid'];
  683. }
  684. }
  685. if ($userRoleIds = $user->getRoles()) {
  686. $roleWorkspacesSql = 'SELECT cpath, userid, max(list) as list FROM users_workspaces_' . $type . ' WHERE userId IN (' . implode(',', $userRoleIds) . ')';
  687. if ($workspaceCids) {
  688. $roleWorkspacesSql .= ' AND cid NOT IN (' . implode(',', $workspaceCids) . ')';
  689. }
  690. $roleWorkspacesSql .= ' GROUP BY cpath';
  691. $roleWorkspaces = $db->fetchAllAssociative($roleWorkspacesSql);
  692. }
  693. $uniquePaths = [];
  694. foreach (array_merge($userWorkspaces, $roleWorkspaces ?? []) as $workspace) {
  695. $uniquePaths[$workspace['cpath']] = $workspace['list'];
  696. }
  697. ksort($uniquePaths);
  698. //TODO: above this should be all in one query (eg. instead of ksort, use sql sort) but had difficulties making the `group by` working properly to let user permissions take precedence
  699. $totalPaths = count($uniquePaths);
  700. $forbidden = [];
  701. $allowed = [];
  702. if ($totalPaths > 0) {
  703. $uniquePathsKeys = array_keys($uniquePaths);
  704. for ($index = 0; $index < $totalPaths; $index++) {
  705. $path = $uniquePathsKeys[$index];
  706. if ($uniquePaths[$path] == 0) {
  707. $forbidden[$path] = [];
  708. for ($findIndex = $index + 1; $findIndex < $totalPaths; $findIndex++) { //NB: the starting index is the last index we got
  709. $findPath = $uniquePathsKeys[$findIndex];
  710. if (str_contains($findPath, $path)) { //it means that we found a children
  711. if ($uniquePaths[$findPath] == 1) {
  712. array_push($forbidden[$path], $findPath); //adding list=1 children
  713. }
  714. } else {
  715. break;
  716. }
  717. }
  718. } else {
  719. $allowed[] = $path;
  720. }
  721. }
  722. } else {
  723. $forbidden['/'] = [];
  724. }
  725. return ['forbidden' => $forbidden, 'allowed' => $allowed];
  726. }
  727. /**
  728. * renews all references, for example after unserializing an ElementInterface
  729. *
  730. * @internal
  731. *
  732. * @param mixed $data
  733. * @param bool $initial
  734. * @param string $key
  735. *
  736. * @return mixed
  737. */
  738. public static function renewReferences($data, $initial = true, $key = null)
  739. {
  740. if ($data instanceof \__PHP_Incomplete_Class) {
  741. Logger::err(sprintf('Renew References: Cannot read data (%s) of incomplete class.', is_null($key) ? 'not available' : $key));
  742. return null;
  743. }
  744. if (is_array($data)) {
  745. foreach ($data as $dataKey => &$value) {
  746. $value = self::renewReferences($value, false, $dataKey);
  747. }
  748. return $data;
  749. }
  750. if (is_object($data)) {
  751. if ($data instanceof ElementInterface && !$initial) {
  752. return self::getElementById(self::getElementType($data), $data->getId());
  753. }
  754. // if this is the initial element set the correct path and key
  755. if ($data instanceof ElementInterface && !DataObject\AbstractObject::doNotRestoreKeyAndPath()) {
  756. $originalElement = self::getElementById(self::getElementType($data), $data->getId());
  757. if ($originalElement) {
  758. //do not override filename for Assets https://github.com/pimcore/pimcore/issues/8316
  759. // if ($data instanceof Asset) {
  760. // /** @var Asset $originalElement */
  761. // $data->setFilename($originalElement->getFilename());
  762. // } else
  763. if ($data instanceof Document) {
  764. /** @var Document $originalElement */
  765. $data->setKey($originalElement->getKey());
  766. } elseif ($data instanceof DataObject\AbstractObject) {
  767. /** @var AbstractObject $originalElement */
  768. $data->setKey($originalElement->getKey());
  769. }
  770. $data->setPath($originalElement->getRealPath());
  771. }
  772. }
  773. if ($data instanceof Model\AbstractModel) {
  774. $properties = $data->getObjectVars();
  775. foreach ($properties as $name => $value) {
  776. $data->setObjectVar($name, self::renewReferences($value, false, $name), true);
  777. }
  778. } else {
  779. $properties = method_exists($data, 'getObjectVars') ? $data->getObjectVars() : get_object_vars($data);
  780. foreach ($properties as $name => $value) {
  781. if (method_exists($data, 'setObjectVar')) {
  782. $data->setObjectVar($name, self::renewReferences($value, false, $name), true);
  783. } else {
  784. $data->$name = self::renewReferences($value, false, $name);
  785. }
  786. }
  787. }
  788. return $data;
  789. }
  790. return $data;
  791. }
  792. /**
  793. * @internal
  794. *
  795. * @param string $path
  796. *
  797. * @return string
  798. */
  799. public static function correctPath(string $path): string
  800. {
  801. // remove trailing slash
  802. if ($path !== '/') {
  803. $path = rtrim($path, '/ ');
  804. }
  805. // correct wrong path (root-node problem)
  806. $path = str_replace('//', '/', $path);
  807. if (str_contains($path, '%')) {
  808. $path = rawurldecode($path);
  809. }
  810. return $path;
  811. }
  812. /**
  813. * @internal
  814. *
  815. * @param ElementInterface $element
  816. *
  817. * @return ElementInterface
  818. */
  819. public static function loadAllFields(ElementInterface $element): ElementInterface
  820. {
  821. if ($element instanceof Document) {
  822. Document\Service::loadAllDocumentFields($element);
  823. } elseif ($element instanceof DataObject\Concrete) {
  824. DataObject\Service::loadAllObjectFields($element);
  825. } elseif ($element instanceof Asset) {
  826. Asset\Service::loadAllFields($element);
  827. }
  828. return $element;
  829. }
  830. /** Callback for array_filter function.
  831. * @param string $var value
  832. *
  833. * @return bool true if value is accepted
  834. */
  835. private static function filterNullValues($var)
  836. {
  837. return strlen($var) > 0;
  838. }
  839. /**
  840. * @param string $path
  841. * @param array $options
  842. *
  843. * @return Asset\Folder|Document\Folder|DataObject\Folder
  844. *
  845. * @throws \Exception
  846. */
  847. public static function createFolderByPath($path, $options = [])
  848. {
  849. $calledClass = static::class;
  850. if ($calledClass === __CLASS__) {
  851. throw new \Exception('This method must be called from a extended class. e.g Asset\\Service, DataObject\\Service, Document\\Service');
  852. }
  853. $type = str_replace('\Service', '', $calledClass);
  854. $type = '\\' . ltrim($type, '\\');
  855. $folderType = $type . '\Folder';
  856. $lastFolder = null;
  857. $pathsArray = [];
  858. $parts = explode('/', $path);
  859. $parts = array_filter($parts, '\\Pimcore\\Model\\Element\\Service::filterNullValues');
  860. $sanitizedPath = '/';
  861. $itemType = self::getElementType(new $type);
  862. foreach ($parts as $part) {
  863. $sanitizedPath = $sanitizedPath . self::getValidKey($part, $itemType) . '/';
  864. }
  865. if (self::pathExists($sanitizedPath, $itemType)) {
  866. return $type::getByPath($sanitizedPath);
  867. }
  868. foreach ($parts as $part) {
  869. $pathPart = $pathsArray[count($pathsArray) - 1] ?? '';
  870. $pathsArray[] = $pathPart . '/' . self::getValidKey($part, $itemType);
  871. }
  872. for ($i = 0; $i < count($pathsArray); $i++) {
  873. $currentPath = $pathsArray[$i];
  874. if (!self::pathExists($currentPath, $itemType)) {
  875. $parentFolderPath = ($i == 0) ? '/' : $pathsArray[$i - 1];
  876. $parentFolder = $type::getByPath($parentFolderPath);
  877. $folder = new $folderType();
  878. $folder->setParent($parentFolder);
  879. if ($parentFolder) {
  880. $folder->setParentId($parentFolder->getId());
  881. } else {
  882. $folder->setParentId(1);
  883. }
  884. $key = substr($currentPath, strrpos($currentPath, '/') + 1, strlen($currentPath));
  885. if (method_exists($folder, 'setKey')) {
  886. $folder->setKey($key);
  887. }
  888. if (method_exists($folder, 'setFilename')) {
  889. $folder->setFilename($key);
  890. }
  891. if (method_exists($folder, 'setType')) {
  892. $folder->setType('folder');
  893. }
  894. $folder->setPath($currentPath);
  895. $folder->setUserModification(0);
  896. $folder->setUserOwner(1);
  897. $folder->setCreationDate(time());
  898. $folder->setModificationDate(time());
  899. $folder->setValues($options);
  900. $folder->save();
  901. $lastFolder = $folder;
  902. }
  903. }
  904. return $lastFolder;
  905. }
  906. /**
  907. * Changes the query according to the custom view config
  908. *
  909. * @internal
  910. *
  911. * @param array $cv
  912. * @param Model\Asset\Listing|Model\DataObject\Listing|Model\Document\Listing $childsList
  913. */
  914. public static function addTreeFilterJoins($cv, $childsList)
  915. {
  916. if ($cv) {
  917. $childsList->onCreateQueryBuilder(static function (DoctrineQueryBuilder $select) use ($cv) {
  918. $where = $cv['where'] ?? null;
  919. if ($where) {
  920. $select->andWhere($where);
  921. }
  922. $fromAlias = $select->getQueryPart('from')[0]['alias'] ?? $select->getQueryPart('from')[0]['table'] ;
  923. $customViewJoins = $cv['joins'] ?? null;
  924. if ($customViewJoins) {
  925. foreach ($customViewJoins as $joinConfig) {
  926. $type = $joinConfig['type'];
  927. $method = $type == 'left' || $type == 'right' ? $method = $type . 'Join' : 'join';
  928. $joinAlias = array_keys($joinConfig['name']);
  929. $joinAlias = reset($joinAlias);
  930. $joinTable = $joinConfig['name'][$joinAlias];
  931. $condition = $joinConfig['condition'];
  932. $columns = $joinConfig['columns'];
  933. $select->addSelect($columns);
  934. $select->$method($fromAlias, $joinTable, $joinAlias, $condition);
  935. }
  936. }
  937. if (!empty($cv['having'])) {
  938. $select->having($cv['having']);
  939. }
  940. });
  941. }
  942. }
  943. /**
  944. * @internal
  945. *
  946. * @param string $id
  947. *
  948. * @return array|null
  949. */
  950. public static function getCustomViewById($id)
  951. {
  952. $customViews = \Pimcore\CustomView\Config::get();
  953. if ($customViews) {
  954. foreach ($customViews as $customView) {
  955. if ($customView['id'] == $id) {
  956. return $customView;
  957. }
  958. }
  959. }
  960. return null;
  961. }
  962. /**
  963. * @param string $key
  964. * @param string $type
  965. *
  966. * @return string
  967. */
  968. public static function getValidKey($key, $type)
  969. {
  970. $event = new GenericEvent(null, [
  971. 'key' => $key,
  972. 'type' => $type,
  973. ]);
  974. \Pimcore::getEventDispatcher()->dispatch($event, SystemEvents::SERVICE_PRE_GET_VALID_KEY);
  975. $key = $event->getArgument('key');
  976. $key = trim($key);
  977. // replace all 4 byte unicode characters
  978. $key = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '-', $key);
  979. // replace left to right marker characters ( lrm )
  980. $key = preg_replace('/(\x{200e}|\x{200f})/u', '-', $key);
  981. // replace slashes with a hyphen
  982. $key = str_replace('/', '-', $key);
  983. if ($type === 'object') {
  984. $key = preg_replace('/[<>]/', '-', $key);
  985. } elseif ($type === 'document') {
  986. // replace URL reserved characters with a hyphen
  987. $key = preg_replace('/[#\?\*\:\\\\<\>\|"%&@=;\+]/', '-', $key);
  988. } elseif ($type === 'asset') {
  989. // keys shouldn't start with a "." (=hidden file) *nix operating systems
  990. // keys shouldn't end with a "." - Windows issue: filesystem API trims automatically . at the end of a folder name (no warning ... et al)
  991. $key = trim($key, '. ');
  992. // windows forbidden filenames + URL reserved characters (at least the ones which are problematic)
  993. $key = preg_replace('/[#\?\*\:\\\\<\>\|"%\+]/', '-', $key);
  994. } else {
  995. $key = ltrim($key, '. ');
  996. }
  997. $key = mb_substr($key, 0, 255);
  998. return $key;
  999. }
  1000. /**
  1001. * @param string $key
  1002. * @param string $type
  1003. *
  1004. * @return bool
  1005. */
  1006. public static function isValidKey($key, $type)
  1007. {
  1008. return self::getValidKey($key, $type) == $key;
  1009. }
  1010. /**
  1011. * @param string $path
  1012. * @param string $type
  1013. *
  1014. * @return bool
  1015. */
  1016. public static function isValidPath($path, $type)
  1017. {
  1018. $parts = explode('/', $path);
  1019. foreach ($parts as $part) {
  1020. if (!self::isValidKey($part, $type)) {
  1021. return false;
  1022. }
  1023. }
  1024. return true;
  1025. }
  1026. /**
  1027. * returns a unique key for an element
  1028. *
  1029. * @param ElementInterface $element
  1030. *
  1031. * @return string|null
  1032. */
  1033. public static function getUniqueKey($element)
  1034. {
  1035. if ($element instanceof DataObject\AbstractObject) {
  1036. return DataObject\Service::getUniqueKey($element);
  1037. }
  1038. if ($element instanceof Document) {
  1039. return Document\Service::getUniqueKey($element);
  1040. }
  1041. if ($element instanceof Asset) {
  1042. return Asset\Service::getUniqueKey($element);
  1043. }
  1044. return null;
  1045. }
  1046. /**
  1047. * @internal
  1048. *
  1049. * @param array $data
  1050. * @param string $type
  1051. *
  1052. * @return array
  1053. */
  1054. public static function fixAllowedTypes($data, $type)
  1055. {
  1056. // this is the new method with Ext.form.MultiSelect
  1057. if (is_array($data) && count($data)) {
  1058. $first = reset($data);
  1059. if (!is_array($first)) {
  1060. $parts = $data;
  1061. $data = [];
  1062. foreach ($parts as $elementType) {
  1063. $data[] = [$type => $elementType];
  1064. }
  1065. } else {
  1066. $newList = [];
  1067. foreach ($data as $key => $item) {
  1068. if ($item) {
  1069. if (is_array($item)) {
  1070. foreach ($item as $itemKey => $itemValue) {
  1071. if ($itemValue) {
  1072. $newList[$key][$itemKey] = $itemValue;
  1073. }
  1074. }
  1075. } else {
  1076. $newList[$key] = $item;
  1077. }
  1078. }
  1079. }
  1080. $data = $newList;
  1081. }
  1082. }
  1083. return $data ? $data : [];
  1084. }
  1085. /**
  1086. * @internal
  1087. *
  1088. * @param Model\Version[] $versions
  1089. *
  1090. * @return array
  1091. */
  1092. public static function getSafeVersionInfo($versions)
  1093. {
  1094. $indexMap = [];
  1095. $result = [];
  1096. if (is_array($versions)) {
  1097. foreach ($versions as $versionObj) {
  1098. $version = [
  1099. 'id' => $versionObj->getId(),
  1100. 'cid' => $versionObj->getCid(),
  1101. 'ctype' => $versionObj->getCtype(),
  1102. 'note' => $versionObj->getNote(),
  1103. 'date' => $versionObj->getDate(),
  1104. 'public' => $versionObj->getPublic(),
  1105. 'versionCount' => $versionObj->getVersionCount(),
  1106. 'autoSave' => $versionObj->isAutoSave(),
  1107. ];
  1108. $version['user'] = ['name' => '', 'id' => ''];
  1109. if ($user = $versionObj->getUser()) {
  1110. $version['user'] = [
  1111. 'name' => $user->getName(),
  1112. 'id' => $user->getId(),
  1113. ];
  1114. }
  1115. $versionKey = $versionObj->getDate() . '-' . $versionObj->getVersionCount();
  1116. if (!isset($indexMap[$versionKey])) {
  1117. $indexMap[$versionKey] = 0;
  1118. }
  1119. $version['index'] = $indexMap[$versionKey];
  1120. $indexMap[$versionKey] = $indexMap[$versionKey] + 1;
  1121. $result[] = $version;
  1122. }
  1123. }
  1124. return $result;
  1125. }
  1126. /**
  1127. * @param ElementInterface $element
  1128. *
  1129. * @return ElementInterface
  1130. */
  1131. public static function cloneMe(ElementInterface $element)
  1132. {
  1133. $deepCopy = new \DeepCopy\DeepCopy();
  1134. $deepCopy->addFilter(new \DeepCopy\Filter\KeepFilter(), new class() implements \DeepCopy\Matcher\Matcher {
  1135. /**
  1136. * {@inheritdoc}
  1137. */
  1138. public function matches($object, $property)
  1139. {
  1140. try {
  1141. $reflectionProperty = new \ReflectionProperty($object, $property);
  1142. $reflectionProperty->setAccessible(true);
  1143. $myValue = $reflectionProperty->getValue($object);
  1144. } catch (\Throwable $e) {
  1145. return false;
  1146. }
  1147. return $myValue instanceof ElementInterface;
  1148. }
  1149. });
  1150. if ($element instanceof Concrete) {
  1151. $deepCopy->addFilter(
  1152. new PimcoreClassDefinitionReplaceFilter(
  1153. function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1154. if ($fieldDefinition instanceof Data\CustomDataCopyInterface) {
  1155. return $fieldDefinition->createDataCopy($object, $currentValue);
  1156. }
  1157. return $currentValue;
  1158. }
  1159. ),
  1160. new PimcoreClassDefinitionMatcher(Data\CustomDataCopyInterface::class)
  1161. );
  1162. }
  1163. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('dao'));
  1164. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('resource'));
  1165. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('writeResource'));
  1166. $deepCopy->addFilter(new \DeepCopy\Filter\Doctrine\DoctrineCollectionFilter(), new \DeepCopy\Matcher\PropertyTypeMatcher(
  1167. Collection::class
  1168. ));
  1169. if ($element instanceof DataObject\Concrete) {
  1170. DataObject\Service::loadAllObjectFields($element);
  1171. }
  1172. $theCopy = $deepCopy->copy($element);
  1173. $theCopy->setId(null);
  1174. $theCopy->setParent(null);
  1175. return $theCopy;
  1176. }
  1177. /**
  1178. * @template T
  1179. *
  1180. * @param T $properties
  1181. *
  1182. * @return T
  1183. */
  1184. public static function cloneProperties(mixed $properties): mixed
  1185. {
  1186. $deepCopy = new \DeepCopy\DeepCopy();
  1187. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('cid'));
  1188. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('ctype'));
  1189. $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('cpath'));
  1190. return $deepCopy->copy($properties);
  1191. }
  1192. /**
  1193. * @internal
  1194. *
  1195. * @param Note $note
  1196. *
  1197. * @return array
  1198. */
  1199. public static function getNoteData(Note $note)
  1200. {
  1201. $cpath = '';
  1202. if ($note->getCid() && $note->getCtype()) {
  1203. if ($element = Service::getElementById($note->getCtype(), $note->getCid())) {
  1204. $cpath = $element->getRealFullPath();
  1205. }
  1206. }
  1207. $e = [
  1208. 'id' => $note->getId(),
  1209. 'type' => $note->getType(),
  1210. 'cid' => $note->getCid(),
  1211. 'ctype' => $note->getCtype(),
  1212. 'cpath' => $cpath,
  1213. 'date' => $note->getDate(),
  1214. 'title' => Pimcore::getContainer()->get(TranslatorInterface::class)->trans($note->getTitle(), [], 'admin'),
  1215. 'description' => $note->getDescription(),
  1216. ];
  1217. // prepare key-values
  1218. $keyValues = [];
  1219. if (is_array($note->getData())) {
  1220. foreach ($note->getData() as $name => $d) {
  1221. $type = $d['type'];
  1222. $data = $d['data'];
  1223. if ($type == 'document' || $type == 'object' || $type == 'asset') {
  1224. if ($d['data'] instanceof ElementInterface) {
  1225. $data = [
  1226. 'id' => $d['data']->getId(),
  1227. 'path' => $d['data']->getRealFullPath(),
  1228. 'type' => $d['data']->getType(),
  1229. ];
  1230. }
  1231. } elseif ($type == 'date') {
  1232. if (is_object($d['data'])) {
  1233. $data = $d['data']->getTimestamp();
  1234. }
  1235. }
  1236. $keyValue = [
  1237. 'type' => $type,
  1238. 'name' => $name,
  1239. 'data' => $data,
  1240. ];
  1241. $keyValues[] = $keyValue;
  1242. }
  1243. }
  1244. $e['data'] = $keyValues;
  1245. // prepare user data
  1246. if ($note->getUser()) {
  1247. $user = Model\User::getById($note->getUser());
  1248. if ($user) {
  1249. $e['user'] = [
  1250. 'id' => $user->getId(),
  1251. 'name' => $user->getName(),
  1252. ];
  1253. } else {
  1254. $e['user'] = '';
  1255. }
  1256. }
  1257. return $e;
  1258. }
  1259. /**
  1260. * @internal
  1261. *
  1262. * @param string $type
  1263. * @param int $elementId
  1264. * @param null|string $postfix
  1265. *
  1266. * @return string
  1267. */
  1268. public static function getSessionKey($type, $elementId, $postfix = '')
  1269. {
  1270. $sessionId = Session::getSessionId();
  1271. $tmpStoreKey = $type . '_session_' . $elementId . '_' . $sessionId . $postfix;
  1272. return $tmpStoreKey;
  1273. }
  1274. /**
  1275. *
  1276. * @param string $type
  1277. * @param int $elementId
  1278. * @param null|string $postfix
  1279. *
  1280. * @return AbstractObject|Document|Asset|null
  1281. */
  1282. public static function getElementFromSession($type, $elementId, $postfix = '')
  1283. {
  1284. $element = null;
  1285. $tmpStoreKey = self::getSessionKey($type, $elementId, $postfix);
  1286. $tmpStore = TmpStore::get($tmpStoreKey);
  1287. if ($tmpStore) {
  1288. $data = $tmpStore->getData();
  1289. if ($data) {
  1290. $element = Serialize::unserialize($data);
  1291. $context = [
  1292. 'source' => __METHOD__,
  1293. 'conversion' => 'unmarshal',
  1294. ];
  1295. $copier = Self::getDeepCopyInstance($element, $context);
  1296. if ($element instanceof Concrete) {
  1297. $copier->addFilter(
  1298. new PimcoreClassDefinitionReplaceFilter(
  1299. function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1300. if ($fieldDefinition instanceof Data\CustomVersionMarshalInterface) {
  1301. return $fieldDefinition->unmarshalVersion($object, $currentValue);
  1302. }
  1303. return $currentValue;
  1304. }
  1305. ),
  1306. new PimcoreClassDefinitionMatcher(Data\CustomVersionMarshalInterface::class)
  1307. );
  1308. }
  1309. return $copier->copy($element);
  1310. }
  1311. }
  1312. return $element;
  1313. }
  1314. /**
  1315. * @internal
  1316. *
  1317. * @param ElementInterface $element
  1318. * @param string $postfix
  1319. * @param bool $clone save a copy
  1320. */
  1321. public static function saveElementToSession($element, $postfix = '', $clone = true)
  1322. {
  1323. if ($clone) {
  1324. $context = [
  1325. 'source' => __METHOD__,
  1326. 'conversion' => 'marshal',
  1327. ];
  1328. $copier = self::getDeepCopyInstance($element, $context);
  1329. if ($element instanceof Concrete) {
  1330. $copier->addFilter(
  1331. new PimcoreClassDefinitionReplaceFilter(
  1332. function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1333. if ($fieldDefinition instanceof Data\CustomVersionMarshalInterface) {
  1334. return $fieldDefinition->marshalVersion($object, $currentValue);
  1335. }
  1336. return $currentValue;
  1337. }
  1338. ),
  1339. new PimcoreClassDefinitionMatcher(Data\CustomVersionMarshalInterface::class)
  1340. );
  1341. }
  1342. $element = $copier->copy($element);
  1343. }
  1344. $elementType = Service::getElementType($element);
  1345. $tmpStoreKey = self::getSessionKey($elementType, $element->getId(), $postfix);
  1346. $tag = $elementType . '-session' . $postfix;
  1347. self::loadAllFields($element);
  1348. $element->setInDumpState(true);
  1349. $serializedData = Serialize::serialize($element);
  1350. TmpStore::set($tmpStoreKey, $serializedData, $tag);
  1351. }
  1352. /**
  1353. * @internal
  1354. *
  1355. * @param string $type
  1356. * @param int $elementId
  1357. * @param string $postfix
  1358. */
  1359. public static function removeElementFromSession($type, $elementId, $postfix = '')
  1360. {
  1361. $tmpStoreKey = self::getSessionKey($type, $elementId, $postfix);
  1362. TmpStore::delete($tmpStoreKey);
  1363. }
  1364. /**
  1365. * @internal
  1366. *
  1367. * @param mixed $element
  1368. * @param array|null $context
  1369. *
  1370. * @return DeepCopy
  1371. */
  1372. public static function getDeepCopyInstance($element, ?array $context = []): DeepCopy
  1373. {
  1374. $copier = new DeepCopy();
  1375. $copier->skipUncloneable(true);
  1376. if ($element instanceof ElementInterface) {
  1377. if (($context['conversion'] ?? false) === 'marshal') {
  1378. $sourceType = Service::getElementType($element);
  1379. $sourceId = $element->getId();
  1380. $copier->addTypeFilter(
  1381. new \DeepCopy\TypeFilter\ReplaceFilter(
  1382. function ($currentValue) {
  1383. if ($currentValue instanceof ElementInterface) {
  1384. $elementType = Service::getElementType($currentValue);
  1385. $descriptor = new ElementDescriptor($elementType, $currentValue->getId());
  1386. return $descriptor;
  1387. }
  1388. return $currentValue;
  1389. }
  1390. ),
  1391. new MarshalMatcher($sourceType, $sourceId)
  1392. );
  1393. } elseif (($context['conversion'] ?? false) === 'unmarshal') {
  1394. $copier->addTypeFilter(
  1395. new \DeepCopy\TypeFilter\ReplaceFilter(
  1396. function ($currentValue) {
  1397. if ($currentValue instanceof ElementDescriptor) {
  1398. $value = Service::getElementById($currentValue->getType(), $currentValue->getId());
  1399. return $value;
  1400. }
  1401. return $currentValue;
  1402. }
  1403. ),
  1404. new UnmarshalMatcher()
  1405. );
  1406. }
  1407. }
  1408. if ($context['defaultFilters'] ?? false) {
  1409. $copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));
  1410. $copier->addFilter(new SetNullFilter(), new PropertyTypeMatcher('Psr\Container\ContainerInterface'));
  1411. $copier->addFilter(new SetNullFilter(), new PropertyTypeMatcher('Pimcore\Model\DataObject\ClassDefinition'));
  1412. }
  1413. $event = new GenericEvent(null, [
  1414. 'copier' => $copier,
  1415. 'element' => $element,
  1416. 'context' => $context,
  1417. ]);
  1418. \Pimcore::getEventDispatcher()->dispatch($event, SystemEvents::SERVICE_PRE_GET_DEEP_COPY);
  1419. return $event->getArgument('copier');
  1420. }
  1421. /**
  1422. * @internal
  1423. *
  1424. * @param array $rowData
  1425. *
  1426. * @return array
  1427. */
  1428. public static function escapeCsvRecord(array $rowData): array
  1429. {
  1430. if (self::$formatter === null) {
  1431. self::$formatter = new EscapeFormula("'", ['=', '-', '+', '@']);
  1432. }
  1433. $rowData = self::$formatter->escapeRecord($rowData);
  1434. return $rowData;
  1435. }
  1436. /**
  1437. * @internal
  1438. *
  1439. * @param string $type
  1440. * @param int|string $id
  1441. *
  1442. * @return string
  1443. */
  1444. public static function getElementCacheTag(string $type, $id): string
  1445. {
  1446. return $type . '_' . $id;
  1447. }
  1448. }