diff --git a/Classes/Aspects/AugmentationAspect.php b/Classes/Aspects/AugmentationAspect.php index c29f45d82d..04d6e6d2aa 100644 --- a/Classes/Aspects/AugmentationAspect.php +++ b/Classes/Aspects/AugmentationAspect.php @@ -22,6 +22,7 @@ use Neos\Neos\Domain\Service\ContentContext; use Neos\Neos\Ui\Domain\Service\UserLocaleService; use Neos\Neos\Ui\Fusion\Helper\NodeInfoHelper; +use Neos\Neos\Ui\Service\NodePolicyService; /** * - Serialize all nodes related to the currently rendered document @@ -61,6 +62,12 @@ class AugmentationAspect */ protected $controllerContext = null; + /** + * @Flow\Inject + * @var NodePolicyService + */ + protected $nodePolicyService; + /** * @Flow\Before("method(Neos\Neos\Fusion\ContentElementWrappingImplementation->evaluate())") * @param JoinPointInterface $joinPoint @@ -145,7 +152,7 @@ public function editableElementAugmentation(JoinPointInterface $joinPoint) return $content; } - if ($this->nodeAuthorizationService->isGrantedToEditNode($node) === false) { + if ($this->nodePolicyService->canReadProperties($node) === false) { return $content; } diff --git a/Classes/Service/NodePolicyService.php b/Classes/Service/NodePolicyService.php index ee575cb351..375bce15af 100644 --- a/Classes/Service/NodePolicyService.php +++ b/Classes/Service/NodePolicyService.php @@ -19,6 +19,7 @@ use Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePropertyPrivilege; use Neos\ContentRepository\Security\Authorization\Privilege\Node\NodePrivilegeSubject; use Neos\ContentRepository\Security\Authorization\Privilege\Node\PropertyAwareNodePrivilegeSubject; +use Neos\ContentRepository\Security\Authorization\Privilege\Node\ReadNodePrivilege; use Neos\ContentRepository\Security\Authorization\Privilege\Node\RemoveNodePrivilege; use Neos\Flow\Annotations as Flow; use Neos\ContentRepository\Domain\Model\NodeInterface; @@ -82,6 +83,7 @@ public function getNodePolicyInformation(NodeInterface $node): array 'disallowedNodeTypes' => $this->getDisallowedNodeTypes($node), 'canRemove' => $this->canRemoveNode($node), 'canEdit' => $this->canEditNode($node), + 'canReadProperties' => $this->canReadProperties($node), 'disallowedProperties' => $this->getDisallowedProperties($node) ]; } @@ -102,6 +104,30 @@ public function isNodeTreePrivilegeGranted(NodeInterface $node): bool ); } + /** + * @param NodeInterface $node + * @return bool + */ + public function canReadProperties(NodeInterface $node): bool + { + // In our current understanding, this canReadProperties check can NEVER return false under normal + // Neos UI circumstances, because we test for ReadNodePrivilege. + // if a node should be hidden via ReadNodePrivilege, it is not returned from persistence, thus + // you would not be able to get the $node instance. + // + // We left the method in there nevertheless for: + // - if users want to override this via AOP + // - if somebody would want to create a new PrivilegeType for it (!! node privilege concept changes with Neos 9 !!) + if (!isset(self::getUsedPrivilegeClassNames($this->objectManager)[ReadNodePrivilege::class])) { + return true; + } + + return $this->privilegeManager->isGranted( + ReadNodePrivilege::class, + new NodePrivilegeSubject($node) + ); + } + /** * @param NodeInterface $node * @return array diff --git a/composer.json b/composer.json index 43e50f09e2..46e7b2349c 100644 --- a/composer.json +++ b/composer.json @@ -7,7 +7,7 @@ ], "require": { "neos/neos": "^8.3.0", - "neos/neos-ui-compiled": "self.version" + "neos/neos-ui-compiled": "8.4.x-dev" }, "autoload": { "psr-4": { diff --git a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js index 9f8a78afe6..ae0b468a5e 100644 --- a/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js +++ b/packages/neos-ui-ckeditor5-bindings/src/ckEditorApi.js @@ -37,7 +37,7 @@ export const bootstrap = _editorConfig => { }; export const createEditor = store => async options => { - const {propertyDomNode, propertyName, editorOptions, globalRegistry, userPreferences, onChange} = options; + const {propertyDomNode, propertyName, editorOptions, globalRegistry, userPreferences, onChange, isReadOnly} = options; const ckEditorConfig = editorConfig.configRegistry.getCkeditorConfig({ editorOptions, userPreferences, @@ -58,26 +58,34 @@ export const createEditor = store => async options => { return NeosEditor .create(propertyDomNode, ckEditorConfig) .then(editor => { - const debouncedOnChange = debounce(() => onChange(cleanupContentBeforeCommit(editor.getData())), 1500, {maxWait: 5000}); - editor.model.document.on('change:data', debouncedOnChange); - editor.ui.focusTracker.on('change:isFocused', event => { - if (!event.source.isFocused) { - // when another editor is focused commit all possible pending changes - debouncedOnChange.flush(); - return - } + editor.isReadOnly = isReadOnly; - currentEditor = editor; - editorConfig.setCurrentlyEditedPropertyName(propertyName); - handleUserInteractionCallback(); - }); + // Set placeholder text as Data if editor is empty because readOnly mode hides the placeholder + if (isReadOnly && editor.getData() === '') { + editor.setData(ckEditorConfig.placeholder, {doNotFireChange: true}); + } else { + const debouncedOnChange = debounce(() => onChange(cleanupContentBeforeCommit(editor.getData())), 1500, {maxWait: 5000}); + editor.model.document.on('change:data', debouncedOnChange); + editor.ui.focusTracker.on('change:isFocused', event => { + if (!event.source.isFocused) { + // when another editor is focused commit all possible pending changes + debouncedOnChange.flush(); + return + } + + currentEditor = editor; + editorConfig.setCurrentlyEditedPropertyName(propertyName); + handleUserInteractionCallback(); + }); - editor.keystrokes.set('Ctrl+K', (_, cancel) => { - store.dispatch(actions.UI.ContentCanvas.toggleLinkEditor()); - cancel(); - }); + editor.keystrokes.set('Ctrl+K', (_, cancel) => { + store.dispatch(actions.UI.ContentCanvas.toggleLinkEditor()); + cancel(); + }); + + editor.model.document.on('change', () => handleUserInteractionCallback()); + } - editor.model.document.on('change', () => handleUserInteractionCallback()); return editor; }).catch(e => { if (e instanceof TypeError && e.message.match(/Class constructor .* cannot be invoked without 'new'/)) { diff --git a/packages/neos-ui-guest-frame/src/initializeGuestFrame.js b/packages/neos-ui-guest-frame/src/initializeGuestFrame.js index 631e1a737b..f81d18d96a 100644 --- a/packages/neos-ui-guest-frame/src/initializeGuestFrame.js +++ b/packages/neos-ui-guest-frame/src/initializeGuestFrame.js @@ -112,9 +112,10 @@ export default ({globalRegistry, store}) => function * initializeGuestFrame() { const editPreviewMode = $get(['ui', 'editPreviewMode'], state); const editPreviewModes = globalRegistry.get('frontendConfiguration').get('editPreviewModes'); - const isWorkspaceReadOnly = selectors.CR.Workspaces.isWorkspaceReadOnlySelector(state); const currentEditMode = editPreviewModes[editPreviewMode]; - if (!currentEditMode || !currentEditMode.isEditingMode || isWorkspaceReadOnly) { + + // ReadOnly workspaces are handled by ckEditor directly + if (!currentEditMode || !currentEditMode.isEditingMode) { return; } diff --git a/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js b/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js index 94d527b5bb..145d6192ee 100644 --- a/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js +++ b/packages/neos-ui-guest-frame/src/initializePropertyDomNode.js @@ -1,6 +1,6 @@ import {$get, $contains} from 'plow-js'; -import {actions} from '@neos-project/neos-ui-redux-store'; +import {actions, selectors} from '@neos-project/neos-ui-redux-store'; import {validateElement} from '@neos-project/neos-ui-validators'; import {getGuestFrameWindow, closestContextPathInGuestFrame} from './dom'; @@ -61,6 +61,7 @@ export default ({store, globalRegistry, nodeTypesRegistry, inlineEditorRegistry, try { if (!propertyDomNode.dataset.neosInlineEditorIsInitialized) { const userPreferences = $get('user.preferences', store.getState()); + const isWorkspaceReadOnly = selectors.CR.Workspaces.isWorkspaceReadOnlySelector(store.getState()); createInlineEditor({ propertyDomNode, @@ -70,10 +71,21 @@ export default ({store, globalRegistry, nodeTypesRegistry, inlineEditorRegistry, editorOptions, globalRegistry, userPreferences, - persistChange: change => store.dispatch( - actions.Changes.persistChanges([change]) - ), + isReadOnly: isWorkspaceReadOnly, + persistChange: change => { + if (isWorkspaceReadOnly) { + return; + } + + store.dispatch( + actions.Changes.persistChanges([change]) + ) + }, onChange: value => { + if (isWorkspaceReadOnly) { + return; + } + const validationResult = validateElement(value, $get(['properties', propertyName], nodeType), globalRegistry.get('validators')); // Update inline validation errors store.dispatch( diff --git a/packages/neos-ui/src/Containers/RightSideBar/Inspector/TabPanel/index.js b/packages/neos-ui/src/Containers/RightSideBar/Inspector/TabPanel/index.js index 06c959a62a..87d9fec5f6 100644 --- a/packages/neos-ui/src/Containers/RightSideBar/Inspector/TabPanel/index.js +++ b/packages/neos-ui/src/Containers/RightSideBar/Inspector/TabPanel/index.js @@ -30,7 +30,7 @@ export default class TabPanel extends PureComponent { return false; } - return $get(['policy', 'canEdit'], node) && !$contains(item.id, 'policy.disallowedProperties', node); + return $get(['policy', 'canReadProperties'], node) && !$contains(item.id, 'policy.disallowedProperties', node); }; renderTabPanel = groups => { diff --git a/packages/neos-ui/src/Containers/RightSideBar/Inspector/index.js b/packages/neos-ui/src/Containers/RightSideBar/Inspector/index.js index 87db2ecd92..c6fcaa4615 100644 --- a/packages/neos-ui/src/Containers/RightSideBar/Inspector/index.js +++ b/packages/neos-ui/src/Containers/RightSideBar/Inspector/index.js @@ -247,7 +247,7 @@ export default class Inspector extends PureComponent { return false; } - return $get(['policy', 'canEdit'], focusedNode) && !$contains(item.id, 'policy.disallowedProperties', focusedNode); + return $get(['policy', 'canReadProperties'], focusedNode) && !$contains(item.id, 'policy.disallowedProperties', focusedNode); }; /**