vendor/sulu/sulu/src/Sulu/Component/Content/Mapper/ContentMapper.php line 295

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Sulu.
  4.  *
  5.  * (c) Sulu GmbH
  6.  *
  7.  * This source file is subject to the MIT license that is bundled
  8.  * with this source code in the file LICENSE.
  9.  */
  10. namespace Sulu\Component\Content\Mapper;
  11. use Doctrine\Common\Cache\ArrayCache;
  12. use Doctrine\Common\Cache\Cache;
  13. use Jackalope\Query\Row;
  14. use PHPCR\NodeInterface;
  15. use PHPCR\Query\QueryInterface;
  16. use PHPCR\Query\QueryResultInterface;
  17. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  18. use Sulu\Bundle\DocumentManagerBundle\Bridge\PropertyEncoder;
  19. use Sulu\Bundle\PageBundle\Admin\PageAdmin;
  20. use Sulu\Bundle\PageBundle\Document\BasePageDocument;
  21. use Sulu\Bundle\PageBundle\Document\HomeDocument;
  22. use Sulu\Bundle\SnippetBundle\Document\SnippetDocument;
  23. use Sulu\Component\Content\BreadcrumbItem;
  24. use Sulu\Component\Content\Compat\Property as LegacyProperty;
  25. use Sulu\Component\Content\Compat\Structure as LegacyStructure;
  26. use Sulu\Component\Content\Compat\StructureInterface;
  27. use Sulu\Component\Content\Compat\StructureManagerInterface;
  28. use Sulu\Component\Content\ContentTypeManager;
  29. use Sulu\Component\Content\ContentTypeManagerInterface;
  30. use Sulu\Component\Content\Document\Behavior\ExtensionBehavior;
  31. use Sulu\Component\Content\Document\Behavior\LocalizedAuthorBehavior;
  32. use Sulu\Component\Content\Document\Behavior\OrderBehavior;
  33. use Sulu\Component\Content\Document\Behavior\RedirectTypeBehavior;
  34. use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
  35. use Sulu\Component\Content\Document\Behavior\SecurityBehavior;
  36. use Sulu\Component\Content\Document\Behavior\ShadowLocaleBehavior;
  37. use Sulu\Component\Content\Document\Behavior\StructureBehavior;
  38. use Sulu\Component\Content\Document\Behavior\WorkflowStageBehavior;
  39. use Sulu\Component\Content\Document\LocalizationState;
  40. use Sulu\Component\Content\Document\RedirectType;
  41. use Sulu\Component\Content\Document\Subscriber\WorkflowStageSubscriber;
  42. use Sulu\Component\Content\Document\WorkflowStage;
  43. use Sulu\Component\Content\Exception\InvalidOrderPositionException;
  44. use Sulu\Component\Content\Exception\TranslatedNodeNotFoundException;
  45. use Sulu\Component\Content\Extension\ExtensionInterface;
  46. use Sulu\Component\Content\Extension\ExtensionManagerInterface;
  47. use Sulu\Component\Content\Mapper\Event\ContentNodeEvent;
  48. use Sulu\Component\Content\Metadata\Factory\Exception\StructureTypeNotFoundException;
  49. use Sulu\Component\Content\Types\ResourceLocator;
  50. use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
  51. use Sulu\Component\DocumentManager\Behavior\Mapping\ParentBehavior;
  52. use Sulu\Component\DocumentManager\Document\UnknownDocument;
  53. use Sulu\Component\DocumentManager\DocumentAccessor;
  54. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  55. use Sulu\Component\DocumentManager\Exception\DocumentNotFoundException;
  56. use Sulu\Component\DocumentManager\NamespaceRegistry;
  57. use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
  58. use Sulu\Component\Security\Authorization\AccessControl\AccessControlManagerInterface;
  59. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  60. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  61. use Symfony\Component\Form\FormFactoryInterface;
  62. use Symfony\Component\Security\Core\Security;
  63. /**
  64.  * Maps content nodes to phpcr nodes with content types and provides utility function to handle content nodes.
  65.  *
  66.  * @deprecated since 1.0-? use the DocumentManager instead
  67.  */
  68. class ContentMapper implements ContentMapperInterface
  69. {
  70.     /**
  71.      * @var ContentTypeManager
  72.      */
  73.     private $contentTypeManager;
  74.     /**
  75.      * @var StructureManagerInterface
  76.      */
  77.     private $structureManager;
  78.     /**
  79.      * @var ExtensionManagerInterface
  80.      */
  81.     private $extensionManager;
  82.     /**
  83.      * @var SessionManagerInterface
  84.      */
  85.     private $sessionManager;
  86.     /**
  87.      * @var EventDispatcherInterface
  88.      */
  89.     private $eventDispatcher;
  90.     /**
  91.      * @var WebspaceManagerInterface
  92.      */
  93.     private $webspaceManager;
  94.     /**
  95.      * @var Cache
  96.      */
  97.     private $extensionDataCache;
  98.     /**
  99.      * @var ResourceLocatorStrategyPoolInterface
  100.      */
  101.     private $resourceLocatorStrategyPool;
  102.     /**
  103.      * @var DocumentManagerInterface
  104.      */
  105.     private $documentManager;
  106.     /**
  107.      * @var FormFactoryInterface
  108.      */
  109.     private $formFactory;
  110.     /**
  111.      * @var DocumentInspector
  112.      */
  113.     private $inspector;
  114.     /**
  115.      * @var PropertyEncoder
  116.      */
  117.     private $encoder;
  118.     /**
  119.      * @var NamespaceRegistry
  120.      */
  121.     private $namespaceRegistry;
  122.     /**
  123.      * @var AccessControlManagerInterface
  124.      */
  125.     private $accessControlManager;
  126.     /**
  127.      * @var array
  128.      */
  129.     private $permissions;
  130.     /**
  131.      * @var ?Security
  132.      */
  133.     private $security;
  134.     public function __construct(
  135.         DocumentManagerInterface $documentManager,
  136.         WebspaceManagerInterface $webspaceManager,
  137.         FormFactoryInterface $formFactory,
  138.         DocumentInspector $inspector,
  139.         PropertyEncoder $encoder,
  140.         StructureManagerInterface $structureManager,
  141.         ExtensionManagerInterface $extensionManager,
  142.         ContentTypeManagerInterface $contentTypeManager,
  143.         SessionManagerInterface $sessionManager,
  144.         EventDispatcherInterface $eventDispatcher,
  145.         ResourceLocatorStrategyPoolInterface $resourceLocatorStrategyPool,
  146.         NamespaceRegistry $namespaceRegistry,
  147.         AccessControlManagerInterface $accessControlManager,
  148.         $permissions,
  149.         ?Security $security null
  150.     ) {
  151.         $this->contentTypeManager $contentTypeManager;
  152.         $this->structureManager $structureManager;
  153.         $this->extensionManager $extensionManager;
  154.         $this->sessionManager $sessionManager;
  155.         $this->webspaceManager $webspaceManager;
  156.         $this->documentManager $documentManager;
  157.         $this->formFactory $formFactory;
  158.         $this->inspector $inspector;
  159.         $this->encoder $encoder;
  160.         $this->namespaceRegistry $namespaceRegistry;
  161.         $this->resourceLocatorStrategyPool $resourceLocatorStrategyPool;
  162.         $this->accessControlManager $accessControlManager;
  163.         $this->permissions $permissions;
  164.         $this->security $security;
  165.         // deprecated
  166.         $this->eventDispatcher $eventDispatcher;
  167.     }
  168.     /**
  169.      * TODO: Refactor this .. this should be handled in a listener or in the form, or something
  170.      * {@inheritdoc}
  171.      */
  172.     public function saveExtension(
  173.         $uuid,
  174.         $data,
  175.         $extensionName,
  176.         $webspaceKey,
  177.         $locale,
  178.         $userId
  179.     ) {
  180.         $document $this->loadDocument(
  181.             $uuid,
  182.             $locale,
  183.             [
  184.                 'exclude_ghost' => true,
  185.             ]
  186.         );
  187.         if (null === $document) {
  188.             throw new TranslatedNodeNotFoundException($uuid$locale);
  189.         }
  190.         if (!$document instanceof ExtensionBehavior) {
  191.             throw new \RuntimeException(
  192.                 \sprintf(
  193.                     'Document of class "%s" must implement the ExtensionableBehavior if it is to be extended',
  194.                     \get_class($document)
  195.                 )
  196.             );
  197.         }
  198.         // save data of extensions
  199.         $extension $this->extensionManager->getExtension($document->getStructureType(), $extensionName);
  200.         $node $this->inspector->getNode($document);
  201.         $extension->save($node$data$webspaceKey$locale);
  202.         $extensionData $extension->load($node$webspaceKey$locale);
  203.         $document->setExtension($extension->getName(), $extensionData);
  204.         $this->documentManager->flush();
  205.         $structure $this->documentToStructure($document);
  206.         $event = new ContentNodeEvent($node$structure);
  207.         $this->eventDispatcher->dispatch($eventContentEvents::NODE_POST_SAVE);
  208.         return $structure;
  209.     }
  210.     public function loadByParent(
  211.         $uuid,
  212.         $webspaceKey,
  213.         $languageCode,
  214.         $depth 1,
  215.         $flat true,
  216.         $ignoreExceptions false,
  217.         $excludeGhosts false
  218.     ) {
  219.         $parent null;
  220.         $options = ['load_ghost_content' => true];
  221.         if ($uuid) {
  222.             $parent $this->documentManager->find($uuid$languageCode$options);
  223.         }
  224.         if (null === $parent) {
  225.             $parent $this->getContentDocument($webspaceKey$languageCode$options);
  226.         }
  227.         $fetchDepth = -1;
  228.         if (false === $flat) {
  229.             $fetchDepth $depth;
  230.         }
  231.         $children $this->inspector->getChildren($parent$options);
  232.         $children $this->documentsToStructureCollection(
  233.             $children->toArray(),
  234.             [
  235.                 'exclude_ghost' => $excludeGhosts,
  236.             ]
  237.         );
  238.         if ($flat) {
  239.             foreach ($children as $child) {
  240.                 if (null === $depth || $depth 1) {
  241.                     $childChildren $this->loadByParent(
  242.                         $child->getUuid(),
  243.                         $webspaceKey,
  244.                         $languageCode,
  245.                         $depth 1,
  246.                         $flat,
  247.                         $ignoreExceptions,
  248.                         $excludeGhosts
  249.                     );
  250.                     $children \array_merge($children$childChildren);
  251.                 }
  252.             }
  253.         }
  254.         return $children;
  255.     }
  256.     public function load($uuid$webspaceKey$locale$loadGhostContent false)
  257.     {
  258.         $document $this->documentManager->find(
  259.             $uuid,
  260.             $locale,
  261.             [
  262.                 'load_ghost_content' => $loadGhostContent,
  263.             ]
  264.         );
  265.         if ($document instanceof UnknownDocument) {
  266.             throw new DocumentNotFoundException();
  267.         }
  268.         return $this->documentToStructure($document);
  269.     }
  270.     public function loadStartPage($webspaceKey$locale)
  271.     {
  272.         $startPage $this->getContentDocument($webspaceKey$locale);
  273.         $startPage->setWorkflowStage(WorkflowStage::PUBLISHED);
  274.         $startPage->setNavigationContexts([]);
  275.         return $this->documentToStructure($startPage);
  276.     }
  277.     public function loadBySql2($sql2$locale$webspaceKey$limit null)
  278.     {
  279.         $query $this->documentManager->createQuery($sql2$locale);
  280.         if (null !== $limit) {
  281.             $query->setMaxResults($limit);
  282.         }
  283.         $documents $query->execute();
  284.         return $this->documentsToStructureCollection($documentsnull);
  285.     }
  286.     public function loadByQuery(
  287.         QueryInterface $query,
  288.         $locale,
  289.         $webspaceKey null,
  290.         $excludeGhost true,
  291.         $loadGhostContent false
  292.     ) {
  293.         $options = [
  294.             'exclude_ghost' => $excludeGhost,
  295.             'load_ghost_content' => $loadGhostContent,
  296.         ];
  297.         $documents $this->documentManager->createQuery($query$locale$options)->execute();
  298.         return $this->documentsToStructureCollection($documents$options);
  299.     }
  300.     public function loadNodeAndAncestors(
  301.         $uuid,
  302.         $locale,
  303.         $webspaceKey null,
  304.         $excludeGhost true,
  305.         $excludeShadow true
  306.     ) {
  307.         $document $this->loadDocument(
  308.             $uuid,
  309.             $locale,
  310.             $options = [
  311.                 'load_ghost_content' => true,
  312.                 'exclude_ghost' => $excludeGhost,
  313.                 'exclude_shadow' => $excludeShadow,
  314.             ],
  315.             false
  316.         );
  317.         if (null === $document) {
  318.             return [];
  319.         }
  320.         $documents = [];
  321.         if (!$this->optionsShouldExcludeDocument($document$options)) {
  322.             $documents[] = $document;
  323.         }
  324.         if ($document instanceof HomeDocument) {
  325.             return $this->documentsToStructureCollection($documents$options);
  326.         }
  327.         while ($document) {
  328.             $parentDocument $this->inspector->getParent($document);
  329.             $documents[] = $parentDocument;
  330.             if ($parentDocument instanceof HomeDocument) {
  331.                 return $this->documentsToStructureCollection($documents$options);
  332.             }
  333.             $document $parentDocument;
  334.         }
  335.         throw new \RuntimeException(
  336.             \sprintf(
  337.                 'Did not traverse an instance of HomeDocument when searching for desendants of document "%s"',
  338.                 $uuid
  339.             )
  340.         );
  341.     }
  342.     /**
  343.      * Load/hydrate a shalow structure with the given node.
  344.      * Shallow structures do not have content properties / extensions
  345.      * hydrated.
  346.      *
  347.      * @param string $localization
  348.      * @param string $webspaceKey
  349.      *
  350.      * @return StructureInterface
  351.      */
  352.     public function loadShallowStructureByNode(NodeInterface $contentNode$localization$webspaceKey)
  353.     {
  354.         $document $this->documentManager->find($contentNode->getPath(), $localization);
  355.         return $this->documentToStructure($document);
  356.     }
  357.     public function loadByNode(
  358.         NodeInterface $node,
  359.         $locale,
  360.         $webspaceKey null,
  361.         $excludeGhost true,
  362.         $loadGhostContent false,
  363.         $excludeShadow true
  364.     ) {
  365.         $document $this->loadDocument(
  366.             $node->getIdentifier(),
  367.             $locale,
  368.             [
  369.                 'load_ghost_content' => $loadGhostContent,
  370.                 'exclude_ghost' => $excludeGhost,
  371.                 'exclude_shadow' => $excludeShadow,
  372.             ]
  373.         );
  374.         return $this->documentToStructure($document);
  375.     }
  376.     public function loadBreadcrumb($uuid$locale$webspaceKey)
  377.     {
  378.         $document $this->documentManager->find($uuid$locale);
  379.         $documents = [];
  380.         $contentDocument $this->getContentDocument($webspaceKey$locale);
  381.         $contentDepth $this->inspector->getDepth($contentDocument);
  382.         $document $this->inspector->getParent($document);
  383.         $documentDepth $this->inspector->getDepth($document);
  384.         while ($document instanceof StructureBehavior && $documentDepth >= $contentDepth) {
  385.             $documents[] = $document;
  386.             $document $this->inspector->getParent($document);
  387.             $documentDepth $this->inspector->getDepth($document);
  388.         }
  389.         $items = [];
  390.         foreach ($documents as $document) {
  391.             $items[] = new BreadcrumbItem(
  392.                 $this->inspector->getDepth($document) - $contentDepth,
  393.                 $this->inspector->getUuid($document),
  394.                 $document->getTitle()
  395.             );
  396.         }
  397.         $items \array_reverse($items);
  398.         return $items;
  399.     }
  400.     public function delete($uuid$webspaceKey/*, bool $forceRemoveChildren = false*/)
  401.     {
  402.         $forceRemoveChildren \func_num_args() >= ? (bool) \func_get_arg(2) : false;
  403.         $document $this->documentManager->find($uuid);
  404.         $this->documentManager->remove($document, [
  405.             'force_remove_children' => $forceRemoveChildren,
  406.         ]);
  407.         $this->documentManager->flush();
  408.     }
  409.     public function copyLanguage(
  410.         $uuid,
  411.         $userId,
  412.         $webspaceKey,
  413.         $srcLocale,
  414.         $destLocales,
  415.         $structureType LegacyStructure::TYPE_PAGE
  416.     ) {
  417.         @trigger_deprecation(
  418.             'sulu/sulu',
  419.             '2.3',
  420.             'The ContentMapperInterface::copyLanguage method is deprecated and will be removed in the future.'
  421.             ' Use DocumentManagerInterface::copyLocale instead.'
  422.         );
  423.         if (!\is_array($destLocales)) {
  424.             $destLocales = [$destLocales];
  425.         }
  426.         $document $this->documentManager->find($uuid$srcLocale);
  427.         $resourceLocatorStrategy null;
  428.         if ($document instanceof ResourceSegmentBehavior) {
  429.             $resourceLocatorStrategy $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey($webspaceKey);
  430.         }
  431.         $parentUuid null;
  432.         if ($document instanceof ParentBehavior) {
  433.             $parentDocument $this->inspector->getParent($document);
  434.             $parentUuid $this->inspector->getUuid($parentDocument);
  435.         }
  436.         foreach ($destLocales as $destLocale) {
  437.             $destDocument $this->documentManager->find(
  438.                 $uuid,
  439.                 $destLocale
  440.             );
  441.             $destDocument->setLocale($destLocale);
  442.             $destDocument->setTitle($document->getTitle());
  443.             $destDocument->setStructureType($document->getStructureType());
  444.             $destDocument->getStructure()->bind($document->getStructure()->toArray());
  445.             if ($document instanceof WorkflowStageBehavior) {
  446.                 $documentAccessor = new DocumentAccessor($destDocument);
  447.                 $documentAccessor->set(WorkflowStageSubscriber::PUBLISHED_FIELDnull);
  448.             }
  449.             if ($document instanceof ExtensionBehavior) {
  450.                 $destDocument->setExtensionsData($document->getExtensionsData());
  451.             }
  452.             // TODO: This can be removed if RoutingAuto replaces the ResourceLocator code.
  453.             if ($destDocument instanceof ResourceSegmentBehavior) {
  454.                 $resourceLocator $resourceLocatorStrategy->generate(
  455.                     $destDocument->getTitle(),
  456.                     $parentUuid,
  457.                     $webspaceKey,
  458.                     $destLocale
  459.                 );
  460.                 $destDocument->setResourceSegment($resourceLocator);
  461.             }
  462.             $this->documentManager->persist($destDocument$destLocale, ['omit_modified_domain_event' => true]);
  463.         }
  464.         $this->documentManager->flush();
  465.         return $this->documentToStructure($document);
  466.     }
  467.     public function orderBefore($uuid$beforeUuid$userId$webspaceKey$locale)
  468.     {
  469.         $document $this->documentManager->find($uuid$locale);
  470.         $this->documentManager->reorder($document$beforeUuid);
  471.         $this->documentManager->persist(
  472.             $document,
  473.             $locale,
  474.             [
  475.                 'user' => $userId,
  476.             ]
  477.         );
  478.         return $this->documentToStructure($document);
  479.     }
  480.     /**
  481.      * TODO: Move this logic to the DocumentManager
  482.      * {@inheritdoc}
  483.      */
  484.     public function orderAt($uuid$position$userId$webspaceKey$locale)
  485.     {
  486.         $document $this->documentManager->find($uuid$locale);
  487.         $parentDocument $this->inspector->getParent($document);
  488.         $siblingDocuments $this->inspector->getChildren($parentDocument);
  489.         $siblings \array_values($siblingDocuments->toArray()); // get indexed array
  490.         $countSiblings \count($siblings);
  491.         $currentPosition \array_search($document$siblings) + 1;
  492.         if ($countSiblings $position || $position <= 0) {
  493.             throw new InvalidOrderPositionException(
  494.                 \sprintf(
  495.                     'Cannot order node "%s" at out-of-range position "%s", must be >= 0 && < %d"',
  496.                     $this->inspector->getPath($document),
  497.                     $position,
  498.                     $countSiblings
  499.                 )
  500.             );
  501.         }
  502.         if ($position === $countSiblings) {
  503.             // move to the end
  504.             $this->documentManager->reorder($documentnull);
  505.         } else {
  506.             if ($currentPosition $position) {
  507.                 $targetSibling $siblings[$position];
  508.                 $this->documentManager->reorder($document$targetSibling->getUuid());
  509.             } elseif ($currentPosition $position) {
  510.                 $targetSibling $siblings[$position 1];
  511.                 $this->documentManager->reorder($document$targetSibling->getUuid());
  512.             }
  513.         }
  514.         $this->documentManager->flush();
  515.         return $this->documentToStructure($document);
  516.     }
  517.     /**
  518.      * Return the resource locator content type.
  519.      *
  520.      * @return ResourceLocator
  521.      */
  522.     public function getResourceLocator()
  523.     {
  524.         return $this->contentTypeManager->get('resource_locator');
  525.     }
  526.     /**
  527.      * Return the content document (aka the home page).
  528.      *
  529.      * @param string $webspaceKey
  530.      *
  531.      * @return BasePageDocument|SnippetDocument
  532.      */
  533.     private function getContentDocument($webspaceKey$locale, array $options = [])
  534.     {
  535.         return $this->documentManager->find(
  536.             $this->sessionManager->getContentPath($webspaceKey),
  537.             $locale,
  538.             $options
  539.         );
  540.     }
  541.     /**
  542.      * Return the node in the content repository which contains all of the routes.
  543.      *
  544.      * @param string $webspaceKey
  545.      * @param string $locale
  546.      * @param string $segment
  547.      *
  548.      * @return NodeInterface
  549.      */
  550.     protected function getRootRouteNode($webspaceKey$locale$segment)
  551.     {
  552.         return $this->documentManager->find(
  553.             $this->sessionManager->getRoutePath($webspaceKey$locale$segment)
  554.         );
  555.     }
  556.     public function convertQueryResultToArray(
  557.         QueryResultInterface $queryResult,
  558.         $webspaceKey,
  559.         $locales,
  560.         $fields,
  561.         $maxDepth,
  562.         $onlyPublished true,
  563.         $permission null
  564.     ) {
  565.         $rootDepth \substr_count($this->sessionManager->getContentPath($webspaceKey), '/');
  566.         $result = [];
  567.         foreach ($locales as $locale) {
  568.             foreach ($queryResult->getRows() as $row) {
  569.                 $pageDepth \substr_count($row->getPath('page'), '/') - $rootDepth;
  570.                 if (null === $maxDepth || $maxDepth || ($maxDepth && $pageDepth <= $maxDepth)) {
  571.                     $item $this->rowToArray($row$locale$webspaceKey$fields$onlyPublished$permission);
  572.                     if (false === $item || \in_array($item$result)) {
  573.                         continue;
  574.                     }
  575.                     $result[] = $item;
  576.                 }
  577.             }
  578.         }
  579.         return $result;
  580.     }
  581.     /**
  582.      * converts a query row to an array.
  583.      */
  584.     private function rowToArray(
  585.         Row $row,
  586.         $locale,
  587.         $webspaceKey,
  588.         $fields,
  589.         $onlyPublished true,
  590.         $permission null
  591.     ) {
  592.         // reset cache
  593.         $this->initializeExtensionCache();
  594.         $templateName $this->encoder->localizedSystemName('template'$locale);
  595.         // check and determine shadow-nodes
  596.         $node $row->getNode('page');
  597.         try {
  598.             $document $this->documentManager->find($node->getIdentifier(), $locale);
  599.         } catch (StructureTypeNotFoundException $e) {
  600.             return false;
  601.         }
  602.         $originalDocument $document;
  603.         if (!$node->hasProperty($templateName) && !$node->hasProperty('template')) {
  604.             return false;
  605.         }
  606.         if ($document instanceof SecurityBehavior
  607.             && $this->security
  608.             && $permission
  609.         ) {
  610.             $permissionKey \array_search($permission$this->permissions);
  611.             $documentWebspaceKey $document->getWebspaceName();
  612.             $documentWebspace $this->webspaceManager->findWebspaceByKey($documentWebspaceKey);
  613.             if ($documentWebspace && $documentWebspace->hasWebsiteSecurity()) {
  614.                 $documentWebspaceSecurity $documentWebspace->getSecurity();
  615.                 $permissions $this->accessControlManager->getUserPermissionByArray(
  616.                     $document->getLocale(),
  617.                     PageAdmin::SECURITY_CONTEXT_PREFIX $documentWebspaceKey,
  618.                     $document->getPermissions(),
  619.                     $this->security->getUser(),
  620.                     $documentWebspaceSecurity->getSystem()
  621.                 );
  622.                 if (isset($permissions[$permissionKey]) && !$permissions[$permissionKey]) {
  623.                     return false;
  624.                 }
  625.             }
  626.         }
  627.         $redirectType null;
  628.         $url null;
  629.         if ($document instanceof RedirectTypeBehavior) {
  630.             $redirectType $document->getRedirectType();
  631.             if (RedirectType::INTERNAL === $redirectType) {
  632.                 $target $document->getRedirectTarget();
  633.                 if (!$target instanceof ResourceSegmentBehavior) {
  634.                     return false;
  635.                 }
  636.                 $url $target->getResourceSegment();
  637.                 $document $target;
  638.                 $node $this->inspector->getNode($document);
  639.             } elseif (RedirectType::EXTERNAL === $redirectType) {
  640.                 $url $document->getRedirectExternal();
  641.             }
  642.         }
  643.         $originLocale $locale;
  644.         if ($document instanceof ShadowLocaleBehavior) {
  645.             $locale $document->isShadowLocaleEnabled() ? $document->getShadowLocale() : $originLocale;
  646.         }
  647.         $nodeState null;
  648.         if ($document instanceof WorkflowStageBehavior) {
  649.             $nodeState $document->getWorkflowStage();
  650.         }
  651.         // if page is not piblished ignore it
  652.         if ($onlyPublished && WorkflowStage::PUBLISHED !== $nodeState) {
  653.             return false;
  654.         }
  655.         if ($document instanceof ResourceSegmentBehavior && !isset($url)) {
  656.             $url $document->getResourceSegment();
  657.         }
  658.         // generate field data
  659.         $fieldsData $this->getFieldsData(
  660.             $row,
  661.             $node,
  662.             $document,
  663.             $fields[$originLocale],
  664.             $document->getStructureType(),
  665.             $webspaceKey,
  666.             $locale
  667.         );
  668.         $structureType $document->getStructureType();
  669.         $shortPath $this->inspector->getContentPath($originalDocument);
  670.         $documentData = [
  671.             'id' => $originalDocument->getUuid(),
  672.             'uuid' => $originalDocument->getUuid(),
  673.             'nodeType' => $redirectType,
  674.             'path' => $shortPath,
  675.             'changed' => $document->getChanged(),
  676.             'changer' => $document->getChanger(),
  677.             'created' => $document->getCreated(),
  678.             'publishedState' => WorkflowStage::PUBLISHED === $nodeState,
  679.             'published' => $document->getPublished(),
  680.             'creator' => $document->getCreator(),
  681.             'title' => $originalDocument->getTitle(),
  682.             'locale' => $locale,
  683.             'webspaceKey' => $this->inspector->getWebspace($document),
  684.             'template' => $structureType,
  685.             'parent' => $this->inspector->getParent($document)->getUuid(),
  686.         ];
  687.         if (isset($url)) {
  688.             $documentData['url'] = $url;
  689.             $documentData['urls'] = $this->inspector->getLocalizedUrlsForPage($document);
  690.         }
  691.         if ($document instanceof LocalizedAuthorBehavior) {
  692.             $documentData['author'] = $document->getAuthor();
  693.             $documentData['authored'] = $document->getAuthored();
  694.         }
  695.         if ($document instanceof OrderBehavior) {
  696.             $documentData['order'] = $document->getSuluOrder();
  697.         }
  698.         return \array_merge($documentData$fieldsData);
  699.     }
  700.     /**
  701.      * Return extracted data (configured by fields array) from node.
  702.      */
  703.     private function getFieldsData(
  704.         Row $row,
  705.         NodeInterface $node,
  706.         $document,
  707.         $fields,
  708.         $templateKey,
  709.         $webspaceKey,
  710.         $locale
  711.     ) {
  712.         $fieldsData = [];
  713.         foreach ($fields as $field) {
  714.             // determine target for data in result array
  715.             if (isset($field['target'])) {
  716.                 if (!isset($fieldsData[$field['target']])) {
  717.                     $fieldsData[$field['target']] = [];
  718.                 }
  719.                 $target = &$fieldsData[$field['target']];
  720.             } else {
  721.                 $target = &$fieldsData;
  722.             }
  723.             // create target
  724.             if (!isset($target[$field['name']])) {
  725.                 $target[$field['name']] = '';
  726.             }
  727.             if (null !== ($data $this->getFieldData(
  728.                 $field,
  729.                 $row,
  730.                 $node,
  731.                 $document,
  732.                 $templateKey,
  733.                 $webspaceKey,
  734.                 $locale
  735.             ))
  736.             ) {
  737.                 $target[$field['name']] = $data;
  738.             }
  739.         }
  740.         return $fieldsData;
  741.     }
  742.     /**
  743.      * Return data for one field.
  744.      */
  745.     private function getFieldData($fieldRow $rowNodeInterface $node$document$templateKey$webspaceKey$locale)
  746.     {
  747.         if (isset($field['column'])) {
  748.             // normal data from node property
  749.             return $row->getValue($field['column']);
  750.         } elseif (isset($field['extension'])) {
  751.             // data from extension
  752.             return $this->getExtensionData(
  753.                 $node,
  754.                 $field['extension'],
  755.                 $field['property'],
  756.                 $webspaceKey,
  757.                 $locale
  758.             );
  759.         } elseif (isset($field['property'])
  760.             && (!isset($field['templateKey']) || $field['templateKey'] === $templateKey)
  761.         ) {
  762.             // not extension data but property of node
  763.             return $this->getPropertyData($document$field['property']);
  764.         }
  765.         return;
  766.     }
  767.     /**
  768.      * Returns data for property.
  769.      */
  770.     private function getPropertyData($documentLegacyProperty $property)
  771.     {
  772.         return $document->getStructure()->getContentViewProperty($property->getName())->getValue();
  773.     }
  774.     /**
  775.      * Returns data for extension and property name.
  776.      */
  777.     private function getExtensionData(
  778.         NodeInterface $node,
  779.         ExtensionInterface $extension,
  780.         $propertyName,
  781.         $webspaceKey,
  782.         $locale
  783.     ) {
  784.         // extension data: load ones
  785.         if (!$this->extensionDataCache->contains($extension->getName())) {
  786.             $this->extensionDataCache->save(
  787.                 $extension->getName(),
  788.                 $this->loadExtensionData(
  789.                     $node,
  790.                     $extension,
  791.                     $webspaceKey,
  792.                     $locale
  793.                 )
  794.             );
  795.         }
  796.         // get extension data from cache
  797.         $data $this->extensionDataCache->fetch($extension->getName());
  798.         return isset($data[$propertyName]) ? $data[$propertyName] : null;
  799.     }
  800.     /**
  801.      * load data from extension.
  802.      */
  803.     private function loadExtensionData(NodeInterface $nodeExtensionInterface $extension$webspaceKey$locale)
  804.     {
  805.         $extension->setLanguageCode($locale$this->namespaceRegistry->getPrefix('extension_localized'), '');
  806.         $data $extension->load(
  807.             $node,
  808.             $webspaceKey,
  809.             $locale
  810.         );
  811.         return $extension->getContentData($data);
  812.     }
  813.     private function loadDocument($pathOrUuid$locale$options$shouldExclude true)
  814.     {
  815.         $document $this->documentManager->find(
  816.             $pathOrUuid,
  817.             $locale,
  818.             [
  819.                 'load_ghost_content' => isset($options['load_ghost_content']) ? $options['load_ghost_content'] : true,
  820.             ]
  821.         );
  822.         if ($shouldExclude && $this->optionsShouldExcludeDocument($document$options)) {
  823.             return;
  824.         }
  825.         return $document;
  826.     }
  827.     private function optionsShouldExcludeDocument($document, ?array $options null)
  828.     {
  829.         if (null === $options) {
  830.             return false;
  831.         }
  832.         $options \array_merge(
  833.             [
  834.                 'exclude_ghost' => true,
  835.                 'exclude_shadow' => true,
  836.             ],
  837.             $options
  838.         );
  839.         $state $this->inspector->getLocalizationState($document);
  840.         if ($options['exclude_ghost'] && LocalizationState::GHOST == $state) {
  841.             return true;
  842.         }
  843.         if ($options['exclude_ghost'] && $options['exclude_shadow'] && LocalizationState::SHADOW == $state) {
  844.             return true;
  845.         }
  846.         return false;
  847.     }
  848.     /**
  849.      * Initializes cache of extension data.
  850.      */
  851.     private function initializeExtensionCache()
  852.     {
  853.         $this->extensionDataCache = new ArrayCache();
  854.     }
  855.     /**
  856.      * Return a structure bridge corresponding to the given document.
  857.      *
  858.      * @param StructureBehavior $document
  859.      *
  860.      * @return StructureInterface
  861.      */
  862.     private function documentToStructure($document)
  863.     {
  864.         if (null === $document) {
  865.             return;
  866.         }
  867.         $structure $this->inspector->getStructureMetadata($document);
  868.         $documentAlias $this->inspector->getMetadata($document)->getAlias();
  869.         $structureBridge $this->structureManager->wrapStructure($documentAlias$structure);
  870.         $structureBridge->setDocument($document);
  871.         return $structureBridge;
  872.     }
  873.     /**
  874.      * Return a collection of structures for the given documents, optionally filtering according
  875.      * to the given options (as defined in optionsShouldExcludeDocument).
  876.      *
  877.      * @param object[] $documents
  878.      * @param array|null $filterOptions
  879.      */
  880.     private function documentsToStructureCollection($documents$filterOptions null)
  881.     {
  882.         $collection = [];
  883.         foreach ($documents as $document) {
  884.             if (!$document instanceof StructureBehavior) {
  885.                 continue;
  886.             }
  887.             if ($this->optionsShouldExcludeDocument($document$filterOptions)) {
  888.                 continue;
  889.             }
  890.             $collection[] = $this->documentToStructure($document);
  891.         }
  892.         return $collection;
  893.     }
  894. }