vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/DataObject/DataObjectController.php line 373

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\Bundle\AdminBundle\Controller\Admin\DataObject;
  15. use Pimcore\Bundle\AdminBundle\Controller\Admin\ElementControllerBase;
  16. use Pimcore\Bundle\AdminBundle\Controller\Traits\AdminStyleTrait;
  17. use Pimcore\Bundle\AdminBundle\Controller\Traits\ApplySchedulerDataTrait;
  18. use Pimcore\Bundle\AdminBundle\Helper\GridHelperService;
  19. use Pimcore\Bundle\AdminBundle\Security\CsrfProtectionHandler;
  20. use Pimcore\Controller\KernelControllerEventInterface;
  21. use Pimcore\Controller\Traits\ElementEditLockHelperTrait;
  22. use Pimcore\Db;
  23. use Pimcore\Event\Admin\ElementAdminStyleEvent;
  24. use Pimcore\Event\AdminEvents;
  25. use Pimcore\Localization\LocaleServiceInterface;
  26. use Pimcore\Logger;
  27. use Pimcore\Model;
  28. use Pimcore\Model\DataObject;
  29. use Pimcore\Model\DataObject\ClassDefinition\Data\ManyToManyObjectRelation;
  30. use Pimcore\Model\DataObject\ClassDefinition\Data\Relations\AbstractRelations;
  31. use Pimcore\Model\DataObject\ClassDefinition\Data\ReverseObjectRelation;
  32. use Pimcore\Model\Element;
  33. use Pimcore\Model\Schedule\Task;
  34. use Pimcore\Model\Version;
  35. use Pimcore\Tool;
  36. use Symfony\Component\EventDispatcher\GenericEvent;
  37. use Symfony\Component\HttpFoundation\JsonResponse;
  38. use Symfony\Component\HttpFoundation\RedirectResponse;
  39. use Symfony\Component\HttpFoundation\Request;
  40. use Symfony\Component\HttpFoundation\Response;
  41. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  42. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  43. use Symfony\Component\Routing\Annotation\Route;
  44. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  45. /**
  46.  * @Route("/object", name="pimcore_admin_dataobject_dataobject_")
  47.  *
  48.  * @internal
  49.  */
  50. class DataObjectController extends ElementControllerBase implements KernelControllerEventInterface
  51. {
  52.     use AdminStyleTrait;
  53.     use ElementEditLockHelperTrait;
  54.     use ApplySchedulerDataTrait;
  55.     use DataObjectActionsTrait;
  56.     /**
  57.      * @var DataObject\Service
  58.      */
  59.     protected DataObject\Service $_objectService;
  60.     /**
  61.      * @var array
  62.      */
  63.     private array $objectData = [];
  64.     /**
  65.      * @var array
  66.      */
  67.     private array $metaData = [];
  68.     /**
  69.      * @Route("/tree-get-childs-by-id", name="treegetchildsbyid", methods={"GET"})
  70.      *
  71.      * @param Request $request
  72.      * @param EventDispatcherInterface $eventDispatcher
  73.      *
  74.      * @return JsonResponse
  75.      */
  76.     public function treeGetChildsByIdAction(Request $requestEventDispatcherInterface $eventDispatcher)
  77.     {
  78.         $allParams array_merge($request->request->all(), $request->query->all());
  79.         $filter $request->get('filter');
  80.         $object DataObject::getById((int) $request->get('node'));
  81.         $objectTypes = [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_FOLDER];
  82.         $objects = [];
  83.         $cv false;
  84.         $offset $total $limit $filteredTotalCount 0;
  85.         if ($object instanceof DataObject\Concrete) {
  86.             $class $object->getClass();
  87.             if ($class->getShowVariants()) {
  88.                 $objectTypes DataObject::$types;
  89.             }
  90.         }
  91.         if ($object->hasChildren($objectTypes)) {
  92.             $offset = (int)$request->get('start');
  93.             $limit = (int)$request->get('limit'100000000);
  94.             if ($view $request->get('view'false)) {
  95.                 $cv Element\Service::getCustomViewById($request->get('view'));
  96.             }
  97.             if (!is_null($filter)) {
  98.                 if (substr($filter, -1) != '*') {
  99.                     $filter .= '*';
  100.                 }
  101.                 $filter str_replace('*''%'$filter);
  102.                 $limit 100;
  103.             }
  104.             $childrenList = new DataObject\Listing();
  105.             $childrenList->setCondition($this->buildChildrenCondition($object$filter$view));
  106.             $childrenList->setLimit($limit);
  107.             $childrenList->setOffset($offset);
  108.             if ($object->getChildrenSortBy() === 'index') {
  109.                 $childrenList->setOrderKey('objects.o_index ASC'false);
  110.             } else {
  111.                 $childrenList->setOrderKey(
  112.                     sprintf(
  113.                         'CAST(objects.o_%s AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci %s',
  114.                         $object->getChildrenSortBy(), $object->getChildrenSortOrder()
  115.                     ),
  116.                     false
  117.                 );
  118.             }
  119.             $childrenList->setObjectTypes($objectTypes);
  120.             Element\Service::addTreeFilterJoins($cv$childrenList);
  121.             $beforeListLoadEvent = new GenericEvent($this, [
  122.                 'list' => $childrenList,
  123.                 'context' => $allParams,
  124.             ]);
  125.             $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::OBJECT_LIST_BEFORE_LIST_LOAD);
  126.             /** @var DataObject\Listing $childrenList */
  127.             $childrenList $beforeListLoadEvent->getArgument('list');
  128.             $children $childrenList->load();
  129.             $filteredTotalCount $childrenList->getTotalCount();
  130.             foreach ($children as $child) {
  131.                 $objectTreeNode $this->getTreeNodeConfig($child);
  132.                 // this if is obsolete since as long as the change with #11714 about list on line 175-179 are working fine, we already filter the list=1 there
  133.                 if ($objectTreeNode['permissions']['list'] == 1) {
  134.                     $objects[] = $objectTreeNode;
  135.                 }
  136.             }
  137.             //pagination for custom view
  138.             $total $cv
  139.                 $filteredTotalCount
  140.                 $object->getChildAmount(null$this->getAdminUser());
  141.         }
  142.         //Hook for modifying return value - e.g. for changing permissions based on object data
  143.         //data need to wrapped into a container in order to pass parameter to event listeners by reference so that they can change the values
  144.         $event = new GenericEvent($this, [
  145.             'objects' => $objects,
  146.         ]);
  147.         $eventDispatcher->dispatch($eventAdminEvents::OBJECT_TREE_GET_CHILDREN_BY_ID_PRE_SEND_DATA);
  148.         $objects $event->getArgument('objects');
  149.         if ($limit) {
  150.             return $this->adminJson([
  151.                 'offset' => $offset,
  152.                 'limit' => $limit,
  153.                 'total' => $total,
  154.                 'overflow' => !is_null($filter) && ($filteredTotalCount $limit),
  155.                 'nodes' => $objects,
  156.                 'fromPaging' => (int)$request->get('fromPaging'),
  157.                 'filter' => $request->get('filter') ? $request->get('filter') : '',
  158.                 'inSearch' => (int)$request->get('inSearch'),
  159.             ]);
  160.         }
  161.         return $this->adminJson($objects);
  162.     }
  163.     /**
  164.      * @param DataObject\AbstractObject $object
  165.      * @param string|null $filter
  166.      * @param string|null $view
  167.      *
  168.      * @return string
  169.      */
  170.     private function buildChildrenCondition(DataObject\AbstractObject $object, ?string $filter, ?string $view): string
  171.     {
  172.         $condition "objects.o_parentId = '" $object->getId() . "'";
  173.         // custom views start
  174.         if ($view) {
  175.             $cv Element\Service::getCustomViewById($view);
  176.             if (!empty($cv['classes'])) {
  177.                 $cvConditions = [];
  178.                 $cvClasses $cv['classes'];
  179.                 foreach ($cvClasses as $key => $cvClass) {
  180.                     $cvConditions[] = "objects.o_classId = '" $key "'";
  181.                 }
  182.                 $cvConditions[] = "objects.o_type = 'folder'";
  183.                 $condition .= ' AND (' implode(' OR '$cvConditions) . ')';
  184.             }
  185.         }
  186.         // custom views end
  187.         if (!$this->getAdminUser()->isAdmin()) {
  188.             $userIds $this->getAdminUser()->getRoles();
  189.             $currentUserId $this->getAdminUser()->getId();
  190.             $userIds[] = $currentUserId;
  191.             $inheritedPermission $object->getDao()->isInheritingPermission('list'$userIds);
  192.             $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_object uwo WHERE userId IN (' implode(','$userIds) . ') AND list=1 AND LOCATE(CONCAT(objects.o_path,objects.o_key),cpath)=1 AND
  193.                 NOT EXISTS(SELECT list FROM users_workspaces_object WHERE userId =' $currentUserId '  AND list=0 AND cpath = uwo.cpath))';
  194.             $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_object WHERE userId IN (' implode(','$userIds) . ')  AND cid = objects.o_id AND list=0)';
  195.             $condition .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  196.         }
  197.         if (!is_null($filter)) {
  198.             $db Db::get();
  199.             $condition .= ' AND CAST(objects.o_key AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci LIKE ' $db->quote($filter);
  200.         }
  201.         return $condition;
  202.     }
  203.     /**
  204.      * @param DataObject\AbstractObject $element
  205.      *
  206.      * @return array
  207.      *
  208.      * @throws \Exception
  209.      */
  210.     protected function getTreeNodeConfig($element): array
  211.     {
  212.         $child $element;
  213.         $tmpObject = [
  214.             'id' => $child->getId(),
  215.             'idx' => (int)$child->getIndex(),
  216.             'key' => $child->getKey(),
  217.             'sortBy' => $child->getChildrenSortBy(),
  218.             'sortOrder' => $child->getChildrenSortOrder(),
  219.             'text' => htmlspecialchars($child->getKey()),
  220.             'type' => $child->getType(),
  221.             'path' => $child->getRealFullPath(),
  222.             'basePath' => $child->getRealPath(),
  223.             'elementType' => 'object',
  224.             'locked' => $child->isLocked(),
  225.             'lockOwner' => $child->getLocked() ? true false,
  226.         ];
  227.         $allowedTypes = [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_FOLDER];
  228.         if ($child instanceof DataObject\Concrete && $child->getClass()->getShowVariants()) {
  229.             $allowedTypes[] = DataObject::OBJECT_TYPE_VARIANT;
  230.         }
  231.         $hasChildren $child->getDao()->hasChildren($allowedTypesnull$this->getAdminUser());
  232.         $tmpObject['allowDrop'] = false;
  233.         $tmpObject['isTarget'] = true;
  234.         if ($tmpObject['type'] != DataObject::OBJECT_TYPE_VARIANT) {
  235.             $tmpObject['allowDrop'] = true;
  236.         }
  237.         $tmpObject['allowChildren'] = true;
  238.         $tmpObject['leaf'] = !$hasChildren;
  239.         $tmpObject['cls'] = 'pimcore_class_icon ';
  240.         if ($child instanceof DataObject\Concrete) {
  241.             $tmpObject['published'] = $child->isPublished();
  242.             $tmpObject['className'] = $child->getClass()->getName();
  243.             if (!$child->isPublished()) {
  244.                 $tmpObject['cls'] .= 'pimcore_unpublished ';
  245.             }
  246.             $tmpObject['allowVariants'] = $child->getClass()->getAllowVariants();
  247.         }
  248.         $this->addAdminStyle($childElementAdminStyleEvent::CONTEXT_TREE$tmpObject);
  249.         $tmpObject['expanded'] = !$hasChildren;
  250.         $tmpObject['permissions'] = $child->getUserPermissions($this->getAdminUser());
  251.         if ($child->isLocked()) {
  252.             $tmpObject['cls'] .= 'pimcore_treenode_locked ';
  253.         }
  254.         if ($child->getLocked()) {
  255.             $tmpObject['cls'] .= 'pimcore_treenode_lockOwner ';
  256.         }
  257.         if ($tmpObject['leaf']) {
  258.             $tmpObject['expandable'] = false;
  259.             $tmpObject['leaf'] = false//this is required to allow drag&drop
  260.             $tmpObject['expanded'] = true;
  261.             $tmpObject['loaded'] = true;
  262.         }
  263.         return $tmpObject;
  264.     }
  265.     /**
  266.      * @Route("/get-id-path-paging-info", name="getidpathpaginginfo", methods={"GET"})
  267.      *
  268.      * @param Request $request
  269.      *
  270.      * @return JsonResponse
  271.      */
  272.     public function getIdPathPagingInfoAction(Request $request): JsonResponse
  273.     {
  274.         $path $request->get('path');
  275.         $pathParts explode('/'$path);
  276.         $id = (int) array_pop($pathParts);
  277.         $limit $request->get('limit');
  278.         if (empty($limit)) {
  279.             $limit 30;
  280.         }
  281.         $data = [];
  282.         $targetObject DataObject::getById($id);
  283.         $object $targetObject;
  284.         while ($parent $object->getParent()) {
  285.             $list = new DataObject\Listing();
  286.             $list->setCondition('o_parentId = ?'$parent->getId());
  287.             $list->setUnpublished(true);
  288.             $total $list->getTotalCount();
  289.             $info = [
  290.                 'total' => $total,
  291.             ];
  292.             if ($total $limit) {
  293.                 $idList $list->loadIdList();
  294.                 $position array_search($object->getId(), $idList);
  295.                 $info['position'] = $position 1;
  296.                 $info['page'] = ceil($info['position'] / $limit);
  297.             }
  298.             $data[$parent->getId()] = $info;
  299.             $object $parent;
  300.         }
  301.         return $this->adminJson($data);
  302.     }
  303.     /**
  304.      * @Route("/get", name="get", methods={"GET"})
  305.      *
  306.      * @param Request $request
  307.      * @param EventDispatcherInterface $eventDispatcher
  308.      *
  309.      * @return JsonResponse
  310.      *
  311.      * @throws \Exception
  312.      */
  313.     public function getAction(Request $requestEventDispatcherInterface $eventDispatcher): JsonResponse
  314.     {
  315.         $objectId = (int)$request->get('id');
  316.         $objectFromDatabase DataObject\Concrete::getById($objectId);
  317.         if ($objectFromDatabase === null) {
  318.             return $this->adminJson(['success' => false'message' => 'element_not_found'], JsonResponse::HTTP_NOT_FOUND);
  319.         }
  320.         $objectFromDatabase = clone $objectFromDatabase;
  321.         // set the latest available version for editmode
  322.         $draftVersion null;
  323.         $object $this->getLatestVersion($objectFromDatabase$draftVersion);
  324.         // check for lock
  325.         if ($object->isAllowed('save') || $object->isAllowed('publish') || $object->isAllowed('unpublish') || $object->isAllowed('delete')) {
  326.             if (Element\Editlock::isLocked($objectId'object')) {
  327.                 return $this->getEditLockResponse($objectId'object');
  328.             }
  329.             Element\Editlock::lock($request->get('id'), 'object');
  330.         }
  331.         // we need to know if the latest version is published or not (a version), because of lazy loaded fields in $this->getDataForObject()
  332.         $objectFromVersion $object !== $objectFromDatabase;
  333.         if ($object->isAllowed('view')) {
  334.             $objectData = [];
  335.             /** -------------------------------------------------------------
  336.              *   Load some general data from published object (if existing)
  337.              *  ------------------------------------------------------------- */
  338.             $objectData['idPath'] = Element\Service::getIdPath($objectFromDatabase);
  339.             $previewGenerator $objectFromDatabase->getClass()->getPreviewGenerator();
  340.             $linkGeneratorReference $objectFromDatabase->getClass()->getLinkGeneratorReference();
  341.             $objectData['hasPreview'] = false;
  342.             if ($objectFromDatabase->getClass()->getPreviewUrl() || $linkGeneratorReference || $previewGenerator) {
  343.                 $objectData['hasPreview'] = true;
  344.             }
  345.             if ($draftVersion && $objectFromDatabase->getModificationDate() < $draftVersion->getDate()) {
  346.                 $objectData['draft'] = [
  347.                     'id' => $draftVersion->getId(),
  348.                     'modificationDate' => $draftVersion->getDate(),
  349.                     'isAutoSave' => $draftVersion->isAutoSave(),
  350.                 ];
  351.             }
  352.             $objectData['general'] = [];
  353.             $allowedKeys = ['o_published''o_key''o_id''o_creationDate''o_classId''o_className''o_type''o_parentId''o_userOwner'];
  354.             foreach ($objectFromDatabase->getObjectVars() as $key => $value) {
  355.                 if (in_array($key$allowedKeys)) {
  356.                     $objectData['general'][$key] = $value;
  357.                 }
  358.             }
  359.             $objectData['general']['fullpath'] = $objectFromDatabase->getRealFullPath();
  360.             $objectData['general']['o_locked'] = $objectFromDatabase->isLocked();
  361.             $objectData['general']['php'] = [
  362.                 'classes' => array_merge([get_class($objectFromDatabase)], array_values(class_parents($objectFromDatabase))),
  363.                 'interfaces' => array_values(class_implements($objectFromDatabase)),
  364.             ];
  365.             $objectData['general']['allowInheritance'] = $objectFromDatabase->getClass()->getAllowInherit();
  366.             $objectData['general']['allowVariants'] = $objectFromDatabase->getClass()->getAllowVariants();
  367.             $objectData['general']['showVariants'] = $objectFromDatabase->getClass()->getShowVariants();
  368.             $objectData['general']['showAppLoggerTab'] = $objectFromDatabase->getClass()->getShowAppLoggerTab();
  369.             $objectData['general']['showFieldLookup'] = $objectFromDatabase->getClass()->getShowFieldLookup();
  370.             if ($objectFromDatabase instanceof DataObject\Concrete) {
  371.                 $objectData['general']['linkGeneratorReference'] = $linkGeneratorReference;
  372.                 if ($previewGenerator) {
  373.                     $objectData['general']['previewConfig'] = $previewGenerator->getPreviewConfig($objectFromDatabase);
  374.                 }
  375.             }
  376.             $objectData['layout'] = $objectFromDatabase->getClass()->getLayoutDefinitions();
  377.             $objectData['userPermissions'] = $objectFromDatabase->getUserPermissions($this->getAdminUser());
  378.             $objectVersions Element\Service::getSafeVersionInfo($objectFromDatabase->getVersions());
  379.             $objectData['versions'] = array_splice($objectVersions, -11);
  380.             $objectData['scheduledTasks'] = array_map(
  381.                 static function (Task $task) {
  382.                     return $task->getObjectVars();
  383.                 },
  384.                 $objectFromDatabase->getScheduledTasks()
  385.             );
  386.             $objectData['childdata']['id'] = $objectFromDatabase->getId();
  387.             $objectData['childdata']['data']['classes'] = $this->prepareChildClasses($objectFromDatabase->getDao()->getClasses());
  388.             $objectData['childdata']['data']['general'] = $objectData['general'];
  389.             /** -------------------------------------------------------------
  390.              *   Load remaining general data from latest version
  391.              *  ------------------------------------------------------------- */
  392.             $allowedKeys = ['o_modificationDate''o_userModification'];
  393.             foreach ($object->getObjectVars() as $key => $value) {
  394.                 if (in_array($key$allowedKeys)) {
  395.                     $objectData['general'][$key] = $value;
  396.                 }
  397.             }
  398.             $this->getDataForObject($object$objectFromVersion);
  399.             $objectData['data'] = $this->objectData;
  400.             $objectData['metaData'] = $this->metaData;
  401.             $objectData['properties'] = Element\Service::minimizePropertiesForEditmode($object->getProperties());
  402.             // this used for the "this is not a published version" hint
  403.             // and for adding the published icon to version overview
  404.             $objectData['general']['versionDate'] = $objectFromDatabase->getModificationDate();
  405.             $objectData['general']['versionCount'] = $objectFromDatabase->getVersionCount();
  406.             $this->addAdminStyle($objectElementAdminStyleEvent::CONTEXT_EDITOR$objectData['general']);
  407.             $currentLayoutId $request->get('layoutId');
  408.             $validLayouts DataObject\Service::getValidLayouts($object);
  409.             //Fallback if $currentLayoutId is not set or empty string
  410.             //Uses first valid layout instead of admin layout when empty
  411.             $ok false;
  412.             foreach ($validLayouts as $layout) {
  413.                 if ($currentLayoutId == $layout->getId()) {
  414.                     $ok true;
  415.                 }
  416.             }
  417.             if (!$ok) {
  418.                 $currentLayoutId null;
  419.             }
  420.             //master layout has id 0 so we check for is_null()
  421.             if ($currentLayoutId === null && !empty($validLayouts)) {
  422.                 if (count($validLayouts) === 1) {
  423.                     $firstLayout reset($validLayouts);
  424.                     $currentLayoutId $firstLayout->getId();
  425.                 } else {
  426.                     foreach ($validLayouts as $checkDefaultLayout) {
  427.                         if ($checkDefaultLayout->getDefault()) {
  428.                             $currentLayoutId $checkDefaultLayout->getId();
  429.                         }
  430.                     }
  431.                 }
  432.             }
  433.             if ($currentLayoutId === null && count($validLayouts) > 0) {
  434.                 $currentLayoutId $validLayouts[0]->getId();
  435.             }
  436.             if (!empty($validLayouts)) {
  437.                 $objectData['validLayouts'] = [];
  438.                 foreach ($validLayouts as $validLayout) {
  439.                     $objectData['validLayouts'][] = ['id' => $validLayout->getId(), 'name' => $validLayout->getName()];
  440.                 }
  441.                 $user Tool\Admin::getCurrentUser();
  442.                 if ($currentLayoutId == -&& $user->isAdmin()) {
  443.                     $layout DataObject\Service::getSuperLayoutDefinition($object);
  444.                     $objectData['layout'] = $layout;
  445.                 } elseif (!empty($currentLayoutId)) {
  446.                     $objectData['layout'] = $validLayouts[$currentLayoutId]->getLayoutDefinitions();
  447.                 }
  448.                 $objectData['currentLayoutId'] = $currentLayoutId;
  449.             }
  450.             //Hook for modifying return value - e.g. for changing permissions based on object data
  451.             //data need to wrapped into a container in order to pass parameter to event listeners by reference so that they can change the values
  452.             $event = new GenericEvent($this, [
  453.                 'data' => $objectData,
  454.                 'object' => $object,
  455.             ]);
  456.             DataObject\Service::enrichLayoutDefinition($objectData['layout'], $object);
  457.             $eventDispatcher->dispatch($eventAdminEvents::OBJECT_GET_PRE_SEND_DATA);
  458.             $data $event->getArgument('data');
  459.             DataObject\Service::removeElementFromSession('object'$object->getId());
  460.             return $this->adminJson($data);
  461.         }
  462.         throw $this->createAccessDeniedHttpException();
  463.     }
  464.     /**
  465.      * @param DataObject\Concrete $object
  466.      * @param bool $objectFromVersion
  467.      */
  468.     private function getDataForObject(DataObject\Concrete $object$objectFromVersion false)
  469.     {
  470.         foreach ($object->getClass()->getFieldDefinitions(['object' => $object]) as $key => $def) {
  471.             $this->getDataForField($object$key$def$objectFromVersion);
  472.         }
  473.     }
  474.     /**
  475.      * gets recursively attribute data from parent and fills objectData and metaData
  476.      *
  477.      * @param DataObject\Concrete $object
  478.      * @param string $key
  479.      * @param DataObject\ClassDefinition\Data $fielddefinition
  480.      * @param bool $objectFromVersion
  481.      * @param int $level
  482.      */
  483.     private function getDataForField($object$key$fielddefinition$objectFromVersion$level 0)
  484.     {
  485.         $parent DataObject\Service::hasInheritableParentObject($object);
  486.         $getter 'get' ucfirst($key);
  487.         // Editmode optimization for lazy loaded relations (note that this is just for AbstractRelations, not for all
  488.         // LazyLoadingSupportInterface types. It tries to optimize fetching the data needed for the editmode without
  489.         // loading the entire target element.
  490.         // ReverseObjectRelation should go in there anyway (regardless if it a version or not),
  491.         // so that the values can be loaded.
  492.         if (
  493.             (!$objectFromVersion && $fielddefinition instanceof AbstractRelations)
  494.             || $fielddefinition instanceof ReverseObjectRelation
  495.         ) {
  496.             $refId null;
  497.             if ($fielddefinition instanceof ReverseObjectRelation) {
  498.                 $refKey $fielddefinition->getOwnerFieldName();
  499.                 $refClass DataObject\ClassDefinition::getByName($fielddefinition->getOwnerClassName());
  500.                 if ($refClass) {
  501.                     $refId $refClass->getId();
  502.                 }
  503.             } else {
  504.                 $refKey $key;
  505.             }
  506.             $relations $object->getRelationData($refKey, !$fielddefinition instanceof ReverseObjectRelation$refId);
  507.             if ($fielddefinition->supportsInheritance() && empty($relations) && !empty($parent)) {
  508.                 $this->getDataForField($parent$key$fielddefinition$objectFromVersion$level 1);
  509.             } else {
  510.                 $data = [];
  511.                 if ($fielddefinition instanceof DataObject\ClassDefinition\Data\ManyToOneRelation) {
  512.                     if (isset($relations[0])) {
  513.                         $data $relations[0];
  514.                         $data['published'] = (bool)$data['published'];
  515.                     } else {
  516.                         $data null;
  517.                     }
  518.                 } elseif (
  519.                     ($fielddefinition instanceof DataObject\ClassDefinition\Data\OptimizedAdminLoadingInterface && $fielddefinition->isOptimizedAdminLoading())
  520.                     || ($fielddefinition instanceof ManyToManyObjectRelation && !$fielddefinition->getVisibleFields() && !$fielddefinition instanceof DataObject\ClassDefinition\Data\AdvancedManyToManyObjectRelation)
  521.                 ) {
  522.                     foreach ($relations as $rkey => $rel) {
  523.                         $index $rkey 1;
  524.                         $rel['fullpath'] = $rel['path'];
  525.                         $rel['classname'] = $rel['subtype'];
  526.                         $rel['rowId'] = $rel['id'] . AbstractRelations::RELATION_ID_SEPARATOR $index AbstractRelations::RELATION_ID_SEPARATOR $rel['type'];
  527.                         $rel['published'] = (bool)$rel['published'];
  528.                         $data[] = $rel;
  529.                     }
  530.                 } else {
  531.                     $fieldData $object->$getter();
  532.                     $data $fielddefinition->getDataForEditmode($fieldData$object, ['objectFromVersion' => $objectFromVersion]);
  533.                 }
  534.                 $this->objectData[$key] = $data;
  535.                 $this->metaData[$key]['objectid'] = $object->getId();
  536.                 $this->metaData[$key]['inherited'] = $level != 0;
  537.             }
  538.         } else {
  539.             $fieldData $object->$getter();
  540.             $isInheritedValue false;
  541.             if ($fielddefinition instanceof DataObject\ClassDefinition\Data\CalculatedValue) {
  542.                 $fieldData = new DataObject\Data\CalculatedValue($fielddefinition->getName());
  543.                 $fieldData->setContextualData('object'nullnullnullnullnull$fielddefinition);
  544.                 $value $fielddefinition->getDataForEditmode($fieldData$object, ['objectFromVersion' => $objectFromVersion]);
  545.             } else {
  546.                 $value $fielddefinition->getDataForEditmode($fieldData$object, ['objectFromVersion' => $objectFromVersion]);
  547.             }
  548.             // following some exceptions for special data types (localizedfields, objectbricks)
  549.             if ($value && ($fieldData instanceof DataObject\Localizedfield || $fieldData instanceof DataObject\Classificationstore)) {
  550.                 // make sure that the localized field participates in the inheritance detection process
  551.                 $isInheritedValue $value['inherited'];
  552.             }
  553.             if ($fielddefinition instanceof DataObject\ClassDefinition\Data\Objectbricks && is_array($value)) {
  554.                 // make sure that the objectbricks participate in the inheritance detection process
  555.                 foreach ($value as $singleBrickData) {
  556.                     if (!empty($singleBrickData['inherited'])) {
  557.                         $isInheritedValue true;
  558.                     }
  559.                 }
  560.             }
  561.             if ($fielddefinition->isEmpty($fieldData) && !empty($parent)) {
  562.                 $this->getDataForField($parent$key$fielddefinition$objectFromVersion$level 1);
  563.                 // exception for classification store. if there are no items then it is empty by definition.
  564.                 // consequence is that we have to preserve the metadata information
  565.                 // see https://github.com/pimcore/pimcore/issues/9329
  566.                 if ($fielddefinition instanceof DataObject\ClassDefinition\Data\Classificationstore && $level == 0) {
  567.                     $this->objectData[$key]['metaData'] = $value['metaData'] ?? [];
  568.                     $this->objectData[$key]['inherited'] = true;
  569.                 }
  570.             } else {
  571.                 $isInheritedValue $isInheritedValue || ($level != 0);
  572.                 $this->metaData[$key]['objectid'] = $object->getId();
  573.                 $this->objectData[$key] = $value;
  574.                 $this->metaData[$key]['inherited'] = $isInheritedValue;
  575.                 if ($isInheritedValue && !$fielddefinition->isEmpty($fieldData) && !$fielddefinition->supportsInheritance()) {
  576.                     $this->objectData[$key] = null;
  577.                     $this->metaData[$key]['inherited'] = false;
  578.                     $this->metaData[$key]['hasParentValue'] = true;
  579.                 }
  580.             }
  581.         }
  582.     }
  583.     /**
  584.      * @Route("/get-folder", name="getfolder", methods={"GET"})
  585.      *
  586.      * @param Request $request
  587.      * @param EventDispatcherInterface $eventDispatcher
  588.      *
  589.      * @return JsonResponse
  590.      */
  591.     public function getFolderAction(Request $requestEventDispatcherInterface $eventDispatcher)
  592.     {
  593.         $objectId = (int)$request->get('id');
  594.         $object DataObject::getById($objectId);
  595.         if (!$object) {
  596.             throw $this->createNotFoundException();
  597.         }
  598.         if ($object->isAllowed('view')) {
  599.             $objectData = [];
  600.             $objectData['general'] = [];
  601.             $objectData['idPath'] = Element\Service::getIdPath($object);
  602.             $objectData['type'] = $object->getType();
  603.             $allowedKeys = ['o_published''o_key''o_id''o_type''o_path''o_modificationDate''o_creationDate''o_userOwner''o_userModification'];
  604.             foreach ($object->getObjectVars() as $key => $value) {
  605.                 if (strstr($key'o_') && in_array($key$allowedKeys)) {
  606.                     $objectData['general'][$key] = $value;
  607.                 }
  608.             }
  609.             $objectData['general']['fullpath'] = $object->getRealFullPath();
  610.             $objectData['general']['o_locked'] = $object->isLocked();
  611.             $objectData['properties'] = Element\Service::minimizePropertiesForEditmode($object->getProperties());
  612.             $objectData['userPermissions'] = $object->getUserPermissions($this->getAdminUser());
  613.             $objectData['classes'] = $this->prepareChildClasses($object->getDao()->getClasses());
  614.             // grid-config
  615.             $configFile PIMCORE_CONFIGURATION_DIRECTORY '/object/grid/' $object->getId() . '-user_' $this->getAdminUser()->getId() . '.psf';
  616.             if (is_file($configFile)) {
  617.                 $gridConfig Tool\Serialize::unserialize(file_get_contents($configFile));
  618.                 if ($gridConfig) {
  619.                     $selectedClassId $gridConfig['classId'];
  620.                     foreach ($objectData['classes'] as $class) {
  621.                         if ($class['id'] == $selectedClassId) {
  622.                             $objectData['selectedClass'] = $selectedClassId;
  623.                             break;
  624.                         }
  625.                     }
  626.                 }
  627.             }
  628.             //Hook for modifying return value - e.g. for changing permissions based on object data
  629.             //data need to wrapped into a container in order to pass parameter to event listeners by reference so that they can change the values
  630.             $event = new GenericEvent($this, [
  631.                 'data' => $objectData,
  632.                 'object' => $object,
  633.             ]);
  634.             $eventDispatcher->dispatch($eventAdminEvents::OBJECT_GET_PRE_SEND_DATA);
  635.             $objectData $event->getArgument('data');
  636.             return $this->adminJson($objectData);
  637.         }
  638.         throw $this->createAccessDeniedHttpException();
  639.     }
  640.     /**
  641.      * @param DataObject\ClassDefinition[] $classes
  642.      *
  643.      * @return array
  644.      */
  645.     protected function prepareChildClasses(array $classes): array
  646.     {
  647.         $reduced = [];
  648.         foreach ($classes as $class) {
  649.             $reduced[] = [
  650.                 'id' => $class->getId(),
  651.                 'name' => $class->getName(),
  652.                 'inheritance' => $class->getAllowInherit(),
  653.             ];
  654.         }
  655.         return $reduced;
  656.     }
  657.     /**
  658.      * @Route("/add", name="add", methods={"POST"})
  659.      *
  660.      * @param Request $request
  661.      * @param Model\FactoryInterface $modelFactory
  662.      *
  663.      * @return JsonResponse
  664.      */
  665.     public function addAction(Request $requestModel\FactoryInterface $modelFactory): JsonResponse
  666.     {
  667.         $message '';
  668.         $parent DataObject::getById((int) $request->get('parentId'));
  669.         if (!$parent->isAllowed('create')) {
  670.             $message 'prevented adding object because of missing permissions';
  671.             Logger::debug($message);
  672.         }
  673.         $intendedPath $parent->getRealFullPath() . '/' $request->get('key');
  674.         if (DataObject\Service::pathExists($intendedPath)) {
  675.             $message 'prevented creating object because object with same path+key already exists';
  676.             Logger::debug($message);
  677.         }
  678.         //return false if missing permissions or path+key already exists
  679.         if (!empty($message)) {
  680.             return $this->adminJson([
  681.                 'success' => false,
  682.                 'message' => $message,
  683.             ]);
  684.         }
  685.         $className 'Pimcore\\Model\\DataObject\\' ucfirst($request->get('className'));
  686.         /** @var DataObject\Concrete $object */
  687.         $object $modelFactory->build($className);
  688.         $object->setOmitMandatoryCheck(true); // allow to save the object although there are mandatory fields
  689.         $object->setClassId($request->get('classId'));
  690.         if ($request->get('variantViaTree')) {
  691.             $parentId $request->get('parentId');
  692.             $parent DataObject\Concrete::getById($parentId);
  693.             $object->setClassId($parent->getClass()->getId());
  694.         }
  695.         $object->setClassName($request->get('className'));
  696.         $object->setParentId($request->get('parentId'));
  697.         $object->setKey($request->get('key'));
  698.         $object->setCreationDate(time());
  699.         $object->setUserOwner($this->getAdminUser()->getId());
  700.         $object->setUserModification($this->getAdminUser()->getId());
  701.         $object->setPublished(false);
  702.         $objectType $request->get('objecttype');
  703.         if (in_array($objectType, [DataObject::OBJECT_TYPE_OBJECTDataObject::OBJECT_TYPE_VARIANT])) {
  704.             $object->setType($objectType);
  705.         }
  706.         try {
  707.             $object->save();
  708.             $return = [
  709.                 'success' => true,
  710.                 'id' => $object->getId(),
  711.                 'type' => $object->getType(),
  712.                 'message' => $message,
  713.             ];
  714.         } catch (\Exception $e) {
  715.             $return = [
  716.                 'success' => false,
  717.                 'message' => $e->getMessage(),
  718.             ];
  719.         }
  720.         return $this->adminJson($return);
  721.     }
  722.     /**
  723.      * @Route("/add-folder", name="addfolder", methods={"POST"})
  724.      *
  725.      * @param Request $request
  726.      *
  727.      * @return JsonResponse
  728.      */
  729.     public function addFolderAction(Request $request)
  730.     {
  731.         $success false;
  732.         $parent DataObject::getById((int) $request->get('parentId'));
  733.         if ($parent->isAllowed('create')) {
  734.             if (!DataObject\Service::pathExists($parent->getRealFullPath() . '/' $request->get('key'))) {
  735.                 $folder DataObject\Folder::create([
  736.                     'o_parentId' => $request->get('parentId'),
  737.                     'o_creationDate' => time(),
  738.                     'o_userOwner' => $this->getAdminUser()->getId(),
  739.                     'o_userModification' => $this->getAdminUser()->getId(),
  740.                     'o_key' => $request->get('key'),
  741.                     'o_published' => true,
  742.                 ]);
  743.                 try {
  744.                     $folder->save();
  745.                     $success true;
  746.                 } catch (\Exception $e) {
  747.                     return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  748.                 }
  749.             }
  750.         } else {
  751.             Logger::debug('prevented creating object id because of missing permissions');
  752.         }
  753.         return $this->adminJson(['success' => $success]);
  754.     }
  755.     /**
  756.      * @Route("/delete", name="delete", methods={"DELETE"})
  757.      *
  758.      * @param Request $request
  759.      *
  760.      * @return JsonResponse
  761.      *
  762.      * @throws \Exception
  763.      */
  764.     public function deleteAction(Request $request)
  765.     {
  766.         $type $request->get('type');
  767.         if ($type === 'childs') {
  768.             trigger_deprecation(
  769.                 'pimcore/pimcore',
  770.                 '10.4',
  771.                 'Type childs is deprecated. Use children instead'
  772.             );
  773.             $type 'children';
  774.         }
  775.         if ($type === 'children') {
  776.             $parentObject DataObject::getById((int) $request->get('id'));
  777.             $list = new DataObject\Listing();
  778.             $list->setCondition('o_path LIKE ' $list->quote($list->escapeLike($parentObject->getRealFullPath()) . '/%'));
  779.             $list->setLimit((int)$request->get('amount'));
  780.             $list->setOrderKey('LENGTH(o_path)'false);
  781.             $list->setOrder('DESC');
  782.             $deletedItems = [];
  783.             foreach ($list as $object) {
  784.                 $deletedItems[$object->getId()] = $object->getRealFullPath();
  785.                 if ($object->isAllowed('delete') && !$object->isLocked()) {
  786.                     $object->delete();
  787.                 }
  788.             }
  789.             return $this->adminJson(['success' => true'deleted' => $deletedItems]);
  790.         }
  791.         if ($id $request->get('id')) {
  792.             $object DataObject::getById((int) $id);
  793.             if ($object) {
  794.                 if (!$object->isAllowed('delete')) {
  795.                     throw $this->createAccessDeniedHttpException();
  796.                 }
  797.                 if ($object->isLocked()) {
  798.                     return $this->adminJson(['success' => false'message' => 'prevented deleting object, because it is locked: ID: ' $object->getId()]);
  799.                 }
  800.                 $object->delete();
  801.             }
  802.             // return true, even when the object doesn't exist, this can be the case when using batch delete incl. children
  803.             return $this->adminJson(['success' => true]);
  804.         }
  805.         return $this->adminJson(['success' => false]);
  806.     }
  807.     /**
  808.      * @Route("/change-children-sort-by", name="changechildrensortby", methods={"PUT"})
  809.      *
  810.      * @param Request $request
  811.      *
  812.      * @return JsonResponse
  813.      *
  814.      * @throws \Exception
  815.      */
  816.     public function changeChildrenSortByAction(Request $request)
  817.     {
  818.         $object DataObject::getById((int) $request->get('id'));
  819.         if ($object) {
  820.             $sortBy $request->get('sortBy');
  821.             $sortOrder $request->get('childrenSortOrder');
  822.             if (!\in_array($sortOrder, ['ASC''DESC'])) {
  823.                 $sortOrder 'ASC';
  824.             }
  825.             $currentSortBy $object->getChildrenSortBy();
  826.             $object->setChildrenSortBy($sortBy);
  827.             $object->setChildrenSortOrder($sortOrder);
  828.             if ($currentSortBy != $sortBy) {
  829.                 $user Tool\Admin::getCurrentUser();
  830.                 if (!$user->isAdmin() && !$user->isAllowed('objects_sort_method')) {
  831.                     return $this->json(['success' => false'message' => 'Changing the sort method is only allowed for admin users']);
  832.                 }
  833.                 if ($sortBy == 'index') {
  834.                     $this->reindexBasedOnSortOrder($object$sortOrder);
  835.                 }
  836.             }
  837.             $object->save();
  838.             return $this->json(['success' => true]);
  839.         }
  840.         return $this->json(['success' => false'message' => 'Unable to change a sorting way of children items.']);
  841.     }
  842.     /**
  843.      * @Route("/update", name="update", methods={"PUT"})
  844.      *
  845.      * @param Request $request
  846.      *
  847.      * @return JsonResponse
  848.      *
  849.      * @throws \Exception
  850.      */
  851.     public function updateAction(Request $request)
  852.     {
  853.         $values $this->decodeJson($request->get('values'));
  854.         $ids $this->decodeJson($request->get('id'));
  855.         if (is_array($ids)) {
  856.             $return = ['success' => true];
  857.             foreach ($ids as $id) {
  858.                 $object DataObject::getById((int)$id);
  859.                 $return $this->executeUpdateAction($object$values);
  860.                 if (!$return['success']) {
  861.                     return $this->adminJson($return);
  862.                 }
  863.             }
  864.         } else {
  865.             $object DataObject::getById((int)$ids);
  866.             $return $this->executeUpdateAction($object$values);
  867.         }
  868.         return $this->adminJson($return);
  869.     }
  870.     /**
  871.      * @return array{success: bool, message?: string}
  872.      *
  873.      * @throws \Exception
  874.      */
  875.     private function executeUpdateAction(DataObject $objectmixed $values): array
  876.     {
  877.         $success false;
  878.         if ($object instanceof DataObject\Concrete) {
  879.             $object->setOmitMandatoryCheck(true);
  880.         }
  881.         // this prevents the user from renaming, relocating (actions in the tree) if the newest version isn't the published one
  882.         // the reason is that otherwise the content of the newer not published version will be overwritten
  883.         if ($object instanceof DataObject\Concrete) {
  884.             $latestVersion $object->getLatestVersion();
  885.             if ($latestVersion && $latestVersion->getData()->getModificationDate() != $object->getModificationDate()) {
  886.                 return ['success' => false'message' => "You can't rename or relocate if there's a newer not published version"];
  887.             }
  888.         }
  889.         $key $values['key'] ?? null;
  890.         if ($object->isAllowed('settings')) {
  891.             if ($key) {
  892.                 if ($object->isAllowed('rename')) {
  893.                     $object->setKey($key);
  894.                 } elseif ($key !== $object->getKey()) {
  895.                     Logger::debug('prevented renaming object because of missing permissions ');
  896.                 }
  897.             }
  898.             if (!empty($values['parentId'])) {
  899.                 $parent DataObject::getById($values['parentId']);
  900.                 //check if parent is changed
  901.                 if ($object->getParentId() != $parent->getId()) {
  902.                     if (!$parent->isAllowed('create')) {
  903.                         throw new \Exception('Prevented moving object - no create permission on new parent ');
  904.                     }
  905.                     $objectWithSamePath DataObject::getByPath($parent->getRealFullPath() . '/' $object->getKey());
  906.                     if ($objectWithSamePath != null) {
  907.                         return ['success' => false'message' => 'prevented creating object because object with same path+key already exists'];
  908.                     }
  909.                     if ($object->isLocked()) {
  910.                         return ['success' => false'message' => 'prevented moving object, because it is locked: ID: ' $object->getId()];
  911.                     }
  912.                     $object->setParentId($values['parentId']);
  913.                 }
  914.             }
  915.             if (array_key_exists('locked'$values)) {
  916.                 $object->setLocked($values['locked']);
  917.             }
  918.             $object->setModificationDate(time());
  919.             $object->setUserModification($this->getAdminUser()->getId());
  920.             try {
  921.                 $isIndexUpdate = isset($values['indices']);
  922.                 if ($isIndexUpdate) {
  923.                     // Ensure the update sort index is already available in the postUpdate eventListener
  924.                     $indexUpdate is_int($values['indices']) ? $values['indices'] : $values['indices'][$object->getId()];
  925.                     $object->setIndex($indexUpdate);
  926.                 }
  927.                 $object->save();
  928.                 if ($isIndexUpdate) {
  929.                     $this->updateIndexesOfObjectSiblings($object$indexUpdate);
  930.                 }
  931.                 $success true;
  932.             } catch (\Exception $e) {
  933.                 Logger::error((string) $e);
  934.                 return ['success' => false'message' => $e->getMessage()];
  935.             }
  936.         } elseif ($key && $object->isAllowed('rename')) {
  937.             return $this->renameObject($object$key);
  938.         } else {
  939.             Logger::debug('prevented update object because of missing permissions.');
  940.         }
  941.         return ['success' => $success];
  942.     }
  943.     private function executeInsideTransaction(callable $fn)
  944.     {
  945.         $maxRetries 5;
  946.         for ($retries 0$retries $maxRetries$retries++) {
  947.             try {
  948.                 Db::get()->beginTransaction();
  949.                 $fn();
  950.                 Db::get()->commit();
  951.                 break;
  952.             } catch (\Exception $e) {
  953.                 Db::get()->rollBack();
  954.                 // we try to start the transaction $maxRetries times again (deadlocks, ...)
  955.                 if ($retries < ($maxRetries 1)) {
  956.                     $run $retries 1;
  957.                     $waitTime rand(15) * 100000// microseconds
  958.                     Logger::warn('Unable to finish transaction (' $run ". run) because of the following reason '" $e->getMessage() . "'. --> Retrying in " $waitTime ' microseconds ... (' . ($run 1) . ' of ' $maxRetries ')');
  959.                     usleep($waitTime); // wait specified time until we restart the transaction
  960.                 } else {
  961.                     // if the transaction still fail after $maxRetries retries, we throw out the exception
  962.                     Logger::error('Finally giving up restarting the same transaction again and again, last message: ' $e->getMessage());
  963.                     throw $e;
  964.                 }
  965.             }
  966.         }
  967.     }
  968.     /**
  969.      * @param DataObject\AbstractObject $parentObject
  970.      * @param string $currentSortOrder
  971.      */
  972.     protected function reindexBasedOnSortOrder(DataObject\AbstractObject $parentObjectstring $currentSortOrder)
  973.     {
  974.         $fn = function () use ($parentObject$currentSortOrder) {
  975.             $list = new DataObject\Listing();
  976.             $db Db::get();
  977.             $result $db->executeStatement(
  978.                 'UPDATE '.$list->getDao()->getTableName().' o,
  979.                     (
  980.                     SELECT newIndex, o_id FROM (
  981.                         SELECT @n := @n +1 AS newIndex, o_id
  982.                         FROM '.$list->getDao()->getTableName().',
  983.                                 (SELECT @n := -1) variable
  984.                                  WHERE o_parentId = ? ORDER BY o_key ' $currentSortOrder
  985.                                .') tmp
  986.                     ) order_table
  987.                     SET o.o_index = order_table.newIndex
  988.                     WHERE o.o_id=order_table.o_id',
  989.                 [
  990.                     $parentObject->getId(),
  991.                 ]
  992.             );
  993.             $db Db::get();
  994.             $children $db->fetchAllAssociative(
  995.                 'SELECT o_id, o_modificationDate, o_versionCount FROM objects'
  996.                 .' WHERE o_parentId = ? ORDER BY o_index ASC',
  997.                 [$parentObject->getId()]
  998.             );
  999.             $index 0;
  1000.             foreach ($children as $child) {
  1001.                 $this->updateLatestVersionIndex($child['o_id'], $child['o_modificationDate']);
  1002.                 $index++;
  1003.                 DataObject::clearDependentCacheByObjectId($child['o_id']);
  1004.             }
  1005.         };
  1006.         $this->executeInsideTransaction($fn);
  1007.     }
  1008.     private function updateLatestVersionIndex($objectId$newIndex)
  1009.     {
  1010.         $object DataObject\Concrete::getById($objectId);
  1011.         if (
  1012.             $object &&
  1013.             $object->getType() != DataObject::OBJECT_TYPE_FOLDER &&
  1014.             $latestVersion $object->getLatestVersion()
  1015.         ) {
  1016.             // don't renew references (which means loading the target elements)
  1017.             // Not needed as we just save a new version with the updated index
  1018.             $object $latestVersion->loadData(false);
  1019.             if ($newIndex !== $object->getIndex()) {
  1020.                 $object->setIndex($newIndex);
  1021.             }
  1022.             $latestVersion->save();
  1023.         }
  1024.     }
  1025.     /**
  1026.      * @param DataObject\AbstractObject $updatedObject
  1027.      * @param int $newIndex
  1028.      */
  1029.     protected function updateIndexesOfObjectSiblings(DataObject\AbstractObject $updatedObject$newIndex)
  1030.     {
  1031.         $fn = function () use ($updatedObject$newIndex) {
  1032.             $list = new DataObject\Listing();
  1033.             $updatedObject->saveIndex($newIndex);
  1034.             // The cte and the limit are needed to order the data before the newIndex is set
  1035.             $db Db::get();
  1036.             $db->executeStatement(
  1037.                 'UPDATE '.$list->getDao()->getTableName().' o,
  1038.                     (
  1039.                         SELECT newIndex, o_id
  1040.                         FROM (
  1041.                             With cte As (SELECT o_index, o_id FROM ' $list->getDao()->getTableName() . ' WHERE o_parentId = ? AND o_id != ? AND o_type IN (\''.implode(
  1042.                     "','", [
  1043.                         DataObject::OBJECT_TYPE_OBJECT,
  1044.                         DataObject::OBJECT_TYPE_VARIANT,
  1045.                         DataObject::OBJECT_TYPE_FOLDER,
  1046.                     ]
  1047.                 ).'\') ORDER BY o_index LIMIT '$updatedObject->getParent()->getChildAmount() .')
  1048.                             SELECT @n := IF(@n = ? - 1,@n + 2,@n + 1) AS newIndex, o_id
  1049.                             FROM cte,
  1050.                             (SELECT @n := -1) variable
  1051.                         ) tmp
  1052.                     ) order_table
  1053.                     SET o.o_index = order_table.newIndex
  1054.                     WHERE o.o_id=order_table.o_id',
  1055.                 [
  1056.                     $updatedObject->getParentId(),
  1057.                     $updatedObject->getId(),
  1058.                     $newIndex,
  1059.                 ]
  1060.             );
  1061.             $siblings $db->fetchAllAssociative(
  1062.                 'SELECT o_id, o_modificationDate, o_versionCount, o_key, o_index FROM objects'
  1063.                 ." WHERE o_parentId = ? AND o_id != ? AND o_type IN ('object', 'variant','folder') ORDER BY o_index ASC",
  1064.                 [$updatedObject->getParentId(), $updatedObject->getId()]
  1065.             );
  1066.             $index 0;
  1067.             foreach ($siblings as $sibling) {
  1068.                 if ($index == $newIndex) {
  1069.                     $index++;
  1070.                 }
  1071.                 $this->updateLatestVersionIndex($sibling['o_id'], $index);
  1072.                 $index++;
  1073.                 DataObject::clearDependentCacheByObjectId($sibling['o_id']);
  1074.             }
  1075.         };
  1076.         $this->executeInsideTransaction($fn);
  1077.     }
  1078.     /**
  1079.      * @Route("/save", name="save", methods={"POST", "PUT"})
  1080.      *
  1081.      * @param Request $request
  1082.      *
  1083.      * @return JsonResponse
  1084.      *
  1085.      * @throws \Exception
  1086.      */
  1087.     public function saveAction(Request $request)
  1088.     {
  1089.         $objectFromDatabase DataObject\Concrete::getById((int) $request->get('id'));
  1090.         if (!$objectFromDatabase instanceof DataObject\Concrete) {
  1091.             return $this->adminJson(['success' => false'message' => 'Could not find object']);
  1092.         }
  1093.         // set the latest available version for editmode
  1094.         $object $this->getLatestVersion($objectFromDatabase);
  1095.         $object->setUserModification($this->getAdminUser()->getId());
  1096.         $objectFromVersion $object !== $objectFromDatabase;
  1097.         $originalModificationDate $objectFromVersion $object->getModificationDate() : $objectFromDatabase->getModificationDate();
  1098.         if ($objectFromVersion) {
  1099.             if (method_exists($object'getLocalizedFields')) {
  1100.                 /** @var DataObject\Localizedfield $localizedFields */
  1101.                 $localizedFields $object->getLocalizedFields();
  1102.                 $localizedFields->setLoadedAllLazyData();
  1103.             }
  1104.         }
  1105.         // data
  1106.         $data = [];
  1107.         if ($request->get('data')) {
  1108.             $data $this->decodeJson($request->get('data'));
  1109.             foreach ($data as $key => $value) {
  1110.                 $fd $object->getClass()->getFieldDefinition($key);
  1111.                 if ($fd) {
  1112.                     if ($fd instanceof DataObject\ClassDefinition\Data\Localizedfields) {
  1113.                         $user Tool\Admin::getCurrentUser();
  1114.                         if (!$user->getAdmin()) {
  1115.                             $allowedLanguages DataObject\Service::getLanguagePermissions($object$user'lEdit');
  1116.                             if (!is_null($allowedLanguages)) {
  1117.                                 $allowedLanguages array_keys($allowedLanguages);
  1118.                                 $submittedLanguages array_keys($data[$key]);
  1119.                                 foreach ($submittedLanguages as $submittedLanguage) {
  1120.                                     if (!in_array($submittedLanguage$allowedLanguages)) {
  1121.                                         unset($value[$submittedLanguage]);
  1122.                                     }
  1123.                                 }
  1124.                             }
  1125.                         }
  1126.                     }
  1127.                     if ($fd instanceof ReverseObjectRelation) {
  1128.                         $remoteClass DataObject\ClassDefinition::getByName($fd->getOwnerClassName());
  1129.                         $relations $object->getRelationData($fd->getOwnerFieldName(), false$remoteClass->getId());
  1130.                         $toAdd $this->detectAddedRemoteOwnerRelations($relations$value);
  1131.                         $toDelete $this->detectDeletedRemoteOwnerRelations($relations$value);
  1132.                         if (count($toAdd) > || count($toDelete) > 0) {
  1133.                             $this->processRemoteOwnerRelations($object$toDelete$toAdd$fd->getOwnerFieldName());
  1134.                         }
  1135.                     } else {
  1136.                         $object->setValue($key$fd->getDataFromEditmode($value$object, ['objectFromVersion' => $objectFromVersion]));
  1137.                     }
  1138.                 }
  1139.             }
  1140.         }
  1141.         // general settings
  1142.         // @TODO: IS THIS STILL NECESSARY?
  1143.         if ($request->get('general')) {
  1144.             $general $this->decodeJson($request->get('general'));
  1145.             // do not allow all values to be set, will cause problems (eg. icon)
  1146.             if (is_array($general) && count($general) > 0) {
  1147.                 foreach ($general as $key => $value) {
  1148.                     if (!in_array($key, ['o_id''o_classId''o_className''o_type''icon''o_userOwner''o_userModification''o_modificationDate'])) {
  1149.                         $object->setValue($key$value);
  1150.                     }
  1151.                 }
  1152.             }
  1153.         }
  1154.         $this->assignPropertiesFromEditmode($request$object);
  1155.         $this->applySchedulerDataToElement($request$object);
  1156.         if (($request->get('task') === 'unpublish' && !$object->isAllowed('unpublish')) || ($request->get('task') === 'publish' && !$object->isAllowed('publish'))) {
  1157.             throw $this->createAccessDeniedHttpException();
  1158.         }
  1159.         if ($request->get('task') == 'unpublish') {
  1160.             $object->setPublished(false);
  1161.         }
  1162.         if ($request->get('task') == 'publish') {
  1163.             $object->setPublished(true);
  1164.         }
  1165.         // unpublish and save version is possible without checking mandatory fields
  1166.         if (in_array($request->get('task'), ['unpublish''version''autoSave'])) {
  1167.             $object->setOmitMandatoryCheck(true);
  1168.         }
  1169.         if (($request->get('task') == 'publish') || ($request->get('task') == 'unpublish')) {
  1170.             // disabled for now: see different approach [Elements] Show users who are working on the same element #9381
  1171.             // https://github.com/pimcore/pimcore/issues/9381
  1172.             //            if ($data) {
  1173.             //                if (!$this->performFieldcollectionModificationCheck($request, $object, $originalModificationDate, $data)) {
  1174.             //                    return $this->adminJson(['success' => false, 'message' => 'Could be that someone messed around with the fieldcollection in the meantime. Please reload and try again']);
  1175.             //                }
  1176.             //            }
  1177.             $object->save();
  1178.             $treeData $this->getTreeNodeConfig($object);
  1179.             $newObject DataObject::getById($object->getId(), ['force' => true]);
  1180.             if ($request->get('task') == 'publish') {
  1181.                 $object->deleteAutoSaveVersions($this->getAdminUser()->getId());
  1182.             }
  1183.             return $this->adminJson([
  1184.                 'success' => true,
  1185.                 'general' => ['o_modificationDate' => $object->getModificationDate(),
  1186.                     'versionDate' => $newObject->getModificationDate(),
  1187.                     'versionCount' => $newObject->getVersionCount(),
  1188.                 ],
  1189.                 'treeData' => $treeData,
  1190.             ]);
  1191.         } elseif ($request->get('task') == 'session') {
  1192.             //TODO https://github.com/pimcore/pimcore/issues/9536
  1193.             DataObject\Service::saveElementToSession($object''false);
  1194.             return $this->adminJson(['success' => true]);
  1195.         } elseif ($request->get('task') == 'scheduler') {
  1196.             if ($object->isAllowed('settings')) {
  1197.                 $object->saveScheduledTasks();
  1198.                 return $this->adminJson(['success' => true]);
  1199.             }
  1200.         } elseif ($object->isAllowed('save') || $object->isAllowed('publish')) {
  1201.             $isAutoSave $request->get('task') == 'autoSave';
  1202.             $draftData = [];
  1203.             if ($object->isPublished() || $isAutoSave) {
  1204.                 $version $object->saveVersion(truetruenull$isAutoSave);
  1205.                 $draftData = [
  1206.                     'id' => $version->getId(),
  1207.                     'modificationDate' => $version->getDate(),
  1208.                     'isAutoSave' => $version->isAutoSave(),
  1209.                 ];
  1210.             } else {
  1211.                 $object->save();
  1212.             }
  1213.             if ($request->get('task') == 'version') {
  1214.                 $object->deleteAutoSaveVersions($this->getAdminUser()->getId());
  1215.             }
  1216.             $treeData $this->getTreeNodeConfig($object);
  1217.             $newObject DataObject::getById($object->getId(), ['force' => true]);
  1218.             return $this->adminJson([
  1219.                 'success' => true,
  1220.                 'general' => ['o_modificationDate' => $object->getModificationDate(),
  1221.                     'versionDate' => $newObject->getModificationDate(),
  1222.                     'versionCount' => $newObject->getVersionCount(),
  1223.                 ],
  1224.                 'draft' => $draftData,
  1225.                 'treeData' => $treeData,
  1226.             ]);
  1227.         }
  1228.         throw $this->createAccessDeniedHttpException();
  1229.     }
  1230.     /**
  1231.      * @param Request $request
  1232.      * @param DataObject\Concrete $object
  1233.      * @param int $originalModificationDate
  1234.      * @param array $data
  1235.      *
  1236.      * @return bool
  1237.      *
  1238.      * @throws \Exception
  1239.      */
  1240.     protected function performFieldcollectionModificationCheck(Request $requestDataObject\Concrete $object$originalModificationDate$data)
  1241.     {
  1242.         $modificationDate $request->get('modificationDate');
  1243.         if ($modificationDate != $originalModificationDate) {
  1244.             $fielddefinitions $object->getClass()->getFieldDefinitions();
  1245.             foreach ($fielddefinitions as $fd) {
  1246.                 if ($fd instanceof DataObject\ClassDefinition\Data\Fieldcollections) {
  1247.                     if (isset($data[$fd->getName()])) {
  1248.                         $allowedTypes $fd->getAllowedTypes();
  1249.                         foreach ($allowedTypes as $type) {
  1250.                             /** @var DataObject\Fieldcollection\Definition $fdDef */
  1251.                             $fdDef DataObject\Fieldcollection\Definition::getByKey($type);
  1252.                             $childDefinitions $fdDef->getFieldDefinitions();
  1253.                             foreach ($childDefinitions as $childDef) {
  1254.                                 if ($childDef instanceof DataObject\ClassDefinition\Data\Localizedfields) {
  1255.                                     return false;
  1256.                                 }
  1257.                             }
  1258.                         }
  1259.                     }
  1260.                 }
  1261.             }
  1262.         }
  1263.         return true;
  1264.     }
  1265.     /**
  1266.      * @Route("/save-folder", name="savefolder", methods={"PUT"})
  1267.      *
  1268.      * @param Request $request
  1269.      *
  1270.      * @return JsonResponse
  1271.      */
  1272.     public function saveFolderAction(Request $request)
  1273.     {
  1274.         $object DataObject::getById((int) $request->get('id'));
  1275.         if (!$object) {
  1276.             throw $this->createNotFoundException('Object not found');
  1277.         }
  1278.         if ($object->isAllowed('publish')) {
  1279.             try {
  1280.                 // general settings
  1281.                 $general $this->decodeJson($request->get('general'));
  1282.                 $object->setValues($general);
  1283.                 $object->setUserModification($this->getAdminUser()->getId());
  1284.                 $this->assignPropertiesFromEditmode($request$object);
  1285.                 $object->save();
  1286.                 return $this->adminJson(['success' => true]);
  1287.             } catch (\Exception $e) {
  1288.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  1289.             }
  1290.         }
  1291.         throw $this->createAccessDeniedHttpException();
  1292.     }
  1293.     /**
  1294.      * @param Request $request
  1295.      * @param DataObject\AbstractObject $object
  1296.      */
  1297.     protected function assignPropertiesFromEditmode(Request $request$object)
  1298.     {
  1299.         if ($request->get('properties')) {
  1300.             $properties = [];
  1301.             // assign inherited properties
  1302.             foreach ($object->getProperties() as $p) {
  1303.                 if ($p->isInherited()) {
  1304.                     $properties[$p->getName()] = $p;
  1305.                 }
  1306.             }
  1307.             $propertiesData $this->decodeJson($request->get('properties'));
  1308.             if (is_array($propertiesData)) {
  1309.                 foreach ($propertiesData as $propertyName => $propertyData) {
  1310.                     $value $propertyData['data'];
  1311.                     try {
  1312.                         $property = new Model\Property();
  1313.                         $property->setType($propertyData['type']);
  1314.                         $property->setName($propertyName);
  1315.                         $property->setCtype('object');
  1316.                         $property->setDataFromEditmode($value);
  1317.                         $property->setInheritable($propertyData['inheritable']);
  1318.                         $properties[$propertyName] = $property;
  1319.                     } catch (\Exception $e) {
  1320.                         Logger::err("Can't add " $propertyName ' to object ' $object->getRealFullPath());
  1321.                     }
  1322.                 }
  1323.             }
  1324.             $object->setProperties($properties);
  1325.         }
  1326.     }
  1327.     /**
  1328.      * @Route("/publish-version", name="publishversion", methods={"POST"})
  1329.      *
  1330.      * @param Request $request
  1331.      *
  1332.      * @return JsonResponse
  1333.      */
  1334.     public function publishVersionAction(Request $request)
  1335.     {
  1336.         $version Model\Version::getById((int) $request->get('id'));
  1337.         if (!$version) {
  1338.             throw $this->createNotFoundException();
  1339.         }
  1340.         $object $version->loadData();
  1341.         $currentObject DataObject::getById($object->getId());
  1342.         if ($currentObject->isAllowed('publish')) {
  1343.             $object->setPublished(true);
  1344.             $object->setUserModification($this->getAdminUser()->getId());
  1345.             try {
  1346.                 $object->save();
  1347.                 $this->addAdminStyle($objectElementAdminStyleEvent::CONTEXT_TREE$treeData);
  1348.                 return $this->adminJson(
  1349.                     [
  1350.                         'success' => true,
  1351.                         'general' => ['o_modificationDate' => $object->getModificationDate() ],
  1352.                         'treeData' => $treeData, ]
  1353.                 );
  1354.             } catch (\Exception $e) {
  1355.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  1356.             }
  1357.         }
  1358.         throw $this->createAccessDeniedHttpException();
  1359.     }
  1360.     /**
  1361.      * @Route("/preview-version", name="previewversion", methods={"GET"})
  1362.      *
  1363.      * @param Request $request
  1364.      *
  1365.      * @throws \Exception
  1366.      *
  1367.      * @return Response
  1368.      */
  1369.     public function previewVersionAction(Request $request)
  1370.     {
  1371.         DataObject::setDoNotRestoreKeyAndPath(true);
  1372.         $id = (int)$request->get('id');
  1373.         $version Model\Version::getById($id);
  1374.         $object $version->loadData();
  1375.         if (method_exists($object'getLocalizedFields')) {
  1376.             /** @var DataObject\Localizedfield $localizedFields */
  1377.             $localizedFields $object->getLocalizedFields();
  1378.             $localizedFields->setLoadedAllLazyData();
  1379.         }
  1380.         DataObject::setDoNotRestoreKeyAndPath(false);
  1381.         if ($object) {
  1382.             if ($object->isAllowed('versions')) {
  1383.                 return $this->render('@PimcoreAdmin/Admin/DataObject/DataObject/previewVersion.html.twig',
  1384.                     [
  1385.                         'object' => $object,
  1386.                         'versionNote' => $version->getNote(),
  1387.                         'validLanguages' => Tool::getValidLanguages(),
  1388.                     ]);
  1389.             }
  1390.             throw $this->createAccessDeniedException('Permission denied, version id [' $id ']');
  1391.         }
  1392.         throw $this->createNotFoundException('Version with id [' $id "] doesn't exist");
  1393.     }
  1394.     /**
  1395.      * @Route("/diff-versions/from/{from}/to/{to}", name="diffversions", methods={"GET"})
  1396.      *
  1397.      * @param Request $request
  1398.      * @param int $from
  1399.      * @param int $to
  1400.      *
  1401.      * @return Response
  1402.      *
  1403.      * @throws \Exception
  1404.      */
  1405.     public function diffVersionsAction(Request $request$from$to)
  1406.     {
  1407.         DataObject::setDoNotRestoreKeyAndPath(true);
  1408.         $id1 = (int)$from;
  1409.         $id2 = (int)$to;
  1410.         $version1 Model\Version::getById($id1);
  1411.         $object1 $version1->loadData();
  1412.         if (method_exists($object1'getLocalizedFields')) {
  1413.             /** @var DataObject\Localizedfield $localizedFields1 */
  1414.             $localizedFields1 $object1->getLocalizedFields();
  1415.             $localizedFields1->setLoadedAllLazyData();
  1416.         }
  1417.         $version2 Model\Version::getById($id2);
  1418.         $object2 $version2->loadData();
  1419.         if (method_exists($object2'getLocalizedFields')) {
  1420.             /** @var DataObject\Localizedfield $localizedFields2 */
  1421.             $localizedFields2 $object2->getLocalizedFields();
  1422.             $localizedFields2->setLoadedAllLazyData();
  1423.         }
  1424.         DataObject::setDoNotRestoreKeyAndPath(false);
  1425.         if ($object1 && $object2) {
  1426.             if ($object1->isAllowed('versions') && $object2->isAllowed('versions')) {
  1427.                 return $this->render('@PimcoreAdmin/Admin/DataObject/DataObject/diffVersions.html.twig',
  1428.                     [
  1429.                         'object1' => $object1,
  1430.                         'versionNote1' => $version1->getNote(),
  1431.                         'object2' => $object2,
  1432.                         'versionNote2' => $version2->getNote(),
  1433.                         'validLanguages' => Tool::getValidLanguages(),
  1434.                     ]);
  1435.             }
  1436.             throw $this->createAccessDeniedException('Permission denied, version ids [' $id1 ', ' $id2 ']');
  1437.         }
  1438.         throw $this->createNotFoundException('Version with ids [' $id1 ', ' $id2 "] doesn't exist");
  1439.     }
  1440.     /**
  1441.      * @Route("/grid-proxy", name="gridproxy", methods={"GET", "POST", "PUT"})
  1442.      *
  1443.      * @param Request $request
  1444.      * @param EventDispatcherInterface $eventDispatcher
  1445.      * @param GridHelperService $gridHelperService
  1446.      * @param LocaleServiceInterface $localeService
  1447.      * @param CsrfProtectionHandler $csrfProtection
  1448.      *
  1449.      * @return JsonResponse
  1450.      */
  1451.     public function gridProxyAction(
  1452.         Request $request,
  1453.         EventDispatcherInterface $eventDispatcher,
  1454.         GridHelperService $gridHelperService,
  1455.         LocaleServiceInterface $localeService,
  1456.         CsrfProtectionHandler $csrfProtection
  1457.     ): JsonResponse {
  1458.         $allParams array_merge($request->request->all(), $request->query->all());
  1459.         if (isset($allParams['context']) && $allParams['context']) {
  1460.             $allParams['context'] = json_decode($allParams['context'], true);
  1461.         } else {
  1462.             $allParams['context'] = [];
  1463.         }
  1464.         $filterPrepareEvent = new GenericEvent($this, [
  1465.             'requestParams' => $allParams,
  1466.         ]);
  1467.         $eventDispatcher->dispatch($filterPrepareEventAdminEvents::OBJECT_LIST_BEFORE_FILTER_PREPARE);
  1468.         $allParams $filterPrepareEvent->getArgument('requestParams');
  1469.         $csrfProtection->checkCsrfToken($request);
  1470.         $result $this->gridProxy(
  1471.             $allParams,
  1472.             DataObject::OBJECT_TYPE_OBJECT,
  1473.             $request,
  1474.             $eventDispatcher,
  1475.             $gridHelperService,
  1476.             $localeService
  1477.         );
  1478.         return $this->adminJson($result);
  1479.     }
  1480.     /**
  1481.      * @Route("/copy-info", name="copyinfo", methods={"GET"})
  1482.      *
  1483.      * @param Request $request
  1484.      *
  1485.      * @return JsonResponse
  1486.      */
  1487.     public function copyInfoAction(Request $request)
  1488.     {
  1489.         $transactionId time();
  1490.         $pasteJobs = [];
  1491.         Tool\Session::useSession(function (AttributeBagInterface $session) use ($transactionId) {
  1492.             $session->set((string) $transactionId, ['idMapping' => []]);
  1493.         }, 'pimcore_copy');
  1494.         if ($request->get('type') == 'recursive' || $request->get('type') == 'recursive-update-references') {
  1495.             $object DataObject::getById((int) $request->get('sourceId'));
  1496.             // first of all the new parent
  1497.             $pasteJobs[] = [[
  1498.                 'url' => $this->generateUrl('pimcore_admin_dataobject_dataobject_copy'),
  1499.                 'method' => 'POST',
  1500.                 'params' => [
  1501.                     'sourceId' => $request->get('sourceId'),
  1502.                     'targetId' => $request->get('targetId'),
  1503.                     'type' => 'child',
  1504.                     'transactionId' => $transactionId,
  1505.                     'saveParentId' => true,
  1506.                 ],
  1507.             ]];
  1508.             if ($object->hasChildren(DataObject::$types)) {
  1509.                 // get amount of children
  1510.                 $list = new DataObject\Listing();
  1511.                 $list->setCondition('o_path LIKE ' $list->quote($list->escapeLike($object->getRealFullPath()) . '/%'));
  1512.                 $list->setOrderKey('LENGTH(o_path)'false);
  1513.                 $list->setOrder('ASC');
  1514.                 $list->setObjectTypes(DataObject::$types);
  1515.                 $childIds $list->loadIdList();
  1516.                 if (count($childIds) > 0) {
  1517.                     foreach ($childIds as $id) {
  1518.                         $pasteJobs[] = [[
  1519.                             'url' => $this->generateUrl('pimcore_admin_dataobject_dataobject_copy'),
  1520.                             'method' => 'POST',
  1521.                             'params' => [
  1522.                                 'sourceId' => $id,
  1523.                                 'targetParentId' => $request->get('targetId'),
  1524.                                 'sourceParentId' => $request->get('sourceId'),
  1525.                                 'type' => 'child',
  1526.                                 'transactionId' => $transactionId,
  1527.                             ],
  1528.                         ]];
  1529.                     }
  1530.                 }
  1531.                 // add id-rewrite steps
  1532.                 if ($request->get('type') == 'recursive-update-references') {
  1533.                     for ($i 0$i < (count($childIds) + 1); $i++) {
  1534.                         $pasteJobs[] = [[
  1535.                             'url' => $this->generateUrl('pimcore_admin_dataobject_dataobject_copyrewriteids'),
  1536.                             'method' => 'PUT',
  1537.                             'params' => [
  1538.                                 'transactionId' => $transactionId,
  1539.                                 '_dc' => uniqid(),
  1540.                             ],
  1541.                         ]];
  1542.                     }
  1543.                 }
  1544.             }
  1545.         } elseif ($request->get('type') == 'child' || $request->get('type') == 'replace') {
  1546.             // the object itself is the last one
  1547.             $pasteJobs[] = [[
  1548.                 'url' => $this->generateUrl('pimcore_admin_dataobject_dataobject_copy'),
  1549.                 'method' => 'POST',
  1550.                 'params' => [
  1551.                     'sourceId' => $request->get('sourceId'),
  1552.                     'targetId' => $request->get('targetId'),
  1553.                     'type' => $request->get('type'),
  1554.                     'transactionId' => $transactionId,
  1555.                 ],
  1556.             ]];
  1557.         }
  1558.         return $this->adminJson([
  1559.             'pastejobs' => $pasteJobs,
  1560.         ]);
  1561.     }
  1562.     /**
  1563.      * @Route("/copy-rewrite-ids", name="copyrewriteids", methods={"PUT"})
  1564.      *
  1565.      * @param Request $request
  1566.      *
  1567.      * @return JsonResponse
  1568.      *
  1569.      * @throws \Exception
  1570.      */
  1571.     public function copyRewriteIdsAction(Request $request)
  1572.     {
  1573.         $transactionId $request->get('transactionId');
  1574.         $idStore Tool\Session::useSession(function (AttributeBagInterface $session) use ($transactionId) {
  1575.             return $session->get($transactionId);
  1576.         }, 'pimcore_copy');
  1577.         if (!array_key_exists('rewrite-stack'$idStore)) {
  1578.             $idStore['rewrite-stack'] = array_values($idStore['idMapping']);
  1579.         }
  1580.         $id array_shift($idStore['rewrite-stack']);
  1581.         $object DataObject::getById($id);
  1582.         // create rewriteIds() config parameter
  1583.         $rewriteConfig = ['object' => $idStore['idMapping']];
  1584.         $object DataObject\Service::rewriteIds($object$rewriteConfig);
  1585.         $object->setUserModification($this->getAdminUser()->getId());
  1586.         $object->save();
  1587.         // write the store back to the session
  1588.         Tool\Session::useSession(function (AttributeBagInterface $session) use ($transactionId$idStore) {
  1589.             $session->set($transactionId$idStore);
  1590.         }, 'pimcore_copy');
  1591.         return $this->adminJson([
  1592.             'success' => true,
  1593.             'id' => $id,
  1594.         ]);
  1595.     }
  1596.     /**
  1597.      * @Route("/copy", name="copy", methods={"POST"})
  1598.      *
  1599.      * @param Request $request
  1600.      *
  1601.      * @return JsonResponse
  1602.      */
  1603.     public function copyAction(Request $request)
  1604.     {
  1605.         $message '';
  1606.         $sourceId = (int)$request->get('sourceId');
  1607.         $source DataObject::getById($sourceId);
  1608.         $session Tool\Session::get('pimcore_copy');
  1609.         $sessionBag $session->get($request->get('transactionId'));
  1610.         $targetId = (int)$request->get('targetId');
  1611.         if ($request->get('targetParentId')) {
  1612.             $sourceParent DataObject::getById((int) $request->get('sourceParentId'));
  1613.             // this is because the key can get the prefix "_copy" if the target does already exists
  1614.             if ($sessionBag['parentId']) {
  1615.                 $targetParent DataObject::getById($sessionBag['parentId']);
  1616.             } else {
  1617.                 $targetParent DataObject::getById((int) $request->get('targetParentId'));
  1618.             }
  1619.             $targetPath preg_replace('@^' preg_quote($sourceParent->getRealFullPath(), '@') . '@'$targetParent '/'$source->getRealPath());
  1620.             $target DataObject::getByPath($targetPath);
  1621.         } else {
  1622.             $target DataObject::getById($targetId);
  1623.         }
  1624.         if ($target->isAllowed('create')) {
  1625.             $source DataObject::getById($sourceId);
  1626.             if ($source != null) {
  1627.                 if ($source instanceof DataObject\Concrete && $latestVersion $source->getLatestVersion()) {
  1628.                     $source $latestVersion->loadData();
  1629.                     $source->setPublished(false); //as latest version is used which is not published
  1630.                 }
  1631.                 if ($request->get('type') == 'child') {
  1632.                     $newObject $this->_objectService->copyAsChild($target$source);
  1633.                     $sessionBag['idMapping'][(int)$source->getId()] = (int)$newObject->getId();
  1634.                     // this is because the key can get the prefix "_copy" if the target does already exists
  1635.                     if ($request->get('saveParentId')) {
  1636.                         $sessionBag['parentId'] = $newObject->getId();
  1637.                     }
  1638.                 } elseif ($request->get('type') == 'replace') {
  1639.                     $this->_objectService->copyContents($target$source);
  1640.                 }
  1641.                 $session->set($request->get('transactionId'), $sessionBag);
  1642.                 Tool\Session::writeClose();
  1643.                 return $this->adminJson(['success' => true'message' => $message]);
  1644.             } else {
  1645.                 Logger::error("could not execute copy/paste, source object with id [ $sourceId ] not found");
  1646.                 return $this->adminJson(['success' => false'message' => 'source object not found']);
  1647.             }
  1648.         } else {
  1649.             throw $this->createAccessDeniedHttpException();
  1650.         }
  1651.     }
  1652.     /**
  1653.      * @Route("/preview", name="preview", methods={"GET"})
  1654.      *
  1655.      * @param Request $request
  1656.      *
  1657.      * @return Response|RedirectResponse
  1658.      */
  1659.     public function previewAction(Request $request)
  1660.     {
  1661.         $id $request->get('id');
  1662.         $object DataObject\Service::getElementFromSession('object'$id);
  1663.         if ($object instanceof DataObject\Concrete) {
  1664.             $url $object->getClass()->getPreviewUrl();
  1665.             if ($url) {
  1666.                 // replace named variables
  1667.                 $vars $object->getObjectVars();
  1668.                 foreach ($vars as $key => $value) {
  1669.                     if (!empty($value) && \is_scalar($value)) {
  1670.                         $url str_replace('%' $keyurlencode($value), $url);
  1671.                     } else {
  1672.                         if (strpos($url'%' $key) !== false) {
  1673.                             return new Response('No preview available, please ensure that all fields which are required for the preview are filled correctly.');
  1674.                         }
  1675.                     }
  1676.                 }
  1677.                 $url str_replace('%_locale'$this->getAdminUser()->getLanguage(), $url);
  1678.             } elseif ($previewService $object->getClass()->getPreviewGenerator()) {
  1679.                 $url $previewService->generatePreviewUrl($objectarray_merge(['preview' => true'context' => $this], $request->query->all()));
  1680.             } elseif ($linkGenerator $object->getClass()->getLinkGenerator()) {
  1681.                 $url $linkGenerator->generate($object, ['preview' => true'context' => $this]);
  1682.             }
  1683.             if (!$url) {
  1684.                 return new Response("Preview not available, it seems that there's a problem with this object.");
  1685.             }
  1686.             // replace all remainaing % signs
  1687.             $url str_replace('%''%25'$url);
  1688.             $urlParts parse_url($url);
  1689.             return $this->redirect($urlParts['path'] . '?pimcore_object_preview=' $id '&_dc=' time() . (isset($urlParts['query']) ? '&' $urlParts['query'] : ''));
  1690.         } else {
  1691.             return new Response("Preview not available, it seems that there's a problem with this object.");
  1692.         }
  1693.     }
  1694.     /**
  1695.      * @param  DataObject\Concrete $object
  1696.      * @param  array $toDelete
  1697.      * @param  array $toAdd
  1698.      * @param  string $ownerFieldName
  1699.      */
  1700.     protected function processRemoteOwnerRelations($object$toDelete$toAdd$ownerFieldName)
  1701.     {
  1702.         $getter 'get' ucfirst($ownerFieldName);
  1703.         $setter 'set' ucfirst($ownerFieldName);
  1704.         foreach ($toDelete as $id) {
  1705.             $owner DataObject::getById($id);
  1706.             //TODO: lock ?!
  1707.             if (method_exists($owner$getter)) {
  1708.                 $currentData $owner->$getter();
  1709.                 if (is_array($currentData)) {
  1710.                     for ($i 0$i count($currentData); $i++) {
  1711.                         if ($currentData[$i]->getId() == $object->getId()) {
  1712.                             unset($currentData[$i]);
  1713.                             $owner->$setter($currentData);
  1714.                             break;
  1715.                         }
  1716.                     }
  1717.                 } else {
  1718.                     if ($currentData->getId() == $object->getId()) {
  1719.                         $owner->$setter(null);
  1720.                     }
  1721.                 }
  1722.             }
  1723.             $owner->setUserModification($this->getAdminUser()->getId());
  1724.             $owner->save();
  1725.             Logger::debug('Saved object id [ ' $owner->getId() . ' ] by remote modification through [' $object->getId() . '], Action: deleted [ ' $object->getId() . " ] from [ $ownerFieldName]");
  1726.         }
  1727.         foreach ($toAdd as $id) {
  1728.             $owner DataObject::getById($id);
  1729.             //TODO: lock ?!
  1730.             if (method_exists($owner$getter)) {
  1731.                 $currentData $owner->$getter();
  1732.                 if (is_array($currentData)) {
  1733.                     $currentData[] = $object;
  1734.                 } else {
  1735.                     $currentData $object;
  1736.                 }
  1737.                 $owner->$setter($currentData);
  1738.                 $owner->setUserModification($this->getAdminUser()->getId());
  1739.                 $owner->save();
  1740.                 Logger::debug('Saved object id [ ' $owner->getId() . ' ] by remote modification through [' $object->getId() . '], Action: added [ ' $object->getId() . " ] to [ $ownerFieldName ]");
  1741.             }
  1742.         }
  1743.     }
  1744.     /**
  1745.      * @param  array $relations
  1746.      * @param  array $value
  1747.      *
  1748.      * @return array
  1749.      */
  1750.     protected function detectDeletedRemoteOwnerRelations($relations$value)
  1751.     {
  1752.         $originals = [];
  1753.         $changed = [];
  1754.         foreach ($relations as $r) {
  1755.             $originals[] = $r['dest_id'];
  1756.         }
  1757.         if (is_array($value)) {
  1758.             foreach ($value as $row) {
  1759.                 $changed[] = $row['id'];
  1760.             }
  1761.         }
  1762.         $diff array_diff($originals$changed);
  1763.         return $diff;
  1764.     }
  1765.     /**
  1766.      * @param  array $relations
  1767.      * @param  array $value
  1768.      *
  1769.      * @return array
  1770.      */
  1771.     protected function detectAddedRemoteOwnerRelations($relations$value)
  1772.     {
  1773.         $originals = [];
  1774.         $changed = [];
  1775.         foreach ($relations as $r) {
  1776.             $originals[] = $r['dest_id'];
  1777.         }
  1778.         if (is_array($value)) {
  1779.             foreach ($value as $row) {
  1780.                 $changed[] = $row['id'];
  1781.             }
  1782.         }
  1783.         $diff array_diff($changed$originals);
  1784.         return $diff;
  1785.     }
  1786.     /**
  1787.      * @template T of DataObject\Concrete
  1788.      *
  1789.      * @param T $object
  1790.      * @param null|Version $draftVersion
  1791.      *
  1792.      * @return T
  1793.      */
  1794.     protected function getLatestVersion(DataObject\Concrete $object, &$draftVersion null): ?DataObject\Concrete
  1795.     {
  1796.         $latestVersion $object->getLatestVersion($this->getAdminUser()->getId());
  1797.         if ($latestVersion) {
  1798.             $latestObj $latestVersion->loadData();
  1799.             if ($latestObj instanceof DataObject\Concrete) {
  1800.                 $draftVersion $latestVersion;
  1801.                 return $latestObj;
  1802.             }
  1803.         }
  1804.         return $object;
  1805.     }
  1806.     /**
  1807.      * @param ControllerEvent $event
  1808.      */
  1809.     public function onKernelControllerEvent(ControllerEvent $event)
  1810.     {
  1811.         if (!$event->isMainRequest()) {
  1812.             return;
  1813.         }
  1814.         // check permissions
  1815.         $this->checkPermission('objects');
  1816.         $this->_objectService = new DataObject\Service($this->getAdminUser());
  1817.     }
  1818. }