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

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