diff --git a/Neos.Neos/Classes/Controller/Frontend/NodeController.php b/Neos.Neos/Classes/Controller/Frontend/NodeController.php index f529f9f3844..1698185dc74 100644 --- a/Neos.Neos/Classes/Controller/Frontend/NodeController.php +++ b/Neos.Neos/Classes/Controller/Frontend/NodeController.php @@ -36,6 +36,7 @@ use Neos\Flow\Session\SessionInterface; use Neos\Flow\Utility\Now; use Neos\Neos\Domain\Service\NodeSiteResolvingService; +use Neos\Neos\Domain\Service\UserInterfaceModeService; use Neos\Neos\FrontendRouting\Exception\InvalidShortcutException; use Neos\Neos\FrontendRouting\Exception\NodeNotFoundException; use Neos\Neos\FrontendRouting\NodeAddress; @@ -108,8 +109,12 @@ class NodeController extends ActionController */ protected $nodeSiteResolvingService; + #[Flow\Inject] + protected UserInterfaceModeService $userInterfaceModeService; + /** * @param string $node Legacy name for backwards compatibility of route components + * @param string $uiModeName Name of the user interface mode to use * @throws NodeNotFoundException * @throws \Neos\Flow\Mvc\Exception\StopActionException * @throws \Neos\Flow\Mvc\Exception\UnsupportedRequestTypeException @@ -120,8 +125,12 @@ class NodeController extends ActionController * 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 */ - public function previewAction(string $node): void + public function previewAction(string $node, string $uiModeName = null): void { + if (is_null($uiModeName)) { + $uiModeName = $this->userInterfaceModeService->findUserInterfaceModeNameByCurrentUser(); + } + $visibilityConstraints = VisibilityConstraints::frontend(); if ($this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess')) { $visibilityConstraints = VisibilityConstraints::withoutRestrictions(); @@ -161,6 +170,8 @@ public function previewAction(string $node): void $this->handleShortcutNode($nodeAddress, $contentRepository); } + $this->view->setOption('userInterfaceModeName', $uiModeName); + $this->view->assignMultiple([ 'value' => $nodeInstance, 'site' => $site, @@ -234,6 +245,8 @@ public function showAction(string $node, bool $showInvisible = false): void $this->handleShortcutNode($nodeAddress, $contentRepository); } + $this->view->setOption('userInterfaceModeName', 'live'); + $this->view->assignMultiple([ 'value' => $nodeInstance, 'site' => $site, diff --git a/Neos.Neos/Classes/Domain/Model/UserInterfaceMode.php b/Neos.Neos/Classes/Domain/Model/UserInterfaceMode.php index 50e24d461ad..c4948accbb4 100644 --- a/Neos.Neos/Classes/Domain/Model/UserInterfaceMode.php +++ b/Neos.Neos/Classes/Domain/Model/UserInterfaceMode.php @@ -23,139 +23,37 @@ class UserInterfaceMode { /** - * @var string - */ - protected $name; - - /** - * @var boolean - */ - protected $preview; - - /** - * @var boolean - */ - protected $edit; - - /** - * @var string - */ - protected $fusionPath; - - /** - * @var string - */ - protected $title; - - /** - * @var array - */ - protected $options; - - /** - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * @param string $name - * @return void - */ - public function setName($name) - { - $this->name = $name; - } - - /** - * @return boolean - */ - public function isPreview() - { - return $this->preview; - } - - /** - * @param boolean $preview - * @return void - */ - public function setPreview($preview) - { - $this->preview = $preview; - } - - /** - * @return boolean - */ - public function isEdit() - { - return $this->edit; - } - - /** - * @param boolean $edit - * @return void - */ - public function setEdit($edit) - { - $this->edit = $edit; - } - - /** - * @return string - */ - public function getFusionPath() - { - return $this->fusionPath; - } - - /** - * @param string $fusionPath - * @return void - */ - public function setFusionPath($fusionPath) - { - $this->fusionPath = $fusionPath; - } - - /** - * @return string - */ - public function getTitle() - { - return $this->title; - } - - /** - * @param string $title - * @return void + * @param array $options */ - public function setTitle($title) - { - $this->title = $title; + public function __construct( + public readonly string $name, + public readonly bool $isPreview, + public readonly bool $isEdit, + public readonly string $title, + public readonly string $fusionPath, + public readonly array $options + ) { + if ($name == 'live' && ($isPreview || $isEdit)) { + throw new \InvalidArgumentException('UserInterfaceMode "Live" mode cannot be edit or preview mode'); + } + if ($isPreview && $isEdit) { + throw new \InvalidArgumentException(sprintf('UserInterfaceMode "%s" cannot can either be edit or preview mode', $name)); + } } - /** - * @return array - */ - public function getOptions() + public function getIsEditMode(): bool { - return $this->options; + return $this->isEdit; } - public function getOptionByPath(string $path): mixed + public function getIsPreviewMode(): bool { - return ObjectAccess::getPropertyPath($this->options, $path); + return $this->isPreview; } - /** - * @param array $options - */ - public function setOptions(array $options): void + public function getIsLive(): bool { - $this->options = $options; + return $this->name === 'live'; } /** @@ -164,24 +62,31 @@ public function setOptions(array $options): void * @param string $modeName * @param array $configuration */ - public static function createByConfiguration($modeName, array $configuration): self + public static function createByConfiguration(string $modeName, array $configuration): UserInterfaceMode { - $mode = new self(); - $mode->setName($modeName); - $mode->setPreview($configuration['isPreviewMode']); - $mode->setEdit($configuration['isEditingMode']); - $mode->setTitle($configuration['title']); - - if (isset($configuration['fusionRenderingPath'])) { - $mode->setFusionPath($configuration['fusionRenderingPath']); - } else { - $mode->setFusionPath(''); - } - - if (isset($configuration['options']) && is_array($configuration['options'])) { - $mode->setOptions($configuration['options']); - } - + $mode = new UserInterfaceMode( + $modeName, + $configuration['isPreviewMode'] ?? false, + $configuration['isEditingMode'] ?? false, + $configuration['title'] ?? $modeName, + $configuration['fusionRenderingPath'] ?? '', + $configuration['options'] ?? [], + ); return $mode; } + + /** + * Creates the live User interface mode + */ + public static function createLive(): UserInterfaceMode + { + return new UserInterfaceMode( + 'live', + false, + false, + 'Live', + '', + [] + ); + } } diff --git a/Neos.Neos/Classes/Domain/Service/UserInterfaceModeService.php b/Neos.Neos/Classes/Domain/Service/UserInterfaceModeService.php index 9a56be5731e..c7185f60d74 100644 --- a/Neos.Neos/Classes/Domain/Service/UserInterfaceModeService.php +++ b/Neos.Neos/Classes/Domain/Service/UserInterfaceModeService.php @@ -54,16 +54,14 @@ class UserInterfaceModeService /** * Get the current rendering mode (editPreviewMode). * Will return a live mode when not in backend. - * - * @return UserInterfaceMode */ - public function findModeByCurrentUser() + public function findUserInterfaceModeNameByCurrentUser(): string { if ( $this->userService->getBackendUser() === null || !$this->privilegeManager->isPrivilegeTargetGranted('Neos.Neos:Backend.GeneralAccess') ) { - return $this->findModeByName('live'); + return 'live'; } $editPreviewMode = $this->userService->getUserPreference('contentEditing.editPreviewMode'); @@ -71,51 +69,32 @@ public function findModeByCurrentUser() $editPreviewMode = $this->defaultEditPreviewMode; } - return $this->findModeByName($editPreviewMode); + return $editPreviewMode; } /** * Returns the default rendering mode. - * - * @return UserInterfaceMode */ - public function findDefaultMode() + public function findDefaultUserInterfaceMode(): string { - $mode = $this->findModeByName($this->defaultEditPreviewMode); - - return $mode; + return $this->defaultEditPreviewMode; } /** * Finds an rendering mode by name. - * - * @param string $modeName - * @return UserInterfaceMode - * @throws Exception */ - public function findModeByName($modeName) + public function findModeByName(string $modeName): UserInterfaceMode { + if ($modeName === 'live') { + return UserInterfaceMode::createLive(); + } if (isset($this->editPreviewModes[$modeName])) { - if ($this->editPreviewModes[$modeName] instanceof UserInterfaceMode) { - $mode = $this->editPreviewModes[$modeName]; - } elseif (is_array($this->editPreviewModes[$modeName])) { - $mode = UserInterfaceMode::createByConfiguration($modeName, $this->editPreviewModes[$modeName]); - $this->editPreviewModes[$modeName] = $mode; - } else { - throw new Exception( - 'The requested interface render mode "' . $modeName . '" is not configured correctly.' - . ' Please make sure it is fully configured.', - 1427716331 - ); - } - } else { - throw new Exception( - 'The requested interface render mode "' . $modeName . '" is not configured.' - . ' Please make sure it exists as key in the Settings path "Neos.Neos.Interface.editPreviewModes".', - 1427715962 - ); + return UserInterfaceMode::createByConfiguration($modeName, $this->editPreviewModes[$modeName]); } - - return $mode; + throw new Exception( + 'The requested interface render mode "' . $modeName . '" is not configured.' + . ' Please make sure it exists as key in the Settings path "Neos.Neos.Interface.editPreviewModes".', + 1427715962 + ); } } diff --git a/Neos.Neos/Classes/View/FusionView.php b/Neos.Neos/Classes/View/FusionView.php index 8264acdd06e..570c8021235 100644 --- a/Neos.Neos/Classes/View/FusionView.php +++ b/Neos.Neos/Classes/View/FusionView.php @@ -27,6 +27,7 @@ use Neos\Neos\Domain\Repository\SiteRepository; use Neos\Neos\Domain\Service\FusionService; use Neos\Neos\Domain\Service\SiteNodeUtility; +use Neos\Neos\Domain\Service\UserInterfaceModeService; use Neos\Neos\Exception; use Psr\Http\Message\ResponseInterface; @@ -49,6 +50,9 @@ class FusionView extends AbstractView #[Flow\Inject] protected SiteRepository $siteRepository; + #[Flow\Inject] + protected UserInterfaceModeService $userInterfaceModeService; + /** * @Flow\Inject * @var ContentRepositoryRegistry @@ -96,6 +100,11 @@ public function render(): string|ResponseInterface null, 'Flag to enable content caching inside Fusion (overriding the global setting).', 'boolean' + ], + 'userInterfaceModeName' => [ + 'live', + 'Name of the user interface mode to use', + 'string' ] ]; @@ -229,8 +238,11 @@ protected function getFusionRuntime(Node $currentSiteNode) $site = $this->siteRepository->findSiteBySiteNode($currentSiteNode); $fusionConfiguration = $this->fusionService->createFusionConfigurationFromSite($site); + $userInterfaceMode = $this->userInterfaceModeService->findModeByName($this->getOption('userInterfaceModeName')); + $fusionGlobals = FusionGlobals::fromArray([ - 'request' => $this->controllerContext->getRequest() + 'request' => $this->controllerContext->getRequest(), + 'userInterfaceMode' => $userInterfaceMode ]); $this->fusionRuntime = $this->runtimeFactory->createFromConfiguration( $fusionConfiguration, diff --git a/Neos.Neos/Resources/Private/Fusion/Override/GlobalCacheIdentifiers.fusion b/Neos.Neos/Resources/Private/Fusion/Override/GlobalCacheIdentifiers.fusion index 6e5417ac858..26d561d070f 100644 --- a/Neos.Neos/Resources/Private/Fusion/Override/GlobalCacheIdentifiers.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Override/GlobalCacheIdentifiers.fusion @@ -6,5 +6,5 @@ prototype(Neos.Fusion:GlobalCacheIdentifiers) { workspaceChain = ${Array.join(Array.keys(Neos.Caching.getWorkspaceChain(documentNode)), ',')} workspaceChain.@if.has = ${!!documentNode} - editPreviewMode = ${editPreviewMode} + userInterfaceMode = ${userInterfaceMode.name} } diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCollection.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCollection.fusion index 995e421037b..2efd8e8efa2 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCollection.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentCollection.fusion @@ -27,7 +27,7 @@ prototype(Neos.Neos:ContentCollection) < prototype(Neos.Fusion:Tag) { attributes { class.@process.collectionClass = ${Array.push(value, 'neos-contentcollection')} data-__neos-insertion-anchor = true - data-__neos-insertion-anchor.@if.onlyRenderInBackend = ${Neos.Node.inBackend(documentNode) && node.context.currentRenderingMode.edit} + data-__neos-insertion-anchor.@if.onlyRenderInBackend = ${userInterfaceMode.isEditMode || userInterfaceMode.isPreviewMode} } # Doesn't need to be set, if the node is a content collection. diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentElementWrapping.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentElementWrapping.fusion index f8f6553aee6..2ecc3556b9d 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentElementWrapping.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/ContentElementWrapping.fusion @@ -5,6 +5,7 @@ # prototype(Neos.Neos:ContentElementWrapping) { @class = 'Neos\\Neos\\Fusion\\ContentElementWrappingImplementation' + @if.inEditMode = ${userInterfaceMode.isEditMode} node = ${node} value = ${value} # Additional attributes in the form '': '' that will be rendered in the ContentElementWrapping diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Editable.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Editable.fusion index 9693c1d04dd..90cadfdac85 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Editable.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Editable.fusion @@ -12,10 +12,7 @@ prototype(Neos.Neos:Editable) < prototype(Neos.Fusion:Component) { renderer = Neos.Fusion:Case { editable { - // TODO: add props.node.context.currentRenderingMode.edit - // condition = ${Neos.Node.inBackend(props.node) && props.node.context.currentRenderingMode.edit} - condition = ${Neos.Node.inBackend(props.node)} - + condition = ${userInterfaceMode.isEditMode} renderer = Neos.Fusion:Tag { tagName = ${props.block ? 'div' : 'span'} content = ${q(props.node).property(props.property)} diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/FallbackNode.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/FallbackNode.fusion index c2126858f69..18ef3e56ba6 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/FallbackNode.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/FallbackNode.fusion @@ -1,4 +1,4 @@ prototype(Neos.Neos:FallbackNode) < prototype(Neos.Neos:Content) { templatePath = 'resource://Neos.Neos/Private/Templates/FusionObjects/FallbackNode.html' - @if.onlyRenderInBackend = ${Neos.Node.inBackend(node)} + @if.onlyRenderInBackend = ${userInterfaceMode.isEditMode || userInterfaceMode.isPreviewMode} } diff --git a/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion b/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion index e519e01980d..4543f836381 100644 --- a/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion +++ b/Neos.Neos/Resources/Private/Fusion/Prototypes/Page.fusion @@ -64,7 +64,7 @@ prototype(Neos.Neos:Page) < prototype(Neos.Fusion:Http.Message) { tagName = 'body' omitClosingTag = true attributes.class.@process.addNeosBackendClass = ${Array.push(value, 'neos-backend')} - attributes.class.@process.addNeosBackendClass.@if.onlyRenderWhenNotInLiveWorkspace = ${Neos.Node.inBackend(documentNode)} + attributes.class.@process.addNeosBackendClass.@if.onlyRenderWhenNotInLiveWorkspace = ${userInterfaceMode.isEditMode} } # Content of the body tag. To be defined by the integrator. diff --git a/Neos.Neos/Resources/Private/Fusion/RawContentMode.fusion b/Neos.Neos/Resources/Private/Fusion/RawContentMode.fusion index ed7324aeec9..c26c78057d9 100644 --- a/Neos.Neos/Resources/Private/Fusion/RawContentMode.fusion +++ b/Neos.Neos/Resources/Private/Fusion/RawContentMode.fusion @@ -1,3 +1,20 @@ include: RawContent/*.fusion -rawContent = Neos.Neos:RawContent.Document +rawContent = Neos.Fusion:Case +rawContent { + shortcut { + prototype(Neos.Neos:Page) { + body = Neos.Neos:Shortcut + } + + @position = 'start' + condition = ${q(node).is('[instanceof Neos.Neos:Shortcut]')} + type = 'Neos.Neos:Page' + } + + default { + @position = 'end' + condition = true + renderer = Neos.Neos:RawContent.Document + } +} diff --git a/Neos.Neos/Resources/Private/Fusion/RootCase.fusion b/Neos.Neos/Resources/Private/Fusion/RootCase.fusion index cd1cf2a84f7..f79a3cd127f 100644 --- a/Neos.Neos/Resources/Private/Fusion/RootCase.fusion +++ b/Neos.Neos/Resources/Private/Fusion/RootCase.fusion @@ -17,9 +17,8 @@ root { editPreviewMode { @position = 'end 9996' - possibleEditPreviewModePath = ${documentNode.context.currentRenderingMode.fusionPath} - condition = ${Neos.Node.inBackend(documentNode) && this.possibleEditPreviewModePath != null && this.possibleEditPreviewModePath != ''} - renderPath = ${'/' + this.possibleEditPreviewModePath} + condition = ${(userInterfaceMode.isEditMode || userInterfaceMode.isPreviewMode) && userInterfaceMode.fusionPath} + renderPath = ${'/' + userInterfaceMode.fusionPath} } format { @@ -44,12 +43,6 @@ root { renderPath = '/page' } - rawContent { - @position = 'end 10000' - condition = ${Neos.Node.inBackend(documentNode) && documentNode.context.currentRenderingMode.edit} - renderPath = '/rawContent' - } - # Fail but create a helpful error message error { @position = 'end 10001'