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

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