diff --git a/Classes/NeosEscrTemplateWriteAdapter.php b/Classes/NeosEscrTemplateWriteAdapter.php new file mode 100644 index 0000000..bf7454e --- /dev/null +++ b/Classes/NeosEscrTemplateWriteAdapter.php @@ -0,0 +1,177 @@ +writeTemplate($command, $contentRepository);` + */ +class NeosEscrTemplateWriteAdapter +{ + public function writeTemplate(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): void + { + $template = [ + 'childNodes' => [ + 'main' => [ + 'name' => 'main', + 'childNodes' => [ + 'content1' => [ + 'type' => "Neos.Demo:Content.Text", + 'properties' => [ + 'text' => "huhu", + ] + ] + ] + ], + 'foo' => [ + 'type' => 'Neos.Demo:Document.Page', + 'childNodes' => [ + 'main' => [ + 'name' => 'main', + 'childNodes' => [ + 'content1' => [ + 'type' => "Neos.Demo:Content.Text", + 'properties' => [ + 'text' => "textiii" + ] + ], + 'content2' => [ + 'type' => "Neos.Demo:Content.Text", + 'properties' => [ + 'text' => "huijkuihjnihujbn" + ] + ] + ] + ], + ] + ] + ] + ]; + + $createNodeCommand = $this->augmentWithTetheredDescendantNodeAggregateIds($createNodeAggregateWithNode, $contentRepository); + + if (isset($template['properties'])) { + // documents generate uripath blabla + $createNodeCommand = $createNodeCommand->initialPropertyValues->merge( + PropertyValuesToWrite::fromArray($this->requireValidProperties($template['properties'])) + ); + } + + $commands = $this->createCommandsRecursivelyFromTemplateChildNodes($createNodeCommand, $template, $contentRepository); + + $contentRepository->handle($createNodeCommand)->block(); + + foreach ($commands as $command) { + $contentRepository->handle($command)->block(); + } + } + + /** + * In the old CR, it was common practice to set internal or meta properties via this syntax: `_hidden` so it was also allowed in templates but not anymore. + * @throws \InvalidArgumentException + */ + private function requireValidProperties(array $properties): array + { + $legacyInternalProperties = [ + '_accessRoles', + '_contentObject', + '_hidden', + '_hiddenAfterDateTime', + '_hiddenBeforeDateTime', + '_hiddenInIndex', + '_index', + '_name', + '_nodeType', + '_removed', + '_workspace' + ]; + foreach ($properties as $propertyName => $propertyValue) { + if (str_starts_with($propertyName, '_')) { + $lowerPropertyName = strtolower($propertyName); + foreach ($legacyInternalProperties as $legacyInternalProperty) { + if ($lowerPropertyName === strtolower($legacyInternalProperty)) { + throw new \InvalidArgumentException('Internal legacy properties are not implement.' . $propertyName); + } + } + } + } + return $properties; + } + + private function createCommandsRecursivelyFromTemplateChildNodes(CreateNodeAggregateWithNode $createParentNodeCommand, array $template, ContentRepository $contentRepository): array + { + $makeCreateNodeCommand = function (NodeAggregateId $parentNodeAggregateId, array $subTemplate) use ($createParentNodeCommand, $contentRepository) { + return $this->augmentWithTetheredDescendantNodeAggregateIds(new CreateNodeAggregateWithNode( + contentStreamId: $createParentNodeCommand->contentStreamId, + nodeAggregateId: NodeAggregateId::create(), + nodeTypeName: NodeTypeName::fromString($subTemplate['type']), + originDimensionSpacePoint: $createParentNodeCommand->originDimensionSpacePoint, + parentNodeAggregateId: $parentNodeAggregateId, + nodeName: NodeName::fromString(uniqid('node-', false)), + initialPropertyValues: isset($subTemplate['properties']) + ? PropertyValuesToWrite::fromArray($this->requireValidProperties($subTemplate['properties'])) + : null + ), $contentRepository); + }; + + $commands = []; + foreach ($template['childNodes'] ?? [] as $childNode) { + if (isset($childNode['name']) && $autoCreatedNodeId = $createParentNodeCommand->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString($childNode['name']))) { + if (isset($childNode['type'])) { + throw new \Exception('For auto-created nodes the type cannot be qualified.'); + } + if (isset($childNode['properties'])) { + $commands[] = new SetNodeProperties( + $createParentNodeCommand->contentStreamId, + $autoCreatedNodeId, + $createParentNodeCommand->originDimensionSpacePoint, + PropertyValuesToWrite::fromArray($this->requireValidProperties($childNode['properties'])) + ); + } + foreach ($childNode['childNodes'] ?? [] as $innerChildNode) { + $commands[] = $newParent = $makeCreateNodeCommand($autoCreatedNodeId, $innerChildNode); + $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $innerChildNode, $contentRepository)]; + } + } else { + // if is document setUriPath based on title + $commands[] = $newParent = $makeCreateNodeCommand($createParentNodeCommand->nodeAggregateId, $childNode); + $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $childNode, $contentRepository)]; + } + } + return $commands; + } + + /** + * Precalculate the nodeIds for the auto-created childNodes, so that we can determine the id beforehand and use it for succeeding operations. + * + * ``` + * $mainContentCollectionNodeId = $createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString('main')) + * ``` + */ + private function augmentWithTetheredDescendantNodeAggregateIds(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): CreateNodeAggregateWithNode + { + $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($createNodeAggregateWithNode->nodeTypeName); + if (!isset($nodeType->getFullConfiguration()['childNodes'])) { + return $createNodeAggregateWithNode; + } + $nodeAggregateIdsByNodePaths = NodeAggregateIdsByNodePaths::createEmpty(); + foreach (array_keys($nodeType->getFullConfiguration()['childNodes']) as $autoCreatedNodeName) { + $nodeAggregateIdsByNodePaths = $nodeAggregateIdsByNodePaths->add( + NodePath::fromString($autoCreatedNodeName), + NodeAggregateId::create() + ); + } + return $createNodeAggregateWithNode->withTetheredDescendantNodeAggregateIds( + $nodeAggregateIdsByNodePaths->merge($createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds) + ); + } +} diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index 0e7dc07..488f124 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -1,11 +1,11 @@ -'Neos.Neos:Node': - options: - nodeCreationHandlers: - templateNodeCreationHandler: - nodeCreationHandler: 'Flowpack\NodeTemplates\NodeCreationHandler\TemplateNodeCreationHandler' - -'Neos.Neos:Document': - options: - nodeCreationHandlers: - documentTitle: - nodeCreationHandler: 'Flowpack\NodeTemplates\NodeCreationHandler\TemplatingDocumentTitleNodeCreationHandler' +# 'Neos.Neos:Node': +# options: +# nodeCreationHandlers: +# templateNodeCreationHandler: +# nodeCreationHandler: 'Flowpack\NodeTemplates\NodeCreationHandler\TemplateNodeCreationHandler' +# +# 'Neos.Neos:Document': +# options: +# nodeCreationHandlers: +# documentTitle: +# nodeCreationHandler: 'Flowpack\NodeTemplates\NodeCreationHandler\TemplatingDocumentTitleNodeCreationHandler' diff --git a/composer.json b/composer.json index 791cd68..0a5d782 100644 --- a/composer.json +++ b/composer.json @@ -4,9 +4,8 @@ "name": "flowpack/nodetemplates", "license": "GPL-3.0+", "require": { - "php": ">=7.4", - "neos/neos": "^7.3 || ^8.0", - "neos/neos-ui": "^7.3 || ^8.0" + "neos/neos": "^9.0", + "neos/neos-ui": "^9.0" }, "autoload": { "psr-4": {