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

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