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

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