vendor/sulu/sulu/src/Sulu/Bundle/WebsiteBundle/Routing/ContentRouteProvider.php line 152

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\WebsiteBundle\Routing;
  11. use PHPCR\RepositoryException;
  12. use Sulu\Bundle\DocumentManagerBundle\Bridge\DocumentInspector;
  13. use Sulu\Bundle\PageBundle\Document\PageDocument;
  14. use Sulu\Component\Content\Compat\Structure\PageBridge;
  15. use Sulu\Component\Content\Compat\StructureManagerInterface;
  16. use Sulu\Component\Content\Document\Behavior\ExtensionBehavior;
  17. use Sulu\Component\Content\Document\Behavior\ResourceSegmentBehavior;
  18. use Sulu\Component\Content\Document\Behavior\WebspaceBehavior;
  19. use Sulu\Component\Content\Document\RedirectType;
  20. use Sulu\Component\Content\Exception\ResourceLocatorMovedException;
  21. use Sulu\Component\Content\Exception\ResourceLocatorNotFoundException;
  22. use Sulu\Component\Content\Types\ResourceLocator\Strategy\ResourceLocatorStrategyPoolInterface;
  23. use Sulu\Component\DocumentManager\DocumentManagerInterface;
  24. use Sulu\Component\Security\Authorization\SecurityCheckerInterface;
  25. use Sulu\Component\Webspace\Analyzer\Attributes\RequestAttributes;
  26. use Sulu\Component\Webspace\Analyzer\RequestAnalyzerInterface;
  27. use Sulu\Component\Webspace\Manager\WebspaceManagerInterface;
  28. use Symfony\Cmf\Component\Routing\RouteProviderInterface;
  29. use Symfony\Component\HttpFoundation\Request;
  30. use Symfony\Component\Routing\Route;
  31. use Symfony\Component\Routing\RouteCollection;
  32. use Webmozart\Assert\Assert;
  33. /**
  34. * The PortalRouteProvider should load the dynamic routes created by Sulu.
  35. */
  36. class ContentRouteProvider implements RouteProviderInterface
  37. {
  38. /**
  39. * @var DocumentManagerInterface
  40. */
  41. private $documentManager;
  42. /**
  43. * @var DocumentInspector
  44. */
  45. private $documentInspector;
  46. /**
  47. * @var ResourceLocatorStrategyPoolInterface
  48. */
  49. private $resourceLocatorStrategyPool;
  50. /**
  51. * @var StructureManagerInterface
  52. */
  53. private $structureManager;
  54. /**
  55. * @var WebspaceManagerInterface
  56. */
  57. private $webspaceManager;
  58. /**
  59. * @var RequestAnalyzerInterface
  60. */
  61. private $requestAnalyzer;
  62. /**
  63. * @var SecurityCheckerInterface|null
  64. */
  65. private $securityChecker;
  66. /**
  67. * @var array
  68. */
  69. private $defaultOptions;
  70. public function __construct(
  71. DocumentManagerInterface $documentManager,
  72. DocumentInspector $documentInspector,
  73. ResourceLocatorStrategyPoolInterface $resourceLocatorStrategyPool,
  74. StructureManagerInterface $structureManager,
  75. WebspaceManagerInterface $webspaceManager,
  76. RequestAnalyzerInterface $requestAnalyzer,
  77. ?SecurityCheckerInterface $securityChecker = null,
  78. array $defaultOptions = []
  79. ) {
  80. $this->documentManager = $documentManager;
  81. $this->documentInspector = $documentInspector;
  82. $this->resourceLocatorStrategyPool = $resourceLocatorStrategyPool;
  83. $this->structureManager = $structureManager;
  84. $this->webspaceManager = $webspaceManager;
  85. $this->requestAnalyzer = $requestAnalyzer;
  86. $this->securityChecker = $securityChecker;
  87. Assert::null($securityChecker, 'The security checker should be called by the SecurityListener not the ContentRouteProvider.'); // people who overwrite the ContentRouteProvider should make aware of that they also need to refactor this
  88. $this->defaultOptions = $defaultOptions;
  89. }
  90. public function getRouteCollectionForRequest(Request $request)
  91. {
  92. $collection = new RouteCollection();
  93. if ('' === $request->getRequestFormat()) {
  94. return $collection;
  95. }
  96. /** @var RequestAttributes $attributes */
  97. $attributes = $request->attributes->get('_sulu');
  98. if (!$attributes) {
  99. return $collection;
  100. }
  101. $matchType = $attributes->getAttribute('matchType');
  102. // no portal information without localization supported
  103. if (null === $attributes->getAttribute('localization')
  104. && RequestAnalyzerInterface::MATCH_TYPE_PARTIAL !== $matchType
  105. && RequestAnalyzerInterface::MATCH_TYPE_REDIRECT !== $matchType
  106. ) {
  107. return $collection;
  108. }
  109. $resourceLocator = $this->decodePathInfo($attributes->getAttribute('resourceLocator'));
  110. $prefix = $attributes->getAttribute('resourceLocatorPrefix');
  111. $pathInfo = $this->decodePathInfo($request->getPathInfo());
  112. $htmlRedirect = $pathInfo !== $prefix . $resourceLocator
  113. && \in_array($request->getRequestFormat(), ['htm', 'html']);
  114. if ($htmlRedirect
  115. || RequestAnalyzerInterface::MATCH_TYPE_REDIRECT == $matchType
  116. || RequestAnalyzerInterface::MATCH_TYPE_PARTIAL == $matchType
  117. ) {
  118. return $collection;
  119. }
  120. // just show the page
  121. $portal = $attributes->getAttribute('portal');
  122. $locale = $attributes->getAttribute('localization')->getLocale();
  123. $resourceLocatorStrategy = $this->resourceLocatorStrategyPool->getStrategyByWebspaceKey(
  124. $portal->getWebspace()->getKey()
  125. );
  126. try {
  127. // load content by url ignore ending trailing slash
  128. /** @var PageDocument $document */
  129. $document = $this->documentManager->find(
  130. $resourceLocatorStrategy->loadByResourceLocator(
  131. \rtrim($resourceLocator, '/'),
  132. $portal->getWebspace()->getKey(),
  133. $locale
  134. ),
  135. $locale,
  136. [
  137. 'load_ghost_content' => false,
  138. ]
  139. );
  140. if (!$document->getTitle()) {
  141. // If the title is empty the document does not exist in this locale
  142. // Necessary because of https://github.com/sulu/sulu/issues/2724, otherwise locale could be checked
  143. return $collection;
  144. }
  145. if (\preg_match('/\/$/', $resourceLocator) && ('/' !== $resourceLocator || $prefix)) {
  146. // redirect page to page without slash at the end
  147. $url = $prefix . \rtrim($resourceLocator, '/');
  148. if ($request->getQueryString()) {
  149. $url .= '?' . $request->getQueryString();
  150. }
  151. $collection->add('redirect_' . \uniqid(), $this->getRedirectRoute($request, $url));
  152. } elseif (RedirectType::INTERNAL === $document->getRedirectType()) {
  153. $redirectTarget = $document->getRedirectTarget();
  154. if (!$redirectTarget instanceof ResourceSegmentBehavior || !$redirectTarget instanceof WebspaceBehavior) {
  155. return $collection;
  156. }
  157. $redirectUrl = $this->webspaceManager->findUrlByResourceLocator(
  158. $redirectTarget->getResourceSegment(),
  159. null,
  160. $document->getLocale(),
  161. $redirectTarget->getWebspaceName()
  162. );
  163. if ($request->getQueryString()) {
  164. $redirectUrl .= '?' . $request->getQueryString();
  165. }
  166. $collection->add(
  167. $document->getStructureType() . '_' . $document->getUuid(),
  168. $this->getRedirectRoute($request, $redirectUrl)
  169. );
  170. } elseif (RedirectType::EXTERNAL === $document->getRedirectType()) {
  171. $collection->add(
  172. $document->getStructureType() . '_' . $document->getUuid(),
  173. $this->getRedirectRoute($request, $document->getRedirectExternal())
  174. );
  175. } elseif (!$this->checkResourceLocator($resourceLocator, $prefix)) {
  176. return $collection;
  177. } else {
  178. if ($document instanceof ExtensionBehavior) {
  179. $documentSegments = $document->getExtensionsData()['excerpt']['segments'] ?? [];
  180. $documentSegmentKey = $documentSegments[$portal->getWebspace()->getKey()] ?? null;
  181. $segment = $this->requestAnalyzer->getSegment();
  182. if ($segment && $documentSegmentKey && $segment->getKey() !== $documentSegmentKey) {
  183. $this->requestAnalyzer->changeSegment($documentSegmentKey);
  184. }
  185. }
  186. // convert the page to a StructureBridge because of BC
  187. $metadata = $this->documentInspector->getStructureMetadata($document);
  188. if (!$metadata) {
  189. return $collection;
  190. }
  191. /** @var PageBridge $structure */
  192. $structure = $this->structureManager->wrapStructure(
  193. $this->documentInspector->getMetadata($document)->getAlias(),
  194. $metadata
  195. );
  196. $structure->setDocument($document);
  197. // show the page
  198. $collection->add(
  199. $document->getStructureType() . '_' . $document->getUuid(),
  200. $this->getStructureRoute($request, $structure)
  201. );
  202. }
  203. } catch (ResourceLocatorNotFoundException $exc) {
  204. // just do not add any routes to the collection
  205. } catch (ResourceLocatorMovedException $exc) {
  206. $url = $prefix . $exc->getNewResourceLocator();
  207. if ($request->getQueryString()) {
  208. $url .= '?' . $request->getQueryString();
  209. }
  210. // old url resource was moved
  211. $collection->add(
  212. $exc->getNewResourceLocatorUuid() . '_' . \uniqid(),
  213. $this->getRedirectRoute($request, $url)
  214. );
  215. } catch (RepositoryException $exc) {
  216. // just do not add any routes to the collection
  217. }
  218. return $collection;
  219. }
  220. public function getRouteByName($name, $parameters = [])
  221. {
  222. // TODO: Implement getRouteByName() method.
  223. }
  224. public function getRoutesByNames($names, $parameters = [])
  225. {
  226. // TODO
  227. return [];
  228. }
  229. /**
  230. * Checks if the resource locator is valid.
  231. * A resource locator with a slash only is not allowed, the only exception is when it is a single language
  232. * website, where the browser automatically adds the slash.
  233. *
  234. * @param string $resourceLocator
  235. * @param string $resourceLocatorPrefix
  236. *
  237. * @return bool
  238. */
  239. private function checkResourceLocator($resourceLocator, $resourceLocatorPrefix)
  240. {
  241. return !('/' === $resourceLocator && $resourceLocatorPrefix);
  242. }
  243. /**
  244. * @param string $url
  245. *
  246. * @return Route
  247. */
  248. protected function getRedirectRoute(Request $request, $url)
  249. {
  250. $requestFormat = $request->getRequestFormat(null);
  251. $formatSuffix = $requestFormat ? '.' . $requestFormat : '';
  252. $urlParts = \explode('?', $url, 2);
  253. $url = $urlParts[0] . $formatSuffix;
  254. if ($urlParts[1] ?? null) {
  255. $url .= '?' . $urlParts[1];
  256. }
  257. // redirect to linked page
  258. return new Route(
  259. $this->decodePathInfo($request->getPathInfo()),
  260. [
  261. '_controller' => 'sulu_website.redirect_controller::redirectAction',
  262. 'url' => $url,
  263. ],
  264. [],
  265. $this->defaultOptions
  266. );
  267. }
  268. /**
  269. * @return Route
  270. */
  271. protected function getStructureRoute(Request $request, PageBridge $content)
  272. {
  273. return new Route(
  274. $this->decodePathInfo($request->getPathInfo()),
  275. [
  276. '_controller' => $content->getController(),
  277. 'structure' => $content,
  278. 'partial' => 'true' === $request->get('partial', 'false'),
  279. ],
  280. [],
  281. $this->defaultOptions
  282. );
  283. }
  284. /**
  285. * Server encodes the url and symfony does not encode it
  286. * Symfony decodes this data here https://github.com/symfony/symfony/blob/3.3/src/Symfony/Component/Routing/Matcher/UrlMatcher.php#L91.
  287. *
  288. * @param string $pathInfo
  289. *
  290. * @return string
  291. */
  292. private function decodePathInfo($pathInfo)
  293. {
  294. if (null === $pathInfo || '' === $pathInfo) {
  295. return '';
  296. }
  297. return '/' . \ltrim(\rawurldecode($pathInfo), '/');
  298. }
  299. }