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

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