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