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 $sourceKey, ElementInterface $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 */$params, string $method, bool $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($className, AbstractObject::class, true)) {
  522.             return 'object';
  523.         }
  524.         if (is_a($className, Asset::class, true)) {
  525.             return 'asset';
  526.         }
  527.         if (is_a($className, Document::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($value, false, $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($name, self::renewReferences($value, false, $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($name, self::renewReferences($value, false, $name), true);
  783.                     } else {
  784.                         $data->$name = self::renewReferences($value, false, $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($currentPath, strrpos($currentPath, '/') + 1, strlen($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($event, SystemEvents::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 left to right marker characters ( lrm )
  980.         $key = preg_replace('/(\x{200e}|\x{200f})/u', '-', $key);
  981.         // replace slashes with a hyphen
  982.         $key = str_replace('/', '-', $key);
  983.         if ($type === 'object') {
  984.             $key = preg_replace('/[<>]/', '-', $key);
  985.         } elseif ($type === 'document') {
  986.             // replace URL reserved characters with a hyphen
  987.             $key = preg_replace('/[#\?\*\:\\\\<\>\|"%&@=;\+]/', '-', $key);
  988.         } elseif ($type === 'asset') {
  989.             // keys shouldn't start with a "." (=hidden file) *nix operating systems
  990.             // keys shouldn't end with a "." - Windows issue: filesystem API trims automatically . at the end of a folder name (no warning ... et al)
  991.             $key = trim($key, '. ');
  992.             // windows forbidden filenames + URL reserved characters (at least the ones which are problematic)
  993.             $key = preg_replace('/[#\?\*\:\\\\<\>\|"%\+]/', '-', $key);
  994.         } else {
  995.             $key = ltrim($key, '. ');
  996.         }
  997.         $key = mb_substr($key, 0, 255);
  998.         return $key;
  999.     }
  1000.     /**
  1001.      * @param string $key
  1002.      * @param string $type
  1003.      *
  1004.      * @return bool
  1005.      */
  1006.     public static function isValidKey($key, $type)
  1007.     {
  1008.         return self::getValidKey($key, $type) == $key;
  1009.     }
  1010.     /**
  1011.      * @param string $path
  1012.      * @param string $type
  1013.      *
  1014.      * @return bool
  1015.      */
  1016.     public static function isValidPath($path, $type)
  1017.     {
  1018.         $parts = explode('/', $path);
  1019.         foreach ($parts as $part) {
  1020.             if (!self::isValidKey($part, $type)) {
  1021.                 return false;
  1022.             }
  1023.         }
  1024.         return true;
  1025.     }
  1026.     /**
  1027.      * returns a unique key for an element
  1028.      *
  1029.      * @param ElementInterface $element
  1030.      *
  1031.      * @return string|null
  1032.      */
  1033.     public static function getUniqueKey($element)
  1034.     {
  1035.         if ($element instanceof DataObject\AbstractObject) {
  1036.             return DataObject\Service::getUniqueKey($element);
  1037.         }
  1038.         if ($element instanceof Document) {
  1039.             return Document\Service::getUniqueKey($element);
  1040.         }
  1041.         if ($element instanceof Asset) {
  1042.             return Asset\Service::getUniqueKey($element);
  1043.         }
  1044.         return null;
  1045.     }
  1046.     /**
  1047.      * @internal
  1048.      *
  1049.      * @param array $data
  1050.      * @param string $type
  1051.      *
  1052.      * @return array
  1053.      */
  1054.     public static function fixAllowedTypes($data, $type)
  1055.     {
  1056.         // this is the new method with Ext.form.MultiSelect
  1057.         if (is_array($data) && count($data)) {
  1058.             $first = reset($data);
  1059.             if (!is_array($first)) {
  1060.                 $parts = $data;
  1061.                 $data = [];
  1062.                 foreach ($parts as $elementType) {
  1063.                     $data[] = [$type => $elementType];
  1064.                 }
  1065.             } else {
  1066.                 $newList = [];
  1067.                 foreach ($data as $key => $item) {
  1068.                     if ($item) {
  1069.                         if (is_array($item)) {
  1070.                             foreach ($item as $itemKey => $itemValue) {
  1071.                                 if ($itemValue) {
  1072.                                     $newList[$key][$itemKey] = $itemValue;
  1073.                                 }
  1074.                             }
  1075.                         } else {
  1076.                             $newList[$key] = $item;
  1077.                         }
  1078.                     }
  1079.                 }
  1080.                 $data = $newList;
  1081.             }
  1082.         }
  1083.         return $data ? $data : [];
  1084.     }
  1085.     /**
  1086.      * @internal
  1087.      *
  1088.      * @param Model\Version[] $versions
  1089.      *
  1090.      * @return array
  1091.      */
  1092.     public static function getSafeVersionInfo($versions)
  1093.     {
  1094.         $indexMap = [];
  1095.         $result = [];
  1096.         if (is_array($versions)) {
  1097.             foreach ($versions as $versionObj) {
  1098.                 $version = [
  1099.                     'id' => $versionObj->getId(),
  1100.                     'cid' => $versionObj->getCid(),
  1101.                     'ctype' => $versionObj->getCtype(),
  1102.                     'note' => $versionObj->getNote(),
  1103.                     'date' => $versionObj->getDate(),
  1104.                     'public' => $versionObj->getPublic(),
  1105.                     'versionCount' => $versionObj->getVersionCount(),
  1106.                     'autoSave' => $versionObj->isAutoSave(),
  1107.                 ];
  1108.                 $version['user'] = ['name' => '', 'id' => ''];
  1109.                 if ($user = $versionObj->getUser()) {
  1110.                     $version['user'] = [
  1111.                         'name' => $user->getName(),
  1112.                         'id' => $user->getId(),
  1113.                     ];
  1114.                 }
  1115.                 $versionKey = $versionObj->getDate() . '-' . $versionObj->getVersionCount();
  1116.                 if (!isset($indexMap[$versionKey])) {
  1117.                     $indexMap[$versionKey] = 0;
  1118.                 }
  1119.                 $version['index'] = $indexMap[$versionKey];
  1120.                 $indexMap[$versionKey] = $indexMap[$versionKey] + 1;
  1121.                 $result[] = $version;
  1122.             }
  1123.         }
  1124.         return $result;
  1125.     }
  1126.     /**
  1127.      * @param ElementInterface $element
  1128.      *
  1129.      * @return ElementInterface
  1130.      */
  1131.     public static function cloneMe(ElementInterface $element)
  1132.     {
  1133.         $deepCopy = new \DeepCopy\DeepCopy();
  1134.         $deepCopy->addFilter(new \DeepCopy\Filter\KeepFilter(), new class() implements \DeepCopy\Matcher\Matcher {
  1135.             /**
  1136.              * {@inheritdoc}
  1137.              */
  1138.             public function matches($object, $property)
  1139.             {
  1140.                 try {
  1141.                     $reflectionProperty = new \ReflectionProperty($object, $property);
  1142.                     $reflectionProperty->setAccessible(true);
  1143.                     $myValue = $reflectionProperty->getValue($object);
  1144.                 } catch (\Throwable $e) {
  1145.                     return false;
  1146.                 }
  1147.                 return $myValue instanceof ElementInterface;
  1148.             }
  1149.         });
  1150.         if ($element instanceof Concrete) {
  1151.             $deepCopy->addFilter(
  1152.                 new PimcoreClassDefinitionReplaceFilter(
  1153.                     function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1154.                         if ($fieldDefinition instanceof Data\CustomDataCopyInterface) {
  1155.                             return $fieldDefinition->createDataCopy($object, $currentValue);
  1156.                         }
  1157.                         return $currentValue;
  1158.                     }
  1159.                 ),
  1160.                 new PimcoreClassDefinitionMatcher(Data\CustomDataCopyInterface::class)
  1161.             );
  1162.         }
  1163.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('dao'));
  1164.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('resource'));
  1165.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('writeResource'));
  1166.         $deepCopy->addFilter(new \DeepCopy\Filter\Doctrine\DoctrineCollectionFilter(), new \DeepCopy\Matcher\PropertyTypeMatcher(
  1167.             Collection::class
  1168.         ));
  1169.         if ($element instanceof DataObject\Concrete) {
  1170.             DataObject\Service::loadAllObjectFields($element);
  1171.         }
  1172.         $theCopy = $deepCopy->copy($element);
  1173.         $theCopy->setId(null);
  1174.         $theCopy->setParent(null);
  1175.         return $theCopy;
  1176.     }
  1177.     /**
  1178.      * @template T
  1179.      *
  1180.      * @param T $properties
  1181.      *
  1182.      * @return T
  1183.      */
  1184.     public static function cloneProperties(mixed $properties): mixed
  1185.     {
  1186.         $deepCopy = new \DeepCopy\DeepCopy();
  1187.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('cid'));
  1188.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('ctype'));
  1189.         $deepCopy->addFilter(new SetNullFilter(), new PropertyNameMatcher('cpath'));
  1190.         return $deepCopy->copy($properties);
  1191.     }
  1192.     /**
  1193.      * @internal
  1194.      *
  1195.      * @param Note $note
  1196.      *
  1197.      * @return array
  1198.      */
  1199.     public static function getNoteData(Note $note)
  1200.     {
  1201.         $cpath = '';
  1202.         if ($note->getCid() && $note->getCtype()) {
  1203.             if ($element = Service::getElementById($note->getCtype(), $note->getCid())) {
  1204.                 $cpath = $element->getRealFullPath();
  1205.             }
  1206.         }
  1207.         $e = [
  1208.             'id' => $note->getId(),
  1209.             'type' => $note->getType(),
  1210.             'cid' => $note->getCid(),
  1211.             'ctype' => $note->getCtype(),
  1212.             'cpath' => $cpath,
  1213.             'date' => $note->getDate(),
  1214.             'title' => Pimcore::getContainer()->get(TranslatorInterface::class)->trans($note->getTitle(), [], 'admin'),
  1215.             'description' => $note->getDescription(),
  1216.         ];
  1217.         // prepare key-values
  1218.         $keyValues = [];
  1219.         if (is_array($note->getData())) {
  1220.             foreach ($note->getData() as $name => $d) {
  1221.                 $type = $d['type'];
  1222.                 $data = $d['data'];
  1223.                 if ($type == 'document' || $type == 'object' || $type == 'asset') {
  1224.                     if ($d['data'] instanceof ElementInterface) {
  1225.                         $data = [
  1226.                             'id' => $d['data']->getId(),
  1227.                             'path' => $d['data']->getRealFullPath(),
  1228.                             'type' => $d['data']->getType(),
  1229.                         ];
  1230.                     }
  1231.                 } elseif ($type == 'date') {
  1232.                     if (is_object($d['data'])) {
  1233.                         $data = $d['data']->getTimestamp();
  1234.                     }
  1235.                 }
  1236.                 $keyValue = [
  1237.                     'type' => $type,
  1238.                     'name' => $name,
  1239.                     'data' => $data,
  1240.                 ];
  1241.                 $keyValues[] = $keyValue;
  1242.             }
  1243.         }
  1244.         $e['data'] = $keyValues;
  1245.         // prepare user data
  1246.         if ($note->getUser()) {
  1247.             $user = Model\User::getById($note->getUser());
  1248.             if ($user) {
  1249.                 $e['user'] = [
  1250.                     'id' => $user->getId(),
  1251.                     'name' => $user->getName(),
  1252.                 ];
  1253.             } else {
  1254.                 $e['user'] = '';
  1255.             }
  1256.         }
  1257.         return $e;
  1258.     }
  1259.     /**
  1260.      * @internal
  1261.      *
  1262.      * @param string $type
  1263.      * @param int $elementId
  1264.      * @param null|string $postfix
  1265.      *
  1266.      * @return string
  1267.      */
  1268.     public static function getSessionKey($type, $elementId, $postfix = '')
  1269.     {
  1270.         $sessionId = Session::getSessionId();
  1271.         $tmpStoreKey = $type . '_session_' . $elementId . '_' . $sessionId . $postfix;
  1272.         return $tmpStoreKey;
  1273.     }
  1274.     /**
  1275.      *
  1276.      * @param string $type
  1277.      * @param int $elementId
  1278.      * @param null|string $postfix
  1279.      *
  1280.      * @return AbstractObject|Document|Asset|null
  1281.      */
  1282.     public static function getElementFromSession($type, $elementId, $postfix = '')
  1283.     {
  1284.         $element = null;
  1285.         $tmpStoreKey = self::getSessionKey($type, $elementId, $postfix);
  1286.         $tmpStore = TmpStore::get($tmpStoreKey);
  1287.         if ($tmpStore) {
  1288.             $data = $tmpStore->getData();
  1289.             if ($data) {
  1290.                 $element = Serialize::unserialize($data);
  1291.                 $context = [
  1292.                     'source' => __METHOD__,
  1293.                     'conversion' => 'unmarshal',
  1294.                 ];
  1295.                 $copier = Self::getDeepCopyInstance($element, $context);
  1296.                 if ($element instanceof Concrete) {
  1297.                     $copier->addFilter(
  1298.                         new PimcoreClassDefinitionReplaceFilter(
  1299.                             function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1300.                                 if ($fieldDefinition instanceof Data\CustomVersionMarshalInterface) {
  1301.                                     return $fieldDefinition->unmarshalVersion($object, $currentValue);
  1302.                                 }
  1303.                                 return $currentValue;
  1304.                             }
  1305.                         ),
  1306.                         new PimcoreClassDefinitionMatcher(Data\CustomVersionMarshalInterface::class)
  1307.                     );
  1308.                 }
  1309.                 return $copier->copy($element);
  1310.             }
  1311.         }
  1312.         return $element;
  1313.     }
  1314.     /**
  1315.      * @internal
  1316.      *
  1317.      * @param ElementInterface $element
  1318.      * @param string $postfix
  1319.      * @param bool $clone save a copy
  1320.      */
  1321.     public static function saveElementToSession($element, $postfix = '', $clone = true)
  1322.     {
  1323.         if ($clone) {
  1324.             $context = [
  1325.                 'source' => __METHOD__,
  1326.                 'conversion' => 'marshal',
  1327.             ];
  1328.             $copier = self::getDeepCopyInstance($element, $context);
  1329.             if ($element instanceof Concrete) {
  1330.                 $copier->addFilter(
  1331.                     new PimcoreClassDefinitionReplaceFilter(
  1332.                         function (Concrete $object, Data $fieldDefinition, $property, $currentValue) {
  1333.                             if ($fieldDefinition instanceof Data\CustomVersionMarshalInterface) {
  1334.                                 return $fieldDefinition->marshalVersion($object, $currentValue);
  1335.                             }
  1336.                             return $currentValue;
  1337.                         }
  1338.                     ),
  1339.                     new PimcoreClassDefinitionMatcher(Data\CustomVersionMarshalInterface::class)
  1340.                 );
  1341.             }
  1342.             $element = $copier->copy($element);
  1343.         }
  1344.         $elementType = Service::getElementType($element);
  1345.         $tmpStoreKey = self::getSessionKey($elementType, $element->getId(), $postfix);
  1346.         $tag = $elementType . '-session' . $postfix;
  1347.         self::loadAllFields($element);
  1348.         $element->setInDumpState(true);
  1349.         $serializedData = Serialize::serialize($element);
  1350.         TmpStore::set($tmpStoreKey, $serializedData, $tag);
  1351.     }
  1352.     /**
  1353.      * @internal
  1354.      *
  1355.      * @param string $type
  1356.      * @param int $elementId
  1357.      * @param string $postfix
  1358.      */
  1359.     public static function removeElementFromSession($type, $elementId, $postfix = '')
  1360.     {
  1361.         $tmpStoreKey = self::getSessionKey($type, $elementId, $postfix);
  1362.         TmpStore::delete($tmpStoreKey);
  1363.     }
  1364.     /**
  1365.      * @internal
  1366.      *
  1367.      * @param mixed $element
  1368.      * @param array|null $context
  1369.      *
  1370.      * @return DeepCopy
  1371.      */
  1372.     public static function getDeepCopyInstance($element, ?array $context = []): DeepCopy
  1373.     {
  1374.         $copier = new DeepCopy();
  1375.         $copier->skipUncloneable(true);
  1376.         if ($element instanceof ElementInterface) {
  1377.             if (($context['conversion'] ?? false) === 'marshal') {
  1378.                 $sourceType = Service::getElementType($element);
  1379.                 $sourceId = $element->getId();
  1380.                 $copier->addTypeFilter(
  1381.                     new \DeepCopy\TypeFilter\ReplaceFilter(
  1382.                         function ($currentValue) {
  1383.                             if ($currentValue instanceof ElementInterface) {
  1384.                                 $elementType = Service::getElementType($currentValue);
  1385.                                 $descriptor = new ElementDescriptor($elementType, $currentValue->getId());
  1386.                                 return $descriptor;
  1387.                             }
  1388.                             return $currentValue;
  1389.                         }
  1390.                     ),
  1391.                     new MarshalMatcher($sourceType, $sourceId)
  1392.                 );
  1393.             } elseif (($context['conversion'] ?? false) === 'unmarshal') {
  1394.                 $copier->addTypeFilter(
  1395.                     new \DeepCopy\TypeFilter\ReplaceFilter(
  1396.                         function ($currentValue) {
  1397.                             if ($currentValue instanceof ElementDescriptor) {
  1398.                                 $value = Service::getElementById($currentValue->getType(), $currentValue->getId());
  1399.                                 return $value;
  1400.                             }
  1401.                             return $currentValue;
  1402.                         }
  1403.                     ),
  1404.                     new UnmarshalMatcher()
  1405.                 );
  1406.             }
  1407.         }
  1408.         if ($context['defaultFilters'] ?? false) {
  1409.             $copier->addFilter(new DoctrineCollectionFilter(), new PropertyTypeMatcher('Doctrine\Common\Collections\Collection'));
  1410.             $copier->addFilter(new SetNullFilter(), new PropertyTypeMatcher('Psr\Container\ContainerInterface'));
  1411.             $copier->addFilter(new SetNullFilter(), new PropertyTypeMatcher('Pimcore\Model\DataObject\ClassDefinition'));
  1412.         }
  1413.         $event = new GenericEvent(null, [
  1414.             'copier' => $copier,
  1415.             'element' => $element,
  1416.             'context' => $context,
  1417.         ]);
  1418.         \Pimcore::getEventDispatcher()->dispatch($event, SystemEvents::SERVICE_PRE_GET_DEEP_COPY);
  1419.         return $event->getArgument('copier');
  1420.     }
  1421.     /**
  1422.      * @internal
  1423.      *
  1424.      * @param array $rowData
  1425.      *
  1426.      * @return array
  1427.      */
  1428.     public static function escapeCsvRecord(array $rowData): array
  1429.     {
  1430.         if (self::$formatter === null) {
  1431.             self::$formatter = new EscapeFormula("'", ['=', '-', '+', '@']);
  1432.         }
  1433.         $rowData = self::$formatter->escapeRecord($rowData);
  1434.         return $rowData;
  1435.     }
  1436.     /**
  1437.      * @internal
  1438.      *
  1439.      * @param string $type
  1440.      * @param int|string $id
  1441.      *
  1442.      * @return string
  1443.      */
  1444.     public static function getElementCacheTag(string $type, $id): string
  1445.     {
  1446.         return $type . '_' . $id;
  1447.     }
  1448. }