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

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