diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c340ced --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# Various temporary and editor files +.fuse_hidden +.sass-cache +bower_components +node_modules +*.DS_Store +/.idea +.remote-sync.json diff --git a/Classes/DataSource/NodeTypeDataSource.php b/Classes/DataSource/NodeTypeDataSource.php new file mode 100644 index 0000000..07f358f --- /dev/null +++ b/Classes/DataSource/NodeTypeDataSource.php @@ -0,0 +1,109 @@ +getContext()->getCurrentSite()->getSiteResourcesPackageKey(); + + $data = []; + foreach($this->getStyleguideObjects($sitePackageKey) as $prototypeName => $styleguideObject) { + $data[] = array( + 'label' => $styleguideObject['title'], + 'value' => $prototypeName, + 'icon' => $styleguideObject['structure']['icon'] + ); + } + + return $data; + } + + /** + * @param $sitePackageKey + * @param $styleguideObject + * @return array + * @throws \Neos\Neos\Domain\Exception + */ + protected function getStyleguideObjects($sitePackageKey): array + { + $fusionAst = $this->fusionService->getMergedFusionObjectTreeForSitePackage($sitePackageKey); + $styleguideObjects = $this->fusionService->getStyleguideObjectsFromFusionAst($fusionAst); + $prototypeStructures = $this->configurationService->getSiteConfiguration($sitePackageKey, 'ui.structure'); + + foreach ($styleguideObjects as $prototypeName => &$styleguideObject) { + $styleguideObject['structure'] = $this->getStructureForPrototypeName($prototypeStructures, $prototypeName); + } + + $hiddenPrototypeNamePatterns = $this->configurationService->getSiteConfiguration($sitePackageKey, 'hiddenPrototypeNamePatterns'); + if (is_array($hiddenPrototypeNamePatterns)) { + $alwaysShowPrototypes = $this->configurationService->getSiteConfiguration($sitePackageKey, 'alwaysShowPrototypes'); + foreach ($hiddenPrototypeNamePatterns as $pattern) { + $styleguideObjects = array_filter( + $styleguideObjects, + function ($prototypeName) use ($pattern, $alwaysShowPrototypes) { + if (in_array($prototypeName, $alwaysShowPrototypes, true)) { + return true; + } + return fnmatch($pattern, $prototypeName) === false; + }, + ARRAY_FILTER_USE_KEY + ); + } + } + return $styleguideObjects; + } + + /** + * Find the matching structure for a prototype + * + * @param $prototypeStructures + * @param $prototypeName + * @return array + */ + protected function getStructureForPrototypeName($prototypeStructures, $prototypeName) + { + foreach ($prototypeStructures as $structure) { + if (preg_match(sprintf('!%s!', $structure['match']), $prototypeName)) { + return $structure; + } + } + + return [ + 'label' => 'Other', + 'icon' => 'icon-question', + 'color' => 'white' + ]; + } +} diff --git a/Configuration/NodeTypes.Content.PrototypeRendererNode.yaml b/Configuration/NodeTypes.Content.PrototypeRendererNode.yaml new file mode 100644 index 0000000..e2971ef --- /dev/null +++ b/Configuration/NodeTypes.Content.PrototypeRendererNode.yaml @@ -0,0 +1,55 @@ +'CodeQ.NeosMonocleRenderer:Content.MonocleRendererNode': + label: "${String.trim(String.substr(q(node).property('prototypeName'), String.lastIndexOf(q(node).property('prototypeName'), '.') + 1))}" + superTypes: + 'Neos.Neos:Content': true + ui: + label: i18n + icon: 'icon-code' + position: 900 + inspector: + groups: + prototype: + label: i18n + icon: 'icon-cogs' + position: 10 + creationDialog: + elements: + prototypeName: + type: string + ui: + label: i18n + editor: 'Content/Inspector/Editors/SelectBoxEditor' + editorOptions: + dataSourceIdentifier: 'codeq-neosmonoclerenderer-nodetype' + validation: + 'Neos.Neos/Validation/NotEmptyValidator': [] + options: + template: + properties: + prototypeName: '${data.prototypeName}' + properties: + prototypeName: + type: string + ui: + label: i18n + reloadIfChanged: true + reloadPageIfChanged: true + inspector: + group: 'prototype' + editor: 'Content/Inspector/Editors/SelectBoxEditor' + editorOptions: + dataSourceIdentifier: 'codeq-neosmonoclerenderer-nodetype' + validation: + 'Neos.Neos/Validation/NotEmptyValidator': [] + json: + type: string + defaultValue: '{}' + ui: + label: i18n + reloadIfChanged: true + inspector: + group: 'prototype' + editor: 'Neos.Neos/Inspector/Editors/CodeEditor' + editorOptions: + buttonLabel: i18n + highlightingMode: 'text/json' diff --git a/Configuration/Policy.yaml b/Configuration/Policy.yaml new file mode 100644 index 0000000..bf5ca06 --- /dev/null +++ b/Configuration/Policy.yaml @@ -0,0 +1,56 @@ +# restrict creation and editing of `CodeQ.NeosMonocleRenderer:Content.PrototypeRendererNode` nodetype to non-admins +privilegeTargets: + 'Neos\ContentRepository\Security\Authorization\Privilege\Node\CreateNodePrivilege': + 'CodeQ.NeosMonocleRenderer:CreatePrototypeRendererNode': + label: 'Create a new PrototypeRendererNode' + matcher: 'createdNodeIsOfType("CodeQ.NeosMonocleRenderer:Content.PrototypeRendererNode")' + 'Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePrivilege': + 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNode': + label: 'Edit a PrototypeRendererNode' + matcher: 'nodeIsOfType("CodeQ.NeosMonocleRenderer:Content.PrototypeRendererNode")' + 'Neos\ContentRepository\Security\Authorization\Privilege\Node\RemoveNodePrivilege': + 'CodeQ.NeosMonocleRenderer:RemovePrototypeRendererNode': + label: 'Remove a PrototypeRendererNode' + matcher: 'nodeIsOfType("CodeQ.NeosMonocleRenderer:Content.PrototypeRendererNode")' + 'Neos\ContentRepository\Security\Authorization\Privilege\Node\EditNodePropertyPrivilege': + 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNodeProperty': + label: 'Edit a PrototypeRendererNode property' + matcher: 'nodeIsOfType("CodeQ.NeosMonocleRenderer:Content.PrototypeRendererNode")' + +roles: + ## + # By default this role is abstract, feel free to use it however you want. + # This role is considered public API + ## + 'CodeQ.NeosMonocleRenderer:PrototypeRendererNodeEditor': + abstract: true + label: 'Prototype Renderer Node Editor' + description: 'A user with this role is able to render any Prototype as content node.' + privileges: + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:CreatePrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:RemovePrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNodeProperty' + permission: GRANT + + 'Neos.Neos:Administrator': + privileges: + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:CreatePrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:RemovePrototypeRendererNode' + permission: GRANT + - + privilegeTarget: 'CodeQ.NeosMonocleRenderer:EditPrototypeRendererNodeProperty' + permission: GRANT diff --git a/Configuration/Settings.yaml b/Configuration/Settings.yaml new file mode 100644 index 0000000..8a266b9 --- /dev/null +++ b/Configuration/Settings.yaml @@ -0,0 +1,13 @@ + +Neos: + Neos: + fusion: + autoInclude: + CodeQ.NeosMonocleRenderer: true + userInterface: + translation: + autoInclude: + CodeQ.NeosMonocleRenderer: + - 'Fusion/*' + - 'NodeTypes/*' + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bdf240d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2018 Roland Schütz + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Resources/Private/Fusion/Content/PrototypeRendererNode/MonocleRendererNode.fusion b/Resources/Private/Fusion/Content/PrototypeRendererNode/MonocleRendererNode.fusion new file mode 100644 index 0000000..e461a33 --- /dev/null +++ b/Resources/Private/Fusion/Content/PrototypeRendererNode/MonocleRendererNode.fusion @@ -0,0 +1,53 @@ +prototype(CodeQ.NeosMonocleRenderer:Content.MonocleRendererNode) < prototype(Neos.Neos:ContentComponent) { + @context { + prototypeName = ${String.trim(q(node).property('prototypeName'))} + prototypeProps = ${Json.parse(q(node).property('json') || '{}')} + } + + renderer = Neos.Fusion:Case { + noPrototypeName { + condition = ${!prototypeName && node.context.inBackend} + renderer = CodeQ.PrototypeRendererNode:Content.PrototypeRendererNode.ErrorMessage { + name = 'noPrototypeNameNotice' + } + } +// jsonIsInvalid { +// condition = ${!prototypeProps && node.context.inBackend} +// renderer = Neos.Fusion:Debug { +// foo = ${prototypeProps} +// } +//// renderer = CodeQ.PrototypeRendererNode:Content.PrototypeRendererNode.ErrorMessage { +//// name = 'jsonIsInvalidNotice' +//// } +// } + canRender { + condition = Neos.Fusion:CanRender { + type = ${prototypeName} + } + renderer = Neos.Fusion:Renderer { + type = ${prototypeName} + element.@apply.props = ${prototypeProps} + } + } + prototypeDoesNotExist { + condition = ${node.context.inBackend} + renderer = CodeQ.PrototypeRendererNode:Content.PrototypeRendererNode.ErrorMessage { + name = 'prototypeDoesNotExistNotice' + } + } + frontend { + condition = true + renderer = false + } + } +} + +prototype(CodeQ.PrototypeRendererNode:Content.PrototypeRendererNode.ErrorMessage) < prototype(Neos.Fusion:Component) { + name = '' + + renderer = afx` +