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

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\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($resourceLocator, NodeInterface $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 (0 !== \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($resourceLocator, NodeInterface $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($resourceLocator, NodeInterface $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 $item1, ResourceLocatorInformation $item2) {
  188. return $item2->getCreated() <=> $item1->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($path, false, false)) {
  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 (0 === \strpos($result, '/')) {
  351. return $result;
  352. }
  353. }
  354. return false;
  355. }
  356. }