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

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