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

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