diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 72fbc45345c..c385e8b5463 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -14,12 +14,15 @@ use Neos\Flow\Annotations as Flow; use Neos\Flow\Http\Component\SetHeaderComponent; use Neos\Flow\Mvc\Controller\ActionController; +use Neos\Flow\Mvc\Exception\NoSuchArgumentException; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Security\Authorization\PrivilegeManagerInterface; +use Neos\Flow\Session\Exception\SessionNotStartedException; use Neos\Flow\Session\SessionInterface; use Neos\Neos\Controller\Exception\NodeNotFoundException; use Neos\Neos\Controller\Exception\UnresolvableShortcutException; use Neos\Neos\Domain\Service\NodeShortcutResolver; +use Neos\Neos\Exception as NeosException; use Neos\Neos\TypeConverter\NodeConverter; use Neos\Neos\View\FusionView; use Neos\ContentRepository\Domain\Model\NodeInterface; @@ -73,32 +76,50 @@ class NodeController extends ActionController protected $propertyMapper; /** - * Allow invisible nodes to be redirected to + * Shows the specified node and takes visibility and access restrictions into + * account. + * + * @param NodeInterface $node + * @return string View output for the specified node + * @throws NodeNotFoundException | UnresolvableShortcutException | NeosException + * @Flow\SkipCsrfProtection We need to skip CSRF protection here because this action could be called with unsafe requests from widgets or plugins that are rendered on the node - For those the CSRF token is validated on the sub-request, so it is safe to be skipped here + * @Flow\IgnoreValidation("node") + */ + public function showAction(NodeInterface $node = null) + { + if ($node === null || !$node->getContext()->isLive()) { + throw new NodeNotFoundException('The requested node does not exist or isn\'t accessible to the current user', 1430218623); + } + + if ($node->getNodeType()->isOfType('Neos.Neos:Shortcut')) { + $this->handleShortcutNode($node); + } + + $this->view->assign('value', $node); + } + + /** + * Allow invisible nodes to be previewed * * @return void + * @throws NoSuchArgumentException */ - protected function initializeShowAction() + protected function initializePreviewAction(): void { - if ($this->arguments->hasArgument('node') - && $this->request->hasArgument('showInvisible') - && (bool)$this->request->getArgument('showInvisible') - && $this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess') - ) { + if ($this->arguments->hasArgument('node') && $this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess')) { $this->arguments->getArgument('node')->getPropertyMappingConfiguration()->setTypeConverterOption(NodeConverter::class, NodeConverter::INVISIBLE_CONTENT_SHOWN, true); } } /** - * Shows the specified node and takes visibility and access restrictions into - * account. + * Previews a node that is not live (i.e. for the Backend Preview & Edit Mode) * * @param NodeInterface $node * @return string View output for the specified node - * @Flow\SkipCsrfProtection We need to skip CSRF protection here because this action could be called with unsafe requests from widgets or plugins that are rendered on the node - For those the CSRF token is validated on the sub-request, so it is safe to be skipped here + * @throws NeosException | NodeNotFoundException | SessionNotStartedException | UnresolvableShortcutException * @Flow\IgnoreValidation("node") - * @throws NodeNotFoundException */ - public function showAction(NodeInterface $node = null) + public function previewAction(NodeInterface $node = null) { if ($node === null) { throw new NodeNotFoundException('The requested node does not exist or isn\'t accessible to the current user', 1430218623); @@ -118,10 +139,9 @@ public function showAction(NodeInterface $node = null) if (!$this->view->canRenderWithNodeAndPath()) { $this->view->setFusionPath('rawContent'); } - } - - if ($this->session->isStarted() && $inBackend) { - $this->session->putData('lastVisitedNode', $node->getContextPath()); + if ($this->session->isStarted()) { + $this->session->putData('lastVisitedNode', $node->getContextPath()); + } } } diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 01d9cb02308..6457689726b 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -244,6 +244,7 @@ public function convertUriToObject($uri, NodeInterface $contextNode = null) * @throws \Neos\Flow\Property\Exception * @throws \Neos\Flow\Security\Exception * @throws HttpException + * @throws \Neos\Flow\Persistence\Exception\IllegalObjectTypeException */ public function createNodeUri(ControllerContext $controllerContext, $node = null, NodeInterface $baseNode = null, $format = null, $absolute = false, array $arguments = [], $section = '', $addQueryString = false, array $argumentsToBeExcludedFromQueryString = [], $resolveShortcuts = true): string { @@ -301,6 +302,7 @@ public function createNodeUri(ControllerContext $controllerContext, $node = null $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($request); + $action = $resolvedNode->getContext()->getWorkspace()->isPublicWorkspace() && !$resolvedNode->isHidden() ? 'show' : 'preview'; $uri = $uriBuilder ->reset() ->setSection($section) @@ -308,7 +310,7 @@ public function createNodeUri(ControllerContext $controllerContext, $node = null ->setAddQueryString($addQueryString) ->setArgumentsToBeExcludedFromQueryString($argumentsToBeExcludedFromQueryString) ->setFormat($format ?: $request->getFormat()) - ->uriFor('show', ['node' => $resolvedNode], 'Frontend\Node', 'Neos.Neos'); + ->uriFor($action, ['node' => $resolvedNode], 'Frontend\Node', 'Neos.Neos'); $siteNode = $resolvedNode->getContext()->getCurrentSiteNode(); if ($siteNode instanceof NodeInterface && NodePaths::isSubPathOf($siteNode->getPath(), $resolvedNode->getPath())) { diff --git a/Neos.Neos/Configuration/Policy.yaml b/Neos.Neos/Configuration/Policy.yaml index bdffca736b3..6cca73fbf2d 100644 --- a/Neos.Neos/Configuration/Policy.yaml +++ b/Neos.Neos/Configuration/Policy.yaml @@ -20,6 +20,9 @@ privilegeTargets: 'Neos.Neos:PublicFrontendAccess': matcher: 'method(Neos\Neos\Controller\Frontend\NodeController->showAction())' + 'Neos.Neos:ContentPreview': + matcher: 'method(Neos\Neos\Controller\Frontend\NodeController->previewAction())' + 'Neos.Neos:BackendLogin': matcher: 'method(Neos\Neos\Controller\LoginController->(index|tokenLogin|authenticate)Action()) || method(Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController->authenticateAction())' @@ -171,6 +174,10 @@ roles: privilegeTarget: 'Neos.Neos:Backend.GeneralAccess' permission: GRANT + - + privilegeTarget: 'Neos.Neos:ContentPreview' + permission: GRANT + - privilegeTarget: 'Neos.Neos:Backend.Module.Content' permission: GRANT diff --git a/Neos.Neos/Configuration/Routes.Frontend.yaml b/Neos.Neos/Configuration/Routes.Frontend.yaml index 25bc198c41f..d0e45dce30f 100644 --- a/Neos.Neos/Configuration/Routes.Frontend.yaml +++ b/Neos.Neos/Configuration/Routes.Frontend.yaml @@ -2,6 +2,13 @@ # "Frontend" subroutes configuration for the Neos.Neos package # # # +- + name: 'Preview' + uriPattern: 'neos/preview' + defaults: + '@action': 'preview' + appendExceedingArguments: true + - name: 'Homepage' uriPattern: '{node}'