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