diff --git a/Neos.Neos/Classes/FrontendRouting/FrontendNodeRoutePartHandlerInterface.php b/Neos.Neos/Classes/FrontendRouting/FrontendNodeRoutePartHandlerInterface.php index 40766ae9654..feb754ba03e 100644 --- a/Neos.Neos/Classes/FrontendRouting/FrontendNodeRoutePartHandlerInterface.php +++ b/Neos.Neos/Classes/FrontendRouting/FrontendNodeRoutePartHandlerInterface.php @@ -20,8 +20,6 @@ * Marker interface which can be used to replace the currently used FrontendNodeRoutePartHandler, * to e.g. use the one with localization support. * - * TODO CORE MIGRATION - * * **See {@see EventSourcedFrontendNodeRoutePartHandler} documentation for a * detailed explanation of the Frontend Routing process.** */ diff --git a/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php b/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php index 6e90d5b1d23..98a374bc247 100644 --- a/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php +++ b/Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php @@ -14,82 +14,144 @@ namespace Neos\Neos\FrontendRouting; +use GuzzleHttp\Psr7\ServerRequest; use GuzzleHttp\Psr7\Uri; use Neos\Flow\Annotations as Flow; use Neos\Flow\Http\Exception as HttpException; +use Neos\Flow\Http\Helper\RequestInformationHelper; +use Neos\Flow\Http\ServerRequestAttributes; use Neos\Flow\Mvc\ActionRequest; use Neos\Flow\Mvc\Exception\NoMatchingRouteException; +use Neos\Flow\Mvc\Routing\Dto\RouteParameters; use Neos\Flow\Mvc\Routing\Exception\MissingActionNameException; +use Neos\Flow\Mvc\Routing\RouterInterface; use Neos\Flow\Mvc\Routing\UriBuilder; +use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult; +use Neos\Utility\Arrays; +use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\UriInterface; + /** - * Builds URIs to nodes, taking workspace (live / shared / user) into account. - * This class can also be used in order to render "preview" URLs to nodes - * that are not in the live workspace (in the Neos Backend and shared workspaces) + * objects yaml to make it possible to inject the `NodeUriBuilder` for the happy web path. + * will use the BaseUriProvider which uses the bootstraps active request handler */ -final class NodeUriBuilder +/* +Neos\Neos\FrontendRouting\NodeUriBuilder: + factoryObjectName: Neos\Neos\FrontendRouting\NodeUriBuilderFactory + factoryMethodName: forRequest + arguments: + 1: + object: + factoryObjectName: Neos\Flow\Http\BaseUriProvider + factoryMethodName: getConfiguredBaseUriOrFallbackToCurrentRequest +*/ + + +#[Flow\Scope('singleton')] +class NodeUriBuilderFactory { - private UriBuilder $uriBuilder; + public function __construct( + private RouterInterface $router + ) { + } - /** - * @Flow\Autowiring(false) - */ - private function __construct(UriBuilder $uriBuilder) + public function forRequest(ServerRequestInterface $request) { - $this->uriBuilder = $uriBuilder; + $baseUri = RequestInformationHelper::generateBaseUri($request); + $routeParameters = $request->getAttribute(ServerRequestAttributes::ROUTING_PARAMETERS) + ?? RouteParameters::createEmpty(); + return new NodeUriBuilder($this->router, $baseUri, $routeParameters); } - public static function fromRequest(ActionRequest $request): self + public function forBaseUri(UriInterface $baseUri) { - $uriBuilder = new UriBuilder(); - $uriBuilder->setRequest($request); + $siteDetectionResult = SiteDetectionResult::fromRequest(new ServerRequest(method: 'GET', uri: $baseUri)); + $routeParameters = $siteDetectionResult->storeInRouteParameters(RouteParameters::createEmpty()); - return new self($uriBuilder); + return new NodeUriBuilder($this->router, $baseUri, $routeParameters); } +} - public static function fromUriBuilder(UriBuilder $uriBuilder): self - { - return new self($uriBuilder); +class NodeUriBuilder +{ + /** + * @internal + */ + public function __construct( + RouterInterface $router, + UriInterface $baseUri, + RouteParameters $routeParameters + ) { } /** - * Renders an URI for the given $nodeAddress - * If the node belongs to the live workspace, the public URL is generated - * Otherwise a preview URI is rendered (@see previewUriFor()) - * - * Note: Shortcut nodes will be resolved in the RoutePartHandler thus the resulting URI will point - * to the shortcut target (node, asset or external URI) + * Return human readable host relative uris if the cr of the current request matches the one of the specified node. + * For cross-links to another cr the resulting uri be absolute and contain the host of the other site's domain. * - * @param NodeAddress $nodeAddress - * @return UriInterface - * @throws NoMatchingRouteException if the node address does not exist + * As the human readable uris are only routed for nodes of the live workspace (see DocumentUriProjection) + * This method requires the node to be passed to be in the live workspace and will throw otherwise. */ - public function uriFor(NodeAddress $nodeAddress): UriInterface - { - if (!$nodeAddress->workspaceName->isLive()) { - // we cannot build a human-readable uri using the showAction as - // the DocumentUriPathProjection only handles the live workspace - return $this->previewUriFor($nodeAddress); - } - return new Uri($this->uriBuilder->uriFor('show', ['node' => $nodeAddress], 'Frontend\Node', 'Neos.Neos')); - } + public function uriFor(NodeUriSpecification $specification): UriInterface; /** - * Renders a stable "preview" URI for the given $nodeAddress - * A preview URI is used to display a node that is not public yet (i.e. not in a live workspace). + * Return human readable absolute uris with host, independent if the node is cross linked or of the current request. + * For nodes of the current cr the passed base uri will be used as host. For cross-linked nodes the host will be derived by the site's domain. * - * @param NodeAddress $nodeAddress - * @return UriInterface - * @throws NoMatchingRouteException if the node address does not exist + * As the human readable uris are only routed for nodes of the live workspace (see DocumentUriProjection) + * This method requires the node to be passed to be in the live workspace and will throw otherwise. */ - public function previewUriFor(NodeAddress $nodeAddress): UriInterface - { - return new Uri($this->uriBuilder->uriFor( - 'preview', - ['node' => $nodeAddress->serializeForUri()], - 'Frontend\Node', - 'Neos.Neos' - )); + public function absoluteUriFor(NodeUriSpecification $specification): UriInterface; + + /** + * Returns a host relative uri with fully qualified node as query parameter encoded. + */ + public function previewUriFor(NodeUriSpecification $specification): UriInterface; +} + +final readonly class NodeUriSpecification +{ + private function __construct( + public NodeIdentity $node, + public string $format, + public array $routingArguments, + ) { } + + public static function create(NodeIdentity $node): self; + + public function withFormat(): self; + + /** @deprecated if you meant to append query parameters, please use withAdditionalQueryParameters instead */ + public function withRoutingArguments(): self; } + + + + +/** + * This context can be inferred from the current request. + * + * For generating node uris in cli context, you can leverage `fromBaseUri` and pass in the desired base uri, + * Wich will be used for when generating host absolute uris. + * If the base uri does not contain a host, absolute uris which would contain the host of the current request + * like from `absoluteUriFor`, will be generated without host. + * + * Flows base uri configuration is ignored if not specifically added via `mergeBaseUri` + */ +final readonly class NodeUriResolveContext // todo find better name +{ + public function __construct( + + ) { + } + + public static function fromActionRequest(ActionRequest $request): self; + + public static function fromBaseUri(UriInterface $baseUri): self; + + public function mergeBaseUri(UriInterface $baseUri): self; +} + + + diff --git a/Neos.Neos/Classes/FrontendRouting/NodeUriSpecification.php b/Neos.Neos/Classes/FrontendRouting/NodeUriSpecification.php new file mode 100644 index 00000000000..7e77af2497e --- /dev/null +++ b/Neos.Neos/Classes/FrontendRouting/NodeUriSpecification.php @@ -0,0 +1,95 @@ +getFormat()); + } + + /** + * Must be used with named arguments. + */ + public function with( + ?bool $absolute = null, + ?string $format = null, + ?array $routingArguments = null, + ?array $queryParameters = null + ) { + return new self( + $absolute ?? $this->absolute, + $format ?? $this->format, + $routingArguments ?? $this->routingArguments, + $queryParameters ?? $this->queryParameters, + ); + } +}