Skip to content

Commit ee9eff9

Browse files
committed
REFACTOR: Extract URI Generation for node into separate service
... because this is useful in different rendering scenarios
1 parent f003a19 commit ee9eff9

2 files changed

Lines changed: 167 additions & 150 deletions

File tree

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Flowpack\DecoupledContentStore\NodeRendering;
6+
7+
use Neos\Flow\Annotations as Flow;
8+
use Flowpack\DecoupledContentStore\Exception;
9+
use Flowpack\DecoupledContentStore\NodeRendering\Extensibility\DocumentRendererInterface;
10+
use GuzzleHttp\Psr7\ServerRequest;
11+
use Neos\ContentRepository\Domain\Model\NodeInterface;
12+
use Neos\Flow\Configuration\ConfigurationManager;
13+
use Neos\Flow\Http\BaseUriProvider;
14+
use Neos\Flow\Http\Helper\RequestInformationHelper;
15+
use Neos\Flow\Http\ServerRequestAttributes;
16+
use Neos\Flow\Mvc\ActionRequest;
17+
use Neos\Flow\Mvc\ActionResponse;
18+
use Neos\Flow\Mvc\Controller\Arguments;
19+
use Neos\Flow\Mvc\Controller\ControllerContext;
20+
use Neos\Flow\Mvc\Routing\Dto\RouteParameters;
21+
use Neos\Flow\Mvc\Routing\UriBuilder;
22+
use Neos\Flow\Security\Context as SecurityContext;
23+
use Neos\Neos\Domain\Model\Site;
24+
use Neos\Neos\Service\LinkingService;
25+
use Neos\Utility\ObjectAccess;
26+
27+
/**
28+
* Helper which is able to render a Node URI in headless / CLI mode. Required usually inside
29+
* {@see DocumentRendererInterface::renderDocumentNodeVariant()}
30+
*/
31+
final class NodeRenderingUriService
32+
{
33+
#[Flow\Inject]
34+
protected ConfigurationManager $configurationManager;
35+
36+
/**
37+
* NOTE: we need to use EAGER injection here, because in buildControllerContextAndSetBaseUri(), we
38+
* directly set a property of the BaseUriProvider using ObjectAccess::setProperty() with forceDirectAccess.
39+
*/
40+
#[Flow\Inject(lazy: false)]
41+
protected BaseUriProvider $baseUriProvider;
42+
43+
#[Flow\Inject]
44+
protected LinkingService $linkingService;
45+
46+
#[Flow\Inject]
47+
protected SecurityContext $securityContext;
48+
49+
/**
50+
* @param NodeInterface $node
51+
* @param array $arguments
52+
* @return string The resolved URI for the given node
53+
* @throws \Exception
54+
*/
55+
public function buildNodeUri(NodeInterface $node, array $arguments): string
56+
{
57+
/** @var Site $currentSite */
58+
$currentSite = $node->getContext()->getCurrentSite();
59+
if (!$currentSite->hasActiveDomains()) {
60+
throw new Exception(sprintf("Site %s has no active domain", $currentSite->getNodeName()), 1666684522);
61+
}
62+
$primaryDomain = $currentSite->getPrimaryDomain();
63+
if ((string)$primaryDomain->getScheme() === '') {
64+
throw new Exception(sprintf("Domain %s for site %s has no scheme defined", $primaryDomain->getHostname(), $currentSite->getNodeName()), 1666684523);
65+
}
66+
67+
// HINT: We cannot use a static URL here, but instead need to use an URL of the current site.
68+
// This is changed from the the old behavior, where we have changed the LinkingService in LinkingServiceAspect,
69+
// to properly generate the domain part of the routes - and this relies on the proper ControllerContext URI path.
70+
$baseControllerContext = $this->buildControllerContextAndSetBaseUri($primaryDomain->__toString(), $node, $arguments);
71+
$format = $arguments['@format'] ?? 'html';
72+
$uri = $this->linkingService->createNodeUri($baseControllerContext, $node, null, $format, true, $arguments, '', false, [], false);
73+
return self::removeQueryPartFromUri($uri);
74+
}
75+
76+
/**
77+
* @param string $uri
78+
* @param NodeInterface $node
79+
* @param array $arguments
80+
* @return ControllerContext
81+
*/
82+
public function buildControllerContextAndSetBaseUri(string $uri, NodeInterface $node, array $arguments = [])
83+
{
84+
$request = $this->buildFakeRequest($uri, $node);
85+
if (isset($arguments['@format'])) {
86+
$request->setFormat($arguments['@format']);
87+
}
88+
89+
// NASTY SIDE-EFFECT: we not only build the controller context, but we also need to inject the "current" base URL to BaseUriProvider,
90+
// as this is now (Flow 6.x) used in the UriBuilder to determine the domain.
91+
$baseUri = rtrim(RequestInformationHelper::generateBaseUri($request->getHttpRequest())->__toString(), '/');
92+
ObjectAccess::setProperty($this->baseUriProvider, 'configuredBaseUri', $baseUri, true);
93+
94+
ObjectAccess::setProperty($this->securityContext, 'initialized', true, true);
95+
$this->securityContext->setRequest($request);
96+
$uriBuilder = $this->uriBuilderForRequest($request);
97+
98+
return new ControllerContext(
99+
$request,
100+
new ActionResponse(),
101+
new Arguments([]),
102+
$uriBuilder
103+
);
104+
}
105+
106+
/**
107+
* @param string $uri
108+
* @param NodeInterface $node
109+
* @return ActionRequest
110+
*/
111+
protected function buildFakeRequest($uri, NodeInterface $node): ActionRequest
112+
{
113+
$_SERVER['FLOW_REWRITEURLS'] = '1';
114+
115+
$httpRequest = new ServerRequest('GET', $uri);
116+
$routingParameters = RouteParameters::createEmpty()->withParameter('requestUriHost', $httpRequest->getUri()->getHost());
117+
$httpRequest = $httpRequest->withAttribute(ServerRequestAttributes::ROUTING_PARAMETERS, $routingParameters);
118+
119+
$request = ActionRequest::fromHttpRequest($httpRequest);
120+
$request->setControllerObjectName('Neos\Neos\Controller\Frontend\NodeController');
121+
$request->setControllerActionName('show');
122+
$request->setFormat('html');
123+
$request->setArgument('node', $node->getContextPath());
124+
125+
return $request;
126+
}
127+
128+
129+
/**
130+
* @param ActionRequest $request
131+
* @return UriBuilder
132+
* @throws \Neos\Utility\Exception\PropertyNotAccessibleException
133+
*/
134+
protected function uriBuilderForRequest(ActionRequest $request): UriBuilder
135+
{
136+
$uriBuilder = new UriBuilder();
137+
$uriBuilder->setRequest($request);
138+
139+
$routesConfiguration = $this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_ROUTES);
140+
$router = ObjectAccess::getProperty($uriBuilder, 'router', true);
141+
142+
$router->setRoutesConfiguration($routesConfiguration);
143+
144+
return $uriBuilder;
145+
}
146+
147+
148+
/**
149+
* @param string $uri
150+
* @return string
151+
*/
152+
protected static function removeQueryPartFromUri($uri)
153+
{
154+
$uriData = explode('?', $uri);
155+
156+
return $uriData[0];
157+
}
158+
}

Classes/NodeRendering/Render/DocumentRenderer.php

Lines changed: 9 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use Flowpack\DecoupledContentStore\Aspects\CacheUrlMappingAspect;
66
use Flowpack\DecoupledContentStore\Exception;
77
use Flowpack\DecoupledContentStore\Core\Infrastructure\ContentReleaseLogger;
8+
use Flowpack\DecoupledContentStore\NodeRendering\NodeRenderingUriService;
89
use Flowpack\DecoupledContentStore\Transfer\Resource\Target\MultisiteFileSystemSymlinkTarget;
910
use GuzzleHttp\Psr7\ServerRequest;
1011
use Neos\Flow\Annotations as Flow;
@@ -38,6 +39,12 @@ class DocumentRenderer
3839
*/
3940
protected $useRelativeResourceUris;
4041

42+
/**
43+
* @Flow\Inject
44+
* @var NodeRenderingUriService
45+
*/
46+
protected $nodeRenderingUriService;
47+
4148
/**
4249
* Add HTTP message if rendering full content
4350
*
@@ -52,51 +59,12 @@ class DocumentRenderer
5259
*/
5360
protected $fusionView;
5461

55-
/**
56-
* @Flow\Inject
57-
* @var SecurityContext
58-
*/
59-
protected $securityContext;
60-
61-
/**
62-
* @Flow\Inject
63-
* @var ConfigurationManager
64-
*/
65-
protected $configurationManager;
66-
6762
/**
6863
* @Flow\Inject(lazy=false)
6964
* @var ResourceManager
7065
*/
7166
protected $resourceManager;
7267

73-
/**
74-
* @Flow\Inject
75-
* @var \Neos\Neos\Service\LinkingService
76-
*/
77-
protected $linkingService;
78-
79-
/**
80-
* NOTE: we need to use EAGER injection here, because in buildControllerContextAndSetBaseUri(), we
81-
* directly set a property of the BaseUriProvider using ObjectAccess::setProperty() with forceDirectAccess.
82-
*
83-
* @Flow\Inject(lazy=false)
84-
* @var BaseUriProvider
85-
*/
86-
protected $baseUriProvider;
87-
88-
/**
89-
* @var \Neos\Flow\Mvc\Routing\Router
90-
* @Flow\Inject
91-
*/
92-
protected $router;
93-
94-
/**
95-
* @Flow\Inject
96-
* @var \Neos\Flow\Property\PropertyMapper
97-
*/
98-
protected $propertyMapper;
99-
10068
/**
10169
* @Flow\Inject
10270
* @var CacheUrlMappingAspect
@@ -119,7 +87,7 @@ class DocumentRenderer
11987
public function renderDocumentNodeVariant(NodeInterface $node, array $arguments, ContentReleaseLogger $contentReleaseLogger): string
12088
{
12189
$this->cacheUrlMappingAspect->beforeDocumentRendering($contentReleaseLogger);
122-
$nodeUri = $this->buildNodeUri($node, $arguments);
90+
$nodeUri = $this->nodeRenderingUriService->buildNodeUri($node, $arguments);
12391

12492
try {
12593
$arguments['node'] = $node->getContextPath();
@@ -131,75 +99,6 @@ public function renderDocumentNodeVariant(NodeInterface $node, array $arguments,
13199
}
132100
}
133101

134-
/**
135-
* @param string $uri
136-
* @param NodeInterface $node
137-
* @param array $arguments
138-
* @return ControllerContext
139-
*/
140-
protected function buildControllerContextAndSetBaseUri(string $uri, NodeInterface $node, array $arguments = [])
141-
{
142-
$request = $this->getRequest($uri, $node);
143-
if (isset($arguments['@format'])) {
144-
$request->setFormat($arguments['@format']);
145-
}
146-
147-
// NASTY SIDE-EFFECT: we not only build the controller context, but we also need to inject the "current" base URL to BaseUriProvider,
148-
// as this is now (Flow 6.x) used in the UriBuilder to determine the domain.
149-
$baseUri = rtrim(RequestInformationHelper::generateBaseUri($request->getHttpRequest()), '/');
150-
ObjectAccess::setProperty($this->baseUriProvider, 'configuredBaseUri', $baseUri, true);
151-
152-
ObjectAccess::setProperty($this->securityContext, 'initialized', true, true);
153-
$this->securityContext->setRequest($request);
154-
$uriBuilder = $this->getUriBuilder($request);
155-
156-
return new ControllerContext(
157-
$request,
158-
new ActionResponse(),
159-
new Arguments([]),
160-
$uriBuilder
161-
);
162-
}
163-
164-
/**
165-
* @param ActionRequest $request
166-
* @return UriBuilder
167-
* @throws \Neos\Utility\Exception\PropertyNotAccessibleException
168-
*/
169-
protected function getUriBuilder($request)
170-
{
171-
$uriBuilder = new UriBuilder();
172-
$uriBuilder->setRequest($request);
173-
174-
$routesConfiguration = $this->configurationManager->getConfiguration(ConfigurationManager::CONFIGURATION_TYPE_ROUTES);
175-
$router = ObjectAccess::getProperty($uriBuilder, 'router', true);
176-
177-
$router->setRoutesConfiguration($routesConfiguration);
178-
179-
return $uriBuilder;
180-
}
181-
182-
/**
183-
* @param string $uri
184-
* @param NodeInterface $node
185-
* @return ActionRequest
186-
*/
187-
protected function getRequest($uri, NodeInterface $node)
188-
{
189-
$_SERVER['FLOW_REWRITEURLS'] = '1';
190-
191-
$httpRequest = new ServerRequest('GET', $uri);
192-
$routingParameters = RouteParameters::createEmpty()->withParameter('requestUriHost', $httpRequest->getUri()->getHost());
193-
$httpRequest = $httpRequest->withAttribute(ServerRequestAttributes::ROUTING_PARAMETERS, $routingParameters);
194-
195-
$request = ActionRequest::fromHttpRequest($httpRequest);
196-
$request->setControllerObjectName('Neos\Neos\Controller\Frontend\NodeController');
197-
$request->setControllerActionName('show');
198-
$request->setFormat('html');
199-
$request->setArgument('node', $node->getContextPath());
200-
201-
return $request;
202-
}
203102

204103
/**
205104
* Render the view of a document node
@@ -231,7 +130,7 @@ protected function renderDocumentView(NodeInterface $node, $uri, array $requestA
231130

232131
$contentReleaseLogger->info('Rendering document for URI ' . $uri, ['baseUri' => $baseUri]);
233132

234-
$controllerContext = $this->buildControllerContextAndSetBaseUri($uri, $node, $requestArguments);
133+
$controllerContext = $this->nodeRenderingUriService->buildControllerContextAndSetBaseUri($uri, $node, $requestArguments);
235134
/** @var ActionRequest $request */
236135
$request = $controllerContext->getRequest();
237136
$request->setArguments($requestArguments);
@@ -282,34 +181,6 @@ private static function wrapInHttpMessage(string $output, ActionResponse $respon
282181
return "HTTP/1.1" . (empty($headerLines) ? "\r\n" : implode("\r\n", $headerLines)) . "\r\n" . $output;
283182
}
284183

285-
286-
/**
287-
* @param NodeInterface $node
288-
* @param array $arguments
289-
* @return string The resolved URI for the given node
290-
* @throws \Exception
291-
*/
292-
protected function buildNodeUri(NodeInterface $node, array $arguments)
293-
{
294-
/** @var Site $currentSite */
295-
$currentSite = $node->getContext()->getCurrentSite();
296-
if (!$currentSite->hasActiveDomains()) {
297-
throw new Exception(sprintf("Site %s has no active domain", $currentSite->getNodeName()), 1666684522);
298-
}
299-
$primaryDomain = $currentSite->getPrimaryDomain();
300-
if ((string)$primaryDomain->getScheme() === '') {
301-
throw new Exception(sprintf("Domain %s for site %s has no scheme defined", $primaryDomain->getHostname(), $currentSite->getNodeName()), 1666684523);
302-
}
303-
304-
// HINT: We cannot use a static URL here, but instead need to use an URL of the current site.
305-
// This is changed from the the old behavior, where we have changed the LinkingService in LinkingServiceAspect,
306-
// to properly generate the domain part of the routes - and this relies on the proper ControllerContext URI path.
307-
$baseControllerContext = $this->buildControllerContextAndSetBaseUri($primaryDomain->__toString(), $node, $arguments);
308-
$format = $arguments['@format'] ?? 'html';
309-
$uri = $this->linkingService->createNodeUri($baseControllerContext, $node, null, $format, true, $arguments, '', false, [], false);
310-
return $this->removeQueryPartFromUri($uri);
311-
}
312-
313184
/**
314185
* @return bool
315186
*/
@@ -318,17 +189,6 @@ public function isRendering()
318189
return $this->isRendering;
319190
}
320191

321-
/**
322-
* @param string $uri
323-
* @return string
324-
*/
325-
protected function removeQueryPartFromUri($uri)
326-
{
327-
$uriData = explode('?', $uri);
328-
329-
return $uriData[0];
330-
}
331-
332192
public function disableCache()
333193
{
334194
$this->fusionView->disableCache();
@@ -338,5 +198,4 @@ public function enableCache()
338198
{
339199
$this->fusionView->enableCache();
340200
}
341-
342201
}

0 commit comments

Comments
 (0)