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

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 $request, EventDispatcherInterface $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 $request, EventDispatcherInterface $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_OBJECT, DataObject::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($beforeListLoadEvent, AdminEvents::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($event, AdminEvents::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_OBJECT, DataObject::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($child, ElementAdminStyleEvent::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 $request, EventDispatcherInterface $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, -1, 1);
  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($object, ElementAdminStyleEvent::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 == -1 && $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($event, AdminEvents::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', null, null, null, null, null, $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 $request, EventDispatcherInterface $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($event, AdminEvents::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 $request, Model\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(1, 5) * 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 $parentObject, string $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) > 0 || 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(true, true, null, $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 $request, DataObject\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($object, ElementAdminStyleEvent::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($filterPrepareEvent, AdminEvents::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($key, 0, 1) == '~') {
  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($brickType, 1);
  1523. $brickDescriptor = json_decode($brickDescriptor, true);
  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($beforeListLoadEvent, AdminEvents::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(), $localeService, false, $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($afterListLoadEvent, AdminEvents::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('%' . $key, urlencode($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($object, array_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. }