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

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