vendor/sulu/sulu/src/Sulu/Component/Content/Document/Subscriber/ResourceSegmentSubscriber.php line 200

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\Document\Subscriber;
  11. use PHPCR\NodeInterface;
  12. use PHPCR\SessionInterface;
  13. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  14. use Sulu\Bundle\PageBundle\Document\HomeDocument;
  15. use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
  16. use Sulu\Component\Content\Document\Behavior\StructureBehavior;
  17. use Sulu\Component\Content\Document\RedirectType;
  18. use Sulu\Component\Content\Metadata\PropertyMetadata;
  19. use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyInterface;
  20. use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
  21. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  22. use Sulu\Component\DocumentManager\Event\AbstractMappingEvent;
  23. use Sulu\Component\DocumentManager\Event\CopyEvent;
  24. use Sulu\Component\DocumentManager\Event\MoveEvent;
  25. use Sulu\Component\DocumentManager\Event\PersistEvent;
  26. use Sulu\Component\DocumentManager\Event\PublishEvent;
  27. use Sulu\Component\DocumentManager\Events;
  28. use Sulu\Component\DocumentManager\PropertyEncoder;
  29. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  30. /**
  31. * TODO: This could be made into a pure metadata subscriber if we make
  32. * the resource locator a system property.
  33. */
  34. class ResourceSegmentSubscriber implements EventSubscriberInterface
  35. {
  36. public function __construct(
  37. private PropertyEncoder $encoder,
  38. private DocumentManagerInterface $documentManager,
  39. private DocumentInspector $documentInspector,
  40. private ResourceLocatorStrategyPoolInterface $resourceLocatorStrategyPool,
  41. private SessionInterface $defaultSession,
  42. private SessionInterface $liveSession,
  43. ) {
  44. }
  45. public static function getSubscribedEvents()
  46. {
  47. return [
  48. // persist should happen before content is mapped
  49. Events::PERSIST => [
  50. ['handlePersistDocument', 10],
  51. ],
  52. // hydrate should happen afterwards
  53. Events::HYDRATE => ['handleHydrate', -200],
  54. Events::MOVE => ['updateMovedDocument', -128],
  55. Events::COPY => ['updateCopiedDocument', -128],
  56. Events::PUBLISH => ['handlePersistRoute', -128],
  57. ];
  58. }
  59. /**
  60. * Checks if the given Document supports the operations done in this Subscriber.
  61. *
  62. * @param object $document
  63. *
  64. * @return bool
  65. */
  66. public function supports($document)
  67. {
  68. return $document instanceof ResourceSegmentBehavior && $document instanceof StructureBehavior;
  69. }
  70. /**
  71. * Sets the ResourceSegment of the document.
  72. */
  73. public function handleHydrate(AbstractMappingEvent $event)
  74. {
  75. $document = $event->getDocument();
  76. if (!$this->supports($document)) {
  77. return;
  78. }
  79. $node = $event->getNode();
  80. $property = $this->getResourceSegmentProperty($document);
  81. if (!$property) {
  82. // do not set a resource segment if the document has no structure
  83. return;
  84. }
  85. $locale = $this->documentInspector->getOriginalLocale($document);
  86. $segment = $node->getPropertyValueWithDefault(
  87. $this->encoder->localizedSystemName(
  88. $property->getName(),
  89. $locale
  90. ),
  91. null
  92. );
  93. $document->setResourceSegment($segment);
  94. }
  95. /**
  96. * Sets the ResourceSegment on the Structure.
  97. */
  98. public function handlePersistDocument(PersistEvent $event)
  99. {
  100. /** @var ResourceSegmentBehavior $document */
  101. $document = $event->getDocument();
  102. if (!$this->supports($document)) {
  103. return;
  104. }
  105. $property = $this->getResourceSegmentProperty($document);
  106. // check if a property for the resource segment is available, this prevents the code from failing in case there
  107. // is no such property for some reason (e.g. the document doesn't have a structure)
  108. if ($property) {
  109. $this->persistDocument($document, $property);
  110. }
  111. }
  112. /**
  113. * Creates or updates the route for the document.
  114. */
  115. public function handlePersistRoute(PublishEvent $event)
  116. {
  117. /** @var ResourceSegmentBehavior $document */
  118. $document = $event->getDocument();
  119. if (!$this->supports($document)) {
  120. return;
  121. }
  122. if (!$event->getLocale()) {
  123. return;
  124. }
  125. if ($document instanceof HomeDocument) {
  126. return;
  127. }
  128. $this->persistRoute($document);
  129. }
  130. /**
  131. * Moves the routes for all localizations of the document in the event.
  132. */
  133. public function updateMovedDocument(MoveEvent $event)
  134. {
  135. $document = $event->getDocument();
  136. if (!$document instanceof ResourceSegmentBehavior) {
  137. return;
  138. }
  139. $webspaceKey = $this->documentInspector->getWebspace($event->getDocument());
  140. if (!$webspaceKey) {
  141. return;
  142. }
  143. $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey($webspaceKey);
  144. if (ResourceLocatorStrategyInterface::INPUT_TYPE_LEAF !== $resourceLocatorStrategy->getInputType()) {
  145. return;
  146. }
  147. $this->updateRoute($document, true);
  148. }
  149. /**
  150. * Copy the routes for all localization of the document in the event.
  151. */
  152. public function updateCopiedDocument(CopyEvent $event)
  153. {
  154. $document = $event->getDocument();
  155. if (!$document instanceof ResourceSegmentBehavior) {
  156. return;
  157. }
  158. $this->updateRoute(
  159. $this->documentManager->find(
  160. $event->getCopiedPath(),
  161. $this->documentInspector->getLocale($document)
  162. ),
  163. false
  164. );
  165. }
  166. /**
  167. * Returns the property of the document's structure containing the ResourceSegment.
  168. *
  169. * @param object $document
  170. *
  171. * @return PropertyMetadata
  172. */
  173. private function getResourceSegmentProperty($document)
  174. {
  175. $structure = $this->documentInspector->getStructureMetadata($document);
  176. if (!$structure) {
  177. return;
  178. }
  179. $property = $structure->getPropertyByTagName('sulu.rlp');
  180. if (!$property) {
  181. throw new \RuntimeException(
  182. \sprintf(
  183. 'Structure "%s" does not have a "sulu.rlp" tag which is required for documents implementing the ' .
  184. 'ResourceSegmentBehavior. In "%s"',
  185. $structure->getName(),
  186. $structure->getResource()
  187. )
  188. );
  189. }
  190. return $property;
  191. }
  192. /**
  193. * Sets the ResourceSegment to the given property of the given document.
  194. */
  195. private function persistDocument(ResourceSegmentBehavior $document, PropertyMetadata $property)
  196. {
  197. $document->getStructure()->getProperty(
  198. $property->getName()
  199. )->setValue($document->getResourceSegment());
  200. }
  201. /**
  202. * Creates or updates the route of the document using the RlpStrategy.
  203. */
  204. private function persistRoute(ResourceSegmentBehavior $document)
  205. {
  206. $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey(
  207. $this->documentInspector->getWebspace($document)
  208. );
  209. $resourceLocatorStrategy->save($document, null);
  210. }
  211. /**
  212. * Updates the route for the given document after a move or copy.
  213. *
  214. * @param object $document
  215. * @param bool $generateRoutes If set to true a route in the routing tree will also be created
  216. */
  217. private function updateRoute($document, $generateRoutes)
  218. {
  219. $locales = $this->documentInspector->getLocales($document);
  220. $webspaceKey = $this->documentInspector->getWebspace($document);
  221. $uuid = $this->documentInspector->getUuid($document);
  222. $path = $this->documentInspector->getPath($document);
  223. $parentUuid = $this->documentInspector->getUuid($this->documentInspector->getParent($document));
  224. $defaultNode = $this->defaultSession->getNode($path);
  225. $liveNode = $this->liveSession->getNode($path);
  226. $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey($webspaceKey);
  227. foreach ($locales as $locale) {
  228. $localizedDocument = $this->documentManager->find($uuid, $locale);
  229. if (RedirectType::NONE !== $localizedDocument->getRedirectType()) {
  230. continue;
  231. }
  232. $resourceSegmentPropertyName = $this->encoder->localizedSystemName(
  233. $this->getResourceSegmentProperty($localizedDocument)->getName(),
  234. $locale
  235. );
  236. $this->updateResourceSegmentProperty(
  237. $defaultNode,
  238. $resourceSegmentPropertyName,
  239. $parentUuid,
  240. $webspaceKey,
  241. $locale
  242. );
  243. if ($liveNode->hasProperty($resourceSegmentPropertyName)) {
  244. $this->updateResourceSegmentProperty(
  245. $liveNode,
  246. $resourceSegmentPropertyName,
  247. $parentUuid,
  248. $webspaceKey,
  249. $locale
  250. );
  251. // if the method is called with the generateRoutes flag it will create a new route
  252. // this happens on a move, but not on copy, because copy results in a draft page without url
  253. if ($generateRoutes) {
  254. $localizedDocument->setResourceSegment($liveNode->getPropertyValue($resourceSegmentPropertyName));
  255. $resourceLocatorStrategy->save($localizedDocument, null);
  256. $localizedDocument->setResourceSegment($defaultNode->getPropertyValue($resourceSegmentPropertyName));
  257. }
  258. }
  259. }
  260. }
  261. /**
  262. * Updates the property for the resource segment on the given node.
  263. *
  264. * @param string $resourceSegmentPropertyName
  265. * @param string $parentUuid
  266. * @param string $webspaceKey
  267. * @param string $locale
  268. */
  269. private function updateResourceSegmentProperty(
  270. NodeInterface $node,
  271. $resourceSegmentPropertyName,
  272. $parentUuid,
  273. $webspaceKey,
  274. $locale
  275. ) {
  276. $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey($webspaceKey);
  277. $childPart = $resourceLocatorStrategy->getChildPart($node->getPropertyValue($resourceSegmentPropertyName));
  278. $node->setProperty(
  279. $resourceSegmentPropertyName,
  280. $resourceLocatorStrategy->generate($childPart, $parentUuid, $webspaceKey, $locale)
  281. );
  282. }
  283. }