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

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