From 7ef3cabf61fb6faf380756c2214171dfdf7dc1f2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Mon, 22 Jan 2024 11:34:44 +0100 Subject: [PATCH] WIP: Feature copyTo --- .../NodeCreation/NodeCreationService.php | 28 ++++++- Classes/Domain/Template/Template.php | 22 +++++- .../TemplateConfigurationProcessor.php | 4 +- .../TemplateConfiguration/TemplatePart.php | 2 +- .../AbstractNodeTemplateTestCase.php | 2 +- .../Functional/ContentRepositoryTestTrait.php | 5 ++ .../Features/CopyNodes/CopyNodesTest.php | 75 +++++++++++++++++++ .../CopyNodes/NodeTypes.StaticNodeCopy.yaml | 12 +++ .../Snapshots/StaticNodeCopy.nodes.json | 22 ++++++ .../Snapshots/StaticNodeCopy.template.json | 15 ++++ .../Functional/JsonSerializeNodeTreeTrait.php | 6 +- 11 files changed, 185 insertions(+), 8 deletions(-) create mode 100644 Tests/Functional/Features/CopyNodes/CopyNodesTest.php create mode 100644 Tests/Functional/Features/CopyNodes/NodeTypes.StaticNodeCopy.yaml create mode 100644 Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.nodes.json create mode 100644 Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.template.json diff --git a/Classes/Domain/NodeCreation/NodeCreationService.php b/Classes/Domain/NodeCreation/NodeCreationService.php index d21e72d..5a98e76 100644 --- a/Classes/Domain/NodeCreation/NodeCreationService.php +++ b/Classes/Domain/NodeCreation/NodeCreationService.php @@ -12,6 +12,7 @@ use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; +use Neos\ContentRepository\Core\Feature\NodeDuplication\Command\CopyNodesRecursively; use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; @@ -105,6 +106,27 @@ public function apply(RootTemplate $template, NodeCreationCommands $commands, No ); } + private function copyTo(Template $template, TransientNode $node) + { + if (empty($template->getCopyFrom())) { + return; + } + + $sources = is_array($template->getCopyFrom()) ? $template->getCopyFrom() : [$template->getCopyFrom()]; + + foreach ($sources as $sourceNode) { + yield CopyNodesRecursively::createFromSubgraphAndStartNode( + $node->subgraph, + $sourceNode, + $node->originDimensionSpacePoint, + $node->nodeAggregateId, + null, + null + ); + } + + } + private function applyTemplateRecursively(Templates $templates, TransientNode $parentNode, NodeCreationCommands $commands, ProcessingErrors $processingErrors): NodeCreationCommands { foreach ($templates as $template) { @@ -138,7 +160,8 @@ private function applyTemplateRecursively(Templates $templates, TransientNode $p $node->nodeAggregateId, $parentNode->originDimensionSpacePoint, $this->referencesProcessor->processAndValidateReferences($node, $processingErrors) - ) + ), + ...iterator_to_array($this->copyTo($template, $node)) ); $commands = $this->applyTemplateRecursively( @@ -214,7 +237,8 @@ private function applyTemplateRecursively(Templates $templates, TransientNode $p $node->nodeAggregateId, $parentNode->originDimensionSpacePoint, $this->referencesProcessor->processAndValidateReferences($node, $processingErrors) - ) + ), + // todo ...iterator_to_array($this->copyTo($template, $node)) ); diff --git a/Classes/Domain/Template/Template.php b/Classes/Domain/Template/Template.php index 645bbb6..3f8b8bd 100644 --- a/Classes/Domain/Template/Template.php +++ b/Classes/Domain/Template/Template.php @@ -25,16 +25,19 @@ class Template implements \JsonSerializable private Templates $childNodes; + private mixed $copyFrom; + /** * @internal * @param array $properties */ - public function __construct(?NodeTypeName $type, ?NodeName $name, array $properties, Templates $childNodes) + public function __construct(?NodeTypeName $type, ?NodeName $name, array $properties, Templates $childNodes, mixed $copyFrom) { $this->type = $type; $this->name = $name; $this->properties = $properties; $this->childNodes = $childNodes; + $this->copyFrom = $copyFrom; } public function getType(): ?NodeTypeName @@ -60,13 +63,28 @@ public function getChildNodes(): Templates return $this->childNodes; } + public function getCopyFrom(): mixed + { + return $this->copyFrom; + } + public function jsonSerialize(): mixed { + if ($this->copyFrom) { + return [ + 'type' => $this->type, + 'name' => $this->name, + 'properties' => $this->properties, + 'childNodes' => $this->childNodes, + 'copyFrom' => $this->copyFrom, + ]; + } + return [ 'type' => $this->type, 'name' => $this->name, 'properties' => $this->properties, - 'childNodes' => $this->childNodes + 'childNodes' => $this->childNodes, ]; } } diff --git a/Classes/Domain/TemplateConfiguration/TemplateConfigurationProcessor.php b/Classes/Domain/TemplateConfiguration/TemplateConfigurationProcessor.php index e5184bf..9202302 100644 --- a/Classes/Domain/TemplateConfiguration/TemplateConfigurationProcessor.php +++ b/Classes/Domain/TemplateConfiguration/TemplateConfigurationProcessor.php @@ -135,11 +135,13 @@ private function createTemplateFromTemplatePart(TemplatePart $templatePart): Tem $type = $templatePart->processConfiguration('type'); $name = $templatePart->processConfiguration('name'); + $copyFrom = $templatePart->processConfiguration('copyFrom'); return new Template( $type !== null ? NodeTypeName::fromString($type) : null, $name !== null ? NodeName::transliterateFromString($name) : null, $processedProperties, - $childNodeTemplates + $childNodeTemplates, + $copyFrom ); } diff --git a/Classes/Domain/TemplateConfiguration/TemplatePart.php b/Classes/Domain/TemplateConfiguration/TemplatePart.php index e04972f..d885436 100644 --- a/Classes/Domain/TemplateConfiguration/TemplatePart.php +++ b/Classes/Domain/TemplateConfiguration/TemplatePart.php @@ -201,7 +201,7 @@ private function validateTemplateConfigurationKeys(): void { $isRootTemplate = $this->fullPathToConfiguration === []; foreach (array_keys($this->configuration) as $key) { - if (!in_array($key, ['type', 'name', 'properties', 'childNodes', 'when', 'withItems', 'withContext'], true)) { + if (!in_array($key, ['type', 'name', 'properties', 'childNodes', 'when', 'withItems', 'withContext', 'copyFrom'], true)) { $this->addProcessingErrorForPath( new \InvalidArgumentException(sprintf('Template configuration has illegal key "%s"', $key), 1686150349274), $key diff --git a/Tests/Functional/AbstractNodeTemplateTestCase.php b/Tests/Functional/AbstractNodeTemplateTestCase.php index 0e5cbaf..3157ba6 100644 --- a/Tests/Functional/AbstractNodeTemplateTestCase.php +++ b/Tests/Functional/AbstractNodeTemplateTestCase.php @@ -52,7 +52,7 @@ abstract class AbstractNodeTemplateTestCase extends TestCase // we don't use Flo protected Node $homePageMainContentCollectionNode; - private ContentSubgraphInterface $subgraph; + protected ContentSubgraphInterface $subgraph; private NodeTemplateDumper $nodeTemplateDumper; diff --git a/Tests/Functional/ContentRepositoryTestTrait.php b/Tests/Functional/ContentRepositoryTestTrait.php index dfea6e7..4678ee1 100644 --- a/Tests/Functional/ContentRepositoryTestTrait.php +++ b/Tests/Functional/ContentRepositoryTestTrait.php @@ -64,4 +64,9 @@ private function initCleanContentRepository(ContentRepositoryId $contentReposito $connection->executeStatement('TRUNCATE ' . $eventTableName); $this->contentRepository->resetProjectionStates(); } + + final public function getContentRepository(): ContentRepository + { + return $this->contentRepository; + } } diff --git a/Tests/Functional/Features/CopyNodes/CopyNodesTest.php b/Tests/Functional/Features/CopyNodes/CopyNodesTest.php new file mode 100644 index 0000000..81f6ced --- /dev/null +++ b/Tests/Functional/Features/CopyNodes/CopyNodesTest.php @@ -0,0 +1,75 @@ +getContentRepository()->handle( + CreateNodeAggregateWithNode::create( + $this->homePageNode->subgraphIdentity->contentStreamId, + $source = NodeAggregateId::fromString('source-node'), + NodeTypeName::fromString('Flowpack.NodeTemplates:Document.Page'), + $this->homePageNode->originDimensionSpacePoint, + $this->homePageNode->nodeAggregateId, + nodeName: NodeName::fromString(uniqid('node-')), + )->withTetheredDescendantNodeAggregateIds(NodeAggregateIdsByNodePaths::fromArray([ + 'main' => $main = NodeAggregateId::fromString('source-node-main-collection'), + ])) + )->block(); + + $this->getContentRepository()->handle( + CreateNodeAggregateWithNode::create( + $this->homePageNode->subgraphIdentity->contentStreamId, + NodeAggregateId::fromString('source-node-text-1'), + NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Text'), + $this->homePageNode->originDimensionSpacePoint, + $main, + nodeName: NodeName::fromString(uniqid('node-')), + initialPropertyValues: PropertyValuesToWrite::fromArray([ + 'text' => 'Lorem ipsum 1' + ]) + ) + )->block(); + + $this->getContentRepository()->handle( + CreateNodeAggregateWithNode::create( + $this->homePageNode->subgraphIdentity->contentStreamId, + NodeAggregateId::fromString('source-node-text-2'), + NodeTypeName::fromString('Flowpack.NodeTemplates:Content.Text'), + $this->homePageNode->originDimensionSpacePoint, + $main, + nodeName: NodeName::fromString(uniqid('node-')), + initialPropertyValues: PropertyValuesToWrite::fromArray([ + 'text' => 'Lorem ipsum 2' + ]) + ) + )->block(); + + + $createdNode = $this->createNodeInto( + $this->homePageMainContentCollectionNode, + 'Flowpack.NodeTemplates:Content.StaticNodeCopy', + [ + 'sourceNode' => $this->subgraph->findNodeById($main) + ] + ); + + $this->assertLastCreatedTemplateMatchesSnapshot('StaticNodeCopy'); + + $this->assertNoExceptionsWereCaught(); + $this->assertNodeDumpAndTemplateDumpMatchSnapshot('StaticNodeCopy', $createdNode); + } +} diff --git a/Tests/Functional/Features/CopyNodes/NodeTypes.StaticNodeCopy.yaml b/Tests/Functional/Features/CopyNodes/NodeTypes.StaticNodeCopy.yaml new file mode 100644 index 0000000..17f37ec --- /dev/null +++ b/Tests/Functional/Features/CopyNodes/NodeTypes.StaticNodeCopy.yaml @@ -0,0 +1,12 @@ +'Flowpack.NodeTemplates:Content.StaticNodeCopy': + superTypes: + 'Neos.Neos:Content': true + childNodes: + column0: + type: 'Neos.Neos:ContentCollection' + options: + template: + childNodes: + column0Tethered: + name: column0 + copyFrom: "${q(parentNode).find('#source-node-main-collection').children().get()}" diff --git a/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.nodes.json b/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.nodes.json new file mode 100644 index 0000000..1f4ce29 --- /dev/null +++ b/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.nodes.json @@ -0,0 +1,22 @@ +{ + "childNodes": [ + { + "nodeTypeName": "Neos.Neos:ContentCollection", + "nodeName": "column0", + "childNodes": [ + { + "nodeTypeName": "Flowpack.NodeTemplates:Content.Text", + "properties": { + "text": "Lorem ipsum 1" + } + }, + { + "nodeTypeName": "Flowpack.NodeTemplates:Content.Text", + "properties": { + "text": "Lorem ipsum 2" + } + } + ] + } + ] +} diff --git a/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.template.json b/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.template.json new file mode 100644 index 0000000..ea28d2c --- /dev/null +++ b/Tests/Functional/Features/CopyNodes/Snapshots/StaticNodeCopy.template.json @@ -0,0 +1,15 @@ +{ + "properties": [], + "childNodes": [ + { + "type": null, + "name": "column0", + "properties": [], + "childNodes": [], + "copyFrom": [ + "Node(source-node-text-1, Flowpack.NodeTemplates:Content.Text)", + "Node(source-node-text-2, Flowpack.NodeTemplates:Content.Text)" + ] + } + ] +} diff --git a/Tests/Functional/JsonSerializeNodeTreeTrait.php b/Tests/Functional/JsonSerializeNodeTreeTrait.php index 952b775..f5f33a1 100644 --- a/Tests/Functional/JsonSerializeNodeTreeTrait.php +++ b/Tests/Functional/JsonSerializeNodeTreeTrait.php @@ -68,7 +68,11 @@ private function serializeValuesInArray(array $array): array $value = $this->serializeValuesInArray($value); } } elseif (is_object($value)) { - $id = ObjectAccess::getProperty($value, 'Persistence_Object_Identifier', true); + try { + $id = ObjectAccess::getProperty($value, 'Persistence_Object_Identifier', true); + } catch (\Exception) { + $id = null; + } $value = sprintf('object(%s%s)', get_class($value), $id ? (sprintf(', %s', $id)) : ''); } else { continue;