vendor/sulu/sulu/src/Sulu/Component/Content/Types/ResourceLocator/Mapper/PhpcrMapper.php line 242

  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\Types\ResourceLocator\Mapper;
  11. use PHPCR\ItemExistsException;
  12. use PHPCR\NodeInterface;
  13. use PHPCR\PathNotFoundException;
  14. use PHPCR\PropertyInterface;
  15. use PHPCR\Util\PathHelper;
  16. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  17. use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
  18. use Sulu\Component\Content\Exception\ResourceLocatorAlreadyExistsException;
  19. use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
  20. use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
  21. use Sulu\Component\Content\Types\ResourceLocator\ResourceLocatorInformation;
  22. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  23. use Sulu\Component\PHPCR\SessionManager\SessionManagerInterface;
  24. /**
  25.  * Manages resource-locators in phpcr.
  26.  */
  27. class PhpcrMapper implements ResourceLocatorMapperInterface
  28. {
  29.     public function __construct(
  30.         private SessionManagerInterface $sessionManager,
  31.         private DocumentManagerInterface $documentManager,
  32.         private DocumentInspector $documentInspector,
  33.     ) {
  34.     }
  35.     public function save(ResourceSegmentBehavior $document)
  36.     {
  37.         $path $document->getResourceSegment();
  38.         $webspaceKey $this->documentInspector->getWebspace($document);
  39.         $locale $this->documentInspector->getOriginalLocale($document);
  40.         $segmentKey null;
  41.         $webspaceRouteRootPath $this->getWebspaceRouteNodeBasePath($webspaceKey$locale$segmentKey);
  42.         try {
  43.             $routeNodePath $this->loadByContent(
  44.                 $this->documentInspector->getNode($document),
  45.                 $webspaceKey,
  46.                 $locale,
  47.                 $segmentKey
  48.             );
  49.             $routeDocument $this->documentManager->find(
  50.                 $webspaceRouteRootPath $routeNodePath,
  51.                 $locale,
  52.                 ['rehydrate' => false]
  53.             );
  54.             $routeDocumentPath $webspaceRouteRootPath $routeNodePath;
  55.         } catch (ResourceLocatorNotFoundException $e) {
  56.             $routeDocument $this->documentManager->create('route');
  57.             $routeDocumentPath $webspaceRouteRootPath $path;
  58.         }
  59.         $routeDocument->setTargetDocument($document);
  60.         try {
  61.             $this->documentManager->persist(
  62.                 $routeDocument,
  63.                 $locale,
  64.                 [
  65.                     'path' => $routeDocumentPath,
  66.                     'auto_create' => true,
  67.                     'override' => true,
  68.                 ]
  69.             );
  70.             $this->documentManager->publish($routeDocument$locale);
  71.         } catch (ItemExistsException $e) {
  72.             throw new ResourceLocatorAlreadyExistsException($document->getResourceSegment(), $routeDocumentPath$e);
  73.         }
  74.     }
  75.     public function loadByContent(NodeInterface $contentNode$webspaceKey$languageCode$segmentKey null)
  76.     {
  77.         $result $this->iterateRouteNodes(
  78.             $contentNode,
  79.             function($resourceLocatorNodeInterface $node) {
  80.                 if (false === $node->getPropertyValue('sulu:history') && false !== $resourceLocator) {
  81.                     return $resourceLocator;
  82.                 }
  83.                 return false;
  84.             },
  85.             $webspaceKey,
  86.             $languageCode,
  87.             $segmentKey
  88.         );
  89.         if (null !== $result) {
  90.             return $result;
  91.         }
  92.         throw new ResourceLocatorNotFoundException();
  93.     }
  94.     /**
  95.      * Iterates over all route nodes assigned by the given node, and executes the callback on it.
  96.      *
  97.      * @param callable $callback will be called foreach route node (stops and return value if not false)
  98.      * @param string $webspaceKey
  99.      * @param string $languageCode
  100.      * @param string $segmentKey
  101.      *
  102.      * @return NodeInterface|null
  103.      */
  104.     private function iterateRouteNodes(
  105.         NodeInterface $node,
  106.         $callback,
  107.         $webspaceKey,
  108.         $languageCode,
  109.         $segmentKey null
  110.     ) {
  111.         if ($node->isNew()) {
  112.             return null;
  113.         }
  114.         $routePath $this->sessionManager->getRoutePath($webspaceKey$languageCode);
  115.         // search for references with name 'content'
  116.         foreach ($node->getReferences('sulu:content') as $ref) {
  117.             if ($ref instanceof PropertyInterface) {
  118.                 $routeNode $ref->getParent();
  119.                 if (!== \strpos($routeNode->getPath(), $routePath)) {
  120.                     continue;
  121.                 }
  122.                 $resourceLocator $this->getResourceLocator(
  123.                     $ref->getParent()->getPath(),
  124.                     $webspaceKey,
  125.                     $languageCode,
  126.                     $segmentKey
  127.                 );
  128.                 $result $callback($resourceLocator$routeNode);
  129.                 if (false !== $result) {
  130.                     return $result;
  131.                 }
  132.             }
  133.         }
  134.         return null;
  135.     }
  136.     public function loadByContentUuid($uuid$webspaceKey$languageCode$segmentKey null)
  137.     {
  138.         $session $this->sessionManager->getSession();
  139.         $contentNode $session->getNodeByIdentifier($uuid);
  140.         return $this->loadByContent($contentNode$webspaceKey$languageCode$segmentKey);
  141.     }
  142.     public function loadHistoryByContentUuid($uuid$webspaceKey$languageCode$segmentKey null)
  143.     {
  144.         // get content node
  145.         $session $this->sessionManager->getSession();
  146.         $contentNode $session->getNodeByIdentifier($uuid);
  147.         // get current path node
  148.         $pathNode $this->iterateRouteNodes(
  149.             $contentNode,
  150.             function($resourceLocatorNodeInterface $node) {
  151.                 if (false === $node->getPropertyValue('sulu:history') && false !== $resourceLocator) {
  152.                     return $node;
  153.                 } else {
  154.                     return false;
  155.                 }
  156.             },
  157.             $webspaceKey,
  158.             $languageCode,
  159.             $segmentKey
  160.         );
  161.         // iterate over history of path node
  162.         $result = [];
  163.         if (!$pathNode) {
  164.             return $result;
  165.         }
  166.         $this->iterateRouteNodes(
  167.             $pathNode,
  168.             function($resourceLocatorNodeInterface $node) use (&$result) {
  169.                 if (false !== $resourceLocator) {
  170.                     // add resourceLocator
  171.                     $result[] = new ResourceLocatorInformation(
  172.                         //backward compability
  173.                         $resourceLocator,
  174.                         $node->getPropertyValueWithDefault('sulu:created', new \DateTime()),
  175.                         $node->getIdentifier()
  176.                     );
  177.                 }
  178.                 return false;
  179.             },
  180.             $webspaceKey,
  181.             $languageCode,
  182.             $segmentKey
  183.         );
  184.         // sort history descending
  185.         \usort(
  186.             $result,
  187.             function(ResourceLocatorInformation $item1ResourceLocatorInformation $item2) {
  188.                 return $item1->getCreated() < $item2->getCreated();
  189.             }
  190.         );
  191.         return $result;
  192.     }
  193.     public function loadByResourceLocator($resourceLocator$webspaceKey$languageCode$segmentKey null)
  194.     {
  195.         $resourceLocator \ltrim($resourceLocator'/');
  196.         $path \sprintf(
  197.             '%s/%s',
  198.             $this->getWebspaceRouteNodeBasePath($webspaceKey$languageCode$segmentKey),
  199.             $resourceLocator
  200.         );
  201.         try {
  202.             if ('' !== $resourceLocator) {
  203.                 if (!PathHelper::assertValidAbsolutePath($pathfalsefalse)) {
  204.                     throw new ResourceLocatorNotFoundException(\sprintf('Path "%s" not found'$path));
  205.                 }
  206.                 // get requested resource locator route node
  207.                 $route $this->sessionManager->getSession()->getNode($path);
  208.             } else {
  209.                 // get home page route node
  210.                 $route $this->getWebspaceRouteNode($webspaceKey$languageCode$segmentKey);
  211.             }
  212.         } catch (PathNotFoundException $e) {
  213.             throw new ResourceLocatorNotFoundException(\sprintf('Path "%s" not found'$path), 0$e);
  214.         }
  215.         if ($route->hasProperty('sulu:content') && $route->hasProperty('sulu:history')) {
  216.             if (!$route->getPropertyValue('sulu:history')) {
  217.                 /** @var NodeInterface $content */
  218.                 $content $route->getPropertyValue('sulu:content');
  219.                 return $content->getIdentifier();
  220.             } else {
  221.                 // get path from history node
  222.                 /** @var NodeInterface $realPath */
  223.                 $realPath $route->getPropertyValue('sulu:content');
  224.                 $locator $this->getResourceLocator($realPath->getPath(), $webspaceKey$languageCode$segmentKey);
  225.                 if (false === $locator) {
  226.                     throw new ResourceLocatorNotFoundException(\sprintf(
  227.                         'Couldn\'t generate resource locator for path: %s',
  228.                         $realPath->getPath()
  229.                     ));
  230.                 }
  231.                 throw new ResourceLocatorMovedException($locator$realPath->getIdentifier());
  232.             }
  233.         } else {
  234.             throw new ResourceLocatorNotFoundException(\sprintf(
  235.                 'Route has "%s" does not have either the "sulu:content" or "sulu:history" properties',
  236.                 $route->getPath()
  237.             ));
  238.         }
  239.     }
  240.     public function unique($path$webspaceKey$languageCode$segmentKey null)
  241.     {
  242.         $routes $this->getWebspaceRouteNode($webspaceKey$languageCode$segmentKey);
  243.         return $this->isUnique($routes$path);
  244.     }
  245.     public function getUniquePath($path$webspaceKey$languageCode$segmentKey null/*, $uuid = null*/)
  246.     {
  247.         $uuid null;
  248.         if (\func_num_args() >= 5) {
  249.             $uuid \func_get_arg(4);
  250.         }
  251.         $routes $this->getWebspaceRouteNode($webspaceKey$languageCode$segmentKey);
  252.         if ($this->isUnique($routes$path$uuid)) {
  253.             // path is already unique
  254.             return $path;
  255.         } else {
  256.             // append -
  257.             $path .= '-';
  258.             // init counter
  259.             $i 1;
  260.             // while $path-$i is not unique raise counter
  261.             while (!$this->isUnique($routes$path $i$uuid)) {
  262.                 ++$i;
  263.             }
  264.             // result is unique
  265.             return $path $i;
  266.         }
  267.     }
  268.     public function deleteById($id$languageCode$segmentKey null)
  269.     {
  270.         $routeDocument $this->documentManager->find($id$languageCode);
  271.         $this->documentManager->remove($routeDocument);
  272.     }
  273.     public function getParentPath($uuid$webspaceKey$languageCode$segmentKey null)
  274.     {
  275.         $session $this->sessionManager->getSession();
  276.         $contentNode $session->getNodeByIdentifier($uuid);
  277.         $parentNode $contentNode->getParent();
  278.         try {
  279.             return $this->loadByContent($parentNode$webspaceKey$languageCode$segmentKey);
  280.         } catch (ResourceLocatorNotFoundException $ex) {
  281.             // parent node donĀ“t have a resource locator
  282.             return;
  283.         }
  284.     }
  285.     /**
  286.      * Check if path is unique from given $root node.
  287.      *
  288.      * @param NodeInterface $root route node
  289.      * @param string $path requested path
  290.      *
  291.      * @return bool path is unique
  292.      */
  293.     private function isUnique(NodeInterface $root$path$uuid null)
  294.     {
  295.         $path \ltrim($path'/');
  296.         if (!$root->hasNode($path)) {
  297.             return true;
  298.         }
  299.         if (!$uuid) {
  300.             return false;
  301.         }
  302.         $route $root->getNode($path);
  303.         return $route->hasProperty('sulu:content')
  304.             && $route->getPropertyValue('sulu:content')->getIdentifier() === $uuid;
  305.     }
  306.     /**
  307.      * Returns base node of routes from phpcr.
  308.      *
  309.      * @param string $webspaceKey current session
  310.      * @param string $languageCode
  311.      * @param string $segmentKey
  312.      *
  313.      * @return NodeInterface base node of routes
  314.      */
  315.     private function getWebspaceRouteNode($webspaceKey$languageCode$segmentKey)
  316.     {
  317.         return $this->sessionManager->getRouteNode($webspaceKey$languageCode$segmentKey);
  318.     }
  319.     /**
  320.      * Returns base path of routes from phpcr.
  321.      *
  322.      * @param string $webspaceKey current session
  323.      * @param string $languageCode
  324.      * @param string $segmentKey
  325.      *
  326.      * @return string
  327.      */
  328.     private function getWebspaceRouteNodeBasePath($webspaceKey$languageCode$segmentKey)
  329.     {
  330.         return $this->sessionManager->getRoutePath($webspaceKey$languageCode$segmentKey);
  331.     }
  332.     /**
  333.      * Returns resource-locator.
  334.      *
  335.      * @param string $path
  336.      * @param string $webspaceKey
  337.      * @param string $languageCode
  338.      * @param string $segmentKey
  339.      *
  340.      * @return string|false
  341.      */
  342.     private function getResourceLocator($path$webspaceKey$languageCode$segmentKey)
  343.     {
  344.         $basePath $this->getWebspaceRouteNodeBasePath($webspaceKey$languageCode$segmentKey);
  345.         if ($path === $basePath) {
  346.             return '/';
  347.         }
  348.         if (false !== \strpos($path$basePath '/')) {
  349.             $result \str_replace($basePath '/''/'$path);
  350.             if (=== \strpos($result'/')) {
  351.                 return $result;
  352.             }
  353.         }
  354.         return false;
  355.     }
  356. }