vendor/sulu/sulu/src/Sulu/Bundle/PageBundle/Repository/NodeRepository.php line 38

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\Bundle\PageBundle\Repository;
  11. use PHPCR\RepositoryException;
  12. use Sulu\Bundle\AdminBundle\UserManager\UserManagerInterface;
  13. use Sulu\Bundle\PageBundle\Content\PageSelectionContainer;
  14. use Sulu\Component\Content\Compat\StructureInterface;
  15. use Sulu\Component\Content\Document\Behavior\SecurityBehavior;
  16. use Sulu\Component\Content\Exception\InvalidOrderPositionException;
  17. use Sulu\Component\Content\Mapper\ContentMapperInterface;
  18. use Sulu\Component\Content\Query\ContentQueryBuilderInterface;
  19. use Sulu\Component\Content\Query\ContentQueryExecutorInterface;
  20. use Sulu\Component\Content\Repository\ContentRepository;
  21. use Sulu\Component\DocumentManager\Exception\DocumentManagerException;
  22. use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
  23. use Sulu\Component\Rest\Exception\RestException;
  24. use Sulu\Component\Security\Authorization\AccessControl\AccessControlManagerInterface;
  25. use Sulu\Component\Security\Authorization\SecurityCondition;
  26. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  27. use Sulu\Component\Webspace\Webspace;
  28. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  29. @trigger_deprecation(
  30.     'sulu/sulu',
  31.     '2.0',
  32.     'The "%s" class is deprecated, use data from "%s" instead.',
  33.     NodeRepository::class,
  34.     ContentRepository::class
  35. );
  36. /**
  37.  * repository for node objects.
  38.  *
  39.  * @deprecated
  40.  */
  41. class NodeRepository implements NodeRepositoryInterface
  42. {
  43.     /**
  44.      * @var ContentMapperInterface
  45.      */
  46.     private $mapper;
  47.     /**
  48.      * @var SessionManagerInterface
  49.      */
  50.     private $sessionManager;
  51.     /**
  52.      * for returning self link in get action.
  53.      *
  54.      * @var string
  55.      */
  56.     private $apiBasePath '/admin/api/nodes';
  57.     /**
  58.      * @var UserManagerInterface
  59.      */
  60.     private $userManager;
  61.     /**
  62.      * @var WebspaceManagerInterface
  63.      */
  64.     private $webspaceManager;
  65.     /**
  66.      * @var ContentQueryBuilderInterface
  67.      */
  68.     private $queryBuilder;
  69.     /**
  70.      * @var ContentQueryExecutorInterface
  71.      */
  72.     private $queryExecutor;
  73.     /**
  74.      * @var AccessControlManagerInterface
  75.      */
  76.     private $accessControlManager;
  77.     /**
  78.      * @var TokenStorageInterface
  79.      */
  80.     private $tokenStorage;
  81.     public function __construct(
  82.         ContentMapperInterface $mapper,
  83.         SessionManagerInterface $sessionManager,
  84.         UserManagerInterface $userManager,
  85.         WebspaceManagerInterface $webspaceManager,
  86.         ContentQueryBuilderInterface $queryBuilder,
  87.         ContentQueryExecutorInterface $queryExecutor,
  88.         AccessControlManagerInterface $accessControlManager,
  89.         ?TokenStorageInterface $tokenStorage null
  90.     ) {
  91.         $this->mapper $mapper;
  92.         $this->sessionManager $sessionManager;
  93.         $this->userManager $userManager;
  94.         $this->webspaceManager $webspaceManager;
  95.         $this->queryBuilder $queryBuilder;
  96.         $this->queryExecutor $queryExecutor;
  97.         $this->accessControlManager $accessControlManager;
  98.         $this->tokenStorage $tokenStorage;
  99.     }
  100.     /**
  101.      * return content mapper.
  102.      *
  103.      * @return ContentMapperInterface
  104.      */
  105.     protected function getMapper()
  106.     {
  107.         return $this->mapper;
  108.     }
  109.     /**
  110.      * returns user fullName.
  111.      *
  112.      * @param int $id userId
  113.      *
  114.      * @return string
  115.      */
  116.     protected function getFullNameByUserId($id)
  117.     {
  118.         return $this->userManager->getFullNameByUserId($id);
  119.     }
  120.     /**
  121.      * returns finished Node (with _links and _embedded).
  122.      *
  123.      * @param string $webspaceKey
  124.      * @param string $languageCode
  125.      * @param int $depth
  126.      * @param bool $complete
  127.      * @param bool $excludeGhosts
  128.      * @param string|null $extension
  129.      *
  130.      * @return array{
  131.      *     _embedded: array{
  132.      *         pages: array
  133.      *     },
  134.      *     _links: array{
  135.      *         self: array{
  136.      *             href: string
  137.      *         },
  138.      *         children: array{
  139.      *             href: string
  140.      *         },
  141.      *     },
  142.      *     _permissions?: mixed[],
  143.      * }
  144.      *
  145.      * @deprecated This part should be split into a serialization handler and using the hateoas bundle
  146.      */
  147.     protected function prepareNode(
  148.         StructureInterface $structure,
  149.         $webspaceKey,
  150.         $languageCode,
  151.         $depth 1,
  152.         $complete true,
  153.         $excludeGhosts false,
  154.         $extension null
  155.     ) {
  156.         $result $structure->toArray($complete);
  157.         // add default embedded property with empty nodes array
  158.         $result['_embedded'] = [];
  159.         $result['_embedded']['pages'] = [];
  160.         // add api links
  161.         $result['_links'] = [
  162.             'self' => [
  163.                 'href' => $this->apiBasePath '/' $structure->getUuid() .
  164.                     (null !== $extension '/' $extension ''),
  165.             ],
  166.             'children' => [
  167.                 'href' => $this->apiBasePath '?parentId=' $structure->getUuid() . '&depth=' $depth .
  168.                     '&webspace=' $webspaceKey '&language=' $languageCode .
  169.                     (true === $excludeGhosts '&exclude-ghosts=true' ''),
  170.             ],
  171.         ];
  172.         if ($this->tokenStorage && ($token $this->tokenStorage->getToken())) {
  173.             $result['_permissions'] = $this->accessControlManager->getUserPermissions(
  174.                 new SecurityCondition(
  175.                     'sulu.webspaces.' $webspaceKey,
  176.                     $languageCode,
  177.                     SecurityBehavior::class,
  178.                     $structure->getUuid()
  179.                 ),
  180.                 $token->getUser()
  181.             );
  182.         }
  183.         return $result;
  184.     }
  185.     /**
  186.      * @param string $apiBasePath
  187.      */
  188.     public function setApiBasePath($apiBasePath)
  189.     {
  190.         $this->apiBasePath $apiBasePath;
  191.     }
  192.     public function getNode(
  193.         $uuid,
  194.         $webspaceKey,
  195.         $languageCode,
  196.         $breadcrumb false,
  197.         $complete true,
  198.         $loadGhostContent false
  199.     ) {
  200.         $structure $this->getMapper()->load($uuid$webspaceKey$languageCode$loadGhostContent);
  201.         $result $this->prepareNode($structure$webspaceKey$languageCode1$complete);
  202.         if ($breadcrumb) {
  203.             $breadcrumb $this->getMapper()->loadBreadcrumb($uuid$languageCode$webspaceKey);
  204.             $result['breadcrumb'] = [];
  205.             foreach ($breadcrumb as $item) {
  206.                 $result['breadcrumb'][$item->getDepth()] = $item->toArray();
  207.             }
  208.         }
  209.         return $result;
  210.     }
  211.     public function getIndexNode($webspaceKey$languageCode)
  212.     {
  213.         $structure $this->getMapper()->loadStartPage($webspaceKey$languageCode);
  214.         return $this->prepareNode($structure$webspaceKey$languageCode);
  215.     }
  216.     public function getReferences($uuid)
  217.     {
  218.         $session $this->sessionManager->getSession();
  219.         $node $session->getNodeByIdentifier($uuid);
  220.         return \iterator_to_array($node->getReferences());
  221.     }
  222.     public function getNodes(
  223.         $parent,
  224.         $webspaceKey,
  225.         $languageCode,
  226.         $depth 1,
  227.         $flat true,
  228.         $complete true,
  229.         $excludeGhosts false
  230.     ) {
  231.         $nodes $this->getMapper()->loadByParent(
  232.             $parent,
  233.             $webspaceKey,
  234.             $languageCode,
  235.             $depth,
  236.             $flat,
  237.             false,
  238.             $excludeGhosts
  239.         );
  240.         $parentNode $this->getParentNode($parent$webspaceKey$languageCode);
  241.         $result $this->prepareNode($parentNode$webspaceKey$languageCode1$complete$excludeGhosts);
  242.         $result['_embedded']['pages'] = $this->prepareNodesTree(
  243.             $nodes,
  244.             $webspaceKey,
  245.             $languageCode,
  246.             $complete,
  247.             $excludeGhosts,
  248.             $flat $depth
  249.         );
  250.         $result['total'] = \count($result['_embedded']['pages']);
  251.         return $result;
  252.     }
  253.     public function getNodesByIds(
  254.         $ids,
  255.         $webspaceKey,
  256.         $languageCode
  257.     ) {
  258.         $result = [];
  259.         $idString '';
  260.         if (!empty($ids)) {
  261.             $container = new PageSelectionContainer(
  262.                 $ids,
  263.                 $this->queryExecutor,
  264.                 $this->queryBuilder,
  265.                 [],
  266.                 $webspaceKey,
  267.                 $languageCode,
  268.                 true
  269.             );
  270.             $result $container->getData();
  271.             $idString \implode(','$ids);
  272.         }
  273.         return [
  274.             '_embedded' => [
  275.                 'pages' => $result,
  276.             ],
  277.             'total' => \count($result),
  278.             '_links' => [
  279.                 'self' => ['href' => $this->apiBasePath '?ids=' $idString],
  280.             ],
  281.         ];
  282.     }
  283.     public function getWebspaceNode(
  284.         $webspaceKey,
  285.         $languageCode,
  286.         $depth 1,
  287.         $excludeGhosts false
  288.     ) {
  289.         // init result
  290.         $data = [];
  291.         // add default empty embedded property
  292.         $data['_embedded'] = [
  293.             'pages' => [$this->createWebspaceNode($webspaceKey$languageCode$depth$excludeGhosts)],
  294.         ];
  295.         // add api links
  296.         $data['_links'] = [
  297.             'self' => [
  298.                 'href' => $this->apiBasePath '/entry?depth=' $depth '&webspace=' $webspaceKey .
  299.                     '&language=' $languageCode . (true === $excludeGhosts '&exclude-ghosts=true' ''),
  300.             ],
  301.         ];
  302.         return $data;
  303.     }
  304.     public function getWebspaceNodes($languageCode)
  305.     {
  306.         // init result
  307.         $data = ['_embedded' => ['pages' => []]];
  308.         /** @var Webspace $webspace */
  309.         foreach ($this->webspaceManager->getWebspaceCollection() as $webspace) {
  310.             $data['_embedded']['pages'][] = $this->createWebspaceNode($webspace->getKey(), $languageCode0);
  311.         }
  312.         // add api links
  313.         $data['_links'] = [
  314.             'self' => [
  315.                 'href' => $this->apiBasePath '/entry?language=' $languageCode,
  316.             ],
  317.         ];
  318.         return $data;
  319.     }
  320.     /**
  321.      * Creates a webspace node.
  322.      *
  323.      * @return array{
  324.      *     id: string,
  325.      *     path: string,
  326.      *     title: string,
  327.      *     hasSub: true,
  328.      *     publishedState: true,
  329.      *     _embedded: array<string, mixed>,
  330.      *     _links: array{
  331.      *         children: array{
  332.      *             href: string
  333.      *         },
  334.      *     },
  335.      * }
  336.      */
  337.     private function createWebspaceNode(
  338.         $webspaceKey,
  339.         $languageCode,
  340.         $depth 1,
  341.         $excludeGhosts false
  342.     ) {
  343.         $webspace $this->webspaceManager->getWebspaceCollection()->getWebspace($webspaceKey);
  344.         if ($depth 0) {
  345.             $nodes $this->getMapper()->loadByParent(
  346.                 null,
  347.                 $webspaceKey,
  348.                 $languageCode,
  349.                 $depth,
  350.                 false,
  351.                 false,
  352.                 $excludeGhosts
  353.             );
  354.             $embedded $this->prepareNodesTree($nodes$webspaceKey$languageCodetrue$excludeGhosts$depth);
  355.         } else {
  356.             $embedded = [];
  357.         }
  358.         return [
  359.             'id' => $this->sessionManager->getContentNode($webspace->getKey())->getIdentifier(),
  360.             'path' => '/',
  361.             'title' => $webspace->getName(),
  362.             'hasSub' => true,
  363.             'publishedState' => true,
  364.             '_embedded' => $embedded,
  365.             '_links' => [
  366.                 'children' => [
  367.                     'href' => $this->apiBasePath '?depth=' $depth '&webspace=' $webspaceKey .
  368.                         '&language=' $languageCode . (true === $excludeGhosts '&exclude-ghosts=true' ''),
  369.                 ],
  370.             ],
  371.         ];
  372.     }
  373.     public function getFilteredNodes(
  374.         array $filterConfig,
  375.         $languageCode,
  376.         $webspaceKey,
  377.         $preview false,
  378.         $api false,
  379.         $exclude = []
  380.     ) {
  381.         $limit = isset($filterConfig['limitResult']) ? $filterConfig['limitResult'] : null;
  382.         $initParams = ['config' => $filterConfig];
  383.         if ($exclude) {
  384.             $initParams['excluded'] = $exclude;
  385.         }
  386.         $this->queryBuilder->init($initParams);
  387.         $data $this->queryExecutor->execute(
  388.             $webspaceKey,
  389.             [$languageCode],
  390.             $this->queryBuilder,
  391.             true,
  392.             -1,
  393.             $limit,
  394.             null,
  395.             false
  396.         );
  397.         if ($api) {
  398.             if (isset($filterConfig['dataSource'])) {
  399.                 if (null !== $this->webspaceManager->findWebspaceByKey($filterConfig['dataSource'])) {
  400.                     $node $this->sessionManager->getContentNode($filterConfig['dataSource']);
  401.                 } else {
  402.                     $node $this->sessionManager->getSession()->getNodeByIdentifier($filterConfig['dataSource']);
  403.                 }
  404.             } else {
  405.                 $node $this->sessionManager->getContentNode($webspaceKey);
  406.             }
  407.             $parentNode $this->getParentNode($node->getIdentifier(), $webspaceKey$languageCode);
  408.             $result $this->prepareNode($parentNode$webspaceKey$languageCode1false);
  409.             $result['_embedded']['pages'] = $data;
  410.             $result['total'] = \count($result['_embedded']['pages']);
  411.         } else {
  412.             $result $data;
  413.         }
  414.         return $result;
  415.     }
  416.     /**
  417.      * if parent is null return home page else the page with given uuid.
  418.      *
  419.      * @param string|null $parent uuid of parent node
  420.      * @param string $webspaceKey
  421.      * @param string $languageCode
  422.      *
  423.      * @return StructureInterface
  424.      */
  425.     private function getParentNode($parent$webspaceKey$languageCode)
  426.     {
  427.         if (null != $parent) {
  428.             return $this->getMapper()->load($parent$webspaceKey$languageCode);
  429.         } else {
  430.             return $this->getMapper()->loadStartPage($webspaceKey$languageCode);
  431.         }
  432.     }
  433.     /**
  434.      * @param StructureInterface[] $nodes
  435.      * @param string $webspaceKey
  436.      * @param string $languageCode
  437.      * @param bool $complete
  438.      * @param bool $excludeGhosts
  439.      *
  440.      * @return array
  441.      */
  442.     private function prepareNodesTree(
  443.         $nodes,
  444.         $webspaceKey,
  445.         $languageCode,
  446.         $complete true,
  447.         $excludeGhosts false,
  448.         $maxDepth 1,
  449.         $currentDepth 0
  450.     ) {
  451.         ++$currentDepth;
  452.         $results = [];
  453.         foreach ($nodes as $node) {
  454.             $result $this->prepareNode($node$webspaceKey$languageCode1$complete$excludeGhosts);
  455.             if (null !== $maxDepth
  456.                 && $currentDepth $maxDepth
  457.                 && $node->getHasChildren()
  458.                 && null != $node->getChildren()
  459.             ) {
  460.                 $result['_embedded']['pages'] = $this->prepareNodesTree(
  461.                     $node->getChildren(),
  462.                     $webspaceKey,
  463.                     $languageCode,
  464.                     $complete,
  465.                     $excludeGhosts,
  466.                     $maxDepth,
  467.                     $currentDepth
  468.                 );
  469.             }
  470.             $results[] = $result;
  471.         }
  472.         return $results;
  473.     }
  474.     public function getNodesTree(
  475.         $uuid,
  476.         $webspaceKey,
  477.         $languageCode,
  478.         $excludeGhosts false,
  479.         $excludeShadows false
  480.     ) {
  481.         $nodes $this->loadNodeAndAncestors($uuid$webspaceKey$languageCode$excludeGhosts$excludeShadowstrue);
  482.         $result = [
  483.             '_embedded' => [
  484.                 'pages' => $nodes,
  485.             ],
  486.         ];
  487.         if ($this->tokenStorage && ($token $this->tokenStorage->getToken())) {
  488.             $result['_permissions'] = $this->accessControlManager->getUserPermissions(
  489.                 new SecurityCondition(
  490.                     'sulu.webspaces.' $webspaceKey
  491.                 ),
  492.                 $token->getUser()
  493.             );
  494.         }
  495.         // add api links
  496.         $result['_links'] = [
  497.             'self' => [
  498.                 'href' => $this->apiBasePath '/tree?uuid=' $uuid '&webspace=' $webspaceKey '&language=' .
  499.                     $languageCode . (true === $excludeGhosts '&exclude-ghosts=true' ''),
  500.             ],
  501.         ];
  502.         return $result;
  503.     }
  504.     /**
  505.      * Load the node and its ancestors and convert them into a HATEOAS representation.
  506.      *
  507.      * @param string $uuid
  508.      * @param string $webspaceKey
  509.      * @param string $locale
  510.      * @param bool $excludeGhosts
  511.      * @param bool $excludeShadows
  512.      * @param bool $complete
  513.      *
  514.      * @return array
  515.      */
  516.     private function loadNodeAndAncestors($uuid$webspaceKey$locale$excludeGhosts$excludeShadows$complete)
  517.     {
  518.         $descendants $this->getMapper()->loadNodeAndAncestors(
  519.             $uuid,
  520.             $locale,
  521.             $webspaceKey,
  522.             $excludeGhosts,
  523.             $excludeShadows
  524.         );
  525.         $descendants \array_reverse($descendants);
  526.         $childTiers = [];
  527.         foreach ($descendants as $descendant) {
  528.             foreach ($descendant->getChildren() as $child) {
  529.                 $type $child->getType();
  530.                 if ($excludeShadows && null !== $type && 'shadow' === $type->getName()) {
  531.                     continue;
  532.                 }
  533.                 if ($excludeGhosts && null !== $type && 'ghost' === $type->getName()) {
  534.                     continue;
  535.                 }
  536.                 if (!isset($childTiers[$descendant->getUuid()])) {
  537.                     $childTiers[$descendant->getUuid()] = [];
  538.                 }
  539.                 $childTiers[$descendant->getUuid()][] = $this->prepareNode(
  540.                     $child,
  541.                     $webspaceKey,
  542.                     $locale,
  543.                     1,
  544.                     $complete,
  545.                     $excludeGhosts
  546.                 );
  547.             }
  548.         }
  549.         $result \array_shift($childTiers);
  550.         $this->iterateTiers($childTiers$result);
  551.         return $result;
  552.     }
  553.     /**
  554.      * Iterate over the ancestor tiers and build up the result.
  555.      *
  556.      * @param array $tiers
  557.      * @param array $result (by rereference)
  558.      */
  559.     private function iterateTiers($tiers, &$result)
  560.     {
  561.         \reset($tiers);
  562.         $uuid \key($tiers);
  563.         $tier \array_shift($tiers);
  564.         $found false;
  565.         $node null;
  566.         if (\is_array($result)) {
  567.             foreach ($result as &$node) {
  568.                 if ($node['id'] === $uuid) {
  569.                     $node['_embedded']['pages'] = $tier;
  570.                     $found true;
  571.                     break;
  572.                 }
  573.             }
  574.         }
  575.         if (!$tiers) {
  576.             return;
  577.         }
  578.         if (!$found) {
  579.             throw new \RuntimeException(
  580.                 \sprintf(
  581.                     'Could not find target node in with UUID "%s" in tier. This should not happen.',
  582.                     $uuid
  583.                 )
  584.             );
  585.         }
  586.         $this->iterateTiers($tiers$node['_embedded']['pages']);
  587.     }
  588.     public function loadExtensionData($uuid$extensionName$webspaceKey$languageCode)
  589.     {
  590.         $structure $this->getMapper()->load($uuid$webspaceKey$languageCode);
  591.         // extract extension
  592.         $extensionData $structure->getExt();
  593.         $data $extensionData[$extensionName];
  594.         // add uuid and path
  595.         $data['id'] = $structure->getUuid();
  596.         $data['path'] = $structure->getPath();
  597.         $data['url'] = $structure->getResourceLocator();
  598.         $data['publishedState'] = $structure->getPublishedState();
  599.         $data['published'] = $structure->getPublished();
  600.         // prepare data
  601.         $data['_links'] = [
  602.             'self' => [
  603.                 'href' => $this->apiBasePath '/' $uuid '/' $extensionName '?webspace=' $webspaceKey .
  604.                     '&language=' $languageCode,
  605.             ],
  606.         ];
  607.         return $data;
  608.     }
  609.     public function saveExtensionData($uuid$data$extensionName$webspaceKey$languageCode$userId)
  610.     {
  611.         $structure $this->getMapper()->saveExtension(
  612.             $uuid,
  613.             $data,
  614.             $extensionName,
  615.             $webspaceKey,
  616.             $languageCode,
  617.             $userId
  618.         );
  619.         // extract extension
  620.         $extensionData $structure->getExt();
  621.         $data $extensionData[$extensionName];
  622.         // add uuid and path
  623.         $data['id'] = $structure->getUuid();
  624.         $data['path'] = $structure->getPath();
  625.         $data['url'] = $structure->getResourceLocator();
  626.         // prepare data
  627.         $data['_links'] = [
  628.             'self' => [
  629.                 'href' => $this->apiBasePath '/' $uuid '/' $extensionName '?webspace=' $webspaceKey .
  630.                     '&language=' $languageCode,
  631.             ],
  632.         ];
  633.         return $data;
  634.     }
  635.     public function orderAt($uuid$position$webspaceKey$languageCode$userId)
  636.     {
  637.         try {
  638.             // call mapper function
  639.             $structure $this->getMapper()->orderAt($uuid$position$userId$webspaceKey$languageCode);
  640.         } catch (DocumentManagerException $ex) {
  641.             throw new RestException($ex->getMessage(), 1$ex);
  642.         } catch (RepositoryException $ex) {
  643.             throw new RestException($ex->getMessage(), 1$ex);
  644.         } catch (InvalidOrderPositionException $ex) {
  645.             throw new RestException($ex->getMessage(), 1$ex);
  646.         }
  647.         return $this->prepareNode($structure$webspaceKey$languageCode);
  648.     }
  649.     public function copyLocale($uuid$userId$webspaceKey$srcLocale$destLocales)
  650.     {
  651.         @trigger_deprecation(
  652.             'sulu/sulu',
  653.             '2.3',
  654.             'The NodeRepository::copyLocale method is deprecated and will be removed in the future.'
  655.             ' Use DocumentManagerInterface::copyLocale instead.'
  656.         );
  657.         try {
  658.             // call mapper function
  659.             $structure $this->getMapper()->copyLanguage($uuid$userId$webspaceKey$srcLocale$destLocales);
  660.         } catch (RepositoryException $ex) {
  661.             throw new RestException($ex->getMessage(), 1$ex);
  662.         }
  663.         return $this->prepareNode($structure$webspaceKey$srcLocale);
  664.     }
  665. }