diff --git a/Neos.Fusion/Classes/View/FusionView.php b/Neos.Fusion/Classes/View/FusionView.php
index c1ba4337296..bf983346f4f 100644
--- a/Neos.Fusion/Classes/View/FusionView.php
+++ b/Neos.Fusion/Classes/View/FusionView.php
@@ -224,10 +224,13 @@ protected function getMergedFusionObjectTree(): FusionConfiguration
*/
public function getFusionPathPatterns(): array
{
- $packageKey = $this->getPackageKey();
$fusionPathPatterns = array_map(
- function ($fusionPathPattern) use ($packageKey) {
- return str_replace('@package', $packageKey, $fusionPathPattern);
+ function ($fusionPathPattern) {
+ if (!str_contains($fusionPathPattern, '@package')) {
+ return $fusionPathPattern;
+ }
+
+ return str_replace('@package', $this->getPackageKey(), $fusionPathPattern);
},
$this->getOption('fusionPathPatterns')
);
@@ -247,8 +250,10 @@ protected function getPackageKey()
if ($packageKey !== null) {
return $packageKey;
} else {
- /** @var $request ActionRequest */
- $request = $this->controllerContext->getRequest();
+ $request = $this->controllerContext?->getRequest();
+ if (!$request) {
+ throw new \RuntimeException(sprintf('To resolve the @package in all fusionPathPatterns, either packageKey has to be specified, or the current request be available.'));
+ }
return $request->getControllerPackageKey();
}
}
diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php
index f5ae466fd07..4bc115447ee 100644
--- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php
+++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php
@@ -207,19 +207,18 @@ public function showAction(string $node): void
VisibilityConstraints::frontend()
);
+ $nodeInstance = $subgraph->findNodeById($nodeAddress->nodeAggregateId);
+ if ($nodeInstance === null) {
+ throw new NodeNotFoundException(sprintf('The cached node address for this uri could not be resolved. Possibly you have to flush the "Flow_Mvc_Routing_Route" cache. %s', $nodeAddress), 1707300738);
+ }
+
$site = $subgraph->findClosestNode($nodeAddress->nodeAggregateId, FindClosestNodeFilter::create(nodeTypes: NodeTypeNameFactory::NAME_SITE));
if ($site === null) {
- throw new NodeNotFoundException("TODO: SITE NOT FOUND; should not happen (for address " . $nodeAddress);
+ throw new NodeNotFoundException(sprintf('The site node of %s could not be resolved.', $nodeAddress), 1707300861);
}
$this->fillCacheWithContentNodes($nodeAddress->nodeAggregateId, $subgraph);
- $nodeInstance = $subgraph->findNodeById($nodeAddress->nodeAggregateId);
-
- if ($nodeInstance === null) {
- throw new NodeNotFoundException('The requested node does not exist', 1596191460);
- }
-
if ($this->getNodeType($nodeInstance)->isOfType(NodeTypeNameFactory::NAME_SHORTCUT)) {
$this->handleShortcutNode($nodeAddress, $contentRepository);
}
diff --git a/Neos.Neos/Classes/Domain/Service/SiteService.php b/Neos.Neos/Classes/Domain/Service/SiteService.php
index c50a5a6f86b..0fe7b730fd0 100644
--- a/Neos.Neos/Classes/Domain/Service/SiteService.php
+++ b/Neos.Neos/Classes/Domain/Service/SiteService.php
@@ -73,7 +73,15 @@ public function pruneSite(Site $site): void
$site->getConfiguration()->contentRepositoryId,
new SiteServiceInternalsFactory()
);
- $siteServiceInternals->removeSiteNode($site->getNodeName());
+
+ try {
+ $siteServiceInternals->removeSiteNode($site->getNodeName());
+ } catch (\Doctrine\DBAL\Exception $exception) {
+ throw new \RuntimeException(sprintf(
+ 'Could not remove site nodes for site "%s", please ensure the content repository is setup.',
+ $site->getName()
+ ), 1707302419, $exception);
+ }
$site->setPrimaryDomain(null);
$this->siteRepository->update($site);
diff --git a/Neos.Neos/Classes/FrontendRouting/EventSourcedFrontendNodeRoutePartHandler.php b/Neos.Neos/Classes/FrontendRouting/EventSourcedFrontendNodeRoutePartHandler.php
index 56f414cbf53..d2d592f61fa 100644
--- a/Neos.Neos/Classes/FrontendRouting/EventSourcedFrontendNodeRoutePartHandler.php
+++ b/Neos.Neos/Classes/FrontendRouting/EventSourcedFrontendNodeRoutePartHandler.php
@@ -34,6 +34,7 @@
use Neos\Neos\FrontendRouting\DimensionResolution\DelegatingResolver;
use Neos\Neos\FrontendRouting\DimensionResolution\RequestToDimensionSpacePointContext;
use Neos\Neos\FrontendRouting\DimensionResolution\DimensionResolverInterface;
+use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionMiddleware;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
use Psr\Http\Message\UriInterface;
@@ -175,7 +176,11 @@ public function matchWithParameters(&$requestPath, RouteParameters $parameters)
return false;
}
- $siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
+ try {
+ $siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
+ } catch (SiteDetectionFailedException) {
+ return false;
+ }
$resolvedSite = $this->siteRepository->findOneByNodeName($siteDetectionResult->siteNodeName);
if ($resolvedSite === null) {
@@ -196,7 +201,6 @@ public function matchWithParameters(&$requestPath, RouteParameters $parameters)
// TODO validate dsp == complete (ContentDimensionZookeeper::getAllowedDimensionSubspace()->contains()...)
// if incomplete -> no match + log
- $siteDetectionResult = SiteDetectionResult::fromRouteParameters($parameters);
$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);
try {
diff --git a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php
index b0ade4bd6e9..413d2dac480 100644
--- a/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php
+++ b/Neos.Neos/Classes/FrontendRouting/Projection/DocumentUriPathFinder.php
@@ -242,7 +242,7 @@ private function fetchSingle(string $where, array $parameters): DocumentNodeInfo
);
} catch (DBALException $e) {
throw new \RuntimeException(sprintf(
- 'Failed to load node for query "%s": %s',
+ 'Failed to fetch a node, please ensure the projection is setup. Query "%s". %s',
$where,
$e->getMessage()
), 1599664746, $e);
@@ -273,7 +273,7 @@ private function fetchMultiple(string $where, array $parameters): DocumentNodeIn
);
} catch (DBALException $e) {
throw new \RuntimeException(sprintf(
- 'Failed to load node for query "%s": %s',
+ 'Failed to fetch multiple nodes, please ensure the projection is setup. Query "%s". %s',
$where,
$e->getMessage()
), 1683808640, $e);
@@ -314,7 +314,7 @@ public function disableCache(): void
public function getDescendantsOfNode(DocumentNodeInfo $node): DocumentNodeInfos
{
return $this->fetchMultiple(
- 'dimensionSpacePointHash = :dimensionSpacePointHash
+ 'dimensionSpacePointHash = :dimensionSpacePointHash
AND nodeAggregateIdPath LIKE :childNodeAggregateIdPathPrefix',
[
'dimensionSpacePointHash' => $node->getDimensionSpacePointHash(),
diff --git a/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionFailedException.php b/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionFailedException.php
new file mode 100644
index 00000000000..0c770cf4b20
--- /dev/null
+++ b/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionFailedException.php
@@ -0,0 +1,12 @@
+handle($request);
}
+ // doctrine is running and we could fetch a site. This makes no promise if the content repository is set up.
$siteDetectionResult = SiteDetectionResult::create($site->getNodeName(), $site->getConfiguration()->contentRepositoryId);
return $handler->handle($siteDetectionResult->storeInRequest($request));
}
diff --git a/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionResult.php b/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionResult.php
index 9fa9eddc4cd..6816ebf1856 100644
--- a/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionResult.php
+++ b/Neos.Neos/Classes/FrontendRouting/SiteDetection/SiteDetectionResult.php
@@ -41,7 +41,11 @@ public static function create(
* Helper to retrieve the previously resolved Site and ContentRepository instance.
*
* @param ServerRequestInterface $request
- * @return static
+ * @throws SiteDetectionFailedException This error will be thrown if a request is passed
+ * where the site detection middleware did not store a site in.
+ * This is likely the case if the request is a mock or
+ * if no site entity exists because Neos was not setup.
+ *
* @api
*/
public static function fromRequest(ServerRequestInterface $request): self
@@ -52,19 +56,21 @@ public static function fromRequest(ServerRequestInterface $request): self
return self::fromRouteParameters($routeParameters);
}
+ /**
+ * @throws SiteDetectionFailedException
+ */
public static function fromRouteParameters(RouteParameters $routeParameters): self
{
$siteNodeName = $routeParameters->getValue(self::ROUTINGPARAMETER_SITENODENAME);
$contentRepositoryId = $routeParameters->getValue(self::ROUTINGPARAMETER_CONTENTREPOSITORYID);
- if ($siteNodeName === null || $contentRepositoryId === null) {
- throw new \RuntimeException(
+ if (!is_string($siteNodeName) || !is_string($contentRepositoryId)) {
+ throw new SiteDetectionFailedException(
'Current site and content repository could not be extracted from the Request.'
- . ' The SiteDetectionMiddleware was not able to determine the site!'
+ . ' The SiteDetectionMiddleware was not able to determine the site!',
+ 1699459565
);
}
- assert(is_string($siteNodeName));
- assert(is_string($contentRepositoryId));
return new self(
SiteNodeName::fromString($siteNodeName),
ContentRepositoryId::fromString($contentRepositoryId)
diff --git a/Neos.Neos/Classes/Routing/Exception/NoHomepageException.php b/Neos.Neos/Classes/Routing/Exception/NoHomepageException.php
deleted file mode 100644
index e7ab63f3907..00000000000
--- a/Neos.Neos/Classes/Routing/Exception/NoHomepageException.php
+++ /dev/null
@@ -1,24 +0,0 @@
-getHttpRequest());
+ try {
+ $siteDetectionResult = SiteDetectionResult::fromRequest($requestHandler->getHttpRequest());
+ } catch (SiteDetectionFailedException) {
+ return;
+ }
$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);
$isEditor = false;
@@ -131,8 +136,10 @@ public function relayEditorAuthentication(Authentication\TokenInterface $token):
return;
}
- /** @var Workspace $baseWorkspace */
$baseWorkspace = $contentRepository->getWorkspaceFinder()->findOneByName(WorkspaceName::forLive());
+ if (!$baseWorkspace) {
+ return;
+ }
$editorsNewContentStreamId = ContentStreamId::create();
$contentRepository->handle(
CreateWorkspace::create(
diff --git a/Neos.Neos/Classes/View/FusionExceptionView.php b/Neos.Neos/Classes/View/FusionExceptionView.php
index 59cddcd535d..bf4807b0e65 100644
--- a/Neos.Neos/Classes/View/FusionExceptionView.php
+++ b/Neos.Neos/Classes/View/FusionExceptionView.php
@@ -38,6 +38,7 @@
use Neos\Neos\Domain\Repository\SiteRepository;
use Neos\Neos\Domain\Service\FusionService;
use Neos\Neos\Domain\Service\SiteNodeUtility;
+use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionFailedException;
use Neos\Neos\FrontendRouting\SiteDetection\SiteDetectionResult;
class FusionExceptionView extends AbstractView
@@ -91,7 +92,7 @@ class FusionExceptionView extends AbstractView
protected DomainRepository $domainRepository;
/**
- * @return string
+ * @return mixed
* @throws \Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException
* @throws \Neos\Fusion\Exception
* @throws \Neos\Neos\Domain\Exception
@@ -107,7 +108,12 @@ public function render()
$httpRequest = $requestHandler->getHttpRequest();
- $siteDetectionResult = SiteDetectionResult::fromRequest($httpRequest);
+ try {
+ $siteDetectionResult = SiteDetectionResult::fromRequest($httpRequest);
+ } catch (SiteDetectionFailedException) {
+ return $this->renderErrorWelcomeScreen();
+ }
+
$contentRepository = $this->contentRepositoryRegistry->get($siteDetectionResult->contentRepositoryId);
$fusionExceptionViewInternals = $this->contentRepositoryRegistry->buildService(
$siteDetectionResult->contentRepositoryId,
@@ -128,6 +134,10 @@ public function render()
);
}
+ if (!$currentSiteNode) {
+ return $this->renderErrorWelcomeScreen();
+ }
+
$request = ActionRequest::fromHttpRequest($httpRequest);
$request->setControllerPackageKey('Neos.Neos');
$request->setFormat('html');
@@ -144,32 +154,27 @@ public function render()
$securityContext = $this->objectManager->get(SecurityContext::class);
$securityContext->setRequest($request);
- if ($currentSiteNode) {
- $fusionRuntime = $this->getFusionRuntime($currentSiteNode, $controllerContext);
-
- $this->setFallbackRuleFromDimension($dimensionSpacePoint);
-
- $fusionRuntime->pushContextArray(array_merge(
- $this->variables,
- [
- 'node' => $currentSiteNode,
- 'documentNode' => $currentSiteNode,
- 'site' => $currentSiteNode,
- 'editPreviewMode' => null
- ]
- ));
-
- try {
- $output = $fusionRuntime->render('error');
- return $this->extractBodyFromOutput($output);
- } catch (RuntimeException $exception) {
- throw $exception->getPrevious() ?: $exception;
- } finally {
- $fusionRuntime->popContext();
- }
+ $fusionRuntime = $this->getFusionRuntime($currentSiteNode, $controllerContext);
+
+ $this->setFallbackRuleFromDimension($dimensionSpacePoint);
+
+ $fusionRuntime->pushContextArray(array_merge(
+ $this->variables,
+ [
+ 'node' => $currentSiteNode,
+ 'documentNode' => $currentSiteNode,
+ 'site' => $currentSiteNode
+ ]
+ ));
+
+ try {
+ $output = $fusionRuntime->render('error');
+ return $this->extractBodyFromOutput($output);
+ } catch (RuntimeException $exception) {
+ throw $exception->getPrevious() ?: $exception;
+ } finally {
+ $fusionRuntime->popContext();
}
-
- return '';
}
/**
@@ -219,4 +224,18 @@ protected function getFusionRuntime(
}
return $this->fusionRuntime;
}
+
+ private function renderErrorWelcomeScreen(): mixed
+ {
+ // in case no neos site being there or no site node we cannot continue with the fusion exception view,
+ // as we wouldn't know the site and cannot get the site's root.fusion
+ // instead we render the welcome screen directly
+ $view = \Neos\Fusion\View\FusionView::createWithOptions([
+ 'fusionPath' => 'Neos/Fusion/NotFoundExceptions',
+ 'fusionPathPatterns' => ['resource://Neos.Neos/Private/Fusion/Error/Root.fusion'],
+ 'enableContentCache' => false,
+ ]);
+ $view->assignMultiple($this->variables);
+ return $view->render();
+ }
}
diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php
index 7cf490e96f5..6ff365b5127 100644
--- a/Neos.Neos/Classes/View/FusionView.php
+++ b/Neos.Neos/Classes/View/FusionView.php
@@ -77,8 +77,7 @@ public function render(): string|ResponseInterface
$fusionRuntime->pushContextArray([
'node' => $currentNode,
'documentNode' => $this->getClosestDocumentNode($currentNode) ?: $currentNode,
- 'site' => $currentSiteNode,
- 'editPreviewMode' => $this->variables['editPreviewMode'] ?? null
+ 'site' => $currentSiteNode
]);
try {
$output = $fusionRuntime->render($this->fusionPath);
diff --git a/Neos.Neos/Configuration/Settings.yaml b/Neos.Neos/Configuration/Settings.yaml
index b1d965074f0..5ad7785a511 100755
--- a/Neos.Neos/Configuration/Settings.yaml
+++ b/Neos.Neos/Configuration/Settings.yaml
@@ -471,18 +471,13 @@ Neos:
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseException
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseConnectionException
- Neos\Flow\Persistence\Doctrine\Exception\DatabaseStructureException
- options: &welcomeTemplate
+ options:
viewClassName: Neos\Fusion\View\FusionView
viewOptions:
fusionPath: 'Neos/Fusion/DatabaseConnectionExceptions'
fusionPathPatterns: ['resource://Neos.Neos/Private/Fusion/Error/Root.fusion']
enableContentCache: false
templatePathAndFilename: ~
-
- noHomepageException:
- matchingExceptionClassNames:
- - Neos\Neos\Routing\Exception\NoHomepageException
- options: *welcomeTemplate
debugger:
ignoredClasses:
Neos\\Neos\\Domain\\Service\\ContentContextFactory: true
diff --git a/Neos.Neos/Resources/Private/Fusion/Error/Root.fusion b/Neos.Neos/Resources/Private/Fusion/Error/Root.fusion
index 0599d2a6abe..05461f5a562 100644
--- a/Neos.Neos/Resources/Private/Fusion/Error/Root.fusion
+++ b/Neos.Neos/Resources/Private/Fusion/Error/Root.fusion
@@ -11,3 +11,8 @@ Neos.Fusion.DatabaseConnectionExceptions = Neos.Neos:Error.View.Welcome {
exception = ${exception}
renderingGroupName = ${renderingOptions.renderingGroup}
}
+
+Neos.Fusion.NotFoundExceptions = Neos.Neos:Error.View.Welcome {
+ exception = ${exception}
+ renderingGroupName = ${renderingOptions.renderingGroup}
+}
diff --git a/Neos.Neos/Resources/Private/Translations/en/Main.xlf b/Neos.Neos/Resources/Private/Translations/en/Main.xlf
index b793d2eec42..a3832801bef 100644
--- a/Neos.Neos/Resources/Private/Translations/en/Main.xlf
+++ b/Neos.Neos/Resources/Private/Translations/en/Main.xlf
@@ -669,16 +669,6 @@
Technical Information
-
- Missing Homepage
-
-
- Either no site has been defined, the site does not contain a homepage or the active site couldn't be determined.
-
-
- You might want to set the site's domain or import a new site in the setup.
-
-
Database Error
@@ -695,6 +685,10 @@
Sorry, the page you requested was not found.
+
+ You might want to import or create a new site in the setup.
+
+
Invalid NodeType