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

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