From c3dc4943fe0509651a28e93e947d497bda311865 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 11 Jun 2023 14:53:37 +0200 Subject: [PATCH 01/30] FEATURE: Allow `NodeCreationHandlerInterface` to append additional commands --- .../Domain/Model/Changes/AbstractCreate.php | 23 +++-- .../ContentTitleNodeCreationHandler.php | 20 ++--- ...reationDialogPropertiesCreationHandler.php | 10 +-- .../DocumentTitleNodeCreationHandler.php | 18 ++-- .../NodeCreationCommands.php | 87 +++++++++++++++++++ .../NodeCreationHandlerInterface.php | 10 +-- .../ImagePropertyNodeCreationHandler.php | 10 +-- 7 files changed, 130 insertions(+), 48 deletions(-) create mode 100644 Classes/NodeCreationHandler/NodeCreationCommands.php diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 6cebb1e932..fc089aa036 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -21,6 +21,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; use Neos\Utility\PositionalArraySorter; @@ -119,9 +120,17 @@ protected function createNode( ); $contentRepository = $this->contentRepositoryRegistry->get($parentNode->subgraphIdentity->contentRepositoryId); - $command = $this->applyNodeCreationHandlers($command, $nodeTypeName, $contentRepository); + $commands = $this->applyNodeCreationHandlers( + new NodeCreationCommands($command), + $nodeTypeName, + $contentRepository + ); + + foreach ($commands as $command) { + $contentRepository->handle($command) + ->block(); + } - $contentRepository->handle($command)->block(); /** @var Node $newlyCreatedNode */ $newlyCreatedNode = $this->contentRepositoryRegistry->subgraphForNode($parentNode) ->findNodeById($nodeAggregateId); @@ -137,15 +146,15 @@ protected function createNode( * @throws InvalidNodeCreationHandlerException */ protected function applyNodeCreationHandlers( - CreateNodeAggregateWithNode $command, + NodeCreationCommands $commands, NodeTypeName $nodeTypeName, ContentRepository $contentRepository - ): CreateNodeAggregateWithNode { + ): NodeCreationCommands { $data = $this->getData() ?: []; $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName); if (!isset($nodeType->getOptions()['nodeCreationHandlers']) || !is_array($nodeType->getOptions()['nodeCreationHandlers'])) { - return $command; + return $commands; } foreach ((new PositionalArraySorter($nodeType->getOptions()['nodeCreationHandlers']))->toArray() as $nodeCreationHandlerConfiguration) { $nodeCreationHandler = new $nodeCreationHandlerConfiguration['nodeCreationHandler'](); @@ -156,8 +165,8 @@ protected function applyNodeCreationHandlers( get_class($nodeCreationHandler) ), 1364759956); } - $command = $nodeCreationHandler->handle($command, $data, $contentRepository); + $commands = $nodeCreationHandler->handle($commands, $data, $contentRepository); } - return $command; + return $commands; } } diff --git a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php index cd2f68fa16..88f703d9d0 100644 --- a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php +++ b/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php @@ -12,11 +12,7 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; -use Neos\Flow\Annotations as Flow; -use Neos\Neos\Service\TransliterationService; /** * Node creation handler that sets the "title" property for new content elements according @@ -28,32 +24,26 @@ */ class ContentTitleNodeCreationHandler implements NodeCreationHandlerInterface { - /** - * @Flow\Inject - * @var TransliterationService - */ - protected $transliterationService; - /** * Set the node title for the newly created Content node * * @param array $data incoming data from the creationDialog * @throws NodeTypeNotFoundException */ - public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode + public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands { if ( - !$contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName) + !$contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Content') ) { - return $command; + return $commands; } - $propertyValues = $command->initialPropertyValues; + $propertyValues = $commands->first->initialPropertyValues; if (isset($data['title'])) { $propertyValues = $propertyValues->withValue('title', $data['title']); } - return $command->withInitialPropertyValues($propertyValues); + return $commands->withInitialPropertyValues($propertyValues); } } diff --git a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php b/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php index dc47e42469..0aaf726d3c 100644 --- a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php +++ b/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php @@ -12,8 +12,6 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; @@ -36,14 +34,14 @@ class CreationDialogPropertiesCreationHandler implements NodeCreationHandlerInte /** * @param array $data */ - public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode + public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands { $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName); - $propertyValues = $command->initialPropertyValues; + $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); + $propertyValues = $commands->first->initialPropertyValues; foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { if ( !isset($propertyConfiguration['ui']['showInCreationDialog']) @@ -63,6 +61,6 @@ public function handle(CreateNodeAggregateWithNode $command, array $data, Conten $propertyValues = $propertyValues->withValue($propertyName, $propertyValue); } - return $command->withInitialPropertyValues($propertyValues); + return $commands->withInitialPropertyValues($propertyValues); } } diff --git a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php index 8efe069f2f..51c19a2a82 100644 --- a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php +++ b/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php @@ -16,8 +16,6 @@ use Behat\Transliterator\Transliterator; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException; use Neos\Flow\I18n\Locale; use Neos\Neos\Service\TransliterationService; @@ -43,15 +41,15 @@ class DocumentTitleNodeCreationHandler implements NodeCreationHandlerInterface /** * @param array $data */ - public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode + public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands { if ( - !$contentRepository->getNodeTypeManager()->getNodeType($command->nodeTypeName) + !$contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Document') ) { - return $command; + return $commands; } - $propertyValues = $command->initialPropertyValues; + $propertyValues = $commands->first->initialPropertyValues; if (isset($data['title'])) { $propertyValues = $propertyValues->withValue('title', $data['title']); } @@ -60,14 +58,14 @@ public function handle(CreateNodeAggregateWithNode $command, array $data, Conten $uriPathSegment = $data['title']; // otherwise, we fall back to the node name - if ($uriPathSegment === null && $command->nodeName !== null) { - $uriPathSegment = $command->nodeName->value; + if ($uriPathSegment === null && $commands->first->nodeName !== null) { + $uriPathSegment = $commands->first->nodeName->value; } // if not empty, we transliterate the uriPathSegment according to the language of the new node if ($uriPathSegment !== null && $uriPathSegment !== '') { $uriPathSegment = $this->transliterateText( - $command->originDimensionSpacePoint->toDimensionSpacePoint(), + $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), $uriPathSegment ); } else { @@ -77,7 +75,7 @@ public function handle(CreateNodeAggregateWithNode $command, array $data, Conten $uriPathSegment = Transliterator::urlize($uriPathSegment); $propertyValues = $propertyValues->withValue('uriPathSegment', $uriPathSegment); - return $command->withInitialPropertyValues($propertyValues); + return $commands->withInitialPropertyValues($propertyValues); } private function transliterateText(DimensionSpacePoint $dimensionSpacePoint, string $text): string diff --git a/Classes/NodeCreationHandler/NodeCreationCommands.php b/Classes/NodeCreationHandler/NodeCreationCommands.php new file mode 100644 index 0000000000..2c502ebc77 --- /dev/null +++ b/Classes/NodeCreationHandler/NodeCreationCommands.php @@ -0,0 +1,87 @@ + + */ + public readonly array $additionalCommands; + + /** + * @internal to guarantee that the initial create command is mostly preserved as intended. + * You can use {@see self::withInitialPropertyValues()} to add new properties of the to be created node. + */ + public function __construct( + CreateNodeAggregateWithNode $first, + CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences ...$additionalCommands + ) { + $this->first = $first; + $this->additionalCommands = $additionalCommands; + } + + /** + * Augment the first {@see CreateNodeAggregateWithNode} command with altered properties. + */ + public function withInitialPropertyValues(PropertyValuesToWrite $newInitialPropertyValues): self + { + return new self( + $this->first->withInitialPropertyValues($newInitialPropertyValues), + ...$this->additionalCommands + ); + } + + public function withAdditionalCommands( + CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences ...$additionalCommands + ): self { + return new self($this->first, ...$this->additionalCommands, ...$additionalCommands); + } + + /** + * @return \Traversable + */ + public function getIterator(): \Traversable + { + yield $this->first; + yield from $this->additionalCommands; + } +} diff --git a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php index b51ed2c408..5304d63850 100644 --- a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php +++ b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php @@ -12,7 +12,6 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; /** * Contract for Node Creation handler that allow to hook into the process just before a node is being added @@ -22,11 +21,12 @@ interface NodeCreationHandlerInterface { /** - * Do something with the newly created node + * You can "enrich" the node creation, by for example adding initial properties {@see NodeCreationCommands::withInitialPropertyValues()} + * or appending additional f.x. create-child nodes commands {@see NodeCreationCommands::withAdditionalCommands()} * - * @param CreateNodeAggregateWithNode $command The original node creation command + * @param NodeCreationCommands $commands original or previous commands, with the first command being the initial intended node creation * @param array $data incoming data from the creationDialog - * @return CreateNodeAggregateWithNode the original command or a new creation command with altered properties + * @return NodeCreationCommands the "enriched" commands, to be passed to the next handler or run at the end */ - public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode; + public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands; } diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php index c12639ebf4..1658a024d9 100644 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php @@ -12,11 +12,11 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; use Neos\Media\Domain\Model\ImageInterface; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; class ImagePropertyNodeCreationHandler implements NodeCreationHandlerInterface @@ -27,17 +27,17 @@ class ImagePropertyNodeCreationHandler implements NodeCreationHandlerInterface */ protected $propertyMapper; - public function handle(CreateNodeAggregateWithNode $command, array $data, ContentRepository $contentRepository): CreateNodeAggregateWithNode + public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands { if (!isset($data['image'])) { - return $command; + return $commands; } $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); $image = $this->propertyMapper->convert($data['image'], ImageInterface::class, $propertyMappingConfiguration); - $propertyValues = $command->initialPropertyValues->withValue('image', $image); - return $command->withInitialPropertyValues($propertyValues); + $propertyValues = $commands->first->initialPropertyValues->withValue('image', $image); + return $commands->withInitialPropertyValues($propertyValues); } } From e7a79910fe9b06db790d07055774a8327f7b9560 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 24 Jun 2023 15:24:20 +0200 Subject: [PATCH 02/30] TASK: Minor refinements --- .../Domain/Model/Changes/AbstractCreate.php | 4 +- .../NodeCreationCommands.php | 42 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index fc089aa036..560c9a604e 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -121,7 +121,9 @@ protected function createNode( $contentRepository = $this->contentRepositoryRegistry->get($parentNode->subgraphIdentity->contentRepositoryId); $commands = $this->applyNodeCreationHandlers( - new NodeCreationCommands($command), + NodeCreationCommands::fromFirstCommand( + $command + ), $nodeTypeName, $contentRepository ); diff --git a/Classes/NodeCreationHandler/NodeCreationCommands.php b/Classes/NodeCreationHandler/NodeCreationCommands.php index 2c502ebc77..d68080520c 100644 --- a/Classes/NodeCreationHandler/NodeCreationCommands.php +++ b/Classes/NodeCreationHandler/NodeCreationCommands.php @@ -16,6 +16,7 @@ use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate; +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; @@ -29,36 +30,53 @@ * * All commands will be executed blocking. * - * @api except the constructor + * You can retrieve the subgraph or the parent node (where the first node will be created in) the following way: + * + * $subgraph = $contentRepository->getContentGraph()->getSubgraph( + * $commands->first->contentStreamId, + * $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), + * VisibilityConstraints::frontend() + * ); + * $parentNode = $subgraph->findNodeById($commands->first->parentNodeAggregateId); + * + * @api Note: The constructor and {@see self::fromFirstCommand} are not part of the public API */ -class NodeCreationCommands implements \IteratorAggregate +final readonly class NodeCreationCommands implements \IteratorAggregate { /** * The initial node creation command. * It is only allowed to change its properties via {@see self::withInitialPropertyValues()} */ - public readonly CreateNodeAggregateWithNode $first; + public CreateNodeAggregateWithNode $first; /** * Add a list of commands that are executed after the initial created command was run. * This allows to create child-nodes and append other allowed commands. * - * @var array + * @var array */ - public readonly array $additionalCommands; + public array $additionalCommands; - /** - * @internal to guarantee that the initial create command is mostly preserved as intended. - * You can use {@see self::withInitialPropertyValues()} to add new properties of the to be created node. - */ - public function __construct( + private function __construct( CreateNodeAggregateWithNode $first, - CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences ...$additionalCommands + CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences|CopyNodesRecursively ...$additionalCommands ) { $this->first = $first; $this->additionalCommands = $additionalCommands; } + /** + * @internal to guarantee that the initial create command is mostly preserved as intended. + * You can use {@see self::withInitialPropertyValues()} to add new properties of the to be created node. + */ + public static function fromFirstCommand( + CreateNodeAggregateWithNode $firstCreateNodeAggregateWithNodeCommand, + ): self { + return new self( + $firstCreateNodeAggregateWithNodeCommand, + ); + } + /** * Augment the first {@see CreateNodeAggregateWithNode} command with altered properties. */ @@ -71,7 +89,7 @@ public function withInitialPropertyValues(PropertyValuesToWrite $newInitialPrope } public function withAdditionalCommands( - CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences ...$additionalCommands + CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences|CopyNodesRecursively ...$additionalCommands ): self { return new self($this->first, ...$this->additionalCommands, ...$additionalCommands); } From 7b751a1c253f2b3e02a10e1d56ff1b55b231ded0 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 23 Jul 2023 19:09:37 +0200 Subject: [PATCH 03/30] TASK: Possibility to make nodeId deterministic (for testing) --- Classes/Domain/Model/Changes/AbstractCreate.php | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 560c9a604e..987cf0be4b 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -49,6 +49,11 @@ abstract class AbstractCreate extends AbstractStructuralChange */ protected ?string $name = null; + /** + * An (optional) node aggregate identifier that will be used for testing + */ + protected ?NodeAggregateId $nodeAggregateId = null; + /** * @param string $nodeTypeName */ @@ -89,6 +94,16 @@ public function getName(): ?string return $this->name; } + public function setNodeAggregateId(string $nodeAggregateId): void + { + $this->nodeAggregateId = NodeAggregateId::fromString($nodeAggregateId); + } + + public function getNodeAggregateId(): ?NodeAggregateId + { + return $this->nodeAggregateId; + } + /** * @param Node $parentNode * @param NodeAggregateId|null $succeedingSiblingNodeAggregateId @@ -107,7 +122,7 @@ protected function createNode( ? NodeName::fromString($this->getName()) : null; - $nodeAggregateId = NodeAggregateId::create(); // generate a new NodeAggregateId + $nodeAggregateId = $this->getNodeAggregateId() ?? NodeAggregateId::create(); // generate a new NodeAggregateId $command = CreateNodeAggregateWithNode::create( $parentNode->subgraphIdentity->contentStreamId, From ac5c5fb63147b2a59b33cd4e9f4830215cc81f3b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 18 Oct 2023 22:52:22 +0200 Subject: [PATCH 04/30] TASK: NodeCreationCommands: Guarantee that the $tetheredDescendantNodeAggregateIds are generated beforehand --- Classes/Domain/Model/Changes/AbstractCreate.php | 3 ++- Classes/NodeCreationHandler/NodeCreationCommands.php | 11 +++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 987cf0be4b..c397d5a970 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -137,7 +137,8 @@ protected function createNode( $commands = $this->applyNodeCreationHandlers( NodeCreationCommands::fromFirstCommand( - $command + $command, + $contentRepository->getNodeTypeManager() ), $nodeTypeName, $contentRepository diff --git a/Classes/NodeCreationHandler/NodeCreationCommands.php b/Classes/NodeCreationHandler/NodeCreationCommands.php index d68080520c..de650447b6 100644 --- a/Classes/NodeCreationHandler/NodeCreationCommands.php +++ b/Classes/NodeCreationHandler/NodeCreationCommands.php @@ -14,12 +14,14 @@ use Neos\ContentRepository\Core\CommandHandler\CommandInterface; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; +use Neos\ContentRepository\Core\Feature\NodeCreation\Dto\NodeAggregateIdsByNodePaths; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\DisableNodeAggregate; use Neos\ContentRepository\Core\Feature\NodeDisabling\Command\EnableNodeAggregate; 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; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; /** * A collection of commands that can be "enriched" via a {@see NodeCreationHandlerInterface} @@ -70,10 +72,15 @@ private function __construct( * You can use {@see self::withInitialPropertyValues()} to add new properties of the to be created node. */ public static function fromFirstCommand( - CreateNodeAggregateWithNode $firstCreateNodeAggregateWithNodeCommand, + CreateNodeAggregateWithNode $first, + NodeTypeManager $nodeTypeManager ): self { + $tetheredDescendantNodeAggregateIds = NodeAggregateIdsByNodePaths::createForNodeType( + $first->nodeTypeName, + $nodeTypeManager + ); return new self( - $firstCreateNodeAggregateWithNodeCommand, + $first->withTetheredDescendantNodeAggregateIds($tetheredDescendantNodeAggregateIds), ); } From 258b43c34434c92d7a593d7f715266c52ef5392b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 20 Oct 2023 10:57:06 +0200 Subject: [PATCH 05/30] TASK: Use factory for `NodeCreationHandlerInterface` and make it implement `ContentRepositoryServiceInterface` --- .../Domain/Model/Changes/AbstractCreate.php | 34 ++++++++++++++++--- .../ContentTitleNodeCreationHandler.php | 11 ++++-- ...reationDialogPropertiesCreationHandler.php | 17 +++++----- .../DocumentTitleNodeCreationHandler.php | 15 ++++---- ...ContentTitleNodeCreationHandlerFactory.php | 21 ++++++++++++ ...DialogPropertiesCreationHandlerFactory.php | 29 ++++++++++++++++ ...ocumentTitleNodeCreationHandlerFactory.php | 29 ++++++++++++++++ .../NodeCreationHandlerInterface.php | 6 ++-- Configuration/NodeTypes.yaml | 10 ++++-- .../ImagePropertyNodeCreationHandler.php | 13 +++---- ...magePropertyNodeCreationHandlerFactory.php | 28 +++++++++++++++ .../NodeTypes/Document/PageWithImage.yaml | 2 +- 12 files changed, 176 insertions(+), 39 deletions(-) create mode 100644 Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php create mode 100644 Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php create mode 100644 Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php create mode 100644 Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index c397d5a970..033bb27bb5 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -14,12 +14,15 @@ use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\DimensionSpace\OriginDimensionSpacePoint; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\SharedModel\Exception\NodeNameIsAlreadyOccupied; use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\Flow\Annotations as Flow; +use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; @@ -32,6 +35,11 @@ */ abstract class AbstractCreate extends AbstractStructuralChange { + /** + * @Flow\Inject + */ + protected ObjectManagerInterface $objectManager; + /** * The type of the node that will be created */ @@ -174,16 +182,34 @@ protected function applyNodeCreationHandlers( || !is_array($nodeType->getOptions()['nodeCreationHandlers'])) { return $commands; } - foreach ((new PositionalArraySorter($nodeType->getOptions()['nodeCreationHandlers']))->toArray() as $nodeCreationHandlerConfiguration) { - $nodeCreationHandler = new $nodeCreationHandlerConfiguration['nodeCreationHandler'](); + foreach ((new PositionalArraySorter($nodeType->getOptions()['nodeCreationHandlers']))->toArray() as $key => $nodeCreationHandlerConfiguration) { + if (!isset($nodeCreationHandlerConfiguration['factoryClassName'])) { + throw new InvalidNodeCreationHandlerException(sprintf( + 'Node creation handler "%s" has no "factoryClassName" specified.', + $key + ), 1697750190); + } + + $nodeCreationHandlerFactory = $this->objectManager->get($nodeCreationHandlerConfiguration['factoryClassName']); + if (!$nodeCreationHandlerFactory instanceof ContentRepositoryServiceFactoryInterface) { + throw new InvalidNodeCreationHandlerException(sprintf( + 'Node creation handler "%s" didnt specify factory class of type %s. Got "%s"', + $key, + ContentRepositoryServiceFactoryInterface::class, + get_class($nodeCreationHandlerFactory) + ), 1697750193); + } + + $nodeCreationHandler = $this->contentRepositoryRegistry->buildService($contentRepository->id, $nodeCreationHandlerFactory); if (!$nodeCreationHandler instanceof NodeCreationHandlerInterface) { throw new InvalidNodeCreationHandlerException(sprintf( - 'Expected %s but got "%s"', + 'Node creation handler "%s" didnt specify factory class of type %s. Got "%s"', + $key, NodeCreationHandlerInterface::class, get_class($nodeCreationHandler) ), 1364759956); } - $commands = $nodeCreationHandler->handle($commands, $data, $contentRepository); + $commands = $nodeCreationHandler->handle($commands, $data); } return $commands; } diff --git a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php index 88f703d9d0..e367ee9547 100644 --- a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php +++ b/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php @@ -22,18 +22,23 @@ * "command enricher" * @internal */ -class ContentTitleNodeCreationHandler implements NodeCreationHandlerInterface +final readonly class ContentTitleNodeCreationHandler implements NodeCreationHandlerInterface { + public function __construct( + private ContentRepository $contentRepository + ) { + } + /** * Set the node title for the newly created Content node * * @param array $data incoming data from the creationDialog * @throws NodeTypeNotFoundException */ - public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands { if ( - !$contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) + !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Content') ) { return $commands; diff --git a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php b/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php index 0aaf726d3c..705db10fb6 100644 --- a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php +++ b/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php @@ -12,7 +12,6 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; use Neos\Utility\TypeHandling; @@ -23,24 +22,24 @@ * and sets the initial property values accordingly * @internal */ -class CreationDialogPropertiesCreationHandler implements NodeCreationHandlerInterface +final readonly class CreationDialogPropertiesCreationHandler implements NodeCreationHandlerInterface { - /** - * @Flow\Inject - * @var PropertyMapper - */ - protected $propertyMapper; + public function __construct( + private ContentRepository $contentRepository, + private PropertyMapper $propertyMapper + ) { + } /** * @param array $data */ - public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands { $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); + $nodeType = $this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); $propertyValues = $commands->first->initialPropertyValues; foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { if ( diff --git a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php index 51c19a2a82..a3dca5bf8b 100644 --- a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php +++ b/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php @@ -12,7 +12,6 @@ */ use Neos\ContentRepository\Core\ContentRepository; -use Neos\Flow\Annotations as Flow; use Behat\Transliterator\Transliterator; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; @@ -32,19 +31,19 @@ */ class DocumentTitleNodeCreationHandler implements NodeCreationHandlerInterface { - /** - * @Flow\Inject - * @var TransliterationService - */ - protected $transliterationService; + public function __construct( + private ContentRepository $contentRepository, + private TransliterationService $transliterationService + ) { + } /** * @param array $data */ - public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands { if ( - !$contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) + !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Document') ) { return $commands; diff --git a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php new file mode 100644 index 0000000000..c50580a8fa --- /dev/null +++ b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php @@ -0,0 +1,21 @@ + + */ +final class ContentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +{ + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + return new ContentTitleNodeCreationHandler($serviceFactoryDependencies->contentRepository); + } +} diff --git a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php new file mode 100644 index 0000000000..7e4cd6fd08 --- /dev/null +++ b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php @@ -0,0 +1,29 @@ + + */ +final class CreationDialogPropertiesCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +{ + /** + * @Flow\Inject + */ + protected PropertyMapper $propertyMapper; + + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + return new CreationDialogPropertiesCreationHandler($serviceFactoryDependencies->contentRepository, $this->propertyMapper); + } +} diff --git a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php new file mode 100644 index 0000000000..e8a75e72fc --- /dev/null +++ b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php @@ -0,0 +1,29 @@ + + */ +final class DocumentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +{ + /** + * @Flow\Inject + */ + protected TransliterationService $transliterationService; + + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + return new DocumentTitleNodeCreationHandler($serviceFactoryDependencies->contentRepository, $this->transliterationService); + } +} diff --git a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php index 5304d63850..a29207b308 100644 --- a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php +++ b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php @@ -11,14 +11,14 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; /** * Contract for Node Creation handler that allow to hook into the process just before a node is being added * via the Neos UI * @api */ -interface NodeCreationHandlerInterface +interface NodeCreationHandlerInterface extends ContentRepositoryServiceInterface { /** * You can "enrich" the node creation, by for example adding initial properties {@see NodeCreationCommands::withInitialPropertyValues()} @@ -28,5 +28,5 @@ interface NodeCreationHandlerInterface * @param array $data incoming data from the creationDialog * @return NodeCreationCommands the "enriched" commands, to be passed to the next handler or run at the end */ - public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands; + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands; } diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index d5330a57ec..8de5857ea1 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -19,13 +19,17 @@ options: nodeCreationHandlers: documentTitle: - nodeCreationHandler: 'Neos\Neos\Ui\NodeCreationHandler\DocumentTitleNodeCreationHandler' + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\DocumentTitleNodeCreationHandlerFactory' + creationDialogProperties: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' 'Neos.Neos:Content': options: nodeCreationHandlers: - documentTitle: - nodeCreationHandler: 'Neos\Neos\Ui\NodeCreationHandler\ContentTitleNodeCreationHandler' + contentTitle: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\ContentTitleNodeCreationHandlerFactory' + creationDialogProperties: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' 'Neos.Neos:ContentCollection': ui: diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php index 1658a024d9..a2b8c767ae 100644 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php @@ -11,8 +11,6 @@ * source code. */ -use Neos\ContentRepository\Core\ContentRepository; -use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; use Neos\Media\Domain\Model\ImageInterface; @@ -21,13 +19,12 @@ class ImagePropertyNodeCreationHandler implements NodeCreationHandlerInterface { - /** - * @Flow\Inject - * @var PropertyMapper - */ - protected $propertyMapper; + public function __construct( + private PropertyMapper $propertyMapper + ) { + } - public function handle(NodeCreationCommands $commands, array $data, ContentRepository $contentRepository): NodeCreationCommands + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands { if (!isset($data['image'])) { return $commands; diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php new file mode 100644 index 0000000000..df1e9ab8ca --- /dev/null +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php @@ -0,0 +1,28 @@ + + */ +final class ImagePropertyNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +{ + /** + * @Flow\Inject + */ + protected PropertyMapper $propertyMapper; + + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + { + return new ImagePropertyNodeCreationHandler($this->propertyMapper); + } +} diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml index 5fae514863..2561d8f4e3 100644 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml @@ -4,7 +4,7 @@ options: nodeCreationHandlers: image: - nodeCreationHandler: 'Neos\TestNodeTypes\NodeCreationHandler\ImagePropertyNodeCreationHandler' + factoryClassName: 'Neos\TestNodeTypes\NodeCreationHandler\ImagePropertyNodeCreationHandlerFactory' ui: label: PageWithImage_Test icon: icon-file-o From a9e248c32de15df78d250595e9047723e8c2200b Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 10:41:04 +0100 Subject: [PATCH 06/30] TASK: Fix `yield from` problem --- .../NodeCreationCommands.php | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/Classes/NodeCreationHandler/NodeCreationCommands.php b/Classes/NodeCreationHandler/NodeCreationCommands.php index de650447b6..6b5a9e8b49 100644 --- a/Classes/NodeCreationHandler/NodeCreationCommands.php +++ b/Classes/NodeCreationHandler/NodeCreationCommands.php @@ -34,14 +34,16 @@ * * You can retrieve the subgraph or the parent node (where the first node will be created in) the following way: * - * $subgraph = $contentRepository->getContentGraph()->getSubgraph( - * $commands->first->contentStreamId, - * $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), - * VisibilityConstraints::frontend() - * ); - * $parentNode = $subgraph->findNodeById($commands->first->parentNodeAggregateId); + * $subgraph = $contentRepository->getContentGraph()->getSubgraph( + * $commands->first->contentStreamId, + * $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), + * VisibilityConstraints::frontend() + * ); + * $parentNode = $subgraph->findNodeById($commands->first->parentNodeAggregateId); * - * @api Note: The constructor and {@see self::fromFirstCommand} are not part of the public API + * @implements \IteratorAggregate + * @api As part of the {@see NodeCreationHandlerInterface} + * Note: The constructors are not part of the public API */ final readonly class NodeCreationCommands implements \IteratorAggregate { @@ -107,6 +109,10 @@ public function withAdditionalCommands( public function getIterator(): \Traversable { yield $this->first; - yield from $this->additionalCommands; + foreach ($this->additionalCommands as $command) { + // if yield from is used, the default setting of iterator_to_array, "preserve_keys: true" + // would cause the first command to be overridden. + yield $command; + } } } From 67b533d11608143629c34bccb271d5c0a19697b6 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 10:54:08 +0100 Subject: [PATCH 07/30] TASK: Use anonymous class pattern instead of factory + class Having a factories AND the handler lying around is just a bit too much, and might lead to confusion what to register in yaml. --- .../ContentTitleNodeCreationHandler.php | 54 ----------- ...reationDialogPropertiesCreationHandler.php | 65 ------------- .../DocumentTitleNodeCreationHandler.php | 92 ------------------- ...ContentTitleNodeCreationHandlerFactory.php | 45 ++++++++- ...DialogPropertiesCreationHandlerFactory.php | 59 ++++++++++-- ...ocumentTitleNodeCreationHandlerFactory.php | 83 +++++++++++++++-- .../ImagePropertyNodeCreationHandler.php | 40 -------- ...magePropertyNodeCreationHandlerFactory.php | 32 ++++++- 8 files changed, 197 insertions(+), 273 deletions(-) delete mode 100644 Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php delete mode 100644 Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php delete mode 100644 Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php delete mode 100644 Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php diff --git a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php deleted file mode 100644 index e367ee9547..0000000000 --- a/Classes/NodeCreationHandler/ContentTitleNodeCreationHandler.php +++ /dev/null @@ -1,54 +0,0 @@ - $data incoming data from the creationDialog - * @throws NodeTypeNotFoundException - */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands - { - if ( - !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) - ->isOfType('Neos.Neos:Content') - ) { - return $commands; - } - - $propertyValues = $commands->first->initialPropertyValues; - if (isset($data['title'])) { - $propertyValues = $propertyValues->withValue('title', $data['title']); - } - - return $commands->withInitialPropertyValues($propertyValues); - } -} diff --git a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php b/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php deleted file mode 100644 index 705db10fb6..0000000000 --- a/Classes/NodeCreationHandler/CreationDialogPropertiesCreationHandler.php +++ /dev/null @@ -1,65 +0,0 @@ - $data - */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands - { - $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); - $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); - $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - - $nodeType = $this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); - $propertyValues = $commands->first->initialPropertyValues; - foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { - if ( - !isset($propertyConfiguration['ui']['showInCreationDialog']) - || $propertyConfiguration['ui']['showInCreationDialog'] !== true - ) { - continue; - } - $propertyType = TypeHandling::normalizeType($propertyConfiguration['type'] ?? 'string'); - if (!isset($data[$propertyName])) { - continue; - } - $propertyValue = $data[$propertyName]; - if ($propertyType !== 'references' && $propertyType !== 'reference' && $propertyType !== TypeHandling::getTypeForValue($propertyValue)) { - $propertyValue = $this->propertyMapper->convert($propertyValue, $propertyType, $propertyMappingConfiguration); - } - - $propertyValues = $propertyValues->withValue($propertyName, $propertyValue); - } - - return $commands->withInitialPropertyValues($propertyValues); - } -} diff --git a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php b/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php deleted file mode 100644 index a3dca5bf8b..0000000000 --- a/Classes/NodeCreationHandler/DocumentTitleNodeCreationHandler.php +++ /dev/null @@ -1,92 +0,0 @@ - $data - */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands - { - if ( - !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) - ->isOfType('Neos.Neos:Document') - ) { - return $commands; - } - $propertyValues = $commands->first->initialPropertyValues; - if (isset($data['title'])) { - $propertyValues = $propertyValues->withValue('title', $data['title']); - } - - // if specified, the uriPathSegment equals the title - $uriPathSegment = $data['title']; - - // otherwise, we fall back to the node name - if ($uriPathSegment === null && $commands->first->nodeName !== null) { - $uriPathSegment = $commands->first->nodeName->value; - } - - // if not empty, we transliterate the uriPathSegment according to the language of the new node - if ($uriPathSegment !== null && $uriPathSegment !== '') { - $uriPathSegment = $this->transliterateText( - $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), - $uriPathSegment - ); - } else { - // alternatively we set it to a random string - $uriPathSegment = uniqid('', true); - } - $uriPathSegment = Transliterator::urlize($uriPathSegment); - $propertyValues = $propertyValues->withValue('uriPathSegment', $uriPathSegment); - - return $commands->withInitialPropertyValues($propertyValues); - } - - private function transliterateText(DimensionSpacePoint $dimensionSpacePoint, string $text): string - { - $languageDimensionValue = $dimensionSpacePoint->getCoordinate(new ContentDimensionId('language')); - if ($languageDimensionValue !== null) { - try { - $language = (new Locale($languageDimensionValue))->getLanguage(); - } catch (InvalidLocaleIdentifierException $e) { - // we don't need to do anything here; we'll just transliterate the text. - } - } - return $this->transliterationService->transliterate($text, $language ?? null); - } -} diff --git a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php index c50580a8fa..b2d914660a 100644 --- a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php @@ -4,18 +4,53 @@ namespace Neos\Neos\Ui\NodeCreationHandler\Factory; +use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; -use Neos\Neos\Ui\NodeCreationHandler\ContentTitleNodeCreationHandler; +use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; /** - * @implements ContentRepositoryServiceFactoryInterface + * Node creation handler that sets the "title" property for new content elements according + * to the incoming title from a creation dialog. + * + * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` + * @implements ContentRepositoryServiceFactoryInterface */ final class ContentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new ContentTitleNodeCreationHandler($serviceFactoryDependencies->contentRepository); + return new class($serviceFactoryDependencies->contentRepository) implements NodeCreationHandlerInterface + { + public function __construct( + private readonly ContentRepository $contentRepository + ) { + } + + /** + * Set the node title for the newly created Content node + * + * @param array $data incoming data from the creationDialog + * @throws NodeTypeNotFoundException + */ + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + { + if ( + !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) + ->isOfType('Neos.Neos:Content') + ) { + return $commands; + } + + $propertyValues = $commands->first->initialPropertyValues; + if (isset($data['title'])) { + $propertyValues = $propertyValues->withValue('title', $data['title']); + } + + return $commands->withInitialPropertyValues($propertyValues); + } + }; } } diff --git a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php index 7e4cd6fd08..9d06f6960a 100644 --- a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php @@ -4,16 +4,23 @@ namespace Neos\Neos\Ui\NodeCreationHandler\Factory; +use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; -use Neos\Neos\Ui\NodeCreationHandler\ContentTitleNodeCreationHandler; -use Neos\Neos\Ui\NodeCreationHandler\CreationDialogPropertiesCreationHandler; +use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; +use Neos\Utility\TypeHandling; /** - * @implements ContentRepositoryServiceFactoryInterface + * Generic creation dialog node creation handler that iterates + * properties that are configured to appear in the Creation Dialog (via "ui.showInCreationDialog" setting) + * and sets the initial property values accordingly + * + * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` + * @implements ContentRepositoryServiceFactoryInterface */ final class CreationDialogPropertiesCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { @@ -22,8 +29,48 @@ final class CreationDialogPropertiesCreationHandlerFactory implements ContentRep */ protected PropertyMapper $propertyMapper; - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new CreationDialogPropertiesCreationHandler($serviceFactoryDependencies->contentRepository, $this->propertyMapper); + return new class($serviceFactoryDependencies->contentRepository, $this->propertyMapper) implements NodeCreationHandlerInterface + { + public function __construct( + private readonly ContentRepository $contentRepository, + private readonly PropertyMapper $propertyMapper + ) { + } + + /** + * @param array $data + */ + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + { + $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); + $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); + $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); + + $nodeType = $this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); + $propertyValues = $commands->first->initialPropertyValues; + foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { + if ( + !isset($propertyConfiguration['ui']['showInCreationDialog']) + || $propertyConfiguration['ui']['showInCreationDialog'] !== true + ) { + continue; + } + $propertyType = TypeHandling::normalizeType($propertyConfiguration['type'] ?? 'string'); + if (!isset($data[$propertyName])) { + continue; + } + $propertyValue = $data[$propertyName]; + if ($propertyType !== 'references' && $propertyType !== 'reference' && $propertyType !== TypeHandling::getTypeForValue($propertyValue)) { + $propertyValue = $this->propertyMapper->convert($propertyValue, $propertyType, $propertyMappingConfiguration); + } + + $propertyValues = $propertyValues->withValue($propertyName, $propertyValue); + } + + return $commands->withInitialPropertyValues($propertyValues); + } + }; } } diff --git a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php index e8a75e72fc..75add5af0b 100644 --- a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php @@ -4,16 +4,27 @@ namespace Neos\Neos\Ui\NodeCreationHandler\Factory; +use Behat\Transliterator\Transliterator; +use Neos\ContentRepository\Core\ContentRepository; +use Neos\ContentRepository\Core\Dimension\ContentDimensionId; +use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\Flow\Annotations as Flow; +use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException; +use Neos\Flow\I18n\Locale; use Neos\Neos\Service\TransliterationService; -use Neos\Neos\Ui\NodeCreationHandler\ContentTitleNodeCreationHandler; -use Neos\Neos\Ui\NodeCreationHandler\DocumentTitleNodeCreationHandler; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; /** - * @implements ContentRepositoryServiceFactoryInterface + * Node creation handler that + * + * - sets the "title" property according to the incoming title from a creation dialog + * - sets the "uriPathSegment" property according to the specified title or node name + * + * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` + * @implements ContentRepositoryServiceFactoryInterface */ final class DocumentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { @@ -22,8 +33,68 @@ final class DocumentTitleNodeCreationHandlerFactory implements ContentRepository */ protected TransliterationService $transliterationService; - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new DocumentTitleNodeCreationHandler($serviceFactoryDependencies->contentRepository, $this->transliterationService); + return new class($serviceFactoryDependencies->contentRepository, $this->transliterationService) implements NodeCreationHandlerInterface + { + public function __construct( + private readonly ContentRepository $contentRepository, + private readonly TransliterationService $transliterationService + ) { + } + + /** + * @param array $data + */ + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + { + if ( + !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) + ->isOfType('Neos.Neos:Document') + ) { + return $commands; + } + $propertyValues = $commands->first->initialPropertyValues; + if (isset($data['title'])) { + $propertyValues = $propertyValues->withValue('title', $data['title']); + } + + // if specified, the uriPathSegment equals the title + $uriPathSegment = $data['title']; + + // otherwise, we fall back to the node name + if ($uriPathSegment === null && $commands->first->nodeName !== null) { + $uriPathSegment = $commands->first->nodeName->value; + } + + // if not empty, we transliterate the uriPathSegment according to the language of the new node + if ($uriPathSegment !== null && $uriPathSegment !== '') { + $uriPathSegment = $this->transliterateText( + $commands->first->originDimensionSpacePoint->toDimensionSpacePoint(), + $uriPathSegment + ); + } else { + // alternatively we set it to a random string + $uriPathSegment = uniqid('', true); + } + $uriPathSegment = Transliterator::urlize($uriPathSegment); + $propertyValues = $propertyValues->withValue('uriPathSegment', $uriPathSegment); + + return $commands->withInitialPropertyValues($propertyValues); + } + + private function transliterateText(DimensionSpacePoint $dimensionSpacePoint, string $text): string + { + $languageDimensionValue = $dimensionSpacePoint->getCoordinate(new ContentDimensionId('language')); + if ($languageDimensionValue !== null) { + try { + $language = (new Locale($languageDimensionValue))->getLanguage(); + } catch (InvalidLocaleIdentifierException $e) { + // we don't need to do anything here; we'll just transliterate the text. + } + } + return $this->transliterationService->transliterate($text, $language ?? null); + } + }; } } diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php deleted file mode 100644 index a2b8c767ae..0000000000 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandler.php +++ /dev/null @@ -1,40 +0,0 @@ -propertyMapper->buildPropertyMappingConfiguration(); - $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); - $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - $image = $this->propertyMapper->convert($data['image'], ImageInterface::class, $propertyMappingConfiguration); - - $propertyValues = $commands->first->initialPropertyValues->withValue('image', $image); - return $commands->withInitialPropertyValues($propertyValues); - } -} diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php index df1e9ab8ca..dfeb5e4723 100644 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php @@ -6,13 +6,15 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\Flow\Annotations as Flow; use Neos\Flow\Property\PropertyMapper; -use Neos\TestNodeTypes\NodeCreationHandler\ImagePropertyNodeCreationHandler; +use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; +use Neos\Media\Domain\Model\ImageInterface; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; /** - * @implements ContentRepositoryServiceFactoryInterface + * @implements ContentRepositoryServiceFactoryInterface */ final class ImagePropertyNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { @@ -21,8 +23,28 @@ final class ImagePropertyNodeCreationHandlerFactory implements ContentRepository */ protected PropertyMapper $propertyMapper; - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): ContentRepositoryServiceInterface + public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new ImagePropertyNodeCreationHandler($this->propertyMapper); + return new class ($this->propertyMapper) implements NodeCreationHandlerInterface + { + public function __construct( + private PropertyMapper $propertyMapper + ) { + } + + public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + { + if (!isset($data['image'])) { + return $commands; + } + $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); + $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); + $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); + $image = $this->propertyMapper->convert($data['image'], ImageInterface::class, $propertyMappingConfiguration); + + $propertyValues = $commands->first->initialPropertyValues->withValue('image', $image); + return $commands->withInitialPropertyValues($propertyValues); + } + }; } } From a308ab7de6f2991c63fe0db7a4814c48f8d08f66 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:08:35 +0100 Subject: [PATCH 08/30] TASK: Replace testing `ImagePropertyNodeCreationHandler` with `showInCreationDialog: true` --- .../Domain/Model/Changes/AbstractCreate.php | 8 +++ ...magePropertyNodeCreationHandlerFactory.php | 50 ------------------- .../NodeTypes/Document/PageWithImage.yaml | 47 +---------------- 3 files changed, 9 insertions(+), 96 deletions(-) delete mode 100644 Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 033bb27bb5..6ccf499bc1 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -23,8 +23,10 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; +use Neos\Neos\Ui\Domain\Service\NodePropertyConversionService; use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; use Neos\Utility\PositionalArraySorter; @@ -40,6 +42,12 @@ abstract class AbstractCreate extends AbstractStructuralChange */ protected ObjectManagerInterface $objectManager; + /** + * @Flow\Inject + * @var NodePropertyConversionService + */ + protected $nodePropertyConversionService; + /** * The type of the node that will be created */ diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php deleted file mode 100644 index dfeb5e4723..0000000000 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/Classes/NodeCreationHandler/ImagePropertyNodeCreationHandlerFactory.php +++ /dev/null @@ -1,50 +0,0 @@ - - */ -final class ImagePropertyNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface -{ - /** - * @Flow\Inject - */ - protected PropertyMapper $propertyMapper; - - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface - { - return new class ($this->propertyMapper) implements NodeCreationHandlerInterface - { - public function __construct( - private PropertyMapper $propertyMapper - ) { - } - - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands - { - if (!isset($data['image'])) { - return $commands; - } - $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); - $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); - $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - $image = $this->propertyMapper->convert($data['image'], ImageInterface::class, $propertyMappingConfiguration); - - $propertyValues = $commands->first->initialPropertyValues->withValue('image', $image); - return $commands->withInitialPropertyValues($propertyValues); - } - }; - } -} diff --git a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml index 2561d8f4e3..320dbc4e14 100644 --- a/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml +++ b/Tests/IntegrationTests/TestDistribution/DistributionPackages/Neos.TestNodeTypes/NodeTypes/Document/PageWithImage.yaml @@ -1,56 +1,10 @@ 'Neos.TestNodeTypes:Document.PageWithImage': superTypes: 'Neos.Neos:Document': true - options: - nodeCreationHandlers: - image: - factoryClassName: 'Neos\TestNodeTypes\NodeCreationHandler\ImagePropertyNodeCreationHandlerFactory' ui: label: PageWithImage_Test icon: icon-file-o position: 100 - creationDialog: - elements: - image: - type: Neos\Media\Domain\Model\ImageInterface - ui: - label: Image - editor: Neos.Neos/Inspector/Editors/ImageEditor - editorOptions: - fileUploadLabel: Neos.Neos:Main:choose - maximumFileSize: - features: - crop: true - upload: true - mediaBrowser: true - resize: false - crop: - aspectRatio: - options: - square: - width: 1 - height: 1 - label: Square - fourFive: - width: 4 - height: 5 - fiveSeven: - width: 5 - height: 7 - twoThree: - width: 2 - height: 3 - fourThree: - width: 4 - height: 3 - sixteenNine: - width: 16 - height: 9 - enableOriginal: true - allowCustom: true - locked: - width: 1 - height: 1 childNodes: main: type: 'Neos.Neos:ContentCollection' @@ -60,6 +14,7 @@ ui: label: 'Image' reloadIfChanged: true + showInCreationDialog: true inspector: group: 'document' editorOptions: From 5d26ea257b58e013fc07a15716fe92350f512040 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:18:49 +0100 Subject: [PATCH 09/30] FEATURE: Introduce `NodeCreationElements` abstraction do encapsulate the serialisation Fixes https://github.com/neos/neos-ui/issues/3719 for Neos 9.0 --- .../Domain/Model/Changes/AbstractCreate.php | 8 +- Classes/Domain/Model/Changes/Property.php | 3 +- .../Service/NodePropertyConversionService.php | 40 +++++++-- ...ContentTitleNodeCreationHandlerFactory.php | 11 ++- ...DialogPropertiesCreationHandlerFactory.php | 42 +++------ ...ocumentTitleNodeCreationHandlerFactory.php | 26 +++--- .../NodeCreationElements.php | 87 +++++++++++++++++++ .../NodeCreationHandlerInterface.php | 4 +- 8 files changed, 159 insertions(+), 62 deletions(-) create mode 100644 Classes/NodeCreationHandler/NodeCreationElements.php diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 6ccf499bc1..03efd293ee 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -156,6 +156,10 @@ protected function createNode( $command, $contentRepository->getNodeTypeManager() ), + $this->nodePropertyConversionService->convertNodeCreationElements( + $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName), + $this->getData() ?: [] + ), $nodeTypeName, $contentRepository ); @@ -181,10 +185,10 @@ protected function createNode( */ protected function applyNodeCreationHandlers( NodeCreationCommands $commands, + NodeCreationElements $elements, NodeTypeName $nodeTypeName, ContentRepository $contentRepository ): NodeCreationCommands { - $data = $this->getData() ?: []; $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($nodeTypeName); if (!isset($nodeType->getOptions()['nodeCreationHandlers']) || !is_array($nodeType->getOptions()['nodeCreationHandlers'])) { @@ -217,7 +221,7 @@ protected function applyNodeCreationHandlers( get_class($nodeCreationHandler) ), 1364759956); } - $commands = $nodeCreationHandler->handle($commands, $data); + $commands = $nodeCreationHandler->handle($commands, $elements); } return $commands; } diff --git a/Classes/Domain/Model/Changes/Property.php b/Classes/Domain/Model/Changes/Property.php index 1e84723168..65970d611f 100644 --- a/Classes/Domain/Model/Changes/Property.php +++ b/Classes/Domain/Model/Changes/Property.php @@ -189,8 +189,7 @@ public function apply(): void ); } else { $value = $this->nodePropertyConversionService->convert( - $this->getNodeType($subject), - $propertyName, + $this->getNodeType($subject)->getPropertyType($propertyName), $this->getValue() ); diff --git a/Classes/Domain/Service/NodePropertyConversionService.php b/Classes/Domain/Service/NodePropertyConversionService.php index 96d893e7cc..c81e062b4b 100644 --- a/Classes/Domain/Service/NodePropertyConversionService.php +++ b/Classes/Domain/Service/NodePropertyConversionService.php @@ -12,11 +12,13 @@ */ use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\Flow\Annotations as Flow; use Neos\Flow\Mvc\Controller\MvcPropertyMappingConfiguration; use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Flow\Property\PropertyMapper; use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; use Neos\Utility\Exception\InvalidTypeException; use Neos\Utility\TypeHandling; @@ -43,17 +45,13 @@ class NodePropertyConversionService * * @param string|array|null $rawValue */ - public function convert(NodeType $nodeType, string $propertyName, string|array|null $rawValue): mixed + public function convert(string $propertyType, string|array|null $rawValue): mixed { - // WORKAROUND: $nodeType->getPropertyType() is missing the "initialize" call, - // so we need to trigger another method beforehand. - $nodeType->getFullConfiguration(); - $propertyType = $nodeType->getPropertyType($propertyName); - if (is_null($rawValue)) { return null; } + $propertyType = TypeHandling::normalizeType($propertyType); switch ($propertyType) { case 'string': return $rawValue; @@ -113,6 +111,36 @@ public function convert(NodeType $nodeType, string $propertyName, string|array|n } } + /** + * @param array $data + */ + public function convertNodeCreationElements(NodeType $nodeType, array $data): NodeCreationElements + { + $convertedProperties = []; + $convertedReferences = []; + /** @var string $elementName */ + foreach ($nodeType->getConfiguration('ui.creationDialog.elements') ?? [] as $elementName => $elementConfiguration) { + $rawValue = $data[$elementName] ?? null; + if ($rawValue === null) { + continue; + } + $propertyType = $elementConfiguration['type'] ?? 'string'; + if ($propertyType === 'references' || $propertyType === 'reference') { + $destinationNodeAggregateIds = []; + if (is_string($rawValue) && !empty($rawValue)) { + $destinationNodeAggregateIds = [$rawValue]; + } elseif (is_array($rawValue)) { + $destinationNodeAggregateIds = $rawValue; + } + $convertedReferences[$elementName] = NodeAggregateIds::fromArray($destinationNodeAggregateIds); + continue; + } + $convertedProperties[$elementName] = $this->convert($propertyType, $rawValue); + } + + return new NodeCreationElements(propertyLikeValues: $convertedProperties, referenceLikeValues: $convertedReferences, serializedValues: $data); + } + /** * Convert raw value to \DateTime * diff --git a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php index b2d914660a..93bb233355 100644 --- a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; /** @@ -22,8 +23,7 @@ final class ContentTitleNodeCreationHandlerFactory implements ContentRepositoryS { public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->contentRepository) implements NodeCreationHandlerInterface - { + return new class($serviceFactoryDependencies->contentRepository) implements NodeCreationHandlerInterface { public function __construct( private readonly ContentRepository $contentRepository ) { @@ -32,10 +32,9 @@ public function __construct( /** * Set the node title for the newly created Content node * - * @param array $data incoming data from the creationDialog * @throws NodeTypeNotFoundException */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { if ( !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) @@ -45,8 +44,8 @@ public function handle(NodeCreationCommands $commands, array $data): NodeCreatio } $propertyValues = $commands->first->initialPropertyValues; - if (isset($data['title'])) { - $propertyValues = $propertyValues->withValue('title', $data['title']); + if ($elements->hasPropertyLike('title')) { + $propertyValues = $propertyValues->withValue('title', $elements->getPropertyLike('title')); } return $commands->withInitialPropertyValues($propertyValues); diff --git a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php index 9d06f6960a..e11dd73fc0 100644 --- a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php @@ -4,15 +4,12 @@ namespace Neos\Neos\Ui\NodeCreationHandler\Factory; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; -use Neos\Flow\Annotations as Flow; -use Neos\Flow\Property\PropertyMapper; -use Neos\Flow\Property\TypeConverter\PersistentObjectConverter; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; -use Neos\Utility\TypeHandling; /** * Generic creation dialog node creation handler that iterates @@ -24,49 +21,30 @@ */ final class CreationDialogPropertiesCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { - /** - * @Flow\Inject - */ - protected PropertyMapper $propertyMapper; - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->contentRepository, $this->propertyMapper) implements NodeCreationHandlerInterface - { + return new class($serviceFactoryDependencies->nodeTypeManager) implements NodeCreationHandlerInterface { public function __construct( - private readonly ContentRepository $contentRepository, - private readonly PropertyMapper $propertyMapper + private readonly NodeTypeManager $nodeTypeManager ) { } - - /** - * @param array $data - */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { - $propertyMappingConfiguration = $this->propertyMapper->buildPropertyMappingConfiguration(); - $propertyMappingConfiguration->forProperty('*')->allowAllProperties(); - $propertyMappingConfiguration->setTypeConverterOption(PersistentObjectConverter::class, PersistentObjectConverter::CONFIGURATION_OVERRIDE_TARGET_TYPE_ALLOWED, true); - - $nodeType = $this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName); + $nodeType = $this->nodeTypeManager->getNodeType($commands->first->nodeTypeName); $propertyValues = $commands->first->initialPropertyValues; foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { if ( !isset($propertyConfiguration['ui']['showInCreationDialog']) || $propertyConfiguration['ui']['showInCreationDialog'] !== true ) { + // not a promoted property continue; } - $propertyType = TypeHandling::normalizeType($propertyConfiguration['type'] ?? 'string'); - if (!isset($data[$propertyName])) { + if (!$elements->hasPropertyLike($propertyName)) { continue; } - $propertyValue = $data[$propertyName]; - if ($propertyType !== 'references' && $propertyType !== 'reference' && $propertyType !== TypeHandling::getTypeForValue($propertyValue)) { - $propertyValue = $this->propertyMapper->convert($propertyValue, $propertyType, $propertyMappingConfiguration); - } - - $propertyValues = $propertyValues->withValue($propertyName, $propertyValue); + // todo support also references https://github.com/neos/neos-ui/issues/3615 + $propertyValues = $propertyValues->withValue($propertyName, $elements->getPropertyLike($propertyName)); } return $commands->withInitialPropertyValues($propertyValues); diff --git a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php index 75add5af0b..3e0a78ecee 100644 --- a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php @@ -5,16 +5,17 @@ namespace Neos\Neos\Ui\NodeCreationHandler\Factory; use Behat\Transliterator\Transliterator; -use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\Flow\Annotations as Flow; use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException; use Neos\Flow\I18n\Locale; use Neos\Neos\Service\TransliterationService; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; +use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; /** @@ -35,32 +36,31 @@ final class DocumentTitleNodeCreationHandlerFactory implements ContentRepository public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->contentRepository, $this->transliterationService) implements NodeCreationHandlerInterface + return new class($serviceFactoryDependencies->nodeTypeManager, $this->transliterationService) implements NodeCreationHandlerInterface { public function __construct( - private readonly ContentRepository $contentRepository, + private readonly NodeTypeManager $nodeTypeManager, private readonly TransliterationService $transliterationService ) { } - /** - * @param array $data - */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands - { + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { if ( - !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) + !$this->nodeTypeManager->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Document') ) { return $commands; } $propertyValues = $commands->first->initialPropertyValues; - if (isset($data['title'])) { - $propertyValues = $propertyValues->withValue('title', $data['title']); + + if ($elements->hasPropertyLike('title')) { + // technically we only need to set the uriPathSegment as the CreationDialogPropertiesCreationHandler + // will take care of setting the title already + $propertyValues = $propertyValues->withValue('title', $elements->getPropertyLike('title')); } // if specified, the uriPathSegment equals the title - $uriPathSegment = $data['title']; + $uriPathSegment = $elements->getPropertyLike('title'); // otherwise, we fall back to the node name if ($uriPathSegment === null && $commands->first->nodeName !== null) { @@ -74,6 +74,8 @@ public function handle(NodeCreationCommands $commands, array $data): NodeCreatio $uriPathSegment ); } else { + // todo in case the title is missing dont set it to something random like 65d0ba5d8f4593-93420885 + // but use the node label name instead like in 8.3. The problem is with two nodes on the same level. // alternatively we set it to a random string $uriPathSegment = uniqid('', true); } diff --git a/Classes/NodeCreationHandler/NodeCreationElements.php b/Classes/NodeCreationHandler/NodeCreationElements.php new file mode 100644 index 0000000000..b422b6dc0b --- /dev/null +++ b/Classes/NodeCreationHandler/NodeCreationElements.php @@ -0,0 +1,87 @@ + $propertyLikeValues + * @param array $referenceLikeValues + * @param array $serializedValues + * @internal you should not need to construct this + */ + public function __construct( + private array $propertyLikeValues, + private array $referenceLikeValues, + private array $serializedValues, + ) { + } + + public function hasPropertyLike(string $name): bool + { + return isset($this->propertyLikeValues[$name]); + } + + public function getPropertyLike(string $name): mixed + { + return $this->propertyLikeValues[$name] ?? null; + } + + public function hasReferenceLike(string $name): bool + { + return isset($this->referenceLikeValues[$name]); + } + + public function getReferenceLike(string $name): NodeAggregateIds + { + return $this->referenceLikeValues[$name] ; + } + + /** + * @return iterable + */ + public function getPropertyLikeValues(): iterable + { + return $this->propertyLikeValues; + } + + /** + * @return iterable + */ + public function getReferenceLikeValues(): iterable + { + return $this->referenceLikeValues; + } + + /** + * @internal returns values formatted by the internal format used for the Ui + * @return iterable + */ + public function serialized(): iterable + { + return $this->serializedValues; + } +} diff --git a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php index a29207b308..5fc56bae42 100644 --- a/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php +++ b/Classes/NodeCreationHandler/NodeCreationHandlerInterface.php @@ -25,8 +25,8 @@ interface NodeCreationHandlerInterface extends ContentRepositoryServiceInterface * or appending additional f.x. create-child nodes commands {@see NodeCreationCommands::withAdditionalCommands()} * * @param NodeCreationCommands $commands original or previous commands, with the first command being the initial intended node creation - * @param array $data incoming data from the creationDialog + * @param NodeCreationElements $elements incoming data from the creationDialog * @return NodeCreationCommands the "enriched" commands, to be passed to the next handler or run at the end */ - public function handle(NodeCreationCommands $commands, array $data): NodeCreationCommands; + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands; } From 2e6a2257dfc144dad4d2633fa3df176da1c3b68c Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:21:18 +0100 Subject: [PATCH 10/30] TASK: Remove obsolete `contentTitle` nodeCreationHandler The `CreationDialogPropertiesCreationHandler` will already cover this case as `title` is just a simple property --- ...ContentTitleNodeCreationHandlerFactory.php | 55 ------------------- Configuration/NodeTypes.yaml | 2 - 2 files changed, 57 deletions(-) delete mode 100644 Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php diff --git a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php deleted file mode 100644 index 93bb233355..0000000000 --- a/Classes/NodeCreationHandler/Factory/ContentTitleNodeCreationHandlerFactory.php +++ /dev/null @@ -1,55 +0,0 @@ - - */ -final class ContentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface -{ - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface - { - return new class($serviceFactoryDependencies->contentRepository) implements NodeCreationHandlerInterface { - public function __construct( - private readonly ContentRepository $contentRepository - ) { - } - - /** - * Set the node title for the newly created Content node - * - * @throws NodeTypeNotFoundException - */ - public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands - { - if ( - !$this->contentRepository->getNodeTypeManager()->getNodeType($commands->first->nodeTypeName) - ->isOfType('Neos.Neos:Content') - ) { - return $commands; - } - - $propertyValues = $commands->first->initialPropertyValues; - if ($elements->hasPropertyLike('title')) { - $propertyValues = $propertyValues->withValue('title', $elements->getPropertyLike('title')); - } - - return $commands->withInitialPropertyValues($propertyValues); - } - }; - } -} diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index 8de5857ea1..fc614ab4df 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -26,8 +26,6 @@ 'Neos.Neos:Content': options: nodeCreationHandlers: - contentTitle: - factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\ContentTitleNodeCreationHandlerFactory' creationDialogProperties: factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' From fa797c920c4e3a9c810a40612cef92fa66acd3a5 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:24:48 +0100 Subject: [PATCH 11/30] TASK: Rename `DocumentTitleNodeCreationHandler` Its primary goal is to handle the uriPathSegment generation. The title will already be set by the other showInCreationTrue handler. --- ...p => UriPathSegmentNodeCreationHandlerFactory.php} | 11 ++++++----- Configuration/NodeTypes.yaml | 4 ++-- 2 files changed, 8 insertions(+), 7 deletions(-) rename Classes/NodeCreationHandler/Factory/{DocumentTitleNodeCreationHandlerFactory.php => UriPathSegmentNodeCreationHandlerFactory.php} (94%) diff --git a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php similarity index 94% rename from Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php rename to Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php index 3e0a78ecee..adc18625f7 100644 --- a/Classes/NodeCreationHandler/Factory/DocumentTitleNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php @@ -21,13 +21,14 @@ /** * Node creation handler that * - * - sets the "title" property according to the incoming title from a creation dialog * - sets the "uriPathSegment" property according to the specified title or node name + * - sets the "title" property according to the incoming title from a creation dialog + * - (actually obsolete with CreationDialogPropertiesCreationHandler) * * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` * @implements ContentRepositoryServiceFactoryInterface */ -final class DocumentTitleNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +final class UriPathSegmentNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { /** * @Flow\Inject @@ -36,15 +37,15 @@ final class DocumentTitleNodeCreationHandlerFactory implements ContentRepository public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->nodeTypeManager, $this->transliterationService) implements NodeCreationHandlerInterface - { + return new class($serviceFactoryDependencies->nodeTypeManager, $this->transliterationService) implements NodeCreationHandlerInterface { public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly TransliterationService $transliterationService ) { } - public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands + { if ( !$this->nodeTypeManager->getNodeType($commands->first->nodeTypeName) ->isOfType('Neos.Neos:Document') diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index fc614ab4df..9f04ff7a9e 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -18,8 +18,8 @@ title: "ClientEval:node.properties.title" options: nodeCreationHandlers: - documentTitle: - factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\DocumentTitleNodeCreationHandlerFactory' + uriPathSegment: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\UriPathSegmentNodeCreationHandlerFactory' creationDialogProperties: factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' From 995c7627a7b2f62df638fa07f659cf6c2a49af27 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:28:26 +0100 Subject: [PATCH 12/30] TASK: Rename `CreationDialogPropertiesCreationHandler` to `PromotedElementsCreationHandler` as in the future it will also handle references. The naming will also better reflect what it does, and that it only handles promote elements (eg ui.showInCreationDialog) --- ...ory.php => PromotedElementsCreationHandlerFactory.php} | 2 +- .../Factory/UriPathSegmentNodeCreationHandlerFactory.php | 4 ++-- Configuration/NodeTypes.yaml | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) rename Classes/NodeCreationHandler/Factory/{CreationDialogPropertiesCreationHandlerFactory.php => PromotedElementsCreationHandlerFactory.php} (95%) diff --git a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/PromotedElementsCreationHandlerFactory.php similarity index 95% rename from Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php rename to Classes/NodeCreationHandler/Factory/PromotedElementsCreationHandlerFactory.php index e11dd73fc0..be567db543 100644 --- a/Classes/NodeCreationHandler/Factory/CreationDialogPropertiesCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/PromotedElementsCreationHandlerFactory.php @@ -19,7 +19,7 @@ * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` * @implements ContentRepositoryServiceFactoryInterface */ -final class CreationDialogPropertiesCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +final class PromotedElementsCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface { public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface { diff --git a/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php index adc18625f7..8761121121 100644 --- a/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/NodeCreationHandler/Factory/UriPathSegmentNodeCreationHandlerFactory.php @@ -23,7 +23,7 @@ * * - sets the "uriPathSegment" property according to the specified title or node name * - sets the "title" property according to the incoming title from a creation dialog - * - (actually obsolete with CreationDialogPropertiesCreationHandler) + * - (actually obsolete with PromotedElementsCreationHandler) * * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` * @implements ContentRepositoryServiceFactoryInterface @@ -55,7 +55,7 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele $propertyValues = $commands->first->initialPropertyValues; if ($elements->hasPropertyLike('title')) { - // technically we only need to set the uriPathSegment as the CreationDialogPropertiesCreationHandler + // technically we only need to set the uriPathSegment as the PromotedElementsCreationHandler // will take care of setting the title already $propertyValues = $propertyValues->withValue('title', $elements->getPropertyLike('title')); } diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index 9f04ff7a9e..0ea3eb2a3b 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -20,14 +20,14 @@ nodeCreationHandlers: uriPathSegment: factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\UriPathSegmentNodeCreationHandlerFactory' - creationDialogProperties: - factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' + promotedElements: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\PromotedElementsCreationHandlerFactory' 'Neos.Neos:Content': options: nodeCreationHandlers: - creationDialogProperties: - factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\CreationDialogPropertiesCreationHandlerFactory' + promotedElements: + factoryClassName: 'Neos\Neos\Ui\NodeCreationHandler\Factory\PromotedElementsCreationHandlerFactory' 'Neos.Neos:ContentCollection': ui: From 0b8cfb4c43c37a77dec3ea633263d9de619f9afd Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 15:42:02 +0100 Subject: [PATCH 13/30] TASK: Move NodeCreation stuff around --- Classes/Domain/Model/Changes/AbstractCreate.php | 6 +++--- .../NodeCreation}/NodeCreationCommands.php | 2 +- .../NodeCreation}/NodeCreationElements.php | 2 +- .../NodeCreation}/NodeCreationHandlerInterface.php | 2 +- Classes/Domain/Service/NodePropertyConversionService.php | 2 +- .../PromotedElementsCreationHandlerFactory.php | 8 ++++---- .../UriPathSegmentNodeCreationHandlerFactory.php | 8 ++++---- Configuration/NodeTypes.yaml | 6 +++--- 8 files changed, 18 insertions(+), 18 deletions(-) rename Classes/{NodeCreationHandler => Domain/NodeCreation}/NodeCreationCommands.php (99%) rename Classes/{NodeCreationHandler => Domain/NodeCreation}/NodeCreationElements.php (97%) rename Classes/{NodeCreationHandler => Domain/NodeCreation}/NodeCreationHandlerInterface.php (96%) rename Classes/{NodeCreationHandler/Factory => Infrastructure/NodeCreation}/PromotedElementsCreationHandlerFactory.php (91%) rename Classes/{NodeCreationHandler/Factory => Infrastructure/NodeCreation}/UriPathSegmentNodeCreationHandlerFactory.php (95%) diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 03efd293ee..afb6855d7c 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -25,9 +25,9 @@ use Neos\Flow\ObjectManagement\ObjectManagerInterface; use Neos\Neos\Ui\Domain\Service\NodePropertyConversionService; use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException; -use Neos\Neos\Ui\NodeCreationHandler\NodeCreationCommands; -use Neos\Neos\Ui\NodeCreationHandler\NodeCreationElements; -use Neos\Neos\Ui\NodeCreationHandler\NodeCreationHandlerInterface; +use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands; +use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationElements; +use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationHandlerInterface; use Neos\Utility\PositionalArraySorter; /** diff --git a/Classes/NodeCreationHandler/NodeCreationCommands.php b/Classes/Domain/NodeCreation/NodeCreationCommands.php similarity index 99% rename from Classes/NodeCreationHandler/NodeCreationCommands.php rename to Classes/Domain/NodeCreation/NodeCreationCommands.php index 6b5a9e8b49..82446501ba 100644 --- a/Classes/NodeCreationHandler/NodeCreationCommands.php +++ b/Classes/Domain/NodeCreation/NodeCreationCommands.php @@ -1,6 +1,6 @@ Date: Sat, 17 Feb 2024 16:01:24 +0100 Subject: [PATCH 14/30] BUGFIX: Reference support in NodeCreationHandler Resolves: https://github.com/neos/neos-ui/issues/3615 --- ...PromotedElementsCreationHandlerFactory.php | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php b/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php index aa8760c455..d050d8d00b 100644 --- a/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php +++ b/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php @@ -6,7 +6,10 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; +use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; +use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferencesToWrite; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationElements; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationHandlerInterface; @@ -31,8 +34,10 @@ public function __construct( public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { $nodeType = $this->nodeTypeManager->getNodeType($commands->first->nodeTypeName); + + // handle properties $propertyValues = $commands->first->initialPropertyValues; - foreach ($nodeType->getConfiguration('properties') as $propertyName => $propertyConfiguration) { + foreach ($nodeType->getProperties() as $propertyName => $propertyConfiguration) { if ( !isset($propertyConfiguration['ui']['showInCreationDialog']) || $propertyConfiguration['ui']['showInCreationDialog'] !== true @@ -43,11 +48,39 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele if (!$elements->hasPropertyLike($propertyName)) { continue; } - // todo support also references https://github.com/neos/neos-ui/issues/3615 $propertyValues = $propertyValues->withValue($propertyName, $elements->getPropertyLike($propertyName)); } - return $commands->withInitialPropertyValues($propertyValues); + // handle references + $setReferencesCommands = []; + foreach ($nodeType->getProperties() as $referenceName => $referenceConfiguration) { + // todo this will be replaced by $nodeType->getReferences() + if ($nodeType->getPropertyType($referenceName) !== 'references' && $nodeType->getPropertyType($referenceName) !== 'reference') { + continue; // no a reference + } + if ( + !isset($referenceConfiguration['ui']['showInCreationDialog']) + || $referenceConfiguration['ui']['showInCreationDialog'] !== true + ) { + // not a promoted reference + continue; + } + if (!$elements->hasReferenceLike($referenceName)) { + continue; + } + + $setReferencesCommands[] = SetNodeReferences::create( + $commands->first->contentStreamId, + $commands->first->nodeAggregateId, + $commands->first->originDimensionSpacePoint, + ReferenceName::fromString($referenceName), + NodeReferencesToWrite::fromNodeAggregateIds($elements->getReferenceLike($referenceName)) + ); + } + + return $commands + ->withInitialPropertyValues($propertyValues) + ->withAdditionalCommands(...$setReferencesCommands); } }; } From ee15b78d85e0a7a74210f1aa053e88a61145dbe9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 17 Feb 2024 21:55:09 +0100 Subject: [PATCH 15/30] TASK: Improve documentation --- .../NodeCreation/NodeCreationCommands.php | 20 ++++++++-- .../NodeCreation/NodeCreationElements.php | 38 ++++++++++++++----- .../NodeCreationHandlerInterface.php | 32 ++++++++++++---- 3 files changed, 69 insertions(+), 21 deletions(-) diff --git a/Classes/Domain/NodeCreation/NodeCreationCommands.php b/Classes/Domain/NodeCreation/NodeCreationCommands.php index 82446501ba..f5a7b38e1b 100644 --- a/Classes/Domain/NodeCreation/NodeCreationCommands.php +++ b/Classes/Domain/NodeCreation/NodeCreationCommands.php @@ -24,12 +24,15 @@ use Neos\ContentRepository\Core\NodeType\NodeTypeManager; /** - * A collection of commands that can be "enriched" via a {@see NodeCreationHandlerInterface} - * The first command points to the initial node creation command. - * It is ensured, that the initial node creation command, will be mostly preserved, to not contradict the users intend. + * A collection of commands that describe a node creation from the Neos Ui. * - * Additional commands can be also appended, to be run after the initial node creation command. + * The node creation can be enriched via a node creation handler {@see NodeCreationHandlerInterface} + * + * The first command points to the triggered node creation command. + * To not contradict the users intend it is ensured that the initial node + * creation will be mostly preserved by only allowing to add additional properties. * + * Additional commands can be also appended, to be run after the initial node creation command. * All commands will be executed blocking. * * You can retrieve the subgraph or the parent node (where the first node will be created in) the following way: @@ -88,6 +91,15 @@ public static function fromFirstCommand( /** * Augment the first {@see CreateNodeAggregateWithNode} command with altered properties. + * + * The properties will be completely replaced. + * To merge the properties please use: + * + * $commands->withInitialPropertyValues( + * $commands->first->initialPropertyValues + * ->withValue('album', 'rep') + * ) + * */ public function withInitialPropertyValues(PropertyValuesToWrite $newInitialPropertyValues): self { diff --git a/Classes/Domain/NodeCreation/NodeCreationElements.php b/Classes/Domain/NodeCreation/NodeCreationElements.php index c35e9a2184..42ec27f313 100644 --- a/Classes/Domain/NodeCreation/NodeCreationElements.php +++ b/Classes/Domain/NodeCreation/NodeCreationElements.php @@ -9,19 +9,37 @@ /** * Access to deserialize elements of the node creation dialog * - * property-like elements are of simple types or objects: + * Property-like elements are of simple types or objects. + * The values will be deserialized according to its type. + * For example myImage will be an actual image object instance. * - * creationDialog: - * elements: - * myString: - * type: string + * Vendor.Site:Content: + * ui: + * creationDialog: + * elements: + * myString: + * type: string + * myImage: + * type: Neos\Media\Domain\Model\ImageInterface + * + * Reference-like elements are of type `references` or `reference` + * And will be available as NodeAggregateIds collection. * - * while reference-like elements are of type `references` or `reference`: + * Vendor.Site:Content: + * ui: + * creationDialog: + * elements: + * myReferences: + * type: references * - * creationDialog: - * elements: - * myReferences: - * type: references + * The same categories apply to promoted elements: + * + * Vendor.Site:Content: + * properties: + * myString: + * type: string + * ui: + * showInCreationDialog: true * * @api As part of the {@see NodeCreationHandlerInterface} */ diff --git a/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php b/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php index 79c0b36782..14aa32713a 100644 --- a/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php +++ b/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php @@ -11,22 +11,40 @@ * source code. */ +use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; /** - * Contract for Node Creation handler that allow to hook into the process just before a node is being added - * via the Neos UI + * Contract to hook into the process before the node creation command is handled by the content repository + * + * You can add additional steps to the node creation. + * For example adding initial properties via {@see NodeCreationCommands::withInitialPropertyValues()}, + * or queuing additional commands like to create a child via {@see NodeCreationCommands::withAdditionalCommands()} + * + * The node creation handlers factory can be registered on a NodeType: + * + * Vendor.Site:Content: + * options: + * nodeCreationHandlers: + * myHandler: + * factoryClassName: 'Vendor\Site\MyHandlerFactory' + * position: end + * + * The factory must implement the {@see ContentRepositoryServiceFactoryInterface} and + * return an implementation with this {@see NodeCreationHandlerInterface} interface. + * + * The current content-repository or NodeType-manager will be accessible via the factory dependencies. + * * @api */ interface NodeCreationHandlerInterface extends ContentRepositoryServiceInterface { /** - * You can "enrich" the node creation, by for example adding initial properties {@see NodeCreationCommands::withInitialPropertyValues()} - * or appending additional f.x. create-child nodes commands {@see NodeCreationCommands::withAdditionalCommands()} - * - * @param NodeCreationCommands $commands original or previous commands, with the first command being the initial intended node creation + * @param NodeCreationCommands $commands original or previous commands, + * with the first command being the initial intended node creation * @param NodeCreationElements $elements incoming data from the creationDialog - * @return NodeCreationCommands the "enriched" commands, to be passed to the next handler or run at the end + * @return NodeCreationCommands the enriched node creation commands, + * to be passed to the next handler or run at the end */ public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands; } From 9890e05d2166d08221204e3ad3a3cf2de6b77bd9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 09:48:47 +0100 Subject: [PATCH 16/30] TASK: Move `NodeCreationHandler` implementations to fitting sub namespace --- Classes/Domain/Model/Changes/AbstractCreate.php | 3 +-- .../PromotedElementsCreationHandlerFactory.php | 2 +- .../UriPathSegmentNodeCreationHandlerFactory.php | 2 +- Configuration/NodeTypes.yaml | 6 +++--- 4 files changed, 6 insertions(+), 7 deletions(-) rename Classes/Infrastructure/{NodeCreation => ContentRepository}/PromotedElementsCreationHandlerFactory.php (98%) rename Classes/Infrastructure/{NodeCreation => Neos}/UriPathSegmentNodeCreationHandlerFactory.php (98%) diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index afb6855d7c..34013b29bc 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -44,9 +44,8 @@ abstract class AbstractCreate extends AbstractStructuralChange /** * @Flow\Inject - * @var NodePropertyConversionService */ - protected $nodePropertyConversionService; + protected NodePropertyConversionService $nodePropertyConversionService; /** * The type of the node that will be created diff --git a/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php b/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php similarity index 98% rename from Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php rename to Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php index d050d8d00b..a566606664 100644 --- a/Classes/Infrastructure/NodeCreation/PromotedElementsCreationHandlerFactory.php +++ b/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Infrastructure\NodeCreation; +namespace Neos\Neos\Ui\Infrastructure\ContentRepository; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; diff --git a/Classes/Infrastructure/NodeCreation/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php similarity index 98% rename from Classes/Infrastructure/NodeCreation/UriPathSegmentNodeCreationHandlerFactory.php rename to Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php index c062d4edd5..59effdc63a 100644 --- a/Classes/Infrastructure/NodeCreation/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Infrastructure\NodeCreation; +namespace Neos\Neos\Ui\Infrastructure\Neos; use Behat\Transliterator\Transliterator; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index 984ebca389..fefd6c9af9 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -19,15 +19,15 @@ options: nodeCreationHandlers: uriPathSegment: - factoryClassName: 'Neos\Neos\Ui\Infrastructure\NodeCreation\UriPathSegmentNodeCreationHandlerFactory' + factoryClassName: 'Neos\Neos\Ui\Infrastructure\Neos\UriPathSegmentNodeCreationHandlerFactory' promotedElements: - factoryClassName: 'Neos\Neos\Ui\Infrastructure\NodeCreation\PromotedElementsCreationHandlerFactory' + factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\PromotedElementsCreationHandlerFactory' 'Neos.Neos:Content': options: nodeCreationHandlers: promotedElements: - factoryClassName: 'Neos\Neos\Ui\Infrastructure\NodeCreation\PromotedElementsCreationHandlerFactory' + factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\PromotedElementsCreationHandlerFactory' 'Neos.Neos:ContentCollection': ui: From 96454ff1180facd7d027cfca87d586c8d5e65039 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 10:39:49 +0100 Subject: [PATCH 17/30] TASK: Simply `NodeCreationElements` by not differentiating between properties and references At this point, they are all elements and some might have the special case of referring to a node which is done via `type: references` --- .../NodeCreation/NodeCreationElements.php | 69 ++++++++----------- .../Service/NodePropertyConversionService.php | 15 ++-- ...PromotedElementsCreationHandlerFactory.php | 68 +++++++++--------- ...iPathSegmentNodeCreationHandlerFactory.php | 6 +- 4 files changed, 71 insertions(+), 87 deletions(-) diff --git a/Classes/Domain/NodeCreation/NodeCreationElements.php b/Classes/Domain/NodeCreation/NodeCreationElements.php index 42ec27f313..514726e8e4 100644 --- a/Classes/Domain/NodeCreation/NodeCreationElements.php +++ b/Classes/Domain/NodeCreation/NodeCreationElements.php @@ -7,10 +7,12 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; /** - * Access to deserialize elements of the node creation dialog + * Holds the deserialized elements of the submitted node creation dialog form * - * Property-like elements are of simple types or objects. - * The values will be deserialized according to its type. + * Elements are configured like properties or references in the schema, + * but its up to the node-creation-handler if they are handled in any way or just left out. + * + * Elements that are of simple types or objects, will be available according to its type. * For example myImage will be an actual image object instance. * * Vendor.Site:Content: @@ -22,8 +24,8 @@ * myImage: * type: Neos\Media\Domain\Model\ImageInterface * - * Reference-like elements are of type `references` or `reference` - * And will be available as NodeAggregateIds collection. + * Elements that refer to nodes are of type `references` or `reference`. + * They will be available as {@see NodeAggregateIds} collection. * * Vendor.Site:Content: * ui: @@ -32,7 +34,11 @@ * myReferences: * type: references * - * The same categories apply to promoted elements: + * The naming `references` in the `element` configuration does not refer to the content repository reference edges. + * Referring to a node will just denote that an editor will be used capable of returning node ids. + * The node ids might be used for setting references but that is up to a node-creation-handler. + * + * To promoted properties / references the same rules apply: * * Vendor.Site:Content: * properties: @@ -41,65 +47,50 @@ * ui: * showInCreationDialog: true * - * @api As part of the {@see NodeCreationHandlerInterface} + * @implements \IteratorAggregate + * @api As part of the {@see NodeCreationHandlerInterface} except the constructor and serialized data */ -final readonly class NodeCreationElements +final readonly class NodeCreationElements implements \IteratorAggregate { /** - * @param array $propertyLikeValues - * @param array $referenceLikeValues + * @param array $elementValues * @param array $serializedValues * @internal you should not need to construct this */ public function __construct( - private array $propertyLikeValues, - private array $referenceLikeValues, + private array $elementValues, private array $serializedValues, ) { } - public function hasPropertyLike(string $name): bool + public function has(string $name): bool { - return isset($this->propertyLikeValues[$name]); - } - - public function getPropertyLike(string $name): mixed - { - return $this->propertyLikeValues[$name] ?? null; - } - - public function hasReferenceLike(string $name): bool - { - return isset($this->referenceLikeValues[$name]); - } - - public function getReferenceLike(string $name): NodeAggregateIds - { - return $this->referenceLikeValues[$name] ; + return isset($this->elementValues[$name]); } /** - * @return iterable + * Returns the type according to the element schema + * For elements that refer to a node {@see NodeAggregateIds} will be returned. */ - public function getPropertyLikeValues(): iterable + public function get(string $name): mixed { - return $this->propertyLikeValues; + return $this->elementValues[$name] ?? null; } /** - * @return iterable + * @internal returns values formatted by the internal format used for the Ui + * @return \Traversable */ - public function getReferenceLikeValues(): iterable + public function serialized(): \Traversable { - return $this->referenceLikeValues; + yield from $this->serializedValues; } /** - * @internal returns values formatted by the internal format used for the Ui - * @return iterable + * @return \Traversable */ - public function serialized(): iterable + public function getIterator(): \Traversable { - return $this->serializedValues; + yield from $this->elementValues; } } diff --git a/Classes/Domain/Service/NodePropertyConversionService.php b/Classes/Domain/Service/NodePropertyConversionService.php index 1f0b417a2e..356404b1b5 100644 --- a/Classes/Domain/Service/NodePropertyConversionService.php +++ b/Classes/Domain/Service/NodePropertyConversionService.php @@ -116,8 +116,7 @@ public function convert(string $propertyType, string|array|null $rawValue): mixe */ public function convertNodeCreationElements(NodeType $nodeType, array $data): NodeCreationElements { - $convertedProperties = []; - $convertedReferences = []; + $convertedElements = []; /** @var string $elementName */ foreach ($nodeType->getConfiguration('ui.creationDialog.elements') ?? [] as $elementName => $elementConfiguration) { $rawValue = $data[$elementName] ?? null; @@ -126,19 +125,19 @@ public function convertNodeCreationElements(NodeType $nodeType, array $data): No } $propertyType = $elementConfiguration['type'] ?? 'string'; if ($propertyType === 'references' || $propertyType === 'reference') { - $destinationNodeAggregateIds = []; + $nodeAggregateIds = []; if (is_string($rawValue) && !empty($rawValue)) { - $destinationNodeAggregateIds = [$rawValue]; + $nodeAggregateIds = [$rawValue]; } elseif (is_array($rawValue)) { - $destinationNodeAggregateIds = $rawValue; + $nodeAggregateIds = $rawValue; } - $convertedReferences[$elementName] = NodeAggregateIds::fromArray($destinationNodeAggregateIds); + $convertedElements[$elementName] = NodeAggregateIds::fromArray($nodeAggregateIds); continue; } - $convertedProperties[$elementName] = $this->convert($propertyType, $rawValue); + $convertedElements[$elementName] = $this->convert($propertyType, $rawValue); } - return new NodeCreationElements(propertyLikeValues: $convertedProperties, referenceLikeValues: $convertedReferences, serializedValues: $data); + return new NodeCreationElements(elementValues: $convertedElements, serializedValues: $data); } /** diff --git a/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php b/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php index a566606664..ee32850097 100644 --- a/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php +++ b/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php @@ -9,6 +9,7 @@ use Neos\ContentRepository\Core\Feature\NodeReferencing\Command\SetNodeReferences; use Neos\ContentRepository\Core\Feature\NodeReferencing\Dto\NodeReferencesToWrite; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateIds; use Neos\ContentRepository\Core\SharedModel\Node\ReferenceName; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationElements; @@ -31,51 +32,44 @@ public function __construct( private readonly NodeTypeManager $nodeTypeManager ) { } + public function handle(NodeCreationCommands $commands, NodeCreationElements $elements): NodeCreationCommands { $nodeType = $this->nodeTypeManager->getNodeType($commands->first->nodeTypeName); - // handle properties $propertyValues = $commands->first->initialPropertyValues; - foreach ($nodeType->getProperties() as $propertyName => $propertyConfiguration) { - if ( - !isset($propertyConfiguration['ui']['showInCreationDialog']) - || $propertyConfiguration['ui']['showInCreationDialog'] !== true - ) { - // not a promoted property - continue; - } - if (!$elements->hasPropertyLike($propertyName)) { - continue; - } - $propertyValues = $propertyValues->withValue($propertyName, $elements->getPropertyLike($propertyName)); - } - - // handle references $setReferencesCommands = []; - foreach ($nodeType->getProperties() as $referenceName => $referenceConfiguration) { - // todo this will be replaced by $nodeType->getReferences() - if ($nodeType->getPropertyType($referenceName) !== 'references' && $nodeType->getPropertyType($referenceName) !== 'reference') { - continue; // no a reference - } - if ( - !isset($referenceConfiguration['ui']['showInCreationDialog']) - || $referenceConfiguration['ui']['showInCreationDialog'] !== true - ) { - // not a promoted reference - continue; - } - if (!$elements->hasReferenceLike($referenceName)) { - continue; + foreach ($elements as $elementName => $elementValue) { + // handle properties + // todo this will be simplified once hasProperty does not return true for references + if ($nodeType->hasProperty($elementName) && ($nodeType->getPropertyType($elementName) !== 'references' && $nodeType->getPropertyType($elementName) !== 'reference')) { + $propertyConfiguration = $nodeType->getProperties()[$elementName]; + if ( + ($propertyConfiguration['ui']['showInCreationDialog'] ?? false) === true + ) { + // a promoted element + $propertyValues = $propertyValues->withValue($elementName, $elementValue); + } } - $setReferencesCommands[] = SetNodeReferences::create( - $commands->first->contentStreamId, - $commands->first->nodeAggregateId, - $commands->first->originDimensionSpacePoint, - ReferenceName::fromString($referenceName), - NodeReferencesToWrite::fromNodeAggregateIds($elements->getReferenceLike($referenceName)) - ); + // handle references + // todo this will be replaced by $nodeType->hasReference() + if ($nodeType->hasProperty($elementName) && ($nodeType->getPropertyType($elementName) === 'references' || $nodeType->getPropertyType($elementName) === 'reference')) { + assert($elementValue instanceof NodeAggregateIds); + $referenceConfiguration = $nodeType->getProperties()[$elementName]; + if ( + ($referenceConfiguration['ui']['showInCreationDialog'] ?? false) === true + ) { + // a promoted element + $setReferencesCommands[] = SetNodeReferences::create( + $commands->first->contentStreamId, + $commands->first->nodeAggregateId, + $commands->first->originDimensionSpacePoint, + ReferenceName::fromString($elementName), + NodeReferencesToWrite::fromNodeAggregateIds($elementValue) + ); + } + } } return $commands diff --git a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php index 59effdc63a..8d220cd0c3 100644 --- a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php @@ -54,14 +54,14 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele } $propertyValues = $commands->first->initialPropertyValues; - if ($elements->hasPropertyLike('title')) { + if ($elements->has('title')) { // technically we only need to set the uriPathSegment as the PromotedElementsCreationHandler // will take care of setting the title already - $propertyValues = $propertyValues->withValue('title', $elements->getPropertyLike('title')); + $propertyValues = $propertyValues->withValue('title', $elements->get('title')); } // if specified, the uriPathSegment equals the title - $uriPathSegment = $elements->getPropertyLike('title'); + $uriPathSegment = $elements->get('title'); // otherwise, we fall back to the node name if ($uriPathSegment === null && $commands->first->nodeName !== null) { From 984700957b9e152e23b3cd52915e2bef53245e86 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 24 Feb 2024 10:46:34 +0100 Subject: [PATCH 18/30] WIP: Add temporary patch for testing --- Tests/IntegrationTests/TestDistribution/composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Tests/IntegrationTests/TestDistribution/composer.json b/Tests/IntegrationTests/TestDistribution/composer.json index 97773a76be..428860f439 100644 --- a/Tests/IntegrationTests/TestDistribution/composer.json +++ b/Tests/IntegrationTests/TestDistribution/composer.json @@ -32,6 +32,9 @@ }, "extra": { "patches": { + "neos/neos-development-collection": { + "TASK: Remove declaration of ui nodeCreationHandlers": "https://github.com/neos/neos-development-collection/pull/4630.patch" + } } }, "repositories": { From 56596240d1f24c781fec2a2362ad51696f6af11d Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:37:37 +0100 Subject: [PATCH 19/30] TASK: Move `CreationDialogPostprocessor` to Neos.Ui NodeType configuration, which resides in `ui.creationDialog` is part of the Neos.Neos.Ui --- .../CreationDialogPostprocessor.php | 160 ++++++++++++++ Configuration/NodeTypes.yaml | 8 + .../Unit/CreationDialogPostprocessorTest.php | 203 ++++++++++++++++++ 3 files changed, 371 insertions(+) create mode 100644 Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php create mode 100644 Tests/Unit/CreationDialogPostprocessorTest.php diff --git a/Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php b/Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php new file mode 100644 index 0000000000..f9971cd420 --- /dev/null +++ b/Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php @@ -0,0 +1,160 @@ + + * @Flow\InjectConfiguration(package="Neos.Neos", path="userInterface.inspector.dataTypes") + */ + protected $dataTypesDefaultConfiguration; + + /** + * @var array + * @phpstan-var array + * @Flow\InjectConfiguration(package="Neos.Neos", path="userInterface.inspector.editors") + */ + protected $editorDefaultConfiguration; + + /** + * @param NodeType $nodeType (uninitialized) The node type to process + * @param array $configuration input configuration + * @param array $options The processor options + * @return void + */ + public function process(NodeType $nodeType, array &$configuration, array $options): void + { + if (!isset($configuration['properties'])) { + return; + } + $creationDialogElements = $configuration['ui']['creationDialog']['elements'] ?? []; + foreach ($configuration['properties'] as $propertyName => $propertyConfiguration) { + if ( + !isset($propertyConfiguration['ui']['showInCreationDialog']) + || $propertyConfiguration['ui']['showInCreationDialog'] !== true + ) { + continue; + } + $creationDialogElement = $this->convertPropertyConfiguration($propertyName, $propertyConfiguration); + if (isset($configuration['ui']['creationDialog']['elements'][$propertyName])) { + $creationDialogElement = Arrays::arrayMergeRecursiveOverrule( + $creationDialogElement, + $configuration['ui']['creationDialog']['elements'][$propertyName] + ); + } + $creationDialogElements[$propertyName] = $creationDialogElement; + } + if ($creationDialogElements !== []) { + $configuration['ui']['creationDialog']['elements'] + = (new PositionalArraySorter($creationDialogElements))->toArray(); + } + } + + /** + * Converts a NodeType property configuration to the corresponding creationDialog "element" configuration + * + * @param string $propertyName + * @param array $propertyConfiguration + * @return array + */ + private function convertPropertyConfiguration(string $propertyName, array $propertyConfiguration): array + { + $dataType = $propertyConfiguration['type'] ?? 'string'; + $dataTypeDefaultConfiguration = $this->dataTypesDefaultConfiguration[$dataType] ?? []; + $convertedConfiguration = [ + 'type' => $dataType, + 'ui' => [ + 'label' => $propertyConfiguration['ui']['label'] ?? $propertyName, + ], + ]; + if (isset($propertyConfiguration['defaultValue'])) { + $convertedConfiguration['defaultValue'] = $propertyConfiguration['defaultValue']; + } + if (isset($propertyConfiguration['ui']['help'])) { + $convertedConfiguration['ui']['help'] = $propertyConfiguration['ui']['help']; + } + if (isset($propertyConfiguration['validation'])) { + $convertedConfiguration['validation'] = $propertyConfiguration['validation']; + } + if (isset($propertyConfiguration['ui']['inspector']['position'])) { + $convertedConfiguration['position'] = $propertyConfiguration['ui']['inspector']['position']; + } + if (isset($propertyConfiguration['ui']['inspector']['hidden'])) { + $convertedConfiguration['ui']['hidden'] = $propertyConfiguration['ui']['inspector']['hidden']; + } + + $editor = $propertyConfiguration['ui']['inspector']['editor'] + ?? $dataTypeDefaultConfiguration['editor'] + ?? 'Neos.Neos/Inspector/Editors/TextFieldEditor'; + $editorOptions = $propertyConfiguration['ui']['inspector']['editorOptions'] ?? []; + if (isset($dataTypeDefaultConfiguration['editorOptions'])) { + $editorOptions = Arrays::arrayMergeRecursiveOverrule( + $dataTypeDefaultConfiguration['editorOptions'], + $editorOptions + ); + } + if (isset($this->editorDefaultConfiguration[$editor]['editorOptions'])) { + $editorOptions = Arrays::arrayMergeRecursiveOverrule( + $this->editorDefaultConfiguration[$editor]['editorOptions'], + $editorOptions + ); + } + + $convertedConfiguration['ui']['editor'] = $editor; + $convertedConfiguration['ui']['editorOptions'] = $editorOptions; + return $convertedConfiguration; + } +} diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index fefd6c9af9..be0d2127db 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -1,4 +1,8 @@ 'Neos.Neos:Document': + postprocessors: + 'CreationDialogPostprocessor': + position: 'after NodeTypePresetPostprocessor' + postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialogPostprocessor' ui: creationDialog: elements: @@ -24,6 +28,10 @@ factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\PromotedElementsCreationHandlerFactory' 'Neos.Neos:Content': + postprocessors: + 'CreationDialogPostprocessor': + position: 'after NodeTypePresetPostprocessor' + postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialogPostprocessor' options: nodeCreationHandlers: promotedElements: diff --git a/Tests/Unit/CreationDialogPostprocessorTest.php b/Tests/Unit/CreationDialogPostprocessorTest.php new file mode 100644 index 0000000000..6d23735f92 --- /dev/null +++ b/Tests/Unit/CreationDialogPostprocessorTest.php @@ -0,0 +1,203 @@ +creationDialogPostprocessor = new CreationDialogPostprocessor(); + $this->mockNodeType = new NodeType(NodeTypeName::fromString('Foo:Bar'), [], [], new DefaultNodeLabelGeneratorFactory()); + } + + /** + * @test + */ + public function processCopiesInspectorConfigurationToCreationDialogElements(): void + { + $configuration = [ + 'properties' => [ + 'somePropertyName' => [ + 'ui' => [ + 'showInCreationDialog' => true, + 'inspector' => [ + 'position' => 123, + 'editor' => 'Some\Editor', + 'editorOptions' => ['some' => 'option'], + 'hidden' => 'ClientEval:false' + ], + ], + 'validation' => [ + 'Neos.Neos/Validation/NotEmptyValidator' => [], + 'Neos.Neos/Validation/StringLengthValidator' => [ + 'minimum' => 1, + 'maximum' => 255, + ] + ], + ], + ], + ]; + + $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + + $expectedElements = [ + 'somePropertyName' => [ + 'type' => 'string', + 'ui' => [ + 'label' => 'somePropertyName', + 'hidden' => 'ClientEval:false', + 'editor' => 'Some\Editor', + 'editorOptions' => ['some' => 'option'], + ], + 'validation' => [ + 'Neos.Neos/Validation/NotEmptyValidator' => [], + 'Neos.Neos/Validation/StringLengthValidator' => [ + 'minimum' => 1, + 'maximum' => 255, + ] + ], + 'position' => 123, + ], + ]; + + self::assertSame($expectedElements, $configuration['ui']['creationDialog']['elements']); + } + + /** + * @test + */ + public function processDoesNotCreateEmptyCreationDialogs(): void + { + $configuration = [ + 'properties' => [ + 'somePropertyName' => [ + 'ui' => [ + 'inspector' => [ + 'editor' => 'Some\Editor', + 'editorOptions' => ['some' => 'option'], + ], + ], + ], + ], + ]; + $originalConfiguration = $configuration; + + $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + + self::assertSame($originalConfiguration, $configuration); + } + + /** + * @test + */ + public function processRespectsDataTypeDefaultConfiguration(): void + { + $configuration = [ + 'properties' => [ + 'somePropertyName' => [ + 'type' => 'SomeType', + 'ui' => [ + 'label' => 'Some Label', + 'showInCreationDialog' => true, + 'inspector' => [ + 'editorOptions' => ['some' => 'option'], + ], + ], + ], + ], + ]; + $this->inject($this->creationDialogPostprocessor, 'dataTypesDefaultConfiguration', [ + 'SomeType' => [ + 'editor' => 'Some\Default\Editor', + 'editorOptions' => [ + 'some' => 'defaultOption', + 'someDefault' => 'option', + ] + ] + ]); + + $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + + $expectedElements = [ + 'somePropertyName' => [ + 'type' => 'SomeType', + 'ui' => [ + 'label' => 'Some Label', + 'editor' => 'Some\Default\Editor', + 'editorOptions' => ['some' => 'option', 'someDefault' => 'option'], + ], + ], + ]; + + self::assertSame($expectedElements, $configuration['ui']['creationDialog']['elements']); + } + + /** + * @test + */ + public function processRespectsEditorDefaultConfiguration(): void + { + $configuration = [ + 'properties' => [ + 'somePropertyName' => [ + 'type' => 'SomeType', + 'ui' => [ + 'showInCreationDialog' => true, + 'inspector' => [ + 'editorOptions' => ['some' => 'option'], + ], + ], + ], + ], + ]; + $this->inject($this->creationDialogPostprocessor, 'editorDefaultConfiguration', [ + 'Some\Editor' => [ + 'editorOptions' => [ + 'some' => 'editorDefault', + 'someDefault' => 'fromEditor', + 'someEditorDefault' => 'fromEditor', + ] + ] + ]); + $this->inject($this->creationDialogPostprocessor, 'dataTypesDefaultConfiguration', [ + 'SomeType' => [ + 'editor' => 'Some\Editor', + 'editorOptions' => [ + 'some' => 'defaultOption', + 'someDefault' => 'fromDataType', + ] + ] + ]); + + + $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + + $expectedElements = [ + 'somePropertyName' => [ + 'type' => 'SomeType', + 'ui' => [ + 'label' => 'somePropertyName', + 'editor' => 'Some\Editor', + 'editorOptions' => ['some' => 'option', 'someDefault' => 'fromDataType', 'someEditorDefault' => 'fromEditor'], + ], + ], + ]; + + self::assertSame($expectedElements, $configuration['ui']['creationDialog']['elements']); + } +} From d2db3524812b4b2a7867c73c3f363fd51b91a203 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 28 Feb 2024 22:45:34 +0100 Subject: [PATCH 20/30] TASK: Rename `CreationDialogPostprocessor` to `CreationDialogNodeTypePostprocessor` ... and colocate code with `PromotedElementsCreationHandler` --- .../CreationDialogNodeTypePostprocessor.php} | 4 ++-- ...PromotedElementsCreationHandlerFactory.php | 2 +- Configuration/NodeTypes.yaml | 8 +++---- ...eationDialogNodeTypePostprocessorTest.php} | 24 +++++++++---------- 4 files changed, 19 insertions(+), 19 deletions(-) rename Classes/Infrastructure/ContentRepository/{CreationDialogPostprocessor.php => CreationDialog/CreationDialogNodeTypePostprocessor.php} (97%) rename Classes/Infrastructure/ContentRepository/{ => CreationDialog}/PromotedElementsCreationHandlerFactory.php (98%) rename Tests/Unit/{CreationDialogPostprocessorTest.php => CreationDialogNodeTypePostprocessorTest.php} (86%) diff --git a/Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php similarity index 97% rename from Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php rename to Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php index f9971cd420..e43b78cff0 100644 --- a/Classes/Infrastructure/ContentRepository/CreationDialogPostprocessor.php +++ b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php @@ -12,7 +12,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Infrastructure\ContentRepository; +namespace Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog; use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypePostprocessorInterface; @@ -53,7 +53,7 @@ * 'someProperty': * # ... */ -class CreationDialogPostprocessor implements NodeTypePostprocessorInterface +class CreationDialogNodeTypePostprocessor implements NodeTypePostprocessorInterface { /** * @var array diff --git a/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php b/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php similarity index 98% rename from Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php rename to Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php index ee32850097..04b36ff2b6 100644 --- a/Classes/Infrastructure/ContentRepository/PromotedElementsCreationHandlerFactory.php +++ b/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Neos\Neos\Ui\Infrastructure\ContentRepository; +namespace Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index be0d2127db..f2a90db920 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -2,7 +2,7 @@ postprocessors: 'CreationDialogPostprocessor': position: 'after NodeTypePresetPostprocessor' - postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialogPostprocessor' + postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\CreationDialogNodeTypePostprocessor' ui: creationDialog: elements: @@ -25,17 +25,17 @@ uriPathSegment: factoryClassName: 'Neos\Neos\Ui\Infrastructure\Neos\UriPathSegmentNodeCreationHandlerFactory' promotedElements: - factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\PromotedElementsCreationHandlerFactory' + factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\PromotedElementsCreationHandlerFactory' 'Neos.Neos:Content': postprocessors: 'CreationDialogPostprocessor': position: 'after NodeTypePresetPostprocessor' - postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialogPostprocessor' + postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\CreationDialogNodeTypePostprocessor' options: nodeCreationHandlers: promotedElements: - factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\PromotedElementsCreationHandlerFactory' + factoryClassName: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\PromotedElementsCreationHandlerFactory' 'Neos.Neos:ContentCollection': ui: diff --git a/Tests/Unit/CreationDialogPostprocessorTest.php b/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php similarity index 86% rename from Tests/Unit/CreationDialogPostprocessorTest.php rename to Tests/Unit/CreationDialogNodeTypePostprocessorTest.php index 6d23735f92..e15fb7b0ba 100644 --- a/Tests/Unit/CreationDialogPostprocessorTest.php +++ b/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php @@ -5,14 +5,14 @@ use Neos\ContentRepository\Core\NodeType\NodeType; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\Flow\Tests\UnitTestCase; -use Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialogPostprocessor; +use Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\CreationDialogNodeTypePostprocessor; -class CreationDialogPostprocessorTest extends UnitTestCase +class CreationDialogNodeTypePostprocessorTest extends UnitTestCase { /** - * @var CreationDialogPostprocessor + * @var CreationDialogNodeTypePostprocessor */ - private $creationDialogPostprocessor; + private $postprocessor; /** * @var NodeType @@ -21,7 +21,7 @@ class CreationDialogPostprocessorTest extends UnitTestCase public function setUp(): void { - $this->creationDialogPostprocessor = new CreationDialogPostprocessor(); + $this->postprocessor = new CreationDialogNodeTypePostprocessor(); $this->mockNodeType = new NodeType(NodeTypeName::fromString('Foo:Bar'), [], [], new DefaultNodeLabelGeneratorFactory()); } @@ -53,7 +53,7 @@ public function processCopiesInspectorConfigurationToCreationDialogElements(): v ], ]; - $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + $this->postprocessor->process($this->mockNodeType, $configuration, []); $expectedElements = [ 'somePropertyName' => [ @@ -97,7 +97,7 @@ public function processDoesNotCreateEmptyCreationDialogs(): void ]; $originalConfiguration = $configuration; - $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + $this->postprocessor->process($this->mockNodeType, $configuration, []); self::assertSame($originalConfiguration, $configuration); } @@ -121,7 +121,7 @@ public function processRespectsDataTypeDefaultConfiguration(): void ], ], ]; - $this->inject($this->creationDialogPostprocessor, 'dataTypesDefaultConfiguration', [ + $this->inject($this->postprocessor, 'dataTypesDefaultConfiguration', [ 'SomeType' => [ 'editor' => 'Some\Default\Editor', 'editorOptions' => [ @@ -131,7 +131,7 @@ public function processRespectsDataTypeDefaultConfiguration(): void ] ]); - $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + $this->postprocessor->process($this->mockNodeType, $configuration, []); $expectedElements = [ 'somePropertyName' => [ @@ -165,7 +165,7 @@ public function processRespectsEditorDefaultConfiguration(): void ], ], ]; - $this->inject($this->creationDialogPostprocessor, 'editorDefaultConfiguration', [ + $this->inject($this->postprocessor, 'editorDefaultConfiguration', [ 'Some\Editor' => [ 'editorOptions' => [ 'some' => 'editorDefault', @@ -174,7 +174,7 @@ public function processRespectsEditorDefaultConfiguration(): void ] ] ]); - $this->inject($this->creationDialogPostprocessor, 'dataTypesDefaultConfiguration', [ + $this->inject($this->postprocessor, 'dataTypesDefaultConfiguration', [ 'SomeType' => [ 'editor' => 'Some\Editor', 'editorOptions' => [ @@ -185,7 +185,7 @@ public function processRespectsEditorDefaultConfiguration(): void ]); - $this->creationDialogPostprocessor->process($this->mockNodeType, $configuration, []); + $this->postprocessor->process($this->mockNodeType, $configuration, []); $expectedElements = [ 'somePropertyName' => [ From 366ec920005d1f5389921889d8ce846a70c67e38 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 1 Mar 2024 16:35:06 +0100 Subject: [PATCH 21/30] TASK: Prepare `CreationDialogPostprocessor` to be more generic --- .../CreationDialogNodeTypePostprocessor.php | 82 +++++++++++-------- 1 file changed, 46 insertions(+), 36 deletions(-) diff --git a/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php index e43b78cff0..9c7557bd97 100644 --- a/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php +++ b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php @@ -21,50 +21,53 @@ use Neos\Utility\PositionalArraySorter; /** - * Node Type post processor that looks for properties flagged with "showInCreationDialog" + * NodeType post processor for the "ui.creationDialog" configuration: + * + * Promotes elements into creation dialog + * -------------------------------------- + * We look for properties flagged with "showInCreationDialog" * and sets the "creationDialog" configuration accordingly * * Example NodeTypes.yaml configuration: * - * 'Some.Node:Type': - * # ... - * properties: - * 'someProperty': - * type: string - * ui: - * label: 'Link' - * showInCreationDialog: true - * inspector: - * editor: 'Neos.Neos/Inspector/Editors/LinkEditor' - * - * Will be converted to: - * - * 'Some.Node:Type': - * # ... - * ui: - * creationDialog: - * elements: + * 'Some.Node:Type': + * # ... + * properties: * 'someProperty': * type: string * ui: * label: 'Link' - * editor: 'Neos.Neos/Inspector/Editors/LinkEditor' - * properties: - * 'someProperty': + * showInCreationDialog: true + * inspector: + * editor: 'Neos.Neos/Inspector/Editors/LinkEditor' + * + * Will be converted to: + * + * 'Some.Node:Type': * # ... + * ui: + * creationDialog: + * elements: + * 'someProperty': + * type: string + * ui: + * label: 'Link' + * editor: 'Neos.Neos/Inspector/Editors/LinkEditor' + * properties: + * 'someProperty': + * # ... + * */ class CreationDialogNodeTypePostprocessor implements NodeTypePostprocessorInterface { /** - * @var array - * @phpstan-var array + * @var array * @Flow\InjectConfiguration(package="Neos.Neos", path="userInterface.inspector.dataTypes") */ protected $dataTypesDefaultConfiguration; /** - * @var array - * @phpstan-var array + * @var array * @Flow\InjectConfiguration(package="Neos.Neos", path="userInterface.inspector.editors") */ protected $editorDefaultConfiguration; @@ -81,26 +84,33 @@ public function process(NodeType $nodeType, array &$configuration, array $option return; } $creationDialogElements = $configuration['ui']['creationDialog']['elements'] ?? []; - foreach ($configuration['properties'] as $propertyName => $propertyConfiguration) { + + $creationDialogElements = $this->promotePropertiesIntoCreationDialog($configuration['properties'], $creationDialogElements); + + if ($creationDialogElements !== []) { + $configuration['ui']['creationDialog']['elements'] = (new PositionalArraySorter($creationDialogElements))->toArray(); + } + } + + private function promotePropertiesIntoCreationDialog(array $properties, array $explicitCreationDialogElements): array + { + foreach ($properties as $propertyName => $propertyConfiguration) { if ( !isset($propertyConfiguration['ui']['showInCreationDialog']) || $propertyConfiguration['ui']['showInCreationDialog'] !== true ) { continue; } - $creationDialogElement = $this->convertPropertyConfiguration($propertyName, $propertyConfiguration); - if (isset($configuration['ui']['creationDialog']['elements'][$propertyName])) { + $creationDialogElement = $this->promotePropertyIntoCreationDialog($propertyName, $propertyConfiguration); + if (isset($explicitCreationDialogElements[$propertyName])) { $creationDialogElement = Arrays::arrayMergeRecursiveOverrule( $creationDialogElement, - $configuration['ui']['creationDialog']['elements'][$propertyName] + $explicitCreationDialogElements[$propertyName] ); } - $creationDialogElements[$propertyName] = $creationDialogElement; - } - if ($creationDialogElements !== []) { - $configuration['ui']['creationDialog']['elements'] - = (new PositionalArraySorter($creationDialogElements))->toArray(); + $explicitCreationDialogElements[$propertyName] = $creationDialogElement; } + return $explicitCreationDialogElements; } /** @@ -110,7 +120,7 @@ public function process(NodeType $nodeType, array &$configuration, array $option * @param array $propertyConfiguration * @return array */ - private function convertPropertyConfiguration(string $propertyName, array $propertyConfiguration): array + private function promotePropertyIntoCreationDialog(string $propertyName, array $propertyConfiguration): array { $dataType = $propertyConfiguration['type'] ?? 'string'; $dataTypeDefaultConfiguration = $this->dataTypesDefaultConfiguration[$dataType] ?? []; From 6f7db586d5b150b196021c2ea30be2324a5a3ba9 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 1 Mar 2024 17:28:22 +0100 Subject: [PATCH 22/30] TASK: Move `creationDialog` logic of `DefaultPropertyEditorPostprocessor` to Neos.Ui Into the `CreationDialogPostprocessor` NodeType configuration, which resides in `ui.creationDialog` is part of the Neos.Neos.Ui --- .../CreationDialogNodeTypePostprocessor.php | 65 ++++- ...reationDialogNodeTypePostprocessorTest.php | 224 +++++++++++++++++- 2 files changed, 283 insertions(+), 6 deletions(-) diff --git a/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php index 9c7557bd97..d0edd45d52 100644 --- a/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php +++ b/Classes/Infrastructure/ContentRepository/CreationDialog/CreationDialogNodeTypePostprocessor.php @@ -57,6 +57,12 @@ * 'someProperty': * # ... * + * + * Sets default editor configurations for elements + * ----------------------------------------------- + * We add default editor configurations for elements based on type and editor. + * Similar to the process for regular properties in {@see \Neos\Neos\NodeTypePostprocessor\DefaultPropertyEditorPostprocessor} + * */ class CreationDialogNodeTypePostprocessor implements NodeTypePostprocessorInterface { @@ -80,18 +86,68 @@ class CreationDialogNodeTypePostprocessor implements NodeTypePostprocessorInterf */ public function process(NodeType $nodeType, array &$configuration, array $options): void { - if (!isset($configuration['properties'])) { - return; - } $creationDialogElements = $configuration['ui']['creationDialog']['elements'] ?? []; - $creationDialogElements = $this->promotePropertiesIntoCreationDialog($configuration['properties'], $creationDialogElements); + if (!empty($configuration['properties'] ?? null)) { + $creationDialogElements = $this->promotePropertiesIntoCreationDialog($configuration['properties'], $creationDialogElements); + } + + $this->mergeDefaultCreationDialogElementEditors($creationDialogElements); if ($creationDialogElements !== []) { $configuration['ui']['creationDialog']['elements'] = (new PositionalArraySorter($creationDialogElements))->toArray(); } } + /** + * @param array $creationDialogElements + */ + private function mergeDefaultCreationDialogElementEditors(array &$creationDialogElements): void + { + foreach ($creationDialogElements as &$elementConfiguration) { + if (!isset($elementConfiguration['type'])) { + continue; + } + + $type = $elementConfiguration['type']; + $defaultConfigurationFromDataType = $this->dataTypesDefaultConfiguration[$type] ?? []; + + // FIRST STEP: Figure out which editor should be used + // - Default: editor as configured from the data type + // - Override: editor as configured from the property configuration. + if (isset($elementConfiguration['ui']['editor'])) { + $editor = $elementConfiguration['ui']['editor']; + } elseif (isset($defaultConfigurationFromDataType['editor'])) { + $editor = $defaultConfigurationFromDataType['editor']; + } else { + // No exception since the configuration could be a partial configuration overriding a property + // with showInCreationDialog flag set + continue; + } + + // SECOND STEP: Build up the full UI configuration by merging: + // - take configuration from editor defaults + // - take configuration from dataType + // - take configuration from creationDialog elements (NodeTypes) + $mergedUiConfiguration = $this->editorDefaultConfiguration[$editor] ?? []; + $mergedUiConfiguration = Arrays::arrayMergeRecursiveOverrule( + $mergedUiConfiguration, + $defaultConfigurationFromDataType + ); + $mergedUiConfiguration = Arrays::arrayMergeRecursiveOverrule( + $mergedUiConfiguration, + $elementConfiguration['ui'] ?? [] + ); + $elementConfiguration['ui'] = $mergedUiConfiguration; + $elementConfiguration['ui']['editor'] = $editor; + } + } + + /** + * @param array $properties + * @param array $explicitCreationDialogElements + * @return array + */ private function promotePropertiesIntoCreationDialog(array $properties, array $explicitCreationDialogElements): array { foreach ($properties as $propertyName => $propertyConfiguration) { @@ -146,6 +202,7 @@ private function promotePropertyIntoCreationDialog(string $propertyName, array $ $convertedConfiguration['ui']['hidden'] = $propertyConfiguration['ui']['inspector']['hidden']; } + // todo maybe duplicated due to mergeDefaultCreationDialogElementEditors $editor = $propertyConfiguration['ui']['inspector']['editor'] ?? $dataTypeDefaultConfiguration['editor'] ?? 'Neos.Neos/Inspector/Editors/TextFieldEditor'; diff --git a/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php b/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php index e15fb7b0ba..38bf059b00 100644 --- a/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php +++ b/Tests/Unit/CreationDialogNodeTypePostprocessorTest.php @@ -26,6 +26,8 @@ public function setUp(): void } /** + * promoted elements (showInCreationDialog: true) + * * @test */ public function processCopiesInspectorConfigurationToCreationDialogElements(): void @@ -103,6 +105,8 @@ public function processDoesNotCreateEmptyCreationDialogs(): void } /** + * promoted elements (showInCreationDialog: true) + * * @test */ public function processRespectsDataTypeDefaultConfiguration(): void @@ -144,10 +148,12 @@ public function processRespectsDataTypeDefaultConfiguration(): void ], ]; - self::assertSame($expectedElements, $configuration['ui']['creationDialog']['elements']); + self::assertEquals($expectedElements, $configuration['ui']['creationDialog']['elements']); } /** + * promoted elements (showInCreationDialog: true) + * * @test */ public function processRespectsEditorDefaultConfiguration(): void @@ -198,6 +204,220 @@ public function processRespectsEditorDefaultConfiguration(): void ], ]; - self::assertSame($expectedElements, $configuration['ui']['creationDialog']['elements']); + self::assertEquals($expectedElements, $configuration['ui']['creationDialog']['elements']); + } + + /** + * default editor + * + * @test + */ + public function processConvertsCreationDialogConfiguration(): void + { + $configuration = [ + 'ui' => [ + 'creationDialog' => [ + 'elements' => [ + 'elementWithoutType' => [ + 'ui' => [ + 'label' => 'Some Label' + ] + ], + 'elementWithUnknownType' => [ + 'type' => 'TypeWithoutDataTypeConfig', + 'ui' => [ + 'label' => 'Some Label', + 'editor' => 'EditorFromPropertyConfig', + ] + ], + 'elementWithEditorFromDataTypeConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'value' => 'fromPropertyConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithEditorFromDataTypeConfigWithoutUiConfig' => [ + 'type' => 'TypeWithDataTypeConfig' + ], + 'elementWithOverriddenEditorConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'editor' => 'EditorFromPropertyConfig', + 'value' => 'fromPropertyConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'editor' => 'EditorWithDefaultConfig', + 'value' => 'fromPropertyConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithEditorDefaultConfig' => [ + 'type' => 'TypeWithDefaultEditorConfig', + 'ui' => [ + 'value' => 'fromPropertyConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig2' => [ + 'type' => 'TypeWithDefaultEditorConfig', + 'ui' => [ + 'editor' => 'EditorWithoutDefaultConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig3' => [ + 'type' => 'TypeWithDefaultEditorConfig2', + 'ui' => [ + 'editor' => 'EditorWithDefaultConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + ], + ], + ], + ]; + + $this->inject($this->postprocessor, 'editorDefaultConfiguration', [ + 'EditorWithDefaultConfig' => [ + 'value' => 'fromEditorDefaultConfig', + 'editorDefaultValue' => 'fromEditorDefaultConfig', + ], + ]); + + $this->inject($this->postprocessor, 'dataTypesDefaultConfiguration', [ + 'TypeWithDataTypeConfig' => [ + 'editor' => 'EditorFromDataTypeConfig', + 'value' => 'fromDataTypeConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + ], + 'TypeWithDefaultEditorConfig' => [ + 'editor' => 'EditorWithDefaultConfig', + 'value' => 'fromDataTypeConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + ], + 'TypeWithDefaultEditorConfig2' => [ + 'editor' => 'EditorWithDefaultConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + ], + ]); + + $expectedResult = [ + 'ui' => [ + 'creationDialog' => [ + 'elements' => [ + 'elementWithoutType' => [ + 'ui' => [ + 'label' => 'Some Label' + ] + ], + 'elementWithUnknownType' => [ + 'type' => 'TypeWithoutDataTypeConfig', + 'ui' => [ + 'label' => 'Some Label', + 'editor' => 'EditorFromPropertyConfig', + ] + ], + 'elementWithEditorFromDataTypeConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'editor' => 'EditorFromDataTypeConfig', + 'value' => 'fromPropertyConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithEditorFromDataTypeConfigWithoutUiConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'editor' => 'EditorFromDataTypeConfig', + 'value' => 'fromDataTypeConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + ] + ], + 'elementWithOverriddenEditorConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'editor' => 'EditorFromPropertyConfig', + 'value' => 'fromPropertyConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig' => [ + 'type' => 'TypeWithDataTypeConfig', + 'ui' => [ + 'value' => 'fromPropertyConfig', + 'editorDefaultValue' => 'fromEditorDefaultConfig', + 'editor' => 'EditorWithDefaultConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithEditorDefaultConfig' => [ + 'type' => 'TypeWithDefaultEditorConfig', + 'ui' => [ + 'value' => 'fromPropertyConfig', + 'editorDefaultValue' => 'fromEditorDefaultConfig', + 'editor' => 'EditorWithDefaultConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig2' => [ + 'type' => 'TypeWithDefaultEditorConfig', + 'ui' => [ + 'editor' => 'EditorWithoutDefaultConfig', + 'value' => 'fromDataTypeConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + 'elementWithOverriddenEditorConfigAndEditorDefaultConfig3' => [ + 'type' => 'TypeWithDefaultEditorConfig2', + 'ui' => [ + 'value' => 'fromEditorDefaultConfig', + 'editorDefaultValue' => 'fromEditorDefaultConfig', + 'editor' => 'EditorWithDefaultConfig', + 'dataTypeValue' => 'fromDataTypeConfig', + 'elementValue' => 'fromPropertyConfig', + ] + ], + ], + ], + ], + ]; + + $this->postprocessor->process($this->mockNodeType, $configuration, []); + + self::assertSame($expectedResult, $configuration); + } + + /** + * @test + */ + public function processDoesNotThrowExceptionIfNoCreationDialogEditorCanBeResolved(): void + { + $configuration = [ + 'ui' => [ + 'creationDialog' => [ + 'elements' => [ + 'someElement' => [ + 'type' => 'string', + 'ui' => ['label' => 'Foo'] + ], + ], + ], + ], + ]; + $expected = $configuration; + + $this->postprocessor->process($this->mockNodeType, $configuration, []); + + self::assertSame($expected, $configuration); } } From ea20dc1372696925fbdb61cc3583185e2d3754f1 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Fri, 1 Mar 2024 19:08:55 +0100 Subject: [PATCH 23/30] BUGFIX: 3509 Unify document title label and node creation title label The declaration `showInCreationDialog` was copied over from Neos.Neos and the explicit configuration of `creationDialog.elements.title` is obsolete. The explicit declaration of ui.label: i18n would lead to `Neos.Neos:NodeTypes.Document:creationDialog.title` instead of `Neos.Neos:NodeTypes.Document:properties.title` being used. That made it hard to set a simple title as the translation was more eager. Resolves https://github.com/neos/neos-ui/issues/3509 The previously used creationDialog translation is now obsolete (resided in Neos.Neos) See also https://github.com/neos/neos-ui/issues/1539 --- Configuration/NodeTypes.yaml | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index f2a90db920..d144da6d90 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -3,17 +3,10 @@ 'CreationDialogPostprocessor': position: 'after NodeTypePresetPostprocessor' postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\CreationDialogNodeTypePostprocessor' - ui: - creationDialog: - elements: - title: - type: string - ui: - label: i18n - editor: 'Neos.Neos/Inspector/Editors/TextFieldEditor' - validation: - 'Neos.Neos/Validation/NotEmptyValidator': {} properties: + title: + ui: + showInCreationDialog: true uriPathSegment: ui: inspector: From 563d83078b931f5290b4f53dc9ec92916d9e26a5 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 2 Mar 2024 17:36:09 +0100 Subject: [PATCH 24/30] BUGFIX: Make sure that the title appears first in the creation dialog As this is a promoted property, it will otherwise appear "after" the explicit elements, which causes the test > SelectBox opens above in creation dialog if there's not enough space below to fail, and just makes sense correcting --- Configuration/NodeTypes.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Configuration/NodeTypes.yaml b/Configuration/NodeTypes.yaml index d144da6d90..d8d0c108ae 100644 --- a/Configuration/NodeTypes.yaml +++ b/Configuration/NodeTypes.yaml @@ -3,6 +3,11 @@ 'CreationDialogPostprocessor': position: 'after NodeTypePresetPostprocessor' postprocessor: 'Neos\Neos\Ui\Infrastructure\ContentRepository\CreationDialog\CreationDialogNodeTypePostprocessor' + ui: + creationDialog: + elements: + title: + position: 'start' properties: title: ui: From 860e9d161119bb7d95ba4fc2c95ba66672f6e00f Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 2 Mar 2024 19:29:25 +0100 Subject: [PATCH 25/30] TASK: Remove obsolete `title` property set operation in `UriPathSegmentNodeCreationHandler` Normally due to the `showInCreationDialog` property the `PromotedElementsCreationHandler` will handle this already. Also, the references editor with `createNew` will leverage this silently but with https://github.com/neos/neos-ui/issues/3730 this has to be refactored either way --- .../Neos/UriPathSegmentNodeCreationHandlerFactory.php | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php index 8d220cd0c3..31f60154f1 100644 --- a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php @@ -52,13 +52,6 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele ) { return $commands; } - $propertyValues = $commands->first->initialPropertyValues; - - if ($elements->has('title')) { - // technically we only need to set the uriPathSegment as the PromotedElementsCreationHandler - // will take care of setting the title already - $propertyValues = $propertyValues->withValue('title', $elements->get('title')); - } // if specified, the uriPathSegment equals the title $uriPathSegment = $elements->get('title'); @@ -81,7 +74,7 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele $uriPathSegment = uniqid('', true); } $uriPathSegment = Transliterator::urlize($uriPathSegment); - $propertyValues = $propertyValues->withValue('uriPathSegment', $uriPathSegment); + $propertyValues = $commands->first->initialPropertyValues->withValue('uriPathSegment', $uriPathSegment); return $commands->withInitialPropertyValues($propertyValues); } From eec2840a8519f36bca5a578618cc164d0adcdff5 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 2 Mar 2024 19:47:33 +0100 Subject: [PATCH 26/30] BUGFIX: Avoid toooo random uri-paths segments like `65d0ba5d8f4593-93420885` As with https://github.com/neos/neos-ui/pull/3515 the `nodeName` will be null, so we dont need to use it for generating the uripath --- .../UriPathSegmentNodeCreationHandlerFactory.php | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php index 31f60154f1..81b6ec99a5 100644 --- a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php @@ -56,11 +56,6 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele // if specified, the uriPathSegment equals the title $uriPathSegment = $elements->get('title'); - // otherwise, we fall back to the node name - if ($uriPathSegment === null && $commands->first->nodeName !== null) { - $uriPathSegment = $commands->first->nodeName->value; - } - // if not empty, we transliterate the uriPathSegment according to the language of the new node if ($uriPathSegment !== null && $uriPathSegment !== '') { $uriPathSegment = $this->transliterateText( @@ -68,10 +63,9 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele $uriPathSegment ); } else { - // todo in case the title is missing dont set it to something random like 65d0ba5d8f4593-93420885 - // but use the node label name instead like in 8.3. The problem is with two nodes on the same level. - // alternatively we set it to a random string - $uriPathSegment = uniqid('', true); + // alternatively we set it to a random string like `document-blog-022` + $nodeTypeSuffix = explode(':', $commands->first->nodeTypeName->value)[1] ?? ''; + $uriPathSegment = sprintf('%s-%03d', $nodeTypeSuffix, random_int(0, 999)); } $uriPathSegment = Transliterator::urlize($uriPathSegment); $propertyValues = $commands->first->initialPropertyValues->withValue('uriPathSegment', $uriPathSegment); From 9b7c3b11ef5d25cae46a6a2df59e0cde57f2f9ed Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Tue, 12 Mar 2024 18:48:07 +0100 Subject: [PATCH 27/30] TASK: Dont leak `ContentRepositoryServiceFactoryDependencies` into the Neos.Ui after a discussion with Wilhelm we concluded that this object should rather stay internal and not be part of the api of the Ui --- Classes/Domain/Model/Changes/AbstractCreate.php | 7 ++++--- .../NodeCreationHandlerFactoryInterface.php | 14 ++++++++++++++ .../NodeCreation/NodeCreationHandlerInterface.php | 4 ++-- .../PromotedElementsCreationHandlerFactory.php | 11 +++++------ .../UriPathSegmentNodeCreationHandlerFactory.php | 11 +++++------ 5 files changed, 30 insertions(+), 17 deletions(-) create mode 100644 Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php diff --git a/Classes/Domain/Model/Changes/AbstractCreate.php b/Classes/Domain/Model/Changes/AbstractCreate.php index 34013b29bc..6bf6a6bcce 100644 --- a/Classes/Domain/Model/Changes/AbstractCreate.php +++ b/Classes/Domain/Model/Changes/AbstractCreate.php @@ -23,6 +23,7 @@ use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\Flow\Annotations as Flow; use Neos\Flow\ObjectManagement\ObjectManagerInterface; +use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationHandlerFactoryInterface; use Neos\Neos\Ui\Domain\Service\NodePropertyConversionService; use Neos\Neos\Ui\Exception\InvalidNodeCreationHandlerException; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands; @@ -202,16 +203,16 @@ protected function applyNodeCreationHandlers( } $nodeCreationHandlerFactory = $this->objectManager->get($nodeCreationHandlerConfiguration['factoryClassName']); - if (!$nodeCreationHandlerFactory instanceof ContentRepositoryServiceFactoryInterface) { + if (!$nodeCreationHandlerFactory instanceof NodeCreationHandlerFactoryInterface) { throw new InvalidNodeCreationHandlerException(sprintf( 'Node creation handler "%s" didnt specify factory class of type %s. Got "%s"', $key, - ContentRepositoryServiceFactoryInterface::class, + NodeCreationHandlerFactoryInterface::class, get_class($nodeCreationHandlerFactory) ), 1697750193); } - $nodeCreationHandler = $this->contentRepositoryRegistry->buildService($contentRepository->id, $nodeCreationHandlerFactory); + $nodeCreationHandler = $nodeCreationHandlerFactory->build($contentRepository); if (!$nodeCreationHandler instanceof NodeCreationHandlerInterface) { throw new InvalidNodeCreationHandlerException(sprintf( 'Node creation handler "%s" didnt specify factory class of type %s. Got "%s"', diff --git a/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php b/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php new file mode 100644 index 0000000000..48c2ce247a --- /dev/null +++ b/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php @@ -0,0 +1,14 @@ + */ -final class PromotedElementsCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +final class PromotedElementsCreationHandlerFactory implements NodeCreationHandlerFactoryInterface { - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface + public function build(ContentRepository $contentRepository): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->nodeTypeManager) implements NodeCreationHandlerInterface { + return new class($contentRepository->getNodeTypeManager()) implements NodeCreationHandlerInterface { public function __construct( private readonly NodeTypeManager $nodeTypeManager ) { diff --git a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php index 81b6ec99a5..c646b1c48c 100644 --- a/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php +++ b/Classes/Infrastructure/Neos/UriPathSegmentNodeCreationHandlerFactory.php @@ -5,10 +5,9 @@ namespace Neos\Neos\Ui\Infrastructure\Neos; use Behat\Transliterator\Transliterator; +use Neos\ContentRepository\Core\ContentRepository; use Neos\ContentRepository\Core\Dimension\ContentDimensionId; use Neos\ContentRepository\Core\DimensionSpace\DimensionSpacePoint; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryDependencies; -use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceFactoryInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\Flow\Annotations as Flow; use Neos\Flow\I18n\Exception\InvalidLocaleIdentifierException; @@ -16,6 +15,7 @@ use Neos\Neos\Service\TransliterationService; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationCommands; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationElements; +use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationHandlerFactoryInterface; use Neos\Neos\Ui\Domain\NodeCreation\NodeCreationHandlerInterface; /** @@ -26,18 +26,17 @@ * - (actually obsolete with PromotedElementsCreationHandler) * * @internal you should not to interact with this factory directly. The node creation handle will already be configured under `nodeCreationHandlers` - * @implements ContentRepositoryServiceFactoryInterface */ -final class UriPathSegmentNodeCreationHandlerFactory implements ContentRepositoryServiceFactoryInterface +final class UriPathSegmentNodeCreationHandlerFactory implements NodeCreationHandlerFactoryInterface { /** * @Flow\Inject */ protected TransliterationService $transliterationService; - public function build(ContentRepositoryServiceFactoryDependencies $serviceFactoryDependencies): NodeCreationHandlerInterface + public function build(ContentRepository $contentRepository): NodeCreationHandlerInterface { - return new class($serviceFactoryDependencies->nodeTypeManager, $this->transliterationService) implements NodeCreationHandlerInterface { + return new class($contentRepository->getNodeTypeManager(), $this->transliterationService) implements NodeCreationHandlerInterface { public function __construct( private readonly NodeTypeManager $nodeTypeManager, private readonly TransliterationService $transliterationService From 4e12c876f850f8571e6fa0afdf229da6f7cc845e Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:41:09 +0100 Subject: [PATCH 28/30] TASK: Make nodecreation handler internal the public api is rather the yaml configuration of FlowPack.NodeTemplates --- Classes/Domain/NodeCreation/NodeCreationCommands.php | 3 +-- Classes/Domain/NodeCreation/NodeCreationElements.php | 2 +- .../NodeCreation/NodeCreationHandlerFactoryInterface.php | 4 ++-- Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php | 5 +++-- Classes/Service/PublishingService.php | 1 - 5 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Classes/Domain/NodeCreation/NodeCreationCommands.php b/Classes/Domain/NodeCreation/NodeCreationCommands.php index f5a7b38e1b..e7ca4c2010 100644 --- a/Classes/Domain/NodeCreation/NodeCreationCommands.php +++ b/Classes/Domain/NodeCreation/NodeCreationCommands.php @@ -45,8 +45,7 @@ * $parentNode = $subgraph->findNodeById($commands->first->parentNodeAggregateId); * * @implements \IteratorAggregate - * @api As part of the {@see NodeCreationHandlerInterface} - * Note: The constructors are not part of the public API + * @internal Especially the constructors */ final readonly class NodeCreationCommands implements \IteratorAggregate { diff --git a/Classes/Domain/NodeCreation/NodeCreationElements.php b/Classes/Domain/NodeCreation/NodeCreationElements.php index 514726e8e4..8148c2112f 100644 --- a/Classes/Domain/NodeCreation/NodeCreationElements.php +++ b/Classes/Domain/NodeCreation/NodeCreationElements.php @@ -48,7 +48,7 @@ * showInCreationDialog: true * * @implements \IteratorAggregate - * @api As part of the {@see NodeCreationHandlerInterface} except the constructor and serialized data + * @internal Especially the constructor and the serialized data */ final readonly class NodeCreationElements implements \IteratorAggregate { diff --git a/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php b/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php index 48c2ce247a..a20d84446b 100644 --- a/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php +++ b/Classes/Domain/NodeCreation/NodeCreationHandlerFactoryInterface.php @@ -5,8 +5,8 @@ use Neos\ContentRepository\Core\ContentRepository; /** - * @see NodeCreationHandlerInterface - * @api + * @see NodeCreationHandlerInterface how to configure a handler + * @internal */ interface NodeCreationHandlerFactoryInterface { diff --git a/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php b/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php index ced7f4511c..5b97a2b0f0 100644 --- a/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php +++ b/Classes/Domain/NodeCreation/NodeCreationHandlerInterface.php @@ -33,9 +33,10 @@ * The factory must implement the {@see NodeCreationHandlerFactoryInterface} and * return an implementation with this {@see NodeCreationHandlerInterface} interface. * - * The current content-repository or NodeType-manager will be accessible via the factory dependencies. + * The current content-repository or NodeType-manager will be accessible via the + * content repository in {@see NodeCreationHandlerFactoryInterface::build()}. * - * @api + * @internal */ interface NodeCreationHandlerInterface { diff --git a/Classes/Service/PublishingService.php b/Classes/Service/PublishingService.php index d6822643a3..d102fe8370 100644 --- a/Classes/Service/PublishingService.php +++ b/Classes/Service/PublishingService.php @@ -23,7 +23,6 @@ /** * A generic ContentRepository Publishing Service * - * @api * @Flow\Scope("singleton") * @internal */ From d4c0bf502c0b7ccbe3f4b4671a155576738611fb Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Wed, 13 Mar 2024 09:46:12 +0100 Subject: [PATCH 29/30] TASK: Simplify `getIterator` --- Classes/Domain/NodeCreation/NodeCreationCommands.php | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Classes/Domain/NodeCreation/NodeCreationCommands.php b/Classes/Domain/NodeCreation/NodeCreationCommands.php index e7ca4c2010..aa761dac28 100644 --- a/Classes/Domain/NodeCreation/NodeCreationCommands.php +++ b/Classes/Domain/NodeCreation/NodeCreationCommands.php @@ -119,11 +119,6 @@ public function withAdditionalCommands( */ public function getIterator(): \Traversable { - yield $this->first; - foreach ($this->additionalCommands as $command) { - // if yield from is used, the default setting of iterator_to_array, "preserve_keys: true" - // would cause the first command to be overridden. - yield $command; - } + yield from [$this->first, ...$this->additionalCommands]; } } From 132dbd64e7ea6998f63061821e69cd2dd624b557 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Thu, 14 Mar 2024 21:05:53 +0100 Subject: [PATCH 30/30] TASK: Adjust to workspace aware commands --- Classes/Domain/NodeCreation/NodeCreationCommands.php | 4 ++-- .../CreationDialog/PromotedElementsCreationHandlerFactory.php | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Classes/Domain/NodeCreation/NodeCreationCommands.php b/Classes/Domain/NodeCreation/NodeCreationCommands.php index aa761dac28..b5d967bed2 100644 --- a/Classes/Domain/NodeCreation/NodeCreationCommands.php +++ b/Classes/Domain/NodeCreation/NodeCreationCommands.php @@ -59,7 +59,7 @@ * Add a list of commands that are executed after the initial created command was run. * This allows to create child-nodes and append other allowed commands. * - * @var array + * @var array */ public array $additionalCommands; @@ -68,7 +68,7 @@ private function __construct( CreateNodeAggregateWithNode|SetNodeProperties|DisableNodeAggregate|EnableNodeAggregate|SetNodeReferences|CopyNodesRecursively ...$additionalCommands ) { $this->first = $first; - $this->additionalCommands = $additionalCommands; + $this->additionalCommands = array_values($additionalCommands); } /** diff --git a/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php b/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php index 8f63f272ca..64993e8359 100644 --- a/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php +++ b/Classes/Infrastructure/ContentRepository/CreationDialog/PromotedElementsCreationHandlerFactory.php @@ -61,7 +61,7 @@ public function handle(NodeCreationCommands $commands, NodeCreationElements $ele ) { // a promoted element $setReferencesCommands[] = SetNodeReferences::create( - $commands->first->contentStreamId, + $commands->first->workspaceName, $commands->first->nodeAggregateId, $commands->first->originDimensionSpacePoint, ReferenceName::fromString($elementName),