vendor/pimcore/pimcore/bundles/AdminBundle/Controller/Admin/Document/DocumentController.php line 140

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\Document;
  15. use Exception;
  16. use Imagick;
  17. use Pimcore;
  18. use Pimcore\Bundle\AdminBundle\Controller\Admin\ElementControllerBase;
  19. use Pimcore\Bundle\AdminBundle\Controller\Traits\DocumentTreeConfigTrait;
  20. use Pimcore\Cache\RuntimeCache;
  21. use Pimcore\Config;
  22. use Pimcore\Controller\KernelControllerEventInterface;
  23. use Pimcore\Db;
  24. use Pimcore\Event\Admin\ElementAdminStyleEvent;
  25. use Pimcore\Event\AdminEvents;
  26. use Pimcore\Image\Chromium;
  27. use Pimcore\Image\HtmlToImage;
  28. use Pimcore\Logger;
  29. use Pimcore\Model\Document;
  30. use Pimcore\Model\Document\DocType;
  31. use Pimcore\Model\Element\Service;
  32. use Pimcore\Model\Exception\ConfigWriteException;
  33. use Pimcore\Model\Redirect;
  34. use Pimcore\Model\Site;
  35. use Pimcore\Model\Version;
  36. use Pimcore\Routing\Dynamic\DocumentRouteHandler;
  37. use Pimcore\Tool;
  38. use Pimcore\Tool\Frontend;
  39. use Pimcore\Tool\Session;
  40. use Symfony\Component\EventDispatcher\GenericEvent;
  41. use Symfony\Component\HttpFoundation\BinaryFileResponse;
  42. use Symfony\Component\HttpFoundation\JsonResponse;
  43. use Symfony\Component\HttpFoundation\Request;
  44. use Symfony\Component\HttpFoundation\Response;
  45. use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBagInterface;
  46. use Symfony\Component\HttpKernel\Event\ControllerEvent;
  47. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  48. use Symfony\Component\Routing\Annotation\Route;
  49. use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
  50. /**
  51.  * @Route("/document")
  52.  *
  53.  * @internal
  54.  */
  55. class DocumentController extends ElementControllerBase implements KernelControllerEventInterface
  56. {
  57.     use DocumentTreeConfigTrait;
  58.     /**
  59.      * @var Document\Service
  60.      */
  61.     protected $_documentService;
  62.     /**
  63.      * @Route("/tree-get-root", name="pimcore_admin_document_document_treegetroot", methods={"GET"})
  64.      *
  65.      * @param Request $request
  66.      *
  67.      * @return JsonResponse
  68.      */
  69.     public function treeGetRootAction(Request $request)
  70.     {
  71.         return parent::treeGetRootAction($request);
  72.     }
  73.     /**
  74.      * @Route("/delete-info", name="pimcore_admin_document_document_deleteinfo", methods={"GET"})
  75.      *
  76.      * @param Request $request
  77.      * @param EventDispatcherInterface $eventDispatcher
  78.      *
  79.      * @return JsonResponse
  80.      */
  81.     public function deleteInfoAction(Request $requestEventDispatcherInterface $eventDispatcher)
  82.     {
  83.         return parent::deleteInfoAction($request$eventDispatcher);
  84.     }
  85.     /**
  86.      * @Route("/get-data-by-id", name="pimcore_admin_document_document_getdatabyid", methods={"GET"})
  87.      *
  88.      * @param Request $request
  89.      *
  90.      * @return JsonResponse
  91.      */
  92.     public function getDataByIdAction(Request $requestEventDispatcherInterface $eventDispatcher)
  93.     {
  94.         $document Document::getById((int) $request->get('id'));
  95.         if (!$document) {
  96.             throw $this->createNotFoundException('Document not found');
  97.         }
  98.         $document = clone $document;
  99.         $data $document->getObjectVars();
  100.         $data['versionDate'] = $document->getModificationDate();
  101.         $data['php'] = [
  102.             'classes' => array_merge([get_class($document)], array_values(class_parents($document))),
  103.             'interfaces' => array_values(class_implements($document)),
  104.         ];
  105.         $this->addAdminStyle($documentElementAdminStyleEvent::CONTEXT_EDITOR$data);
  106.         $event = new GenericEvent($this, [
  107.             'data' => $data,
  108.             'document' => $document,
  109.         ]);
  110.         $eventDispatcher->dispatch($eventAdminEvents::DOCUMENT_GET_PRE_SEND_DATA);
  111.         $data $event->getArgument('data');
  112.         if ($document->isAllowed('view')) {
  113.             return $this->adminJson($data);
  114.         }
  115.         throw $this->createAccessDeniedHttpException();
  116.     }
  117.     /**
  118.      * @Route("/tree-get-childs-by-id", name="pimcore_admin_document_document_treegetchildsbyid", methods={"GET"})
  119.      *
  120.      * @param Request $request
  121.      *
  122.      * @return JsonResponse
  123.      */
  124.     public function treeGetChildsByIdAction(Request $requestEventDispatcherInterface $eventDispatcher)
  125.     {
  126.         $allParams array_merge($request->request->all(), $request->query->all());
  127.         $filter $request->get('filter');
  128.         $limit = (int)($allParams['limit'] ?? 100000000);
  129.         $offset = (int)($allParams['start'] ?? 0);
  130.         if (!is_null($filter)) {
  131.             if (substr($filter, -1) != '*') {
  132.                 $filter .= '*';
  133.             }
  134.             $filter str_replace('*''%'$filter);
  135.             $limit 100;
  136.             $offset 0;
  137.         }
  138.         $document Document::getById($allParams['node']);
  139.         if (!$document) {
  140.             throw $this->createNotFoundException('Document was not found');
  141.         }
  142.         $documents = [];
  143.         $cv false;
  144.         if ($document->hasChildren()) {
  145.             if ($allParams['view']) {
  146.                 $cv Service::getCustomViewById($allParams['view']);
  147.             }
  148.             $db Db::get();
  149.             $list = new Document\Listing();
  150.             $condition 'parentId =  ' $db->quote($document->getId());
  151.             if (!$this->getAdminUser()->isAdmin()) {
  152.                 $userIds $this->getAdminUser()->getRoles();
  153.                 $currentUserId $this->getAdminUser()->getId();
  154.                 $userIds[] = $currentUserId;
  155.                 $inheritedPermission $document->getDao()->isInheritingPermission('list'$userIds);
  156.                 $anyAllowedRowOrChildren 'EXISTS(SELECT list FROM users_workspaces_document uwd WHERE userId IN (' implode(','$userIds) . ') AND list=1 AND LOCATE(CONCAT(path,`key`),cpath)=1 AND
  157.                 NOT EXISTS(SELECT list FROM users_workspaces_document WHERE userId =' $currentUserId '  AND list=0 AND cpath = uwd.cpath))';
  158.                 $isDisallowedCurrentRow 'EXISTS(SELECT list FROM users_workspaces_document WHERE userId IN (' implode(','$userIds) . ')  AND cid = id AND list=0)';
  159.                 $condition .= ' AND IF(' $anyAllowedRowOrChildren ',1,IF(' $inheritedPermission ', ' $isDisallowedCurrentRow ' = 0, 0)) = 1';
  160.             }
  161.             if ($filter) {
  162.                 $condition '(' $condition ')' ' AND CAST(documents.key AS CHAR CHARACTER SET utf8) COLLATE utf8_general_ci LIKE ' $db->quote($filter);
  163.             }
  164.             $list->setCondition($condition);
  165.             $list->setOrderKey(['index''id']);
  166.             $list->setOrder(['asc''asc']);
  167.             $list->setLimit($limit);
  168.             $list->setOffset($offset);
  169.             Service::addTreeFilterJoins($cv$list);
  170.             $beforeListLoadEvent = new GenericEvent($this, [
  171.                 'list' => $list,
  172.                 'context' => $allParams,
  173.             ]);
  174.             $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::DOCUMENT_LIST_BEFORE_LIST_LOAD);
  175.             /** @var Document\Listing $list */
  176.             $list $beforeListLoadEvent->getArgument('list');
  177.             $childrenList $list->load();
  178.             foreach ($childrenList as $childDocument) {
  179.                 $documentTreeNode $this->getTreeNodeConfig($childDocument);
  180.                 // the !isset is for printContainer case, there are no permissions sets there
  181.                 if (!isset($documentTreeNode['permissions']['list']) || $documentTreeNode['permissions']['list'] == 1) {
  182.                     $documents[] = $documentTreeNode;
  183.                 }
  184.             }
  185.         }
  186.         //Hook for modifying return value - e.g. for changing permissions based on document data
  187.         $event = new GenericEvent($this, [
  188.             'documents' => $documents,
  189.         ]);
  190.         $eventDispatcher->dispatch($eventAdminEvents::DOCUMENT_TREE_GET_CHILDREN_BY_ID_PRE_SEND_DATA);
  191.         $documents $event->getArgument('documents');
  192.         if ($allParams['limit']) {
  193.             return $this->adminJson([
  194.                 'offset' => $offset,
  195.                 'limit' => $limit,
  196.                 'total' => $document->getChildAmount($this->getAdminUser()),
  197.                 'nodes' => $documents,
  198.                 'filter' => $request->get('filter') ? $request->get('filter') : '',
  199.                 'inSearch' => (int)$request->get('inSearch'),
  200.             ]);
  201.         } else {
  202.             return $this->adminJson($documents);
  203.         }
  204.     }
  205.     /**
  206.      * @Route("/add", name="pimcore_admin_document_document_add", methods={"POST"})
  207.      *
  208.      * @param Request $request
  209.      *
  210.      * @return JsonResponse
  211.      */
  212.     public function addAction(Request $request)
  213.     {
  214.         $success false;
  215.         $errorMessage '';
  216.         // check for permission
  217.         $parentDocument Document::getById((int)$request->get('parentId'));
  218.         $document null;
  219.         if ($parentDocument->isAllowed('create')) {
  220.             $intendedPath $parentDocument->getRealFullPath() . '/' $request->get('key');
  221.             if (!Document\Service::pathExists($intendedPath)) {
  222.                 $createValues = [
  223.                     'userOwner' => $this->getAdminUser()->getId(),
  224.                     'userModification' => $this->getAdminUser()->getId(),
  225.                     'published' => false,
  226.                 ];
  227.                 $createValues['key'] = Service::getValidKey($request->get('key'), 'document');
  228.                 // check for a docType
  229.                 $docType Document\DocType::getById($request->get('docTypeId'));
  230.                 if ($docType) {
  231.                     $createValues['template'] = $docType->getTemplate();
  232.                     $createValues['controller'] = $docType->getController();
  233.                     $createValues['staticGeneratorEnabled'] = $docType->getStaticGeneratorEnabled();
  234.                 } elseif ($translationsBaseDocumentId $request->get('translationsBaseDocument')) {
  235.                     $translationsBaseDocument Document::getById((int) $translationsBaseDocumentId);
  236.                     if ($translationsBaseDocument instanceof Document\PageSnippet) {
  237.                         $createValues['template'] = $translationsBaseDocument->getTemplate();
  238.                         $createValues['controller'] = $translationsBaseDocument->getController();
  239.                     }
  240.                 } elseif ($request->get('type') == 'page' || $request->get('type') == 'snippet' || $request->get('type') == 'email') {
  241.                     $createValues['controller'] = $this->getParameter('pimcore.documents.default_controller');
  242.                 } elseif ($request->get('type') == 'printpage') {
  243.                     $createValues['controller'] = $this->getParameter('pimcore.documents.web_to_print.default_controller_print_page');
  244.                 } elseif ($request->get('type') == 'printcontainer') {
  245.                     $createValues['controller'] = $this->getParameter('pimcore.documents.web_to_print.default_controller_print_container');
  246.                 }
  247.                 if ($request->get('inheritanceSource')) {
  248.                     $createValues['contentMasterDocumentId'] = $request->get('inheritanceSource');
  249.                 }
  250.                 switch ($request->get('type')) {
  251.                     case 'page':
  252.                         $document Document\Page::create($request->get('parentId'), $createValuesfalse);
  253.                         $document->setTitle($request->get('title'null));
  254.                         $document->setProperty('navigation_name''text'$request->get('name'null), falsefalse);
  255.                         $document->save();
  256.                         $success true;
  257.                         break;
  258.                     case 'snippet':
  259.                         $document Document\Snippet::create($request->get('parentId'), $createValues);
  260.                         $success true;
  261.                         break;
  262.                     case 'email'//ckogler
  263.                         $document Document\Email::create($request->get('parentId'), $createValues);
  264.                         $success true;
  265.                         break;
  266.                     case 'link':
  267.                         $document Document\Link::create($request->get('parentId'), $createValues);
  268.                         $success true;
  269.                         break;
  270.                     case 'hardlink':
  271.                         $document Document\Hardlink::create($request->get('parentId'), $createValues);
  272.                         $success true;
  273.                         break;
  274.                     case 'folder':
  275.                         $document Document\Folder::create($request->get('parentId'), $createValues);
  276.                         $document->setPublished(true);
  277.                         try {
  278.                             $document->save();
  279.                             $success true;
  280.                         } catch (Exception $e) {
  281.                             return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  282.                         }
  283.                         break;
  284.                     default:
  285.                         $classname '\\Pimcore\\Model\\Document\\' ucfirst($request->get('type'));
  286.                         // this is the fallback for custom document types using prefixes
  287.                         // so we need to check if the class exists first
  288.                         if (!Tool::classExists($classname)) {
  289.                             $oldStyleClass '\\Document_' ucfirst($request->get('type'));
  290.                             if (Tool::classExists($oldStyleClass)) {
  291.                                 $classname $oldStyleClass;
  292.                             }
  293.                         }
  294.                         if (Tool::classExists($classname)) {
  295.                             $document $classname::create($request->get('parentId'), $createValues);
  296.                             try {
  297.                                 $document->save();
  298.                                 $success true;
  299.                             } catch (Exception $e) {
  300.                                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  301.                             }
  302.                             break;
  303.                         } else {
  304.                             Logger::debug("Unknown document type, can't add [ " $request->get('type') . ' ] ');
  305.                         }
  306.                         break;
  307.                 }
  308.             } else {
  309.                 $errorMessage "prevented adding a document because document with same path+key [ $intendedPath ] already exists";
  310.                 Logger::debug($errorMessage);
  311.             }
  312.         } else {
  313.             $errorMessage 'prevented adding a document because of missing permissions';
  314.             Logger::debug($errorMessage);
  315.         }
  316.         if ($success && $document instanceof Document) {
  317.             if ($translationsBaseDocumentId $request->get('translationsBaseDocument')) {
  318.                 $translationsBaseDocument Document::getById((int) $translationsBaseDocumentId);
  319.                 $properties $translationsBaseDocument->getProperties();
  320.                 $properties array_merge($properties$document->getProperties());
  321.                 $document->setProperties($properties);
  322.                 $document->setProperty('language''text'$request->get('language'), falsetrue);
  323.                 $document->save();
  324.                 $service = new Document\Service();
  325.                 $service->addTranslation($translationsBaseDocument$document);
  326.             }
  327.             return $this->adminJson([
  328.                 'success' => $success,
  329.                 'id' => $document->getId(),
  330.                 'type' => $document->getType(),
  331.             ]);
  332.         }
  333.         return $this->adminJson([
  334.             'success' => $success,
  335.             'message' => $errorMessage,
  336.         ]);
  337.     }
  338.     /**
  339.      * @Route("/delete", name="pimcore_admin_document_document_delete", methods={"DELETE"})
  340.      *
  341.      * @param Request $request
  342.      *
  343.      * @return JsonResponse
  344.      */
  345.     public function deleteAction(Request $request)
  346.     {
  347.         $type $request->get('type');
  348.         if ($type === 'childs') {
  349.             trigger_deprecation(
  350.                 'pimcore/pimcore',
  351.                 '10.4',
  352.                 'Type childs is deprecated. Use children instead'
  353.             );
  354.             $type 'children';
  355.         }
  356.         if ($type === 'children') {
  357.             $parentDocument Document::getById((int) $request->get('id'));
  358.             $list = new Document\Listing();
  359.             $list->setCondition('path LIKE ?', [$list->escapeLike($parentDocument->getRealFullPath()) . '/%']);
  360.             $list->setLimit((int)$request->get('amount'));
  361.             $list->setOrderKey('LENGTH(path)'false);
  362.             $list->setOrder('DESC');
  363.             $documents $list->load();
  364.             $deletedItems = [];
  365.             foreach ($documents as $document) {
  366.                 $deletedItems[$document->getId()] = $document->getRealFullPath();
  367.                 if ($document->isAllowed('delete') && !$document->isLocked()) {
  368.                     $document->delete();
  369.                 }
  370.             }
  371.             return $this->adminJson(['success' => true'deleted' => $deletedItems]);
  372.         }
  373.         if ($id $request->get('id')) {
  374.             $document Document::getById((int) $id);
  375.             if ($document && $document->isAllowed('delete')) {
  376.                 try {
  377.                     if ($document->isLocked()) {
  378.                         throw new Exception('prevented deleting document, because it is locked: ID: ' $document->getId());
  379.                     }
  380.                     $document->delete();
  381.                     return $this->adminJson(['success' => true]);
  382.                 } catch (Exception $e) {
  383.                     Logger::err((string) $e);
  384.                     return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  385.                 }
  386.             }
  387.         }
  388.         throw $this->createAccessDeniedHttpException();
  389.     }
  390.     /**
  391.      * @Route("/update", name="pimcore_admin_document_document_update", methods={"PUT"})
  392.      *
  393.      * @param Request $request
  394.      *
  395.      * @return JsonResponse
  396.      *
  397.      * @throws Exception
  398.      */
  399.     public function updateAction(Request $request)
  400.     {
  401.         $success false;
  402.         $allowUpdate true;
  403.         $document Document::getById((int) $request->get('id'));
  404.         $oldPath $document->getDao()->getCurrentFullPath();
  405.         $oldDocument Document::getById($document->getId(), ['force' => true]);
  406.         // this prevents the user from renaming, relocating (actions in the tree) if the newest version isn't the published one
  407.         // the reason is that otherwise the content of the newer not published version will be overwritten
  408.         if ($document instanceof Document\PageSnippet) {
  409.             $latestVersion $document->getLatestVersion();
  410.             if ($latestVersion && $latestVersion->getData()->getModificationDate() != $document->getModificationDate()) {
  411.                 return $this->adminJson(['success' => false'message' => "You can't rename or relocate if there's a newer not published version"]);
  412.             }
  413.         }
  414.         if ($document->isAllowed('settings')) {
  415.             // if the position is changed the path must be changed || also from the children
  416.             if ($parentId $request->get('parentId')) {
  417.                 $parentDocument Document::getById((int) $parentId);
  418.                 //check if parent is changed
  419.                 if ($document->getParentId() != $parentDocument->getId()) {
  420.                     if (!$parentDocument->isAllowed('create')) {
  421.                         throw new Exception('Prevented moving document - no create permission on new parent ');
  422.                     }
  423.                     $intendedPath $parentDocument->getRealPath();
  424.                     $pKey $parentDocument->getKey();
  425.                     if (!empty($pKey)) {
  426.                         $intendedPath .= $parentDocument->getKey() . '/';
  427.                     }
  428.                     $documentWithSamePath Document::getByPath($intendedPath $document->getKey());
  429.                     if ($documentWithSamePath != null) {
  430.                         $allowUpdate false;
  431.                     }
  432.                     if ($document->isLocked()) {
  433.                         $allowUpdate false;
  434.                     }
  435.                 }
  436.             }
  437.             if ($allowUpdate) {
  438.                 $blockedVars = ['controller''action''module'];
  439.                 if (!$document->isAllowed('rename') && $request->get('key')) {
  440.                     $blockedVars[] = 'key';
  441.                     Logger::debug('prevented renaming document because of missing permissions ');
  442.                 }
  443.                 $updateData array_merge($request->request->all(), $request->query->all());
  444.                 foreach ($updateData as $key => $value) {
  445.                     if (!in_array($key$blockedVars)) {
  446.                         $document->setValue($key$value);
  447.                     }
  448.                 }
  449.                 $document->setUserModification($this->getAdminUser()->getId());
  450.                 try {
  451.                     $document->save();
  452.                     if ($request->get('index') !== null) {
  453.                         $this->updateIndexesOfDocumentSiblings($document$request->get('index'));
  454.                     }
  455.                     $success true;
  456.                     $this->createRedirectForFormerPath($request$document$oldPath$oldDocument);
  457.                 } catch (Exception $e) {
  458.                     return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  459.                 }
  460.             } else {
  461.                 $msg 'Prevented moving document, because document with same path+key already exists or the document is locked. ID: ' $document->getId();
  462.                 Logger::debug($msg);
  463.                 return $this->adminJson(['success' => false'message' => $msg]);
  464.             }
  465.         } elseif ($document->isAllowed('rename') && $request->get('key')) {
  466.             //just rename
  467.             try {
  468.                 $document->setKey($request->get('key'));
  469.                 $document->setUserModification($this->getAdminUser()->getId());
  470.                 $document->save();
  471.                 $success true;
  472.                 $this->createRedirectForFormerPath($request$document$oldPath$oldDocument);
  473.             } catch (Exception $e) {
  474.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  475.             }
  476.         } else {
  477.             Logger::debug('Prevented update document, because of missing permissions.');
  478.         }
  479.         return $this->adminJson(['success' => $success]);
  480.     }
  481.     private function createRedirectForFormerPath(Request $requestDocument $documentstring $oldPathDocument $oldDocument)
  482.     {
  483.         if ($document instanceof Document\Page || $document instanceof Document\Hardlink) {
  484.             if ($request->get('create_redirects') === 'true' && $this->getAdminUser()->isAllowed('redirects')) {
  485.                 if ($oldPath && $oldPath != $document->getRealFullPath()) {
  486.                     $sourceSite Frontend::getSiteForDocument($oldDocument);
  487.                     if ($sourceSite) {
  488.                         $oldPath preg_replace('@^' preg_quote($sourceSite->getRootPath(), '@') . '@'''$oldPath);
  489.                     }
  490.                     $targetSite Frontend::getSiteForDocument($document);
  491.                     $this->doCreateRedirectForFormerPath($oldPath$document->getId(), $sourceSite$targetSite);
  492.                     if ($document->hasChildren()) {
  493.                         $list = new Document\Listing();
  494.                         $list->setCondition('path LIKE :path', [
  495.                             'path' => $list->escapeLike($document->getRealFullPath()) . '/%',
  496.                         ]);
  497.                         $childrenList $list->loadIdPathList();
  498.                         $count 0;
  499.                         foreach ($childrenList as $child) {
  500.                             $source preg_replace('@^' preg_quote($document->getRealFullPath(), '@') . '@'$oldDocument$child['path']);
  501.                             if ($sourceSite) {
  502.                                 $source preg_replace('@^' preg_quote($sourceSite->getRootPath(), '@') . '@'''$source);
  503.                             }
  504.                             $target $child['id'];
  505.                             $this->doCreateRedirectForFormerPath($source$target$sourceSite$targetSite);
  506.                             $count++;
  507.                             if ($count 10 === 0) {
  508.                                 Pimcore::collectGarbage();
  509.                             }
  510.                         }
  511.                     }
  512.                 }
  513.             }
  514.         }
  515.     }
  516.     private function doCreateRedirectForFormerPath(string $sourceint $targetId, ?Site $sourceSite, ?Site $targetSite)
  517.     {
  518.         $redirect = new Redirect();
  519.         $redirect->setType(Redirect::TYPE_AUTO_CREATE);
  520.         $redirect->setRegex(false);
  521.         $redirect->setTarget((string) $targetId);
  522.         $redirect->setSource($source);
  523.         $redirect->setStatusCode(301);
  524.         $redirect->setExpiry(time() + 86400 365); // this entry is removed automatically after 1 year
  525.         if ($sourceSite) {
  526.             $redirect->setSourceSite($sourceSite->getId());
  527.         }
  528.         if ($targetSite) {
  529.             $redirect->setTargetSite($targetSite->getId());
  530.         }
  531.         $redirect->save();
  532.     }
  533.     /**
  534.      * @param Document $document
  535.      * @param int $newIndex
  536.      */
  537.     protected function updateIndexesOfDocumentSiblings(Document $document$newIndex)
  538.     {
  539.         $updateLatestVersionIndex = function ($document$newIndex) {
  540.             if ($document instanceof Document\PageSnippet && $latestVersion $document->getLatestVersion()) {
  541.                 $document $latestVersion->loadData();
  542.                 $document->setIndex($newIndex);
  543.                 $latestVersion->save();
  544.             }
  545.         };
  546.         // if changed the index change also all documents on the same level
  547.         $newIndex = (int)$newIndex;
  548.         $document->saveIndex($newIndex);
  549.         $list = new Document\Listing();
  550.         $list->setCondition('parentId = ? AND id != ?', [$document->getParentId(), $document->getId()]);
  551.         $list->setOrderKey('index');
  552.         $list->setOrder('asc');
  553.         $childsList $list->load();
  554.         $count 0;
  555.         foreach ($childsList as $child) {
  556.             if ($count == $newIndex) {
  557.                 $count++;
  558.             }
  559.             $child->saveIndex($count);
  560.             $updateLatestVersionIndex($child$count);
  561.             $count++;
  562.         }
  563.     }
  564.     /**
  565.      * @Route("/doc-types", name="pimcore_admin_document_document_doctypesget", methods={"GET"})
  566.      *
  567.      * @param Request $request
  568.      *
  569.      * @return JsonResponse
  570.      */
  571.     public function docTypesGetAction(Request $request)
  572.     {
  573.         // get list of types
  574.         $list = new Document\DocType\Listing();
  575.         $list->load();
  576.         $docTypes = [];
  577.         foreach ($list->getDocTypes() as $type) {
  578.             if ($this->getAdminUser()->isAllowed($type->getId(), 'docType')) {
  579.                 $data $type->getObjectVars();
  580.                 $data['writeable'] = $type->isWriteable();
  581.                 $docTypes[] = $data;
  582.             }
  583.         }
  584.         return $this->adminJson(['data' => $docTypes'success' => true'total' => count($docTypes)]);
  585.     }
  586.     /**
  587.      * @Route("/doc-types", name="pimcore_admin_document_document_doctypes", methods={"PUT", "POST", "DELETE"})
  588.      *
  589.      * @param Request $request
  590.      *
  591.      * @return JsonResponse
  592.      */
  593.     public function docTypesAction(Request $request)
  594.     {
  595.         if ($request->get('data')) {
  596.             $this->checkPermission('document_types');
  597.             $data $this->decodeJson($request->get('data'));
  598.             if ($request->get('xaction') === 'destroy') {
  599.                 $type Document\DocType::getById($data['id']);
  600.                 if (!$type->isWriteable()) {
  601.                     throw new ConfigWriteException();
  602.                 }
  603.                 $type->delete();
  604.                 return $this->adminJson(['success' => true'data' => []]);
  605.             } elseif ($request->get('xaction') === 'update') {
  606.                 // save type
  607.                 $type Document\DocType::getById($data['id']);
  608.                 if (!$type->isWriteable()) {
  609.                     throw new ConfigWriteException();
  610.                 }
  611.                 $type->setValues($data);
  612.                 $type->save();
  613.                 $responseData $type->getObjectVars();
  614.                 $responseData['writeable'] = $type->isWriteable();
  615.                 return $this->adminJson(['data' => $responseData'success' => true]);
  616.             } elseif ($request->get('xaction') === 'create') {
  617.                 if (!(new DocType())->isWriteable()) {
  618.                     throw new ConfigWriteException();
  619.                 }
  620.                 unset($data['id']);
  621.                 // save type
  622.                 $type Document\DocType::create();
  623.                 $type->setValues($data);
  624.                 $type->save();
  625.                 $responseData $type->getObjectVars();
  626.                 $responseData['writeable'] = $type->isWriteable();
  627.                 return $this->adminJson(['data' => $responseData'success' => true]);
  628.             }
  629.         }
  630.         return $this->adminJson(false);
  631.     }
  632.     /**
  633.      * @Route("/get-doc-types", name="pimcore_admin_document_document_getdoctypes", methods={"GET"})
  634.      *
  635.      * @param Request $request
  636.      *
  637.      * @throws BadRequestHttpException If type is invalid
  638.      *
  639.      * @return JsonResponse
  640.      */
  641.     public function getDocTypesAction(Request $request)
  642.     {
  643.         $list = new Document\DocType\Listing();
  644.         if ($type $request->get('type')) {
  645.             if (!Document\Service::isValidType($type)) {
  646.                 throw new BadRequestHttpException('Invalid type: ' $type);
  647.             }
  648.             $list->setFilter(function (Document\DocType $docType) use ($type) {
  649.                 return $docType->getType() === $type;
  650.             });
  651.         }
  652.         $docTypes = [];
  653.         foreach ($list->getDocTypes() as $type) {
  654.             $docTypes[] = $type->getObjectVars();
  655.         }
  656.         return $this->adminJson(['docTypes' => $docTypes]);
  657.     }
  658.     /**
  659.      * @Route("/version-to-session", name="pimcore_admin_document_document_versiontosession", methods={"POST"})
  660.      *
  661.      * @param Request $request
  662.      *
  663.      * @return Response
  664.      */
  665.     public function versionToSessionAction(Request $request)
  666.     {
  667.         $id = (int)$request->get('id');
  668.         $version Version::getById($id);
  669.         $document $version?->loadData();
  670.         if (!$document) {
  671.             throw $this->createNotFoundException('Version with id [' $id "] doesn't exist");
  672.         }
  673.         Document\Service::saveElementToSession($document);
  674.         return new Response();
  675.     }
  676.     /**
  677.      * @Route("/publish-version", name="pimcore_admin_document_document_publishversion", methods={"POST"})
  678.      *
  679.      * @param Request $request
  680.      *
  681.      * @return JsonResponse
  682.      */
  683.     public function publishVersionAction(Request $request)
  684.     {
  685.         $this->versionToSessionAction($request);
  686.         $id = (int)$request->get('id');
  687.         $version Version::getById($id);
  688.         $document $version?->loadData();
  689.         if (!$document) {
  690.             throw $this->createNotFoundException('Version with id [' $id "] doesn't exist");
  691.         }
  692.         $currentDocument Document::getById($document->getId());
  693.         if ($currentDocument->isAllowed('publish')) {
  694.             $document->setPublished(true);
  695.             try {
  696.                 $document->setKey($currentDocument->getKey());
  697.                 $document->setPath($currentDocument->getRealPath());
  698.                 $document->setUserModification($this->getAdminUser()->getId());
  699.                 $document->save();
  700.             } catch (Exception $e) {
  701.                 return $this->adminJson(['success' => false'message' => $e->getMessage()]);
  702.             }
  703.         }
  704.         $this->addAdminStyle($documentElementAdminStyleEvent::CONTEXT_EDITOR$treeData);
  705.         return $this->adminJson(['success' => true'treeData' => $treeData]);
  706.     }
  707.     /**
  708.      * @Route("/update-site", name="pimcore_admin_document_document_updatesite", methods={"PUT"})
  709.      *
  710.      * @param Request $request
  711.      *
  712.      * @return JsonResponse
  713.      */
  714.     public function updateSiteAction(Request $request)
  715.     {
  716.         $domains $request->get('domains');
  717.         $domains str_replace(' '''$domains);
  718.         $domains explode("\n"$domains);
  719.         if (!$site Site::getByRootId((int)$request->get('id'))) {
  720.             $site Site::create([
  721.                 'rootId' => (int)$request->get('id'),
  722.             ]);
  723.         }
  724.         $localizedErrorDocuments = [];
  725.         $validLanguages Tool::getValidLanguages();
  726.         foreach ($validLanguages as $language) {
  727.             // localized error pages
  728.             $requestValue $request->get('errorDocument_localized_' $language);
  729.             if (isset($requestValue)) {
  730.                 $localizedErrorDocuments[$language] = $requestValue;
  731.             }
  732.         }
  733.         $site->setDomains($domains);
  734.         $site->setMainDomain($request->get('mainDomain'));
  735.         $site->setErrorDocument($request->get('errorDocument'));
  736.         $site->setLocalizedErrorDocuments($localizedErrorDocuments);
  737.         $site->setRedirectToMainDomain(($request->get('redirectToMainDomain') == 'true') ? true false);
  738.         $site->save();
  739.         $site->setRootDocument(null); // do not send the document to the frontend
  740.         return $this->adminJson($site->getObjectVars());
  741.     }
  742.     /**
  743.      * @Route("/remove-site", name="pimcore_admin_document_document_removesite", methods={"DELETE"})
  744.      *
  745.      * @param Request $request
  746.      *
  747.      * @return JsonResponse
  748.      */
  749.     public function removeSiteAction(Request $request)
  750.     {
  751.         $site Site::getByRootId((int)$request->get('id'));
  752.         $site->delete();
  753.         return $this->adminJson(['success' => true]);
  754.     }
  755.     /**
  756.      * @Route("/copy-info", name="pimcore_admin_document_document_copyinfo", methods={"GET"})
  757.      *
  758.      * @param Request $request
  759.      *
  760.      * @return JsonResponse
  761.      */
  762.     public function copyInfoAction(Request $request)
  763.     {
  764.         $transactionId time();
  765.         $pasteJobs = [];
  766.         Session::useSession(function (AttributeBagInterface $session) use ($transactionId) {
  767.             $session->set((string) $transactionId, ['idMapping' => []]);
  768.         }, 'pimcore_copy');
  769.         if ($request->get('type') == 'recursive' || $request->get('type') == 'recursive-update-references') {
  770.             $document Document::getById((int) $request->get('sourceId'));
  771.             // first of all the new parent
  772.             $pasteJobs[] = [[
  773.                 'url' => $this->generateUrl('pimcore_admin_document_document_copy'),
  774.                 'method' => 'POST',
  775.                 'params' => [
  776.                     'sourceId' => $request->get('sourceId'),
  777.                     'targetId' => $request->get('targetId'),
  778.                     'type' => 'child',
  779.                     'language' => $request->get('language'),
  780.                     'enableInheritance' => $request->get('enableInheritance'),
  781.                     'transactionId' => $transactionId,
  782.                     'saveParentId' => true,
  783.                     'resetIndex' => true,
  784.                 ],
  785.             ]];
  786.             $childIds = [];
  787.             if ($document->hasChildren()) {
  788.                 // get amount of children
  789.                 $list = new Document\Listing();
  790.                 $list->setCondition('path LIKE ?', [$list->escapeLike($document->getRealFullPath()) . '/%']);
  791.                 $list->setOrderKey('LENGTH(path)'false);
  792.                 $list->setOrder('ASC');
  793.                 $childIds $list->loadIdList();
  794.                 if (count($childIds) > 0) {
  795.                     foreach ($childIds as $id) {
  796.                         $pasteJobs[] = [[
  797.                             'url' => $this->generateUrl('pimcore_admin_document_document_copy'),
  798.                             'method' => 'POST',
  799.                             'params' => [
  800.                                 'sourceId' => $id,
  801.                                 'targetParentId' => $request->get('targetId'),
  802.                                 'sourceParentId' => $request->get('sourceId'),
  803.                                 'type' => 'child',
  804.                                 'language' => $request->get('language'),
  805.                                 'enableInheritance' => $request->get('enableInheritance'),
  806.                                 'transactionId' => $transactionId,
  807.                             ],
  808.                         ]];
  809.                     }
  810.                 }
  811.             }
  812.             // add id-rewrite steps
  813.             if ($request->get('type') == 'recursive-update-references') {
  814.                 for ($i 0$i < (count($childIds) + 1); $i++) {
  815.                     $pasteJobs[] = [[
  816.                         'url' => $this->generateUrl('pimcore_admin_document_document_copyrewriteids'),
  817.                         'method' => 'PUT',
  818.                         'params' => [
  819.                             'transactionId' => $transactionId,
  820.                             'enableInheritance' => $request->get('enableInheritance'),
  821.                             '_dc' => uniqid(),
  822.                         ],
  823.                     ]];
  824.                 }
  825.             }
  826.         } elseif ($request->get('type') == 'child' || $request->get('type') == 'replace') {
  827.             // the object itself is the last one
  828.             $pasteJobs[] = [[
  829.                 'url' => $this->generateUrl('pimcore_admin_document_document_copy'),
  830.                 'method' => 'POST',
  831.                 'params' => [
  832.                     'sourceId' => $request->get('sourceId'),
  833.                     'targetId' => $request->get('targetId'),
  834.                     'type' => $request->get('type'),
  835.                     'language' => $request->get('language'),
  836.                     'enableInheritance' => $request->get('enableInheritance'),
  837.                     'transactionId' => $transactionId,
  838.                     'resetIndex' => ($request->get('type') == 'child'),
  839.                 ],
  840.             ]];
  841.         }
  842.         return $this->adminJson([
  843.             'pastejobs' => $pasteJobs,
  844.         ]);
  845.     }
  846.     /**
  847.      * @Route("/copy-rewrite-ids", name="pimcore_admin_document_document_copyrewriteids", methods={"PUT"})
  848.      *
  849.      * @param Request $request
  850.      *
  851.      * @return JsonResponse
  852.      */
  853.     public function copyRewriteIdsAction(Request $request)
  854.     {
  855.         $transactionId $request->get('transactionId');
  856.         $idStore Session::useSession(function (AttributeBagInterface $session) use ($transactionId) {
  857.             return $session->get($transactionId);
  858.         }, 'pimcore_copy');
  859.         if (!array_key_exists('rewrite-stack'$idStore)) {
  860.             $idStore['rewrite-stack'] = array_values($idStore['idMapping']);
  861.         }
  862.         $id array_shift($idStore['rewrite-stack']);
  863.         $document Document::getById($id);
  864.         if ($document) {
  865.             // create rewriteIds() config parameter
  866.             $rewriteConfig = ['document' => $idStore['idMapping']];
  867.             $document Document\Service::rewriteIds($document$rewriteConfig, [
  868.                 'enableInheritance' => ($request->get('enableInheritance') == 'true') ? true false,
  869.             ]);
  870.             $document->setUserModification($this->getAdminUser()->getId());
  871.             $document->save();
  872.         }
  873.         // write the store back to the session
  874.         Session::useSession(function (AttributeBagInterface $session) use ($transactionId$idStore) {
  875.             $session->set($transactionId$idStore);
  876.         }, 'pimcore_copy');
  877.         return $this->adminJson([
  878.             'success' => true,
  879.             'id' => $id,
  880.         ]);
  881.     }
  882.     /**
  883.      * @Route("/copy", name="pimcore_admin_document_document_copy", methods={"POST"})
  884.      *
  885.      * @param Request $request
  886.      *
  887.      * @return JsonResponse
  888.      */
  889.     public function copyAction(Request $request)
  890.     {
  891.         $success false;
  892.         $sourceId = (int)$request->get('sourceId');
  893.         $source Document::getById($sourceId);
  894.         $session Session::get('pimcore_copy');
  895.         $targetId = (int)$request->get('targetId');
  896.         $sessionBag $session->get($request->get('transactionId'));
  897.         if ($request->get('targetParentId')) {
  898.             $sourceParent Document::getById((int) $request->get('sourceParentId'));
  899.             // this is because the key can get the prefix "_copy" if the target does already exists
  900.             if ($sessionBag['parentId']) {
  901.                 $targetParent Document::getById($sessionBag['parentId']);
  902.             } else {
  903.                 $targetParent Document::getById((int) $request->get('targetParentId'));
  904.             }
  905.             $targetPath preg_replace('@^' $sourceParent->getRealFullPath() . '@'$targetParent '/'$source->getRealPath());
  906.             $target Document::getByPath($targetPath);
  907.         } else {
  908.             $target Document::getById($targetId);
  909.         }
  910.         if ($target instanceof Document) {
  911.             if ($target->isAllowed('create')) {
  912.                 if ($source != null) {
  913.                     if ($source instanceof Document\PageSnippet && $latestVersion $source->getLatestVersion()) {
  914.                         $source $latestVersion->loadData();
  915.                         $source->setPublished(false); //as latest version is used which is not published
  916.                     }
  917.                     if ($request->get('type') == 'child') {
  918.                         $enableInheritance = ($request->get('enableInheritance') == 'true') ? true false;
  919.                         $language false;
  920.                         if (Tool::isValidLanguage($request->get('language'))) {
  921.                             $language $request->get('language');
  922.                         }
  923.                         $resetIndex = ($request->get('resetIndex') == 'true') ? true false;
  924.                         $newDocument $this->_documentService->copyAsChild($target$source$enableInheritance$resetIndex$language);
  925.                         $sessionBag['idMapping'][(int)$source->getId()] = (int)$newDocument->getId();
  926.                         // this is because the key can get the prefix "_copy" if the target does already exists
  927.                         if ($request->get('saveParentId')) {
  928.                             $sessionBag['parentId'] = $newDocument->getId();
  929.                         }
  930.                         $session->set($request->get('transactionId'), $sessionBag);
  931.                         Session::writeClose();
  932.                     } elseif ($request->get('type') == 'replace') {
  933.                         $this->_documentService->copyContents($target$source);
  934.                     }
  935.                     $success true;
  936.                 } else {
  937.                     Logger::error('prevended copy/paste because document with same path+key already exists in this location');
  938.                 }
  939.             } else {
  940.                 Logger::error('could not execute copy/paste because of missing permissions on target [ ' $targetId ' ]');
  941.                 throw $this->createAccessDeniedHttpException();
  942.             }
  943.         }
  944.         return $this->adminJson(['success' => $success]);
  945.     }
  946.     /**
  947.      * @Route("/diff-versions/from/{from}/to/{to}", name="pimcore_admin_document_document_diffversions", requirements={"from": "\d+", "to": "\d+"}, methods={"GET"})
  948.      *
  949.      * @param Request $request
  950.      * @param int $from
  951.      * @param int $to
  952.      *
  953.      * @return Response
  954.      */
  955.     public function diffVersionsAction(Request $request$from$to)
  956.     {
  957.         // return with error if prerequisites do not match
  958.         if ((!Chromium::isSupported() && !HtmlToImage::isSupported()) || !class_exists('Imagick')) {
  959.             return $this->render('@PimcoreAdmin/Admin/Document/Document/diff-versions-unsupported.html.twig');
  960.         }
  961.         $versionFrom Version::getById($from);
  962.         $docFrom $versionFrom?->loadData();
  963.         if (!$docFrom) {
  964.             throw $this->createNotFoundException('Version with id [' $from "] doesn't exist");
  965.         }
  966.         $prefix Config::getSystemConfiguration('documents')['preview_url_prefix'];
  967.         if (empty($prefix)) {
  968.             $prefix $request->getSchemeAndHttpHost();
  969.         }
  970.         $prefix .= $docFrom->getRealFullPath() . '?pimcore_version=';
  971.         $fromUrl $prefix $from;
  972.         $toUrl $prefix $to;
  973.         $toFileId uniqid();
  974.         $fromFileId uniqid();
  975.         $diffFileId uniqid();
  976.         $fromFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/version-diff-tmp-' $fromFileId '.png';
  977.         $toFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/version-diff-tmp-' $toFileId '.png';
  978.         $diffFile PIMCORE_SYSTEM_TEMP_DIRECTORY '/version-diff-tmp-' $diffFileId '.png';
  979.         $viewParams = [];
  980.         if (Chromium::isSupported()) {
  981.             $tool Chromium::class;
  982.         } else {
  983.             $tool HtmlToImage::class;
  984.         }
  985.         /** @var Chromium|HtmlToImage $tool */
  986.         $tool::convert($fromUrl$fromFile);
  987.         $tool::convert($toUrl$toFile);
  988.         $image1 = new Imagick($fromFile);
  989.         $image2 = new Imagick($toFile);
  990.         if ($image1->getImageWidth() == $image2->getImageWidth() && $image1->getImageHeight() == $image2->getImageHeight()) {
  991.             $result $image1->compareImages($image2Imagick::METRIC_MEANSQUAREERROR);
  992.             $result[0]->setImageFormat('png');
  993.             $result[0]->writeImage($diffFile);
  994.             $result[0]->clear();
  995.             $result[0]->destroy();
  996.             $viewParams['image'] = $diffFileId;
  997.         } else {
  998.             $viewParams['image1'] = $fromFileId;
  999.             $viewParams['image2'] = $toFileId;
  1000.         }
  1001.         // cleanup
  1002.         $image1->clear();
  1003.         $image1->destroy();
  1004.         $image2->clear();
  1005.         $image2->destroy();
  1006.         return $this->render('@PimcoreAdmin/Admin/Document/Document/diff-versions.html.twig'$viewParams);
  1007.     }
  1008.     /**
  1009.      * @Route("/diff-versions-image", name="pimcore_admin_document_document_diffversionsimage", methods={"GET"})
  1010.      *
  1011.      * @param Request $request
  1012.      *
  1013.      * @return BinaryFileResponse
  1014.      */
  1015.     public function diffVersionsImageAction(Request $request)
  1016.     {
  1017.         $file PIMCORE_SYSTEM_TEMP_DIRECTORY '/version-diff-tmp-' $request->get('id') . '.png';
  1018.         if (file_exists($file)) {
  1019.             $response = new BinaryFileResponse($file);
  1020.             $response->headers->set('Content-Type''image/png');
  1021.             return $response;
  1022.         }
  1023.         throw $this->createNotFoundException('Version diff file not found');
  1024.     }
  1025.     /**
  1026.      * @Route("/get-id-for-path", name="pimcore_admin_document_document_getidforpath", methods={"GET"})
  1027.      *
  1028.      * @param Request $request
  1029.      *
  1030.      * @return JsonResponse
  1031.      */
  1032.     public function getIdForPathAction(Request $request)
  1033.     {
  1034.         if ($doc Document::getByPath($request->get('path'))) {
  1035.             return $this->adminJson([
  1036.                 'id' => $doc->getId(),
  1037.                 'type' => $doc->getType(),
  1038.             ]);
  1039.         } else {
  1040.             return $this->adminJson(false);
  1041.         }
  1042.     }
  1043.     /**
  1044.      * SEO PANEL
  1045.      */
  1046.     /**
  1047.      * @Route("/seopanel-tree-root", name="pimcore_admin_document_document_seopaneltreeroot", methods={"GET"})
  1048.      *
  1049.      * @param DocumentRouteHandler $documentRouteHandler
  1050.      *
  1051.      * @return JsonResponse
  1052.      */
  1053.     public function seopanelTreeRootAction(DocumentRouteHandler $documentRouteHandler)
  1054.     {
  1055.         $this->checkPermission('seo_document_editor');
  1056.         /** @var Document\Page $root */
  1057.         $root Document\Page::getById(1);
  1058.         if ($root->isAllowed('list')) {
  1059.             // make sure document routes are also built for unpublished documents
  1060.             $documentRouteHandler->setForceHandleUnpublishedDocuments(true);
  1061.             $nodeConfig $this->getSeoNodeConfig($root);
  1062.             return $this->adminJson($nodeConfig);
  1063.         }
  1064.         throw $this->createAccessDeniedHttpException();
  1065.     }
  1066.     /**
  1067.      * @Route("/seopanel-tree", name="pimcore_admin_document_document_seopaneltree", methods={"GET"})
  1068.      *
  1069.      * @param Request $request
  1070.      * @param EventDispatcherInterface $eventDispatcher
  1071.      * @param DocumentRouteHandler $documentRouteHandler
  1072.      *
  1073.      * @return JsonResponse
  1074.      */
  1075.     public function seopanelTreeAction(
  1076.         Request $request,
  1077.         EventDispatcherInterface $eventDispatcher,
  1078.         DocumentRouteHandler $documentRouteHandler
  1079.     ) {
  1080.         $allParams array_merge($request->request->all(), $request->query->all());
  1081.         $filterPrepareEvent = new GenericEvent($this, [
  1082.             'requestParams' => $allParams,
  1083.         ]);
  1084.         $eventDispatcher->dispatch($filterPrepareEventAdminEvents::DOCUMENT_LIST_BEFORE_FILTER_PREPARE);
  1085.         $allParams $filterPrepareEvent->getArgument('requestParams');
  1086.         $this->checkPermission('seo_document_editor');
  1087.         // make sure document routes are also built for unpublished documents
  1088.         $documentRouteHandler->setForceHandleUnpublishedDocuments(true);
  1089.         $document Document::getById($allParams['node']);
  1090.         $documents = [];
  1091.         if ($document->hasChildren()) {
  1092.             $list = new Document\Listing();
  1093.             $list->setCondition('parentId = ?'$document->getId());
  1094.             $list->setOrderKey('index');
  1095.             $list->setOrder('asc');
  1096.             $beforeListLoadEvent = new GenericEvent($this, [
  1097.                 'list' => $list,
  1098.                 'context' => $allParams,
  1099.             ]);
  1100.             $eventDispatcher->dispatch($beforeListLoadEventAdminEvents::DOCUMENT_LIST_BEFORE_LIST_LOAD);
  1101.             /** @var Document\Listing $list */
  1102.             $list $beforeListLoadEvent->getArgument('list');
  1103.             $childsList $list->load();
  1104.             foreach ($childsList as $childDocument) {
  1105.                 // only display document if listing is allowed for the current user
  1106.                 if ($childDocument->isAllowed('list')) {
  1107.                     $list = new Document\Listing();
  1108.                     $list->setCondition('path LIKE ? and type = ?', [$list->escapeLike($childDocument->getRealFullPath()). '/%''page']);
  1109.                     if ($childDocument instanceof Document\Page || $list->getTotalCount() > 0) {
  1110.                         $documents[] = $this->getSeoNodeConfig($childDocument);
  1111.                     }
  1112.                 }
  1113.             }
  1114.         }
  1115.         $result = ['data' => $documents'success' => true'total' => count($documents)];
  1116.         $afterListLoadEvent = new GenericEvent($this, [
  1117.             'list' => $result,
  1118.             'context' => $allParams,
  1119.         ]);
  1120.         $eventDispatcher->dispatch($afterListLoadEventAdminEvents::DOCUMENT_LIST_AFTER_LIST_LOAD);
  1121.         $result $afterListLoadEvent->getArgument('list');
  1122.         return $this->adminJson($result['data']);
  1123.     }
  1124.     /**
  1125.      * @Route("/language-tree", name="pimcore_admin_document_document_languagetree", methods={"GET"})
  1126.      *
  1127.      * @param Request $request
  1128.      *
  1129.      * @return JsonResponse
  1130.      */
  1131.     public function languageTreeAction(Request $request)
  1132.     {
  1133.         $document Document::getById((int) $request->query->get('node'));
  1134.         $languages explode(','$request->get('languages'));
  1135.         $result = [];
  1136.         foreach ($document->getChildren() as $child) {
  1137.             $result[] = $this->getTranslationTreeNodeConfig($child$languages);
  1138.         }
  1139.         return $this->adminJson($result);
  1140.     }
  1141.     /**
  1142.      * @Route("/language-tree-root", name="pimcore_admin_document_document_languagetreeroot", methods={"GET"})
  1143.      *
  1144.      * @param Request $request
  1145.      *
  1146.      * @return JsonResponse
  1147.      *
  1148.      * @throws Exception
  1149.      */
  1150.     public function languageTreeRootAction(Request $request)
  1151.     {
  1152.         $document Document::getById((int) $request->query->get('id'));
  1153.         if (!$document) {
  1154.             return $this->adminJson([
  1155.                 'success' => false,
  1156.             ]);
  1157.         }
  1158.         $service = new Document\Service();
  1159.         $locales Tool::getSupportedLocales();
  1160.         $lang $document->getProperty('language');
  1161.         $columns = [
  1162.             [
  1163.                 'xtype' => 'treecolumn',
  1164.                 'text' => $lang $locales[$lang] : '',
  1165.                 'dataIndex' => 'text',
  1166.                 'cls' => $lang 'x-column-header_' strtolower($lang) : null,
  1167.                 'width' => 300,
  1168.                 'sortable' => false,
  1169.             ],
  1170.         ];
  1171.         $translations $service->getTranslations($document);
  1172.         $combinedTranslations $translations;
  1173.         if ($parentDocument $document->getParent()) {
  1174.             $parentTranslations $service->getTranslations($parentDocument);
  1175.             foreach ($parentTranslations as $language => $languageDocumentId) {
  1176.                 $combinedTranslations[$language] = $translations[$language] ?? $languageDocumentId;
  1177.             }
  1178.         }
  1179.         foreach ($combinedTranslations as $language => $languageDocumentId) {
  1180.             $languageDocument Document::getById($languageDocumentId);
  1181.             if ($languageDocument && $languageDocument->isAllowed('list') && $language != $document->getProperty('language')) {
  1182.                 $columns[] = [
  1183.                     'text' => $locales[$language],
  1184.                     'dataIndex' => $language,
  1185.                     'cls' => 'x-column-header_' strtolower($language),
  1186.                     'width' => 300,
  1187.                     'sortable' => false,
  1188.                 ];
  1189.             }
  1190.         }
  1191.         return $this->adminJson([
  1192.             'root' => $this->getTranslationTreeNodeConfig($documentarray_keys($translations), $translations),
  1193.             'columns' => $columns,
  1194.             'languages' => array_keys($translations),
  1195.         ]);
  1196.     }
  1197.     private function getTranslationTreeNodeConfig($document, array $languages, array $translations null)
  1198.     {
  1199.         $service = new Document\Service();
  1200.         $config $this->getTreeNodeConfig($document);
  1201.         $translations is_null($translations) ? $service->getTranslations($document) : $translations;
  1202.         foreach ($languages as $language) {
  1203.             if ($languageDocument $translations[$language] ?? false) {
  1204.                 $languageDocument Document::getById($languageDocument);
  1205.                 $config[$language] = [
  1206.                     'text' => $languageDocument->getKey(),
  1207.                     'id' => $languageDocument->getId(),
  1208.                     'type' => $languageDocument->getType(),
  1209.                     'fullPath' => $languageDocument->getFullPath(),
  1210.                     'published' => $languageDocument->getPublished(),
  1211.                     'itemType' => 'document',
  1212.                     'permissions' => $languageDocument->getUserPermissions($this->getAdminUser()),
  1213.                 ];
  1214.             } elseif (!$document instanceof Document\Folder) {
  1215.                 $config[$language] = [
  1216.                     'text' => '--',
  1217.                     'itemType' => 'empty',
  1218.                 ];
  1219.             }
  1220.         }
  1221.         return $config;
  1222.     }
  1223.     /**
  1224.      * @Route("/convert", name="pimcore_admin_document_document_convert", methods={"PUT"})
  1225.      *
  1226.      * @param Request $request
  1227.      *
  1228.      * @return JsonResponse
  1229.      */
  1230.     public function convertAction(Request $request)
  1231.     {
  1232.         $document Document::getById((int) $request->get('id'));
  1233.         if (!$document) {
  1234.             throw $this->createNotFoundException();
  1235.         }
  1236.         $type $request->get('type');
  1237.         $class '\\Pimcore\\Model\\Document\\' ucfirst($type);
  1238.         if (Tool::classExists($class)) {
  1239.             $new = new $class;
  1240.             // overwrite internal store to avoid "duplicate full path" error
  1241.             RuntimeCache::set('document_' $document->getId(), $new);
  1242.             $props $document->getObjectVars();
  1243.             foreach ($props as $name => $value) {
  1244.                 $new->setValue($name$value);
  1245.             }
  1246.             if ($type == 'hardlink' || $type == 'folder') {
  1247.                 // remove navigation settings
  1248.                 foreach (['name''title''target''exclude''class''anchor''parameters''relation''accesskey''tabindex'] as $propertyName) {
  1249.                     $new->removeProperty('navigation_' $propertyName);
  1250.                 }
  1251.             }
  1252.             $new->setType($type);
  1253.             $new->save();
  1254.         }
  1255.         return $this->adminJson(['success' => true]);
  1256.     }
  1257.     /**
  1258.      * @Route("/translation-determine-parent", name="pimcore_admin_document_document_translationdetermineparent", methods={"GET"})
  1259.      *
  1260.      * @param Request $request
  1261.      *
  1262.      * @return JsonResponse
  1263.      */
  1264.     public function translationDetermineParentAction(Request $request)
  1265.     {
  1266.         $success false;
  1267.         $targetDocument null;
  1268.         $document Document::getById((int) $request->get('id'));
  1269.         if ($document) {
  1270.             $service = new Document\Service();
  1271.             $document $document->getId() === $document $document->getParent();
  1272.             $translations $service->getTranslations($document);
  1273.             if (isset($translations[$request->get('language')])) {
  1274.                 $targetDocument Document::getById($translations[$request->get('language')]);
  1275.                 $success true;
  1276.             }
  1277.         }
  1278.         return $this->adminJson([
  1279.             'success' => $success,
  1280.             'targetPath' => $targetDocument $targetDocument->getRealFullPath() : null,
  1281.             'targetId' => $targetDocument $targetDocument->getid() : null,
  1282.         ]);
  1283.     }
  1284.     /**
  1285.      * @Route("/translation-add", name="pimcore_admin_document_document_translationadd", methods={"POST"})
  1286.      *
  1287.      * @param Request $request
  1288.      *
  1289.      * @return JsonResponse
  1290.      */
  1291.     public function translationAddAction(Request $request)
  1292.     {
  1293.         $sourceDocument Document::getById((int) $request->get('sourceId'));
  1294.         $targetDocument Document::getByPath($request->get('targetPath'));
  1295.         if ($sourceDocument && $targetDocument) {
  1296.             if (empty($sourceDocument->getProperty('language'))) {
  1297.                 throw new Exception(sprintf('Source Document(ID:%s) Language(Properties) missing'$sourceDocument->getId()));
  1298.             }
  1299.             if (empty($targetDocument->getProperty('language'))) {
  1300.                 throw new Exception(sprintf('Target Document(ID:%s) Language(Properties) missing'$sourceDocument->getId()));
  1301.             }
  1302.             $service = new Document\Service;
  1303.             if ($service->getTranslationSourceId($targetDocument) != $targetDocument->getId()) {
  1304.                 throw new Exception('Target Document already linked to Source Document ID('.$service->getTranslationSourceId($targetDocument).'). Please unlink existing relation first.');
  1305.             }
  1306.             $service->addTranslation($sourceDocument$targetDocument);
  1307.         }
  1308.         return $this->adminJson([
  1309.             'success' => true,
  1310.         ]);
  1311.     }
  1312.     /**
  1313.      * @Route("/translation-remove", name="pimcore_admin_document_document_translationremove", methods={"DELETE"})
  1314.      *
  1315.      * @param Request $request
  1316.      *
  1317.      * @return JsonResponse
  1318.      */
  1319.     public function translationRemoveAction(Request $request)
  1320.     {
  1321.         $sourceDocument Document::getById((int) $request->get('sourceId'));
  1322.         $targetDocument Document::getById((int) $request->get('targetId'));
  1323.         if ($sourceDocument && $targetDocument) {
  1324.             $service = new Document\Service;
  1325.             $service->removeTranslationLink($sourceDocument$targetDocument);
  1326.         }
  1327.         return $this->adminJson([
  1328.             'success' => true,
  1329.         ]);
  1330.     }
  1331.     /**
  1332.      * @Route("/translation-check-language", name="pimcore_admin_document_document_translationchecklanguage", methods={"GET"})
  1333.      *
  1334.      * @param Request $request
  1335.      *
  1336.      * @return JsonResponse
  1337.      */
  1338.     public function translationCheckLanguageAction(Request $request)
  1339.     {
  1340.         $success false;
  1341.         $language null;
  1342.         $translationLinks null;
  1343.         $document Document::getByPath($request->get('path'));
  1344.         if ($document) {
  1345.             $language $document->getProperty('language');
  1346.             if ($language) {
  1347.                 $success true;
  1348.             }
  1349.             //check if document is already linked to other langauges
  1350.             $translationLinks array_keys($this->_documentService->getTranslations($document));
  1351.         }
  1352.         return $this->adminJson([
  1353.             'success' => $success,
  1354.             'language' => $language,
  1355.             'translationLinks' => $translationLinks,
  1356.         ]);
  1357.     }
  1358.     /**
  1359.      * @param Document $document
  1360.      *
  1361.      * @return array
  1362.      */
  1363.     private function getSeoNodeConfig($document)
  1364.     {
  1365.         $nodeConfig $this->getTreeNodeConfig($document);
  1366.         if ($document instanceof Document\Page) {
  1367.             // analyze content
  1368.             $nodeConfig['prettyUrl'] = $document->getPrettyUrl();
  1369.             $title $document->getTitle();
  1370.             $description $document->getDescription();
  1371.             $nodeConfig['title'] = $title;
  1372.             $nodeConfig['description'] = $description;
  1373.             $nodeConfig['title_length'] = mb_strlen($title);
  1374.             $nodeConfig['description_length'] = mb_strlen($description);
  1375.         }
  1376.         return $nodeConfig;
  1377.     }
  1378.     /**
  1379.      * @param ControllerEvent $event
  1380.      */
  1381.     public function onKernelControllerEvent(ControllerEvent $event)
  1382.     {
  1383.         if (!$event->isMainRequest()) {
  1384.             return;
  1385.         }
  1386.         // check permissions
  1387.         $this->checkActionPermission($event'documents', ['docTypesGetAction']);
  1388.         $this->_documentService = new Document\Service($this->getAdminUser());
  1389.     }
  1390. }