From be2c98c21be264311b2dff5bc1c7c0620210c73a Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Sun, 30 Jun 2024 23:05:32 +0200 Subject: [PATCH 1/6] TASK: Update FrontendNodeRoutePartHandler to current "original" The FrontendNodeRoutePartHandler lacked quite some changes from the implementation in Neos, and thus - did not resolve shortcuts directly to their target - did not know about the `nodeType` options Also the exception codes have been "uniqueified". --- .../Routing/FrontendNodeRoutePartHandler.php | 221 +++++++++++++----- composer.json | 4 +- 2 files changed, 166 insertions(+), 59 deletions(-) diff --git a/Classes/Routing/FrontendNodeRoutePartHandler.php b/Classes/Routing/FrontendNodeRoutePartHandler.php index a055861..f9abd5b 100644 --- a/Classes/Routing/FrontendNodeRoutePartHandler.php +++ b/Classes/Routing/FrontendNodeRoutePartHandler.php @@ -13,23 +13,34 @@ */ use Flowpack\Neos\DimensionResolver\Http; +use GuzzleHttp\Psr7\Uri; +use Neos\ContentRepository\Domain\Model\NodeInterface; +use Neos\ContentRepository\Domain\Utility\NodePaths; +use Neos\ContentRepository\Exception\NodeException; use Neos\Flow\Annotations as Flow; -use Psr\Log\LoggerInterface; +use Neos\Flow\Log\Utility\LogEnvironment; use Neos\Flow\Mvc\Routing\Dto\MatchResult; use Neos\Flow\Mvc\Routing\Dto\ResolveResult; use Neos\Flow\Mvc\Routing\Dto\RouteTags; +use Neos\Flow\Mvc\Routing\Dto\UriConstraints; use Neos\Flow\Mvc\Routing\DynamicRoutePart; +use Neos\Flow\Persistence\Exception\IllegalObjectTypeException; use Neos\Flow\Security\Context; +use Neos\Neos\Domain\Model\Site; use Neos\Neos\Domain\Repository\DomainRepository; use Neos\Neos\Domain\Repository\SiteRepository; use Neos\Neos\Domain\Service\ContentContext; use Neos\Neos\Domain\Service\ContentContextFactory; use Neos\Neos\Domain\Service\ContentDimensionPresetSourceInterface; +use Neos\Neos\Domain\Service\NodeShortcutResolver; use Neos\Neos\Domain\Service\SiteService; -use Neos\ContentRepository\Domain\Model\NodeInterface; -use Neos\ContentRepository\Domain\Utility\NodePaths; -use Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface; +use Neos\Neos\Exception as NeosException; use Neos\Neos\Routing\Exception; +use Neos\Neos\Routing\Exception\NoSiteException; +use Neos\Neos\Routing\Exception\NoSuchNodeException; +use Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface; +use Psr\Http\Message\UriInterface; +use Psr\Log\LoggerInterface; /** * A route part handler for finding nodes specifically in the website's frontend. @@ -73,25 +84,21 @@ class FrontendNodeRoutePartHandler extends DynamicRoutePart implements FrontendN protected $contentSubgraphUriProcessor; /** - * @Flow\InjectConfiguration("routing.supportEmptySegmentForDimensions") - * @var boolean + * @Flow\Inject + * @var ContentDimensionPresetSourceInterface */ - protected $supportEmptySegmentForDimensions; + protected $contentDimensionPresetSource; /** * @Flow\Inject - * @var ContentDimensionPresetSourceInterface + * @var NodeShortcutResolver */ - protected $contentDimensionPresetSource; + protected $nodeShortcutResolver; - const DIMENSION_REQUEST_PATH_MATCHER = '|^ - (?[^/@]+) # the first part of the URI, before the first slash, may contain the encoded dimension preset - (?: # start of non-capturing submatch for the remaining URL - /? # a "/"; optional. it must also match en@user-admin - (?.*) # the remaining request path - )? # ... and this whole remaining URL is optional - $ # make sure we consume the full string - |x'; + /** + * @var Site[] indexed by the corresponding host name + */ + protected $siteByHostRuntimeCache = []; /** * Extracts the node path from the request path. @@ -121,13 +128,12 @@ protected function findValueToMatch($requestPath) * in time the route part handler is invoked, the security framework is not yet fully initialized. * * @param string $requestPath The request path (without leading "/", relative to the current Site Node) - * @return bool|MatchResult An instance of MatchResult if value could be matched successfully, otherwise false. + * @return bool|MatchResult An instance of MatchResult if the route matches the $requestPath, otherwise FALSE. @see DynamicRoutePart::matchValue() * @throws \Exception * @throws Exception\NoHomepageException if no node could be found on the homepage (empty $requestPath) */ protected function matchValue($requestPath) { - $contentContext = $this->buildContentContextFromParameters(); try { /** @var NodeInterface $node */ $node = null; @@ -138,17 +144,21 @@ protected function matchValue($requestPath) $node = $this->convertRequestPathToNode($requestPath); }); } catch (Exception $exception) { - $this->systemLogger->debug('FrontendNodeRoutePartHandler matchValue(): ' . $exception->getMessage()); + $this->systemLogger->debug('FrontendNodeRoutePartHandler matchValue(): ' . $exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__)); if ($requestPath === '') { - throw new Exception\NoHomepageException('Homepage could not be loaded. Probably you haven\'t imported a site yet', 1346950755, $exception); + throw new Exception\NoHomepageException('Homepage could not be loaded. Probably you haven\'t imported a site yet', 1719778805, $exception); } return false; } + if (!$this->nodeTypeIsAllowed($node)) { + return false; + } if ($this->onlyMatchSiteNodes() && $node !== $node->getContext()->getCurrentSiteNode()) { return false; } + $contentContext = $this->buildContentContextFromParameters(); $tagArray = [$contentContext->getWorkspace()->getName(), $node->getIdentifier()]; $parent = $node->getParent(); while ($parent) { @@ -163,15 +173,13 @@ protected function matchValue($requestPath) * Returns the initialized node that is referenced by $requestPath, based on the node's * "uriPathSegment" property. * - * Note that $requestPath will be modified (passed by reference) by buildContextFromRequestPath(). - * * @param string $requestPath The request path, for example /the/node/path@some-workspace * @return NodeInterface * @throws Exception\NoSiteException * @throws Exception\NoSiteNodeException - * @throws Exception\NoSuchNodeException + * @throws NoSuchNodeException * @throws Exception\NoWorkspaceException - * @throws \Neos\ContentRepository\Exception\NodeException + * @throws NodeException */ protected function convertRequestPathToNode($requestPath) { @@ -180,29 +188,30 @@ protected function convertRequestPathToNode($requestPath) $workspace = $contentContext->getWorkspace(); if ($workspace === null) { - throw new Exception\NoWorkspaceException(sprintf('No workspace found for request path "%s"', $requestPath), 1346949318); + throw new Exception\NoWorkspaceException(sprintf('No workspace found for request path "%s"', $requestPath), 1719778821); } $site = $contentContext->getCurrentSite(); if ($site === null) { - throw new Exception\NoSiteException(sprintf('No site found for request path "%s"', $requestPath), 1346949693); + throw new Exception\NoSiteException(sprintf('No site found for request path "%s"', $requestPath), 1719778836); } $siteNode = $contentContext->getCurrentSiteNode(); if ($siteNode === null) { $currentDomain = $contentContext->getCurrentDomain() ? 'Domain with hostname "' . $contentContext->getCurrentDomain()->getHostname() . '" matched.' : 'No specific domain matched.'; - throw new Exception\NoSiteNodeException(sprintf('No site node found for request path "%s". %s', $requestPath, $currentDomain), 1346949728); + throw new Exception\NoSiteNodeException(sprintf('No site node found for request path "%s". %s', $requestPath, $currentDomain), 1719778849); } if ($requestPathWithoutContext === '') { $node = $siteNode; } else { + $requestPathWithoutContext = $this->truncateUriPathSuffix((string)$requestPathWithoutContext); $relativeNodePath = $this->getRelativeNodePathByUriPathSegmentProperties($siteNode, $requestPathWithoutContext); $node = ($relativeNodePath !== false) ? $siteNode->getNode($relativeNodePath) : null; } if (!$node instanceof NodeInterface) { - throw new Exception\NoSuchNodeException(sprintf('No node found on request path "%s"', $requestPath), 1346949857); + throw new NoSuchNodeException(sprintf('No node found on request path "%s"', $requestPath), 1719778866); } return $node; @@ -220,14 +229,16 @@ protected function convertRequestPathToNode($requestPath) * absolute node path: /sites/neostypo3org/homepage/about@user-admin * $this->value: homepage/about@user-admin * - * @param mixed $node Either a Node object or an absolute context node path - * @return ResolveResult|false ResolveResult if value could be resolved successfully, otherwise false. - * @throws Exception\MissingNodePropertyException - * @throws \Flowpack\Neos\DimensionResolver\Http\Exception\InvalidDimensionPresetLinkProcessorException - * @throws \Neos\ContentRepository\Exception\NodeException + * @param NodeInterface|string|string[] $node Either a Node object or an absolute context node path (potentially wrapped in an array as ['__contextNodePath' => '']) + * @return bool|ResolveResult An instance of ResolveResult if the route could resolve the $node, otherwise FALSE. @see DynamicRoutePart::resolveValue() + * @throws Exception\MissingNodePropertyException | NeosException | IllegalObjectTypeException | NodeException + * @see NodeIdentityConverterAspect */ protected function resolveValue($node) { + if (is_array($node) && isset($node['__contextNodePath'])) { + $node = $node['__contextNodePath']; + } if (!$node instanceof NodeInterface && !is_string($node)) { return false; } @@ -248,19 +259,64 @@ protected function resolveValue($node) $contentContext = $node->getContext(); } - if (!$node->getNodeType()->isOfType('Neos.Neos:Document')) { + if (!$this->nodeTypeIsAllowed($node)) { return false; } - $siteNode = $contentContext->getCurrentSiteNode(); if ($this->onlyMatchSiteNodes() && $node !== $siteNode) { return false; } - $routePath = $this->resolveRoutePathForNode($node); - $uriConstraints = $this->contentSubgraphUriProcessor->resolveDimensionUriConstraints($node); + try { + $nodeOrUri = $this->resolveShortcutNode($node); + } catch (Exception\InvalidShortcutException $exception) { + $this->systemLogger->debug('FrontendNodeRoutePartHandler resolveValue(): ' . $exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__)); + return false; + } + if ($nodeOrUri instanceof UriInterface) { + return new ResolveResult('', UriConstraints::fromUri($nodeOrUri), null); + } + + $uriConstraints = $this->contentSubgraphUriProcessor->resolveDimensionUriConstraints($nodeOrUri); + $uriPath = $this->resolveRoutePathForNode($nodeOrUri); + return new ResolveResult($uriPath, $uriConstraints); + } - return new ResolveResult($routePath, $uriConstraints); + /** + * Removes the configured suffix from the given $uriPath + * If the "uriPathSuffix" option is not set (or set to an empty string) the unaltered $uriPath is returned + * + * @param string $uriPath + * @return false|string|null + * @throws Exception\InvalidRequestPathException + */ + protected function truncateUriPathSuffix(string $uriPath): false|string|null + { + if (empty($this->options['uriPathSuffix'])) { + return $uriPath; + } + $suffixLength = strlen($this->options['uriPathSuffix']); + if (substr($uriPath, -$suffixLength) !== $this->options['uriPathSuffix']) { + throw new Exception\InvalidRequestPathException(sprintf('The request path "%s" doesn\'t contain the configured uriPathSuffix "%s"', $uriPath, $this->options['uriPathSuffix']), 1719778525); + } + return substr($uriPath, 0, -$suffixLength); + } + + /** + * @param NodeInterface $node + * @return NodeInterface|Uri The original, unaltered $node if it's not a shortcut node. Otherwise the nodes shortcut target (a node or an URI for external & asset shortcuts) + * @throws Exception\InvalidShortcutException + */ + protected function resolveShortcutNode(NodeInterface $node): Uri|NodeInterface + { + $resolvedNode = $this->nodeShortcutResolver->resolveShortcutTarget($node); + if (is_string($resolvedNode)) { + return new Uri($resolvedNode); + } + if (!$resolvedNode instanceof NodeInterface) { + throw new Exception\InvalidShortcutException(sprintf('Could not resolve shortcut target for node "%s"', $node->getPath()), 1719778533); + } + return $resolvedNode; } /** @@ -286,7 +342,7 @@ protected function buildContextFromPath($path, $convertLiveDimensions) /** * @param string $workspaceName - * @param array $dimensions + * @param array|null $dimensions * @return ContentContext */ protected function buildContextFromWorkspaceName($workspaceName, array $dimensions = null) @@ -301,7 +357,9 @@ protected function buildContextFromWorkspaceName($workspaceName, array $dimensio $contextProperties['dimensions'] = $dimensions; } - return $this->contextFactory->create($contextProperties); + /** @var ContentContext $context */ + $context = $this->contextFactory->create($contextProperties); + return $context; } /** @@ -311,6 +369,7 @@ protected function buildContextFromWorkspaceName($workspaceName, array $dimensio * @param string $workspaceName Name of the workspace to use in the context * @param array $dimensionsAndDimensionValues An array of dimension names (index) and their values (array of strings). See also: ContextFactory * @return ContentContext + * @throws Exception\NoSiteException */ protected function buildContextFromWorkspaceNameAndDimensions(string $workspaceName, array $dimensionsAndDimensionValues): ContentContext { @@ -318,7 +377,8 @@ protected function buildContextFromWorkspaceNameAndDimensions(string $workspaceN 'workspaceName' => $workspaceName, 'invisibleContentShown' => ($workspaceName !== 'live'), 'inaccessibleContentShown' => ($workspaceName !== 'live'), - 'dimensions' => $dimensionsAndDimensionValues + 'dimensions' => $dimensionsAndDimensionValues, + 'currentSite' => $this->getCurrentSite(), ]; /** @var ContentContext $context */ @@ -328,7 +388,8 @@ protected function buildContextFromWorkspaceNameAndDimensions(string $workspaceN } /** - * @return ContentContext|null + * @return ContentContext + * @throws NoSiteException */ protected function buildContentContextFromParameters() { @@ -348,7 +409,7 @@ protected function wasUriPathSegmentUsedDuringSubgraphDetection(): bool /** * @param string $path an absolute or relative node path which possibly contains context information, for example "/sites/somesite/the/node/path@some-workspace" - * @return string the same path without context information + * @return string|null the same path without context information */ protected function removeContextFromPath($path) { @@ -360,18 +421,14 @@ protected function removeContextFromPath($path) $path = $pivot === false ? '' : mb_substr($path, $pivot + 1); } - if ($path === '' || !NodePaths::isContextPath($path)) { + if ($path === '' || NodePaths::isContextPath($path) === false) { return $path; } - try { - if (strpos($path, '@') === 0) { + if (str_starts_with($path, '@')) { $path = '/' . $path; } $nodePathAndContext = NodePaths::explodeContextPath($path); - - // This is a workaround as we potentially prepend the context path with "/" in buildContextFromRequestPath to create a valid context path, - // the code in this class expects an empty nodePath though for the site node, so we remove it again at this point. return $nodePathAndContext['nodePath'] === '/' ? '' : $nodePathAndContext['nodePath']; } catch (\InvalidArgumentException $exception) { } @@ -389,6 +446,18 @@ protected function onlyMatchSiteNodes() return isset($this->options['onlyMatchSiteNodes']) && $this->options['onlyMatchSiteNodes'] === true; } + /** + * Whether the given $node is allowed according to the "nodeType" option + * + * @param NodeInterface $node + * @return bool + */ + protected function nodeTypeIsAllowed(NodeInterface $node) + { + $allowedNodeType = !empty($this->options['nodeType']) ? $this->options['nodeType'] : 'Neos.Neos:Document'; + return $node->getNodeType()->isOfType($allowedNodeType); + } + /** * Resolves the request path, also known as route path, identifying the given node. * @@ -399,7 +468,6 @@ protected function onlyMatchSiteNodes() * @param NodeInterface $node The node where the generated path should lead to * @return string The relative route path, possibly prefixed with a segment for identifying the current content dimension values * @throws Exception\MissingNodePropertyException - * @throws \Neos\ContentRepository\Exception\NodeException */ protected function resolveRoutePathForNode(NodeInterface $node) { @@ -422,8 +490,8 @@ protected function resolveRoutePathForNode(NodeInterface $node) * * @param NodeInterface $siteNode The site node, used as a starting point while traversing the tree * @param string $relativeRequestPath The request path, relative to the site's root path - * @return string - * @throws \Neos\ContentRepository\Exception\NodeException + * @return false|string + * @throws NodeException */ protected function getRelativeNodePathByUriPathSegmentProperties(NodeInterface $siteNode, $relativeRequestPath) { @@ -433,7 +501,6 @@ protected function getRelativeNodePathByUriPathSegmentProperties(NodeInterface $ foreach (explode('/', $relativeRequestPath) as $pathSegment) { $foundNodeInThisSegment = false; foreach ($node->getChildNodes('Neos.Neos:Document') as $node) { - /** @var NodeInterface $node */ if ($node->getProperty('uriPathSegment') === $pathSegment) { $relativeNodePathSegments[] = $node->getName(); $foundNodeInThisSegment = true; @@ -454,7 +521,6 @@ protected function getRelativeNodePathByUriPathSegmentProperties(NodeInterface $ * @param NodeInterface $node The node where the generated path should lead to * @return string A relative request path * @throws Exception\MissingNodePropertyException if the given node doesn't have a "uriPathSegment" property set - * @throws \Neos\ContentRepository\Exception\NodeException */ protected function getRequestPathByNode(NodeInterface $node) { @@ -473,8 +539,7 @@ protected function getRequestPathByNode(NodeInterface $node) $requestPathSegments = []; while ($currentNode instanceof NodeInterface && $currentNode->getParentPath() !== SiteService::SITES_ROOT_PATH) { if (!$currentNode->hasProperty('uriPathSegment')) { - throw new Exception\MissingNodePropertyException(sprintf('Missing "uriPathSegment" property for node "%s". Nodes can be migrated with the "flow node:repair" command.', - $node->getPath()), 1415020326); + throw new Exception\MissingNodePropertyException(sprintf('Missing "uriPathSegment" property for node "%s". Nodes can be migrated with the "flow node:repair" command.', $node->getPath()), 1719774283); } $pathSegment = $currentNode->getProperty('uriPathSegment'); @@ -484,4 +549,46 @@ protected function getRequestPathByNode(NodeInterface $node) return implode('/', array_reverse($requestPathSegments)); } + + /** + * Determines the currently active site based on the "requestUriHost" parameter (that has to be set via HTTP middleware) + * + * @return Site + * @throws Exception\NoSiteException + */ + protected function getCurrentSite() + { + $requestUriHost = $this->parameters->getValue('requestUriHost'); + if (!is_string($requestUriHost)) { + throw new Exception\NoSiteException('Failed to determine current site because the "requestUriHost" Routing parameter is not set', 1719778761); + } + if (!array_key_exists($requestUriHost, $this->siteByHostRuntimeCache)) { + $this->siteByHostRuntimeCache[$requestUriHost] = $this->getSiteByHostName($requestUriHost); + } + return $this->siteByHostRuntimeCache[$requestUriHost]; + } + + /** + * Returns a site matching the given $hostName + * + * @param string $hostName + * @return Site + * @throws Exception\NoSiteException + */ + protected function getSiteByHostName(string $hostName) + { + $domain = $this->domainRepository->findOneByHost($hostName, true); + if ($domain !== null) { + return $domain->getSite(); + } + try { + $defaultSite = $this->siteRepository->findDefault(); + if ($defaultSite === null) { + throw new Exception\NoSiteException('Failed to determine current site because no default site is configured', 1719778771); + } + } catch (NeosException $exception) { + throw new Exception\NoSiteException(sprintf('Failed to determine current site because no domain is specified matching host of "%s" and no default site could be found: %s', $hostName, $exception->getMessage()), 1719778778, $exception); + } + return $defaultSite; + } } diff --git a/composer.json b/composer.json index a21bdab..375c3b8 100644 --- a/composer.json +++ b/composer.json @@ -4,8 +4,8 @@ "license": "GPL-3.0+", "description": "A support package for Neos CMS that allows for arbitrary content dimension resolution.", "require": { - "neos/neos": "^7.0 || ^8.0 || dev-master", - "neos/flow": "^7.0 || ^8.0 || dev-master" + "neos/neos": "^8.0", + "neos/flow": "^8.0" }, "autoload": { "psr-4": { From 5c17a4b9b92d5f4778bbbeebfade29a43c090d6f Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Tue, 2 Jul 2024 19:18:32 +0200 Subject: [PATCH 2/6] BUGFIX: Respect the configured value for `uriPathSuffix` Inspired by PR #11 opened by @c4ll-m3-j4ck this adds the missing piece of the puzzle to support the `uriPathSuffix`. --- Classes/Routing/FrontendNodeRoutePartHandler.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Classes/Routing/FrontendNodeRoutePartHandler.php b/Classes/Routing/FrontendNodeRoutePartHandler.php index f9abd5b..be6b37a 100644 --- a/Classes/Routing/FrontendNodeRoutePartHandler.php +++ b/Classes/Routing/FrontendNodeRoutePartHandler.php @@ -279,6 +279,11 @@ protected function resolveValue($node) $uriConstraints = $this->contentSubgraphUriProcessor->resolveDimensionUriConstraints($nodeOrUri); $uriPath = $this->resolveRoutePathForNode($nodeOrUri); + + if (!empty($this->options['uriPathSuffix']) && $node->getParentPath() !== SiteService::SITES_ROOT_PATH) { + $uriConstraints = $uriConstraints->withPathSuffix($this->options['uriPathSuffix']); + } + return new ResolveResult($uriPath, $uriConstraints); } From db053adce24e1b72b495c8099223386e33fc8837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Tue, 2 Jul 2024 20:49:02 +0200 Subject: [PATCH 3/6] Add all necessary Exception use statements --- .../Routing/FrontendNodeRoutePartHandler.php | 51 ++++++++++--------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/Classes/Routing/FrontendNodeRoutePartHandler.php b/Classes/Routing/FrontendNodeRoutePartHandler.php index be6b37a..4adc62b 100644 --- a/Classes/Routing/FrontendNodeRoutePartHandler.php +++ b/Classes/Routing/FrontendNodeRoutePartHandler.php @@ -36,8 +36,13 @@ use Neos\Neos\Domain\Service\SiteService; use Neos\Neos\Exception as NeosException; use Neos\Neos\Routing\Exception; +use Neos\Neos\Routing\Exception\InvalidRequestPathException; +use Neos\Neos\Routing\Exception\InvalidShortcutException; +use Neos\Neos\Routing\Exception\MissingNodePropertyException; +use Neos\Neos\Routing\Exception\NoHomepageException; use Neos\Neos\Routing\Exception\NoSiteException; use Neos\Neos\Routing\Exception\NoSuchNodeException; +use Neos\Neos\Routing\Exception\NoWorkspaceException; use Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface; use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; @@ -130,7 +135,7 @@ protected function findValueToMatch($requestPath) * @param string $requestPath The request path (without leading "/", relative to the current Site Node) * @return bool|MatchResult An instance of MatchResult if the route matches the $requestPath, otherwise FALSE. @see DynamicRoutePart::matchValue() * @throws \Exception - * @throws Exception\NoHomepageException if no node could be found on the homepage (empty $requestPath) + * @throws NoHomepageException if no node could be found on the homepage (empty $requestPath) */ protected function matchValue($requestPath) { @@ -146,7 +151,7 @@ protected function matchValue($requestPath) } catch (Exception $exception) { $this->systemLogger->debug('FrontendNodeRoutePartHandler matchValue(): ' . $exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__)); if ($requestPath === '') { - throw new Exception\NoHomepageException('Homepage could not be loaded. Probably you haven\'t imported a site yet', 1719778805, $exception); + throw new NoHomepageException('Homepage could not be loaded. Probably you haven\'t imported a site yet', 1719778805, $exception); } return false; @@ -175,10 +180,10 @@ protected function matchValue($requestPath) * * @param string $requestPath The request path, for example /the/node/path@some-workspace * @return NodeInterface - * @throws Exception\NoSiteException - * @throws Exception\NoSiteNodeException + * @throws NoSiteException + * @throws NoSiteNodeException * @throws NoSuchNodeException - * @throws Exception\NoWorkspaceException + * @throws NoWorkspaceException * @throws NodeException */ protected function convertRequestPathToNode($requestPath) @@ -188,18 +193,18 @@ protected function convertRequestPathToNode($requestPath) $workspace = $contentContext->getWorkspace(); if ($workspace === null) { - throw new Exception\NoWorkspaceException(sprintf('No workspace found for request path "%s"', $requestPath), 1719778821); + throw new NoWorkspaceException(sprintf('No workspace found for request path "%s"', $requestPath), 1719778821); } $site = $contentContext->getCurrentSite(); if ($site === null) { - throw new Exception\NoSiteException(sprintf('No site found for request path "%s"', $requestPath), 1719778836); + throw new NoSiteException(sprintf('No site found for request path "%s"', $requestPath), 1719778836); } $siteNode = $contentContext->getCurrentSiteNode(); if ($siteNode === null) { $currentDomain = $contentContext->getCurrentDomain() ? 'Domain with hostname "' . $contentContext->getCurrentDomain()->getHostname() . '" matched.' : 'No specific domain matched.'; - throw new Exception\NoSiteNodeException(sprintf('No site node found for request path "%s". %s', $requestPath, $currentDomain), 1719778849); + throw new NoSiteNodeException(sprintf('No site node found for request path "%s". %s', $requestPath, $currentDomain), 1719778849); } if ($requestPathWithoutContext === '') { @@ -231,7 +236,7 @@ protected function convertRequestPathToNode($requestPath) * * @param NodeInterface|string|string[] $node Either a Node object or an absolute context node path (potentially wrapped in an array as ['__contextNodePath' => '']) * @return bool|ResolveResult An instance of ResolveResult if the route could resolve the $node, otherwise FALSE. @see DynamicRoutePart::resolveValue() - * @throws Exception\MissingNodePropertyException | NeosException | IllegalObjectTypeException | NodeException + * @throws MissingNodePropertyException | NeosException | IllegalObjectTypeException | NodeException * @see NodeIdentityConverterAspect */ protected function resolveValue($node) @@ -269,7 +274,7 @@ protected function resolveValue($node) try { $nodeOrUri = $this->resolveShortcutNode($node); - } catch (Exception\InvalidShortcutException $exception) { + } catch (InvalidShortcutException $exception) { $this->systemLogger->debug('FrontendNodeRoutePartHandler resolveValue(): ' . $exception->getMessage(), LogEnvironment::fromMethodName(__METHOD__)); return false; } @@ -293,7 +298,7 @@ protected function resolveValue($node) * * @param string $uriPath * @return false|string|null - * @throws Exception\InvalidRequestPathException + * @throws InvalidRequestPathException */ protected function truncateUriPathSuffix(string $uriPath): false|string|null { @@ -302,7 +307,7 @@ protected function truncateUriPathSuffix(string $uriPath): false|string|null } $suffixLength = strlen($this->options['uriPathSuffix']); if (substr($uriPath, -$suffixLength) !== $this->options['uriPathSuffix']) { - throw new Exception\InvalidRequestPathException(sprintf('The request path "%s" doesn\'t contain the configured uriPathSuffix "%s"', $uriPath, $this->options['uriPathSuffix']), 1719778525); + throw new InvalidRequestPathException(sprintf('The request path "%s" doesn\'t contain the configured uriPathSuffix "%s"', $uriPath, $this->options['uriPathSuffix']), 1719778525); } return substr($uriPath, 0, -$suffixLength); } @@ -310,7 +315,7 @@ protected function truncateUriPathSuffix(string $uriPath): false|string|null /** * @param NodeInterface $node * @return NodeInterface|Uri The original, unaltered $node if it's not a shortcut node. Otherwise the nodes shortcut target (a node or an URI for external & asset shortcuts) - * @throws Exception\InvalidShortcutException + * @throws InvalidShortcutException */ protected function resolveShortcutNode(NodeInterface $node): Uri|NodeInterface { @@ -319,7 +324,7 @@ protected function resolveShortcutNode(NodeInterface $node): Uri|NodeInterface return new Uri($resolvedNode); } if (!$resolvedNode instanceof NodeInterface) { - throw new Exception\InvalidShortcutException(sprintf('Could not resolve shortcut target for node "%s"', $node->getPath()), 1719778533); + throw new InvalidShortcutException(sprintf('Could not resolve shortcut target for node "%s"', $node->getPath()), 1719778533); } return $resolvedNode; } @@ -374,7 +379,7 @@ protected function buildContextFromWorkspaceName($workspaceName, array $dimensio * @param string $workspaceName Name of the workspace to use in the context * @param array $dimensionsAndDimensionValues An array of dimension names (index) and their values (array of strings). See also: ContextFactory * @return ContentContext - * @throws Exception\NoSiteException + * @throws NoSiteException */ protected function buildContextFromWorkspaceNameAndDimensions(string $workspaceName, array $dimensionsAndDimensionValues): ContentContext { @@ -472,7 +477,7 @@ protected function nodeTypeIsAllowed(NodeInterface $node) * * @param NodeInterface $node The node where the generated path should lead to * @return string The relative route path, possibly prefixed with a segment for identifying the current content dimension values - * @throws Exception\MissingNodePropertyException + * @throws MissingNodePropertyException */ protected function resolveRoutePathForNode(NodeInterface $node) { @@ -525,7 +530,7 @@ protected function getRelativeNodePathByUriPathSegmentProperties(NodeInterface $ * * @param NodeInterface $node The node where the generated path should lead to * @return string A relative request path - * @throws Exception\MissingNodePropertyException if the given node doesn't have a "uriPathSegment" property set + * @throws MissingNodePropertyException if the given node doesn't have a "uriPathSegment" property set */ protected function getRequestPathByNode(NodeInterface $node) { @@ -544,7 +549,7 @@ protected function getRequestPathByNode(NodeInterface $node) $requestPathSegments = []; while ($currentNode instanceof NodeInterface && $currentNode->getParentPath() !== SiteService::SITES_ROOT_PATH) { if (!$currentNode->hasProperty('uriPathSegment')) { - throw new Exception\MissingNodePropertyException(sprintf('Missing "uriPathSegment" property for node "%s". Nodes can be migrated with the "flow node:repair" command.', $node->getPath()), 1719774283); + throw new MissingNodePropertyException(sprintf('Missing "uriPathSegment" property for node "%s". Nodes can be migrated with the "flow node:repair" command.', $node->getPath()), 1719774283); } $pathSegment = $currentNode->getProperty('uriPathSegment'); @@ -559,13 +564,13 @@ protected function getRequestPathByNode(NodeInterface $node) * Determines the currently active site based on the "requestUriHost" parameter (that has to be set via HTTP middleware) * * @return Site - * @throws Exception\NoSiteException + * @throws NoSiteException */ protected function getCurrentSite() { $requestUriHost = $this->parameters->getValue('requestUriHost'); if (!is_string($requestUriHost)) { - throw new Exception\NoSiteException('Failed to determine current site because the "requestUriHost" Routing parameter is not set', 1719778761); + throw new NoSiteException('Failed to determine current site because the "requestUriHost" Routing parameter is not set', 1719778761); } if (!array_key_exists($requestUriHost, $this->siteByHostRuntimeCache)) { $this->siteByHostRuntimeCache[$requestUriHost] = $this->getSiteByHostName($requestUriHost); @@ -578,7 +583,7 @@ protected function getCurrentSite() * * @param string $hostName * @return Site - * @throws Exception\NoSiteException + * @throws NoSiteException */ protected function getSiteByHostName(string $hostName) { @@ -589,10 +594,10 @@ protected function getSiteByHostName(string $hostName) try { $defaultSite = $this->siteRepository->findDefault(); if ($defaultSite === null) { - throw new Exception\NoSiteException('Failed to determine current site because no default site is configured', 1719778771); + throw new NoSiteException('Failed to determine current site because no default site is configured', 1719778771); } } catch (NeosException $exception) { - throw new Exception\NoSiteException(sprintf('Failed to determine current site because no domain is specified matching host of "%s" and no default site could be found: %s', $hostName, $exception->getMessage()), 1719778778, $exception); + throw new NoSiteException(sprintf('Failed to determine current site because no domain is specified matching host of "%s" and no default site could be found: %s', $hostName, $exception->getMessage()), 1719778778, $exception); } return $defaultSite; } From 966eb63dd8a134095f6d97b2f74cc3a890262b30 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Tue, 2 Jul 2024 23:39:34 +0200 Subject: [PATCH 4/6] TASK: Add another missing exception import --- Classes/Routing/FrontendNodeRoutePartHandler.php | 1 + 1 file changed, 1 insertion(+) diff --git a/Classes/Routing/FrontendNodeRoutePartHandler.php b/Classes/Routing/FrontendNodeRoutePartHandler.php index 4adc62b..9666308 100644 --- a/Classes/Routing/FrontendNodeRoutePartHandler.php +++ b/Classes/Routing/FrontendNodeRoutePartHandler.php @@ -43,6 +43,7 @@ use Neos\Neos\Routing\Exception\NoSiteException; use Neos\Neos\Routing\Exception\NoSuchNodeException; use Neos\Neos\Routing\Exception\NoWorkspaceException; +use Neos\Neos\Routing\Exception\NoSiteNodeException; use Neos\Neos\Routing\FrontendNodeRoutePartHandlerInterface; use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; From c7d156cb6044623842ef1c512974198c3610da18 Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Tue, 2 Jul 2024 23:39:47 +0200 Subject: [PATCH 5/6] TASK: Tweak `@throws` --- Classes/Routing/FrontendNodeRoutePartHandler.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Classes/Routing/FrontendNodeRoutePartHandler.php b/Classes/Routing/FrontendNodeRoutePartHandler.php index 9666308..6852911 100644 --- a/Classes/Routing/FrontendNodeRoutePartHandler.php +++ b/Classes/Routing/FrontendNodeRoutePartHandler.php @@ -181,6 +181,8 @@ protected function matchValue($requestPath) * * @param string $requestPath The request path, for example /the/node/path@some-workspace * @return NodeInterface + * @throws IllegalObjectTypeException + * @throws InvalidRequestPathException * @throws NoSiteException * @throws NoSiteNodeException * @throws NoSuchNodeException @@ -237,7 +239,7 @@ protected function convertRequestPathToNode($requestPath) * * @param NodeInterface|string|string[] $node Either a Node object or an absolute context node path (potentially wrapped in an array as ['__contextNodePath' => '']) * @return bool|ResolveResult An instance of ResolveResult if the route could resolve the $node, otherwise FALSE. @see DynamicRoutePart::resolveValue() - * @throws MissingNodePropertyException | NeosException | IllegalObjectTypeException | NodeException + * @throws MissingNodePropertyException | NeosException | IllegalObjectTypeException * @see NodeIdentityConverterAspect */ protected function resolveValue($node) From 2367c09d9d333d862e298a5a88b6b56c4b1e5f1b Mon Sep 17 00:00:00 2001 From: Karsten Dambekalns Date: Tue, 2 Jul 2024 23:40:04 +0200 Subject: [PATCH 6/6] TASK: Add `ext-mbstring` as dependency --- composer.json | 1 + 1 file changed, 1 insertion(+) diff --git a/composer.json b/composer.json index 375c3b8..0b3b328 100644 --- a/composer.json +++ b/composer.json @@ -4,6 +4,7 @@ "license": "GPL-3.0+", "description": "A support package for Neos CMS that allows for arbitrary content dimension resolution.", "require": { + "ext-mbstring": "*", "neos/neos": "^8.0", "neos/flow": "^8.0" },