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

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