From 6b892cc1221b710741d9eec65acbaa5c3f3b1d14 Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Fri, 23 Aug 2019 13:19:14 +0200 Subject: [PATCH 1/5] BUGFIX: Custom Route for node preview Resolves: #2653 --- .../Controller/Frontend/NodeController.php | 25 ++++++++++++++++++- Neos.Neos/Classes/Service/LinkingService.php | 5 +++- Neos.Neos/Configuration/Policy.yaml | 2 +- Neos.Neos/Configuration/Routes.Frontend.yaml | 7 ++++++ .../Private/Fusion/Prototypes/Page.fusion | 11 -------- 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 880c34159b5..383b0a9f9f8 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -16,10 +16,12 @@ use Neos\Flow\Mvc\Controller\ActionController; 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\View\FusionView; use Neos\ContentRepository\Domain\Model\NodeInterface; use Neos\ContentRepository\Domain\Service\ContextFactoryInterface; @@ -77,11 +79,32 @@ class NodeController extends ActionController * * @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") - * @throws NodeNotFoundException */ 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); + } + + /** + * 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 + * @throws NeosException | NodeNotFoundException | SessionNotStartedException | UnresolvableShortcutException + * @Flow\IgnoreValidation("node") + */ + 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); diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index e7f305417fd..3fd9e4c17da 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -229,6 +229,7 @@ public function convertUriToObject($uri, NodeInterface $contextNode = null) * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException * @throws \Neos\Flow\Property\Exception * @throws \Neos\Flow\Security\Exception + * @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 { @@ -286,6 +287,8 @@ public function createNodeUri(ControllerContext $controllerContext, $node = null $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($request); + #$action = $node->getContext()->isLive() ? 'show' : 'preview'; + $action = $resolvedNode->getContext()->getWorkspace()->isPublicWorkspace() ? 'show' : 'preview'; $uri = $uriBuilder ->reset() ->setSection($section) @@ -293,7 +296,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..3d475a01058 100644 --- a/Neos.Neos/Configuration/Policy.yaml +++ b/Neos.Neos/Configuration/Policy.yaml @@ -18,7 +18,7 @@ privilegeTargets: matcher: 'method(Neos\FluidAdaptor\ViewHelpers\Widget\Controller\AutocompleteController->(index|autocomplete)Action()) || method(Neos\FluidAdaptor\ViewHelpers\Widget\Controller\PaginateController->indexAction()) || method(Neos\ContentRepository\ViewHelpers\Widget\Controller\PaginateController->indexAction()) || method(Neos\Neos\ViewHelpers\Widget\Controller\LinkRepositoryController->(index|search|lookup)Action())' 'Neos.Neos:PublicFrontendAccess': - matcher: 'method(Neos\Neos\Controller\Frontend\NodeController->showAction())' + matcher: 'method(Neos\Neos\Controller\Frontend\NodeController->(show|preview)Action())' 'Neos.Neos:BackendLogin': matcher: 'method(Neos\Neos\Controller\LoginController->(index|tokenLogin|authenticate)Action()) || method(Neos\Flow\Security\Authentication\Controller\AbstractAuthenticationController->authenticateAction())' diff --git a/Neos.Neos/Configuration/Routes.Frontend.yaml b/Neos.Neos/Configuration/Routes.Frontend.yaml index 25bc198c41f..f7fc6d57bcf 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: 'preview' + defaults: + '@action': 'preview' + appendExceedingArguments: true + - name: 'Homepage' uriPattern: '{node}' diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion index 084bfc118cd..317257402b5 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion @@ -127,17 +127,6 @@ prototype(Neos.Neos:Page) < prototype(Neos.Fusion:Http.Message) { @if.onlyRenderWhenNotInLiveWorkspace = ${documentNode.context.inBackend} } - neosBackendMetaData = Neos.Fusion:Tag { - tagName = 'div' - @position = 'after neosBackendDocumentNodeData' - attributes = Neos.Fusion:Attributes { - data-preview-uri = Neos.Neos:NodeUri { - node = ${q(documentNode).context({'workspaceName': documentNode.context.workspace.baseWorkspace.name}).get(0)} - } - } - @if.onlyRenderWhenNotInLiveWorkspace = ${documentNode.context.inBackend} - } - # Content of the body tag. To be defined by the integrator. body = Neos.Fusion:Template { @position = 'after bodyTag' From 1f2bf4e42605ba0917bf94843d6bec21316a055a Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Thu, 28 Nov 2019 15:34:55 +0100 Subject: [PATCH 2/5] Tweak NodeController and adjust to recent changes --- Neos.Neos/Classes/Controller/Frontend/NodeController.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index 0afa7f8ad9a..ba296b64db7 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -79,7 +79,7 @@ class NodeController extends ActionController * * @return void */ - protected function initializeShowAction() + protected function initializeAction() { if ($this->arguments->hasArgument('node') && $this->request->hasArgument('showInvisible') @@ -141,10 +141,9 @@ public function previewAction(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()); + } } } From 12032ffbd94d5d912827f5b14d4c001854ea2b7a Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Thu, 28 Nov 2019 15:35:42 +0100 Subject: [PATCH 3/5] Prefix Preview route with "neos/" ..so that request patterns looking at the URL match it! --- Neos.Neos/Configuration/Routes.Frontend.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.Neos/Configuration/Routes.Frontend.yaml b/Neos.Neos/Configuration/Routes.Frontend.yaml index f7fc6d57bcf..d0e45dce30f 100644 --- a/Neos.Neos/Configuration/Routes.Frontend.yaml +++ b/Neos.Neos/Configuration/Routes.Frontend.yaml @@ -4,7 +4,7 @@ - name: 'Preview' - uriPattern: 'preview' + uriPattern: 'neos/preview' defaults: '@action': 'preview' appendExceedingArguments: true From 39b4364261fa86a8fb18fa5a0fcae94c11b76178 Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Fri, 29 Nov 2019 14:52:04 +0100 Subject: [PATCH 4/5] Fix preview of hidden nodes --- .../Controller/Frontend/NodeController.php | 30 +++++++++---------- Neos.Neos/Classes/Service/LinkingService.php | 3 +- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index ba296b64db7..c385e8b5463 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -14,6 +14,7 @@ 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; @@ -74,22 +75,6 @@ class NodeController extends ActionController */ protected $propertyMapper; - /** - * Allow invisible nodes to be redirected to - * - * @return void - */ - protected function initializeAction() - { - if ($this->arguments->hasArgument('node') - && $this->request->hasArgument('showInvisible') - && (bool)$this->request->getArgument('showInvisible') - && $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. @@ -113,6 +98,19 @@ public function showAction(NodeInterface $node = null) $this->view->assign('value', $node); } + /** + * Allow invisible nodes to be previewed + * + * @return void + * @throws NoSuchArgumentException + */ + protected function initializePreviewAction(): void + { + 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); + } + } + /** * Previews a node that is not live (i.e. for the Backend Preview & Edit Mode) * diff --git a/Neos.Neos/Classes/Service/LinkingService.php b/Neos.Neos/Classes/Service/LinkingService.php index 5d8dbf68d93..6457689726b 100644 --- a/Neos.Neos/Classes/Service/LinkingService.php +++ b/Neos.Neos/Classes/Service/LinkingService.php @@ -302,8 +302,7 @@ public function createNodeUri(ControllerContext $controllerContext, $node = null $uriBuilder = clone $controllerContext->getUriBuilder(); $uriBuilder->setRequest($request); - #$action = $node->getContext()->isLive() ? 'show' : 'preview'; - $action = $resolvedNode->getContext()->getWorkspace()->isPublicWorkspace() ? 'show' : 'preview'; + $action = $resolvedNode->getContext()->getWorkspace()->isPublicWorkspace() && !$resolvedNode->isHidden() ? 'show' : 'preview'; $uri = $uriBuilder ->reset() ->setSection($section) From 847ec14b0eb660398d80314bb3db27422ff883ae Mon Sep 17 00:00:00 2001 From: bwaidelich Date: Fri, 29 Nov 2019 15:02:29 +0100 Subject: [PATCH 5/5] Custom privilege for content preview --- Neos.Neos/Configuration/Policy.yaml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Neos.Neos/Configuration/Policy.yaml b/Neos.Neos/Configuration/Policy.yaml index 3d475a01058..6cca73fbf2d 100644 --- a/Neos.Neos/Configuration/Policy.yaml +++ b/Neos.Neos/Configuration/Policy.yaml @@ -18,7 +18,10 @@ privilegeTargets: matcher: 'method(Neos\FluidAdaptor\ViewHelpers\Widget\Controller\AutocompleteController->(index|autocomplete)Action()) || method(Neos\FluidAdaptor\ViewHelpers\Widget\Controller\PaginateController->indexAction()) || method(Neos\ContentRepository\ViewHelpers\Widget\Controller\PaginateController->indexAction()) || method(Neos\Neos\ViewHelpers\Widget\Controller\LinkRepositoryController->(index|search|lookup)Action())' 'Neos.Neos:PublicFrontendAccess': - matcher: 'method(Neos\Neos\Controller\Frontend\NodeController->(show|preview)Action())' + 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