Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
mhsdesign committed Feb 14, 2024
1 parent f0e4deb commit 54ebe9f
Show file tree
Hide file tree
Showing 7 changed files with 358 additions and 62 deletions.
Expand Up @@ -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.**
*/
Expand Down
156 changes: 111 additions & 45 deletions Neos.Neos/Classes/FrontendRouting/NodeUriBuilder.php
Expand Up @@ -14,78 +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
) {
}

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;
}



95 changes: 95 additions & 0 deletions Neos.Neos/Classes/FrontendRouting/NodeUriSpecification.php
@@ -0,0 +1,95 @@
<?php

declare(strict_types=1);

namespace Neos\Neos\FrontendRouting;

use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Routing\Dto\RouteParameters;
use Neos\Neos\Domain\Repository\SiteRepository;
use Psr\Http\Message\UriInterface;

#[Flow\Scope('singleton')]
class NodeUriBuilder
{
/**
* If a uri is cross-linked to another cr than the one in the current request, the resulting uri will still be absolute and contain the host of the other site.
*/
public function uriFor(NodeUriSpecification $specification, NodeUriResolveContext $context): UriInterface;

/**
* Will always return an absolute uri.
* For nodes of the current cr (by request) it will use the current base uri. For foreign nodes the host will be inferred by the site entity.
*/
public function absoluteUriFor(NodeUriSpecification $specification, NodeUriResolveContext $context): UriInterface;

/**
* Will resolve a relative uri with fully qualified node encoded.
*/
public function previewUriFor(NodeUriSpecification $specification): UriInterface;
}

final readonly class NodeUriResolveContext
{
public function __construct(
UriInterface $baseUri,
RouteParameters $routeParameters
) {
}

public static function fromActionRequest(ActionRequest $request): self;
}


final readonly class NodeUriSpecification
{
public function __construct(
public NodeIdentity $node,
public string $format,
public array $routingArguments,
public array $queryParameters,
) {
}
}

/**
* Must be used with named arguments.
*/
public static function create(
?bool $absolute = null,
?string $format = null,
?array $routingArguments = null,
?array $queryParameters = null
) {
return new self(
$absolute ?? false,
$format ?? '',
$routingArguments ?? [],
$queryParameters ?? [],
);
}

public static function fromActionRequest(ActionRequest $request): self
{
return self::create(format: $request->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,
);
}
}
72 changes: 65 additions & 7 deletions Neos.Neos/Classes/Fusion/NodeUriImplementation.php
Expand Up @@ -18,11 +18,18 @@
use Neos\ContentRepositoryRegistry\ContentRepositoryRegistry;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Log\Utility\LogEnvironment;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\ActionResponse;
use Neos\Flow\Mvc\Controller\Arguments;
use Neos\Flow\Mvc\Controller\ControllerContext;
use Neos\Flow\Mvc\Exception\NoMatchingRouteException;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Fusion\Exception;
use Neos\Fusion\FusionObjects\AbstractFusionObject;
use Neos\Neos\Exception as NeosException;
use Neos\Neos\FrontendRouting\NodeAddressFactory;
use Neos\Neos\FrontendRouting\NodeUriBuilder;
use Neos\Neos\Service\LinkingService;
use Psr\Log\LoggerInterface;

/**
Expand All @@ -42,6 +49,12 @@ class NodeUriImplementation extends AbstractFusionObject
*/
protected $systemLogger;

/**
* @Flow\Inject
* @var LinkingService
*/
protected $linkingService;

/**
* A node object or a string node path or NULL to resolve the current document node
*/
Expand Down Expand Up @@ -120,13 +133,8 @@ public function getBaseNodeName()
return $this->fusionValue('baseNodeName');
}

/**
* Render the Uri.
*
* @return string The rendered URI or NULL if no URI could be resolved for the given node
* @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
*/
public function evaluate()

public function evaluate2()
{
$baseNode = null;
$baseNodeName = $this->getBaseNodeName() ?: 'documentNode';
Expand Down Expand Up @@ -175,4 +183,54 @@ public function evaluate()
}
return '';
}

/**
* Render the Uri.
*
* @return string The rendered URI or NULL if no URI could be resolved for the given node
*/
public function evaluate()
{
$baseNode = null;
$baseNodeName = $this->getBaseNodeName() ?: 'documentNode';
$currentContext = $this->runtime->getCurrentContext();
if (isset($currentContext[$baseNodeName])) {
$baseNode = $currentContext[$baseNodeName];
} else {
throw new NeosException(sprintf('Could not find a node instance in Fusion context with name "%s" and no node instance was given to the node argument. Set a node instance in the Fusion context or pass a node object to resolve the URI.', $baseNodeName), 1373100400);
}

$actionRequest = $this->getRuntime()->fusionGlobals->get('request');
if (!$actionRequest instanceof ActionRequest) {
throw new \Neos\Flow\Exception('The request is expected to be an ActionRequest.', 1707728033);
}

$uriBuilder = new UriBuilder();
$uriBuilder->setRequest($actionRequest);

$fakeControllerContext = new ControllerContext(
$actionRequest,
new ActionResponse(),
new Arguments(),
$uriBuilder
);

$node = $this->getNode();
try {
return $this->linkingService->createNodeUri(
$fakeControllerContext,
$node,
$baseNode,
$this->getFormat(),
$this->isAbsolute(),
$this->getAdditionalParams(),
$this->getSection(),
$this->getAddQueryString(),
$this->getArgumentsToBeExcludedFromQueryString()
);
} catch (NoMatchingRouteException) {
$this->systemLogger->warning(sprintf('Could not resolve "%s" to a node uri. Arguments: %s', $node instanceof Node ? $node->nodeAggregateId->value : $node, json_encode($uriBuilder->getLastArguments())), LogEnvironment::fromMethodName(__METHOD__));
return '';
}
}
}

0 comments on commit 54ebe9f

Please sign in to comment.