From ed2bb0f360b5bce1c163a5e3a8ca3e6f0c3a39b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 15 Sep 2023 20:06:39 +0200 Subject: [PATCH 01/13] !!!TASK: Remove NodeTypeManager from NodeType Disentangle NodeTypeManager from NodeType. This is breaking because it removes API methods. Resolves: #4515 --- .../Feature/Common/ConstraintChecks.php | 12 +- .../Feature/NodeAggregateCommandHandler.php | 7 +- .../Feature/NodeCreation/NodeCreation.php | 8 +- .../Feature/NodeTypeChange/NodeTypeChange.php | 7 +- .../Classes/NodeType/ConstraintCheck.php | 137 +++++++++++ .../Exception/ChildNodeNotConfigured.php | 9 + .../Classes/NodeType/NodeType.php | 220 ++---------------- .../Classes/NodeType/NodeTypeManager.php | 80 +++++++ .../Unit/NodeType/NodeTypeManagerTest.php | 18 ++ .../Tests/Unit/NodeType/NodeTypeTest.php | 17 -- .../DisallowedChildNodeAdjustment.php | 5 +- .../Adjustment/TetheredNodeAdjustments.php | 2 +- .../Classes/Service/NodeTypeSchemaBuilder.php | 5 +- 13 files changed, 287 insertions(+), 240 deletions(-) create mode 100644 Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php create mode 100644 Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index cc837381021..6e9b145a44d 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -163,7 +163,7 @@ protected function requireRootNodeTypeToBeUnoccupied( */ protected function requireTetheredDescendantNodeTypesToExist(NodeType $nodeType): void { - foreach ($nodeType->getAutoCreatedChildNodes() as $childNodeType) { + foreach ($this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType) as $childNodeType) { $this->requireTetheredDescendantNodeTypesToExist($childNodeType); } } @@ -174,7 +174,7 @@ protected function requireTetheredDescendantNodeTypesToExist(NodeType $nodeType) */ protected function requireTetheredDescendantNodeTypesToNotBeOfTypeRoot(NodeType $nodeType): void { - foreach ($nodeType->getAutoCreatedChildNodes() as $tetheredChildNodeType) { + foreach ($this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType) as $tetheredChildNodeType) { if ($tetheredChildNodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME)) { throw new NodeTypeIsOfTypeRoot( 'Node type "' . $nodeType->name->value . '" for tethered descendant is of type root.', @@ -300,11 +300,11 @@ protected function requireNodeTypeConstraintsImposedByParentToBeMet( if ( $nodeName && $parentsNodeType->hasAutoCreatedChildNode($nodeName) - && !$parentsNodeType->getTypeOfAutoCreatedChildNode($nodeName)?->name->equals($nodeType->name) + && !$this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) ) { throw new NodeConstraintException( 'Node type "' . $nodeType->name->value . '" does not match configured "' - . $parentsNodeType->getTypeOfAutoCreatedChildNode($nodeName)?->name->value + . $this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->value . '" for auto created child nodes for parent type "' . $parentsNodeType->name->value . '" with name "' . $nodeName->value . '"' ); @@ -323,7 +323,7 @@ protected function areNodeTypeConstraintsImposedByParentValid( if ( $nodeName && $parentsNodeType->hasAutoCreatedChildNode($nodeName) - && !$parentsNodeType->getTypeOfAutoCreatedChildNode($nodeName)?->name->equals($nodeType->name) + && !$this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) ) { return false; } @@ -361,7 +361,7 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( if ( $parentNodeName && $grandParentsNodeType->hasAutoCreatedChildNode($parentNodeName) - && !$grandParentsNodeType->allowsGrandchildNodeType($parentNodeName->value, $nodeType) + && !$this->getNodeTypeManager()->allowsGrandchildNodeType($grandParentsNodeType,$parentNodeName, $nodeType) ) { return false; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index 2896af0cd29..f3d6719087c 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -212,7 +212,7 @@ protected function checkConstraintsImposedByAncestors( if ( $nodeAggregate->nodeName && $parentsNodeType->hasAutoCreatedChildNode($nodeAggregate->nodeName) - && $parentsNodeType->getTypeOfAutoCreatedChildNode($nodeAggregate->nodeName)?->name + && $this->nodeTypeManager->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeAggregate->nodeName)->name !== $command->newNodeTypeName->value ) { throw new NodeConstraintException( @@ -233,8 +233,9 @@ protected function checkConstraintsImposedByAncestors( if ( $parentAggregate->nodeName && $grandParentsNodeType->hasAutoCreatedChildNode($parentAggregate->nodeName) - && !$grandParentsNodeType->allowsGrandchildNodeType( - $parentAggregate->nodeName->value, + && !$this->nodeTypeManager->allowsGrandchildNodeType( + $grandParentsNodeType, + $parentAggregate->nodeName, $newNodeType ) ) { diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 46ef4e6a46b..ce1aef18311 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -31,6 +31,7 @@ use Neos\ContentRepository\Core\Infrastructure\Property\PropertyConverter; use Neos\ContentRepository\Core\Infrastructure\Property\PropertyType; use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; use Neos\ContentRepository\Core\SharedModel\Exception\ContentStreamDoesNotExistYet; @@ -61,6 +62,8 @@ abstract protected function requireNodeTypeToBeOfTypeRoot(NodeType $nodeType): v abstract protected function getPropertyConverter(): PropertyConverter; + abstract protected function getNodeTypeManager(): NodeTypeManager; + private function handleCreateNodeAggregateWithNode( CreateNodeAggregateWithNode $command, ContentRepository $contentRepository @@ -282,7 +285,7 @@ private function handleTetheredChildNodes( ContentRepository $contentRepository, ): Events { $events = []; - foreach ($nodeType->getAutoCreatedChildNodes() as $rawNodeName => $childNodeType) { + foreach ($this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType) as $rawNodeName => $childNodeType) { assert($childNodeType instanceof NodeType); $nodeName = NodeName::fromString($rawNodeName); $childNodePath = $nodePath @@ -343,6 +346,7 @@ private function createTetheredWithNode( protected static function populateNodeAggregateIds( NodeType $nodeType, + NodeTypeManager $nodeTypeManager, ?NodeAggregateIdsByNodePaths $nodeAggregateIds, NodePath $childPath = null ): NodeAggregateIdsByNodePaths { @@ -350,7 +354,7 @@ protected static function populateNodeAggregateIds( $nodeAggregateIds = NodeAggregateIdsByNodePaths::createEmpty(); } // TODO: handle Multiple levels of autocreated child nodes - foreach ($nodeType->getAutoCreatedChildNodes() as $rawChildName => $childNodeType) { + foreach ($nodeTypeManager->getAutoCreatedChildNodesFor($nodeType) as $rawChildName => $childNodeType) { $childName = NodeName::fromString($rawChildName); $childPath = $childPath ? $childPath->appendPathSegment($childName) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index f174092536a..3ac83babee9 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -26,6 +26,7 @@ use Neos\ContentRepository\Core\Feature\NodeTypeChange\Command\ChangeNodeAggregateType; use Neos\ContentRepository\Core\Feature\NodeTypeChange\Event\NodeAggregateTypeWasChanged; use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\Projection\ContentGraph\Node; use Neos\ContentRepository\Core\Projection\ContentGraph\NodeAggregate; use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; @@ -49,6 +50,8 @@ */ trait NodeTypeChange { + abstract protected function getNodeTypeManager(): NodeTypeManager; + abstract protected function requireProjectedNodeAggregate( ContentStreamId $contentStreamId, NodeAggregateId $nodeAggregateId, @@ -190,7 +193,7 @@ private function handleChangeNodeAggregateType( } // new tethered child nodes - $expectedTetheredNodes = $newNodeType->getAutoCreatedChildNodes(); + $expectedTetheredNodes = $this->getNodeTypeManager()->getAutoCreatedChildNodesFor($newNodeType); foreach ($nodeAggregate->getNodes() as $node) { assert($node instanceof Node); foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { @@ -371,7 +374,7 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( NodeType $newNodeType, ContentRepository $contentRepository ): Events { - $expectedTetheredNodes = $newNodeType->getAutoCreatedChildNodes(); + $expectedTetheredNodes = $this->getNodeTypeManager()->getAutoCreatedChildNodesFor($newNodeType); $events = []; // find disallowed tethered nodes diff --git a/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php b/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php new file mode 100644 index 00000000000..f8e63f018b3 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php @@ -0,0 +1,137 @@ + $constraints + */ + public function __construct( + private readonly array $constraints + ) + { + } + + public function isNodeTypeAllowed(NodeType $nodeType): bool + { + $directConstraintsResult = $this->isNodeTypeAllowedByDirectConstraints($nodeType); + if ($directConstraintsResult !== null) { + return $directConstraintsResult; + } + + $inheritanceConstraintsResult = $this->isNodeTypeAllowedByInheritanceConstraints($nodeType); + if ($inheritanceConstraintsResult !== null) { + return $inheritanceConstraintsResult; + } + + if (isset($this->constraints['*'])) { + return (bool)$this->constraints['*']; + } + + return false; + } + + /** + * @return boolean|null true if the passed $nodeType is allowed by the $constraints, null if couldn't be decided + */ + protected function isNodeTypeAllowedByDirectConstraints(NodeType $nodeType): ?bool + { + if ($this->constraints === []) { + return true; + } + + if ( + array_key_exists($nodeType->name->value, $this->constraints) + && $this->constraints[$nodeType->name->value] === true + ) { + return true; + } + + if ( + array_key_exists($nodeType->name->value, $this->constraints) + && $this->constraints[$nodeType->name->value] === false + ) { + return false; + } + + return null; + } + + /** + * This method loops over the constraints and finds node types that the given node type inherits from. For all + * matched super types, their super types are traversed to find the closest super node with a constraint which + * is used to evaluated if the node type is allowed. It finds the closest results for true and false, and uses + * the distance to choose which one wins (lowest). If no result is found the node type is allowed. + * + * @return ?boolean (null if no constraint matched) + */ + protected function isNodeTypeAllowedByInheritanceConstraints(NodeType $nodeType): ?bool + { + $constraintDistanceForTrue = null; + $constraintDistanceForFalse = null; + foreach ($this->constraints as $superType => $constraint) { + if ($nodeType->isOfType($superType)) { + $distance = $this->traverseSuperTypes($nodeType, $superType, 0); + + if ( + $constraint === true + && ($constraintDistanceForTrue === null || $constraintDistanceForTrue > $distance) + ) { + $constraintDistanceForTrue = $distance; + } + if ( + $constraint === false + && ($constraintDistanceForFalse === null || $constraintDistanceForFalse > $distance) + ) { + $constraintDistanceForFalse = $distance; + } + } + } + + if ($constraintDistanceForTrue !== null && $constraintDistanceForFalse !== null) { + return $constraintDistanceForTrue < $constraintDistanceForFalse; + } + + if ($constraintDistanceForFalse !== null) { + return false; + } + + if ($constraintDistanceForTrue !== null) { + return true; + } + + return null; + } + + /** + * This method traverses the given node type to find the first super type that matches the constraint node type. + * In case the hierarchy has more than one way of finding a path to the node type it's not taken into account, + * since the first matched is returned. This is accepted on purpose for performance reasons and due to the fact + * that such hierarchies should be avoided. + * + * Returns null if no NodeType matched + */ + protected function traverseSuperTypes( + NodeType $currentNodeType, + string $constraintNodeTypeName, + int $distance + ): ?int { + if ($currentNodeType->name->value === $constraintNodeTypeName) { + return $distance; + } + + $distance++; + foreach ($currentNodeType->getDeclaredSuperTypes() as $superType) { + $result = $this->traverseSuperTypes($superType, $constraintNodeTypeName, $distance); + if ($result !== null) { + return $result; + } + } + + return null; + } +} diff --git a/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php b/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php new file mode 100644 index 00000000000..b540d64b985 --- /dev/null +++ b/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php @@ -0,0 +1,9 @@ +initialize(); + return $this->fullConfiguration; } @@ -351,6 +353,7 @@ public function hasConfiguration(string $configurationPath): bool public function getConfiguration(string $configurationPath): mixed { $this->initialize(); + return ObjectAccess::getPropertyPath($this->fullConfiguration, $configurationPath); } @@ -424,6 +427,7 @@ public function getPropertyType(string $propertyName): string ) { return 'string'; } + return $this->fullConfiguration['properties'][$propertyName]['type']; } @@ -452,50 +456,28 @@ public function getDefaultValuesForProperties(): array return $defaultValues; } - /** - * Return an array with child nodes which should be automatically created - * - * @return array the key of this array is the name of the child, and the value its NodeType. - * @api - */ - public function getAutoCreatedChildNodes(): array - { - $this->initialize(); - if (!isset($this->fullConfiguration['childNodes'])) { - return []; - } - - $autoCreatedChildNodes = []; - foreach ($this->fullConfiguration['childNodes'] as $childNodeName => $childNodeConfiguration) { - if (isset($childNodeConfiguration['type'])) { - $autoCreatedChildNodes[NodeName::transliterateFromString($childNodeName)->value] - = $this->nodeTypeManager->getNodeType($childNodeConfiguration['type']); - } - } - - return $autoCreatedChildNodes; - } - /** * @return bool true if $nodeName is an autocreated child node, false otherwise */ public function hasAutoCreatedChildNode(NodeName $nodeName): bool { $this->initialize(); + return isset($this->fullConfiguration['childNodes'][$nodeName->value]); } /** - * @throws NodeTypeNotFoundException + * @param NodeName $nodeName + * @return string */ - public function getTypeOfAutoCreatedChildNode(NodeName $nodeName): ?NodeType + public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): string { - return isset($this->fullConfiguration['childNodes'][$nodeName->value]['type']) - ? $this->nodeTypeManager->getNodeType($this->fullConfiguration['childNodes'][$nodeName->value]['type']) - : null; + if (!isset($this->fullConfiguration['childNodes'][$nodeName->value]['type'])) { + throw new ChildNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); + } + return $this->fullConfiguration['childNodes'][$nodeName->value]['type']; } - /** * Checks if the given NodeType is acceptable as sub-node with the configured constraints, * not taking constraints of auto-created nodes into account. Thus, this method only returns @@ -508,179 +490,7 @@ public function getTypeOfAutoCreatedChildNode(NodeName $nodeName): ?NodeType public function allowsChildNodeType(NodeType $nodeType): bool { $constraints = $this->getConfiguration('constraints.nodeTypes') ?: []; - - return $this->isNodeTypeAllowedByConstraints($nodeType, $constraints); - } - - /** - * Checks if the given $nodeType is allowed as a childNode of the given $childNodeName - * (which must be auto-created in $this NodeType). - * - * Only allowed to be called if $childNodeName is auto-created. - * - * @param string $childNodeName The name of a configured childNode of this NodeType - * @param NodeType $nodeType The NodeType to check constraints for. - * @return bool true if the $nodeType is allowed as grandchild node, false otherwise. - * @throws \InvalidArgumentException If the given $childNodeName is not configured to be auto-created in $this. - */ - public function allowsGrandchildNodeType(string $childNodeName, NodeType $nodeType): bool - { - $autoCreatedChildNodes = $this->getAutoCreatedChildNodes(); - if (!isset($autoCreatedChildNodes[$childNodeName])) { - throw new \InvalidArgumentException( - 'The method "allowsGrandchildNodeType" can only be used on auto-created childNodes, ' - . 'given $childNodeName "' . $childNodeName . '" is not auto-created.', - 1403858395 - ); - } - $constraints = $autoCreatedChildNodes[$childNodeName]->getConfiguration('constraints.nodeTypes') ?: []; - - $childNodeConfiguration = []; - foreach ($this->getConfiguration('childNodes') as $name => $configuration) { - $childNodeConfiguration[NodeName::transliterateFromString($name)->value] = $configuration; - } - $childNodeConstraintConfiguration = ObjectAccess::getPropertyPath( - $childNodeConfiguration, - $childNodeName . '.constraints.nodeTypes' - ) ?: []; - - $constraints = Arrays::arrayMergeRecursiveOverrule($constraints, $childNodeConstraintConfiguration); - - return $this->isNodeTypeAllowedByConstraints($nodeType, $constraints); - } - - /** - * Internal method to check whether the passed-in $nodeType is allowed by the $constraints array. - * - * $constraints is an associative array where the key is the Node Type Name. If the value is "true", - * the node type is explicitly allowed. If the value is "false", the node type is explicitly denied. - * If nothing is specified, the fallback "*" is used. If that one is also not specified, we DENY by - * default. - * - * Super types of the given node types are also checked, so if a super type is constrained - * it will also take affect on the inherited node types. The closest constrained super type match is used. - * - * @param array $constraints - */ - protected function isNodeTypeAllowedByConstraints(NodeType $nodeType, array $constraints): bool - { - $directConstraintsResult = $this->isNodeTypeAllowedByDirectConstraints($nodeType, $constraints); - if ($directConstraintsResult !== null) { - return $directConstraintsResult; - } - - $inheritanceConstraintsResult = $this->isNodeTypeAllowedByInheritanceConstraints($nodeType, $constraints); - if ($inheritanceConstraintsResult !== null) { - return $inheritanceConstraintsResult; - } - - if (isset($constraints['*'])) { - return (bool)$constraints['*']; - } - - return false; - } - - /** - * @param array $constraints - * @return boolean true if the passed $nodeType is allowed by the $constraints - */ - protected function isNodeTypeAllowedByDirectConstraints(NodeType $nodeType, array $constraints): ?bool - { - if ($constraints === []) { - return true; - } - - if ( - array_key_exists($nodeType->name->value, $constraints) - && $constraints[$nodeType->name->value] === true - ) { - return true; - } - - if ( - array_key_exists($nodeType->name->value, $constraints) - && $constraints[$nodeType->name->value] === false - ) { - return false; - } - - return null; - } - - /** - * This method loops over the constraints and finds node types that the given node type inherits from. For all - * matched super types, their super types are traversed to find the closest super node with a constraint which - * is used to evaluated if the node type is allowed. It finds the closest results for true and false, and uses - * the distance to choose which one wins (lowest). If no result is found the node type is allowed. - * - * @param array $constraints - * @return ?boolean (null if no constraint matched) - */ - protected function isNodeTypeAllowedByInheritanceConstraints(NodeType $nodeType, array $constraints): ?bool - { - $constraintDistanceForTrue = null; - $constraintDistanceForFalse = null; - foreach ($constraints as $superType => $constraint) { - if ($nodeType->isOfType($superType)) { - $distance = $this->traverseSuperTypes($nodeType, $superType, 0); - - if ( - $constraint === true - && ($constraintDistanceForTrue === null || $constraintDistanceForTrue > $distance) - ) { - $constraintDistanceForTrue = $distance; - } - if ( - $constraint === false - && ($constraintDistanceForFalse === null || $constraintDistanceForFalse > $distance) - ) { - $constraintDistanceForFalse = $distance; - } - } - } - - if ($constraintDistanceForTrue !== null && $constraintDistanceForFalse !== null) { - return $constraintDistanceForTrue < $constraintDistanceForFalse; - } - - if ($constraintDistanceForFalse !== null) { - return false; - } - - if ($constraintDistanceForTrue !== null) { - return true; - } - - return null; - } - - /** - * This method traverses the given node type to find the first super type that matches the constraint node type. - * In case the hierarchy has more than one way of finding a path to the node type it's not taken into account, - * since the first matched is returned. This is accepted on purpose for performance reasons and due to the fact - * that such hierarchies should be avoided. - * - * Returns null if no NodeType matched - */ - protected function traverseSuperTypes( - NodeType $currentNodeType, - string $constraintNodeTypeName, - int $distance - ): ?int { - if ($currentNodeType->getName() === $constraintNodeTypeName) { - return $distance; - } - - $distance++; - foreach ($currentNodeType->getDeclaredSuperTypes() as $superType) { - $result = $this->traverseSuperTypes($superType, $constraintNodeTypeName, $distance); - if ($result !== null) { - return $result; - } - } - - return null; + return (new ConstraintCheck($constraints))->isNodeTypeAllowed($nodeType); } /** diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php index a2f2be011a8..02d8012005b 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php @@ -14,9 +14,13 @@ namespace Neos\ContentRepository\Core\NodeType; +use Neos\ContentRepository\Core\NodeType\Exception\ChildNodeNotConfigured; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConfigurationException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsFinalException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; +use Neos\Utility\Arrays; +use Neos\Utility\Exception\PropertyNotAccessibleException; /** * Manager for node types @@ -198,6 +202,82 @@ public function overrideNodeTypes(array $completeNodeTypeConfiguration): void } } + /** + * @param NodeType $nodeType + * @param NodeName $childNodeName + * @return NodeType + */ + public function getTypeOfAutoCreatedChildNode(NodeType $nodeType, NodeName $childNodeName): NodeType + { + $childNodeTypeName = $nodeType->getNodeTypeNameOfAutoCreatedChildNode($childNodeName); + return $this->getNodeType($childNodeTypeName); + } + + /** + * Return an array with child nodes which should be automatically created + * + * @return array the key of this array is the name of the child, and the value its NodeType. + * @api + */ + public function getAutoCreatedChildNodesFor(NodeType $nodeType): array + { + $childNodeConfiguration = $nodeType->getConfiguration('childNodes'); + if (!isset($childNodeConfiguration)) { + return []; + } + + $autoCreatedChildNodes = []; + foreach ($childNodeConfiguration as $childNodeName => $configurationForChildNode) { + if (isset($configurationForChildNode['type'])) { + $autoCreatedChildNodes[NodeName::transliterateFromString($childNodeName)->value] + = $this->getNodeType($configurationForChildNode['type']); + } + } + + return $autoCreatedChildNodes; + } + + /** + * Checks if the given $nodeType is allowed as a childNode of the given $childNodeName + * (which must be auto-created in $this NodeType). + * + * Only allowed to be called if $childNodeName is auto-created. + * + * @param NodeType $parentNodeType The NodeType to check constraints based on. + * @param NodeName $childNodeName The name of a configured childNode of this NodeType + * @param NodeType $nodeType The NodeType to check constraints for. + * @return bool true if the $nodeType is allowed as grandchild node, false otherwise. + * @throws \InvalidArgumentException If the given $childNodeName is not configured to be auto-created in $this. + */ + public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $childNodeName, NodeType $nodeType): bool + { + try { + $childNodeType = $this->getTypeOfAutoCreatedChildNode($parentNodeType, $childNodeName); + } catch (ChildNodeNotConfigured $exception) { + throw new \InvalidArgumentException( + 'The method "allowsGrandchildNodeType" can only be used on auto-created childNodes, ' + . 'given $childNodeName "' . $childNodeName->value . '" is not auto-created.', + 1403858395, + $exception + ); + } + + // Constraints configured on the NodeType for the child node + $constraints = $childNodeType->getConfiguration('constraints.nodeTypes') ?: []; + + // Constraints configured at the childNode configuration of the parent. + try { + $childNodeConstraintConfiguration = $parentNodeType->getConfiguration('childNodes.' . $childNodeName->value . '.constraints.nodeTypes'); + } catch (PropertyNotAccessibleException $exception) { + // We ignore this because the configuration might just not have any constraints, if the childNode was not configured the exception above would have been thrown. + $childNodeConstraintConfiguration = []; + } + + $constraints = Arrays::arrayMergeRecursiveOverrule($constraints, $childNodeConstraintConfiguration); + + return (new ConstraintCheck($constraints))->isNodeTypeAllowed($nodeType); + } + /** * Load one node type, if it is not loaded yet. * diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php index 0dc9812c21c..878ab295ad5 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php @@ -12,6 +12,8 @@ */ use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; +use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConfigurationException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsFinalException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; @@ -122,6 +124,11 @@ protected function prepareNodeTypeManager(array $nodeTypesFixtureData, string $f ], 'Neos.ContentRepository.Testing:Page2' => [ 'superTypes' => ['Neos.ContentRepository.Testing:Document' => true], + 'childNodes' => [ + 'nodeName' => [ + 'type' => 'Neos.ContentRepository.Testing:Document' + ] + ] ], 'Neos.ContentRepository.Testing:Page3' => [ 'superTypes' => ['Neos.ContentRepository.Testing:Document' => true], @@ -369,4 +376,15 @@ public function getSubNodeTypesWithDifferentIncludeFlagValuesReturnsCorrectValue $subNodeTypes = $this->nodeTypeManager->getSubNodeTypes('Neos.ContentRepository.Testing:ContentObject', false); self::assertArrayNotHasKey('Neos.ContentRepository.Testing:AbstractType', $subNodeTypes); } + + /** + * @test + */ + public function getAutoCreatedChildNodesReturnsLowercaseNames() + { + $parentNodeType = $this->nodeTypeManager->getNodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Page2')); + $autoCreatedChildNodes = $this->nodeTypeManager->getAutoCreatedChildNodesFor($parentNodeType); + // This is configured as "nodeName" above, but should be normalized to "nodename" + self::assertArrayHasKey('nodename', $autoCreatedChildNodes); + } } diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php index bd38fc10066..47a97c76f36 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php @@ -405,21 +405,4 @@ protected function getNodeType(string $nodeTypeName): ?NodeType new DefaultNodeLabelGeneratorFactory() ); } - - /** - * @test - */ - public function getAutoCreatedChildNodesReturnsLowercasePaths() - { - $childNodeConfiguration = ['type' => 'Neos.ContentRepository:Base']; - $mockNodeTypeManager = $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(); - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [ - 'childNodes' => ['nodeName' => $childNodeConfiguration] - ], $mockNodeTypeManager, new DefaultNodeLabelGeneratorFactory()); - $mockNodeTypeManager->expects(self::any())->method('getNodeType')->will(self::returnValue($baseType)); - - $autoCreatedChildNodes = $mockNodeTypeManager->getNodeType('Neos.ContentRepository:Base')->getAutoCreatedChildNodes(); - - self::assertArrayHasKey('nodename', $autoCreatedChildNodes); - } } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index 0dfa463e7ee..4f2cb9651f2 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -86,8 +86,9 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat ) { $grandparentNodeType = $this->loadNodeType($grandparentNode->nodeTypeName); if ($grandparentNodeType !== null) { - $allowedByGrandparent = $grandparentNodeType->allowsGrandchildNodeType( - $parentNode->nodeName->value, + $allowedByGrandparent = $this->nodeTypeManager->allowsGrandchildNodeType( + $grandparentNodeType, + $parentNode->nodeName, $nodeType ); } diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index 7bff3925f22..ba38d0e86cc 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -53,7 +53,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat // In case we cannot find the expected tethered nodes, this fix cannot do anything. return; } - $expectedTetheredNodes = $nodeType->getAutoCreatedChildNodes(); + $expectedTetheredNodes = $this->nodeTypeManager->getAutoCreatedChildNodesFor($nodeType); foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { // find missing tethered nodes diff --git a/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php b/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php index f3a96315ec6..5ddabab8dd7 100644 --- a/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php +++ b/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php @@ -17,6 +17,7 @@ use Neos\ContentRepository\Core\Factory\ContentRepositoryServiceInterface; use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeType; +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; /** * Renders the Node Type Schema in a format the User Interface understands; @@ -104,9 +105,9 @@ protected function generateConstraints() } } - foreach ($nodeType->getAutoCreatedChildNodes() as $key => $_x) { + foreach ($this->nodeTypeManager->getAutoCreatedChildNodesFor($nodeType) as $key => $_x) { foreach ($nodeTypes as $innerNodeTypeName => $innerNodeType) { - if ($nodeType->allowsGrandchildNodeType($key, $innerNodeType)) { + if ($this->nodeTypeManager->allowsGrandchildNodeType($nodeType, NodeName::fromString($key), $innerNodeType)) { $constraints[$nodeTypeName]['childNodes'][$key]['nodeTypes'][$innerNodeTypeName] = true; } } From e9d2fac44d275d813c8cae5f24ce975941b552e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 15 Sep 2023 20:18:57 +0200 Subject: [PATCH 02/13] Remove constructor injection of NodeTypeManager in NodeType --- .../Classes/NodeType/NodeType.php | 1 - .../Classes/NodeType/NodeTypeManager.php | 3 +- .../Tests/Unit/NodeType/NodeTypeTest.php | 40 ++++++------------- 3 files changed, 14 insertions(+), 30 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index 2b790866eb2..a308f541201 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -91,7 +91,6 @@ public function __construct( NodeTypeName $name, array $declaredSuperTypes, array $configuration, - private readonly NodeTypeManager $nodeTypeManager, private readonly NodeLabelGeneratorFactoryInterface $nodeLabelGeneratorFactory ) { $this->name = $name; diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php index 02d8012005b..a6fc58dc4ce 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php @@ -267,7 +267,7 @@ public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $chi // Constraints configured at the childNode configuration of the parent. try { - $childNodeConstraintConfiguration = $parentNodeType->getConfiguration('childNodes.' . $childNodeName->value . '.constraints.nodeTypes'); + $childNodeConstraintConfiguration = $parentNodeType->getConfiguration('childNodes.' . $childNodeName->value . '.constraints.nodeTypes') ?? []; } catch (PropertyNotAccessibleException $exception) { // We ignore this because the configuration might just not have any constraints, if the childNode was not configured the exception above would have been thrown. $childNodeConstraintConfiguration = []; @@ -334,7 +334,6 @@ protected function loadNodeType(string $nodeTypeName, array &$completeNodeTypeCo NodeTypeName::fromString($nodeTypeName), $superTypes, $nodeTypeConfiguration, - $this, $this->nodeLabelGeneratorFactory ); diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php index 47a97c76f36..3554fd3bd6e 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php @@ -130,9 +130,7 @@ class NodeTypeTest extends TestCase */ public function aNodeTypeHasAName() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Text'), [], [], - $this->getMockBuilder(NodeTypeManager::class) - ->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Text'), [], [], new DefaultNodeLabelGeneratorFactory()); self::assertSame('Neos.ContentRepository.Testing:Text', $nodeType->name->value); } @@ -142,9 +140,7 @@ public function aNodeTypeHasAName() public function setDeclaredSuperTypesExpectsAnArrayOfNodeTypesAsKeys() { $this->expectException(\InvalidArgumentException::class); - new NodeType(NodeTypeName::fromString('ContentRepository:Folder'), ['foo' => true], [], - $this->getMockBuilder(NodeTypeManager::class) - ->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() + new NodeType(NodeTypeName::fromString('ContentRepository:Folder'), ['foo' => true], [], new DefaultNodeLabelGeneratorFactory() ); } @@ -154,8 +150,7 @@ public function setDeclaredSuperTypesExpectsAnArrayOfNodeTypesAsKeys() public function setDeclaredSuperTypesAcceptsAnArrayOfNodeTypes() { $this->expectException(\InvalidArgumentException::class); - new NodeType(NodeTypeName::fromString('ContentRepository:Folder'), ['foo'], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + new NodeType(NodeTypeName::fromString('ContentRepository:Folder'), ['foo'], [], new DefaultNodeLabelGeneratorFactory()); } /** @@ -163,13 +158,11 @@ public function setDeclaredSuperTypesAcceptsAnArrayOfNodeTypes() */ public function nodeTypesCanHaveAnyNumberOfSuperTypes() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); $timeableNodeType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:TimeableContent'), - [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() + [], [], new DefaultNodeLabelGeneratorFactory() ); $documentType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:Document'), @@ -177,13 +170,12 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() 'Neos.ContentRepository:Base' => $baseType, 'Neos.ContentRepository.Testing:TimeableContent' => $timeableNodeType, ], - [], $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() + [], new DefaultNodeLabelGeneratorFactory() ); $hideableNodeType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:HideableContent'), - [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() + [], [], new DefaultNodeLabelGeneratorFactory() ); $pageType = new NodeType( NodeTypeName::fromString('Neos.ContentRepository.Testing:Page'), @@ -193,7 +185,6 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() 'Neos.ContentRepository.Testing:TimeableContent' => null, ], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() ); @@ -219,8 +210,7 @@ public function nodeTypesCanHaveAnyNumberOfSuperTypes() */ public function labelIsEmptyStringByDefault() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); self::assertSame('', $baseType->getLabel()); } @@ -229,8 +219,7 @@ public function labelIsEmptyStringByDefault() */ public function propertiesAreEmptyArrayByDefault() { - $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $baseType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); self::assertSame([], $baseType->getProperties()); } @@ -243,7 +232,7 @@ public function hasConfigurationReturnsTrueIfSpecifiedConfigurationPathExists() 'someKey' => [ 'someSubKey' => 'someValue' ] - ], $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + ], new DefaultNodeLabelGeneratorFactory()); self::assertTrue($nodeType->hasConfiguration('someKey.someSubKey')); } @@ -252,8 +241,7 @@ public function hasConfigurationReturnsTrueIfSpecifiedConfigurationPathExists() */ public function hasConfigurationReturnsFalseIfSpecifiedConfigurationPathDoesNotExist() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); self::assertFalse($nodeType->hasConfiguration('some.nonExisting.path')); } @@ -266,7 +254,7 @@ public function getConfigurationReturnsTheConfigurationWithTheSpecifiedPath() 'someKey' => [ 'someSubKey' => 'someValue' ] - ], $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + ], new DefaultNodeLabelGeneratorFactory()); self::assertSame('someValue', $nodeType->getConfiguration('someKey.someSubKey')); } @@ -275,8 +263,7 @@ public function getConfigurationReturnsTheConfigurationWithTheSpecifiedPath() */ public function getConfigurationReturnsNullIfTheSpecifiedPathDoesNotExist() { - $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory()); + $nodeType = new NodeType(NodeTypeName::fromString('Neos.ContentRepository:Base'), [], [], new DefaultNodeLabelGeneratorFactory()); self::assertNull($nodeType->getConfiguration('some.nonExisting.path')); } @@ -401,7 +388,6 @@ protected function getNodeType(string $nodeTypeName): ?NodeType NodeTypeName::fromString($nodeTypeName), $declaredSuperTypes, $configuration, - $this->getMockBuilder(NodeTypeManager::class)->disableOriginalConstructor()->getMock(), new DefaultNodeLabelGeneratorFactory() ); } From 5acb733e73a79f917e9e2dc713ee31d554a81996 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 16 Sep 2023 09:45:46 +0200 Subject: [PATCH 03/13] TASK: Remove accidental `public` declaration of NodeType::$abstract regression from 266fc1a34bc3aa988f8e2ab040dda5f77164499c --- Neos.ContentRepository.Core/Classes/NodeType/NodeType.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index a308f541201..9881240d095 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -58,7 +58,7 @@ class NodeType /** * Is this node type marked abstract */ - public bool $abstract = false; + protected bool $abstract = false; /** * Is this node type marked final From 23d715ff8f6f231c64a2f3721b0dc9d4cbad80c2 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sat, 16 Sep 2023 09:46:19 +0200 Subject: [PATCH 04/13] TASK: Declare construction of NodeType `@internal` --- .../Classes/NodeType/NodeType.php | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index 9881240d095..1dfe766a840 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -28,11 +28,7 @@ * not need to deal with creating or managing node types manually. New node types * should be defined in a NodeTypes.yaml file. * - * TODO: REFACTOR TO immutable readonly; and value objects - * - * TODO: I'd love to make NodeType final; but this breaks quite some unit and functional tests. - * - * @api + * @api Note: The constructor is not part of the public API */ class NodeType { @@ -80,12 +76,12 @@ class NodeType protected bool $initialized = false; /** - * Constructs this node type - * * @param NodeTypeName $name Name of the node type * @param array $declaredSuperTypes Parent types of this node type * @param array $configuration the configuration for this node type which is defined in the schema * @throws \InvalidArgumentException + * + * @internal */ public function __construct( NodeTypeName $name, From 56868982d608f08ee464021c755e3fc67090f3c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sat, 16 Sep 2023 11:20:51 +0200 Subject: [PATCH 05/13] Adjust signature of `populateNodeAggregateIds` --- .../Classes/Feature/NodeCreation/NodeCreation.php | 1 + .../Classes/Feature/NodeTypeChange/NodeTypeChange.php | 2 ++ 2 files changed, 3 insertions(+) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index ce1aef18311..40a9fd6e443 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -206,6 +206,7 @@ private function handleCreateNodeAggregateWithNodeAndSerializedProperties( } $descendantNodeAggregateIds = self::populateNodeAggregateIds( $nodeType, + $this->getNodeTypeManager(), $command->tetheredDescendantNodeAggregateIds ); // Write the auto-created descendant node aggregate ids back to the command; diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 3ac83babee9..74f7d3cebe0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -92,6 +92,7 @@ abstract protected function areNodeTypeConstraintsImposedByGrandparentValid( abstract protected static function populateNodeAggregateIds( NodeType $nodeType, + NodeTypeManager $nodeTypeManager, NodeAggregateIdsByNodePaths $nodeAggregateIds, NodePath $childPath = null ): NodeAggregateIdsByNodePaths; @@ -161,6 +162,7 @@ private function handleChangeNodeAggregateType( **************/ $descendantNodeAggregateIds = static::populateNodeAggregateIds( $newNodeType, + $this->getNodeTypeManager(), $command->tetheredDescendantNodeAggregateIds ); // Write the auto-created descendant node aggregate ids back to the command; From 0a2a126ccd0e937054fa0e85517fa89a1667ab66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Sun, 17 Sep 2023 15:50:45 +0200 Subject: [PATCH 06/13] Fix linting errors --- .../Classes/Feature/Common/ConstraintChecks.php | 2 +- .../Classes/NodeType/ConstraintCheck.php | 4 ++-- .../Classes/NodeType/Exception/ChildNodeNotConfigured.php | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 6e9b145a44d..58c1e8f4dac 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -361,7 +361,7 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( if ( $parentNodeName && $grandParentsNodeType->hasAutoCreatedChildNode($parentNodeName) - && !$this->getNodeTypeManager()->allowsGrandchildNodeType($grandParentsNodeType,$parentNodeName, $nodeType) + && !$this->getNodeTypeManager()->allowsGrandchildNodeType($grandParentsNodeType, $parentNodeName, $nodeType) ) { return false; } diff --git a/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php b/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php index f8e63f018b3..91058e9b8b8 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/ConstraintCheck.php @@ -1,4 +1,5 @@ Date: Sat, 23 Sep 2023 18:47:35 +0200 Subject: [PATCH 07/13] Address PR comments --- .../Classes/NodeType/Exception/ChildNodeNotConfigured.php | 2 +- Neos.ContentRepository.Core/Classes/NodeType/NodeType.php | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php b/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php index 9cefcf7eb29..1bfa49cc3be 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php @@ -3,7 +3,7 @@ namespace Neos\ContentRepository\Core\NodeType\Exception; /** - * + * @api Might be encountered when childNode information is requested for a child node which was never configured. */ class ChildNodeNotConfigured extends \DomainException { diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index 1dfe766a840..c52694719de 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -463,14 +463,14 @@ public function hasAutoCreatedChildNode(NodeName $nodeName): bool /** * @param NodeName $nodeName - * @return string + * @return NodeTypeName */ - public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): string + public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): NodeTypeName { if (!isset($this->fullConfiguration['childNodes'][$nodeName->value]['type'])) { throw new ChildNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); } - return $this->fullConfiguration['childNodes'][$nodeName->value]['type']; + return NodeTypeName::fromString($this->fullConfiguration['childNodes'][$nodeName->value]['type']); } /** From 22b41cf5d804553c018137d0b77a461123402478 Mon Sep 17 00:00:00 2001 From: mhsdesign <85400359+mhsdesign@users.noreply.github.com> Date: Sun, 24 Sep 2023 21:05:36 +0200 Subject: [PATCH 08/13] BUGFIX: Fix 4344 for Neos 9.0 Resolves: #4344 --- .../Classes/NodeType/NodeType.php | 24 +++++++++++++------ .../Classes/NodeType/NodeTypeManager.php | 17 +++++++------ 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index c52694719de..5ada7eed239 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -457,20 +457,30 @@ public function getDefaultValuesForProperties(): array public function hasAutoCreatedChildNode(NodeName $nodeName): bool { $this->initialize(); - - return isset($this->fullConfiguration['childNodes'][$nodeName->value]); + foreach ($this->fullConfiguration['childNodes'] ?? [] as $rawChildNodeName => $configurationForChildNode) { + if (isset($configurationForChildNode['type'])) { + if (NodeName::transliterateFromString($rawChildNodeName)->equals($nodeName)) { + return true; + } + } + } + return false; } /** - * @param NodeName $nodeName - * @return NodeTypeName + * @throws ChildNodeNotConfigured if the requested childNode is not configured. Check via {@see NodeType::hasAutoCreatedChildNode()}. */ public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): NodeTypeName { - if (!isset($this->fullConfiguration['childNodes'][$nodeName->value]['type'])) { - throw new ChildNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); + $this->initialize(); + foreach ($this->fullConfiguration['childNodes'] ?? [] as $rawChildNodeName => $configurationForChildNode) { + if (isset($configurationForChildNode['type'])) { + if (NodeName::transliterateFromString($rawChildNodeName)->equals($nodeName)) { + return NodeTypeName::fromString($configurationForChildNode['type']); + } + } } - return NodeTypeName::fromString($this->fullConfiguration['childNodes'][$nodeName->value]['type']); + throw new ChildNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); } /** diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php index a6fc58dc4ce..26d9964b341 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php @@ -205,6 +205,7 @@ public function overrideNodeTypes(array $completeNodeTypeConfiguration): void /** * @param NodeType $nodeType * @param NodeName $childNodeName + * @throws ChildNodeNotConfigured if the requested childNode is not configured. Check via {@see NodeType::hasAutoCreatedChildNode()}. * @return NodeType */ public function getTypeOfAutoCreatedChildNode(NodeType $nodeType, NodeName $childNodeName): NodeType @@ -222,18 +223,13 @@ public function getTypeOfAutoCreatedChildNode(NodeType $nodeType, NodeName $chil public function getAutoCreatedChildNodesFor(NodeType $nodeType): array { $childNodeConfiguration = $nodeType->getConfiguration('childNodes'); - if (!isset($childNodeConfiguration)) { - return []; - } - $autoCreatedChildNodes = []; - foreach ($childNodeConfiguration as $childNodeName => $configurationForChildNode) { + foreach ($childNodeConfiguration ?? [] as $childNodeName => $configurationForChildNode) { if (isset($configurationForChildNode['type'])) { $autoCreatedChildNodes[NodeName::transliterateFromString($childNodeName)->value] = $this->getNodeType($configurationForChildNode['type']); } } - return $autoCreatedChildNodes; } @@ -247,7 +243,7 @@ public function getAutoCreatedChildNodesFor(NodeType $nodeType): array * @param NodeName $childNodeName The name of a configured childNode of this NodeType * @param NodeType $nodeType The NodeType to check constraints for. * @return bool true if the $nodeType is allowed as grandchild node, false otherwise. - * @throws \InvalidArgumentException If the given $childNodeName is not configured to be auto-created in $this. + * @throws \InvalidArgumentException if the requested childNode is not configured in the parent NodeType. Check via {@see NodeType::hasAutoCreatedChildNode()}. */ public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $childNodeName, NodeType $nodeType): bool { @@ -255,8 +251,11 @@ public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $chi $childNodeType = $this->getTypeOfAutoCreatedChildNode($parentNodeType, $childNodeName); } catch (ChildNodeNotConfigured $exception) { throw new \InvalidArgumentException( - 'The method "allowsGrandchildNodeType" can only be used on auto-created childNodes, ' - . 'given $childNodeName "' . $childNodeName->value . '" is not auto-created.', + sprintf( + 'Cannot determine if grandchild is allowed in %s. Because the given child node name "%s" is not auto-created.', + $parentNodeType->name->value, + $childNodeName->value + ), 1403858395, $exception ); From a2a04475c7acdff9ca184ff1abe4c1e8c40829da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 13 Oct 2023 17:29:12 +0200 Subject: [PATCH 09/13] Rename methods to use tethered instead of child node --- .../Feature/Common/ConstraintChecks.php | 22 +++++------ .../Feature/NodeAggregateCommandHandler.php | 8 ++-- .../Feature/NodeCreation/NodeCreation.php | 4 +- .../Feature/NodeTypeChange/NodeTypeChange.php | 4 +- ...ured.php => TetheredNodeNotConfigured.php} | 2 +- .../Classes/NodeType/NodeType.php | 12 +++--- .../Classes/NodeType/NodeTypeManager.php | 39 +++++++++---------- .../Unit/NodeType/NodeTypeManagerTest.php | 2 +- .../Classes/NodeDataToEventsProcessor.php | 2 +- .../DisallowedChildNodeAdjustment.php | 2 +- .../Adjustment/TetheredNodeAdjustments.php | 2 +- .../Classes/Service/NodeTypeSchemaBuilder.php | 4 +- 12 files changed, 51 insertions(+), 52 deletions(-) rename Neos.ContentRepository.Core/Classes/NodeType/Exception/{ChildNodeNotConfigured.php => TetheredNodeNotConfigured.php} (77%) diff --git a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php index 48962ac6ee4..7397fbf44f4 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php +++ b/Neos.ContentRepository.Core/Classes/Feature/Common/ConstraintChecks.php @@ -164,9 +164,9 @@ protected function requireRootNodeTypeToBeUnoccupied( protected function requireTetheredDescendantNodeTypesToExist(NodeType $nodeType): void { // this getter throws if any of the child nodeTypes doesnt exist! - $childNodeTypes = $this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType); - foreach ($childNodeTypes as $childNodeType) { - $this->requireTetheredDescendantNodeTypesToExist($childNodeType); + $tetheredNodeTypes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType); + foreach ($tetheredNodeTypes as $tetheredNodeType) { + $this->requireTetheredDescendantNodeTypesToExist($tetheredNodeType); } } @@ -176,7 +176,7 @@ protected function requireTetheredDescendantNodeTypesToExist(NodeType $nodeType) */ protected function requireTetheredDescendantNodeTypesToNotBeOfTypeRoot(NodeType $nodeType): void { - foreach ($this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType) as $tetheredChildNodeType) { + foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $tetheredChildNodeType) { if ($tetheredChildNodeType->isOfType(NodeTypeName::ROOT_NODE_TYPE_NAME)) { throw new NodeTypeIsOfTypeRoot( 'Node type "' . $nodeType->name->value . '" for tethered descendant is of type root.', @@ -301,12 +301,12 @@ protected function requireNodeTypeConstraintsImposedByParentToBeMet( } if ( $nodeName - && $parentsNodeType->hasAutoCreatedChildNode($nodeName) - && !$this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) + && $parentsNodeType->hasTetheredNode($nodeName) + && !$this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) ) { throw new NodeConstraintException( 'Node type "' . $nodeType->name->value . '" does not match configured "' - . $this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->value + . $this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->value . '" for auto created child nodes for parent type "' . $parentsNodeType->name->value . '" with name "' . $nodeName->value . '"' ); @@ -324,8 +324,8 @@ protected function areNodeTypeConstraintsImposedByParentValid( } if ( $nodeName - && $parentsNodeType->hasAutoCreatedChildNode($nodeName) - && !$this->getNodeTypeManager()->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) + && $parentsNodeType->hasTetheredNode($nodeName) + && !$this->getNodeTypeManager()->getTypeOfTetheredNode($parentsNodeType, $nodeName)->name->equals($nodeType->name) ) { return false; } @@ -362,8 +362,8 @@ protected function areNodeTypeConstraintsImposedByGrandparentValid( ): bool { if ( $parentNodeName - && $grandParentsNodeType->hasAutoCreatedChildNode($parentNodeName) - && !$this->getNodeTypeManager()->allowsGrandchildNodeType($grandParentsNodeType, $parentNodeName, $nodeType) + && $grandParentsNodeType->hasTetheredNode($parentNodeName) + && !$this->getNodeTypeManager()->isNodeTypeAllowedAsChildToTetheredNode($grandParentsNodeType, $parentNodeName, $nodeType) ) { return false; } diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php index f3d6719087c..fb8e7de7aa0 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeAggregateCommandHandler.php @@ -211,8 +211,8 @@ protected function checkConstraintsImposedByAncestors( } if ( $nodeAggregate->nodeName - && $parentsNodeType->hasAutoCreatedChildNode($nodeAggregate->nodeName) - && $this->nodeTypeManager->getTypeOfAutoCreatedChildNode($parentsNodeType, $nodeAggregate->nodeName)->name + && $parentsNodeType->hasTetheredNode($nodeAggregate->nodeName) + && $this->nodeTypeManager->getTypeOfTetheredNode($parentsNodeType, $nodeAggregate->nodeName)->name !== $command->newNodeTypeName->value ) { throw new NodeConstraintException( @@ -232,8 +232,8 @@ protected function checkConstraintsImposedByAncestors( ); if ( $parentAggregate->nodeName - && $grandParentsNodeType->hasAutoCreatedChildNode($parentAggregate->nodeName) - && !$this->nodeTypeManager->allowsGrandchildNodeType( + && $grandParentsNodeType->hasTetheredNode($parentAggregate->nodeName) + && !$this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( $grandParentsNodeType, $parentAggregate->nodeName, $newNodeType diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php index 40a9fd6e443..8a7b8d12554 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeCreation/NodeCreation.php @@ -286,7 +286,7 @@ private function handleTetheredChildNodes( ContentRepository $contentRepository, ): Events { $events = []; - foreach ($this->getNodeTypeManager()->getAutoCreatedChildNodesFor($nodeType) as $rawNodeName => $childNodeType) { + foreach ($this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($nodeType) as $rawNodeName => $childNodeType) { assert($childNodeType instanceof NodeType); $nodeName = NodeName::fromString($rawNodeName); $childNodePath = $nodePath @@ -355,7 +355,7 @@ protected static function populateNodeAggregateIds( $nodeAggregateIds = NodeAggregateIdsByNodePaths::createEmpty(); } // TODO: handle Multiple levels of autocreated child nodes - foreach ($nodeTypeManager->getAutoCreatedChildNodesFor($nodeType) as $rawChildName => $childNodeType) { + foreach ($nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType) as $rawChildName => $childNodeType) { $childName = NodeName::fromString($rawChildName); $childPath = $childPath ? $childPath->appendPathSegment($childName) diff --git a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php index 74f7d3cebe0..478bab25b41 100644 --- a/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php +++ b/Neos.ContentRepository.Core/Classes/Feature/NodeTypeChange/NodeTypeChange.php @@ -195,7 +195,7 @@ private function handleChangeNodeAggregateType( } // new tethered child nodes - $expectedTetheredNodes = $this->getNodeTypeManager()->getAutoCreatedChildNodesFor($newNodeType); + $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); foreach ($nodeAggregate->getNodes() as $node) { assert($node instanceof Node); foreach ($expectedTetheredNodes as $serializedTetheredNodeName => $expectedTetheredNodeType) { @@ -376,7 +376,7 @@ private function deleteObsoleteTetheredNodesWhenChangingNodeType( NodeType $newNodeType, ContentRepository $contentRepository ): Events { - $expectedTetheredNodes = $this->getNodeTypeManager()->getAutoCreatedChildNodesFor($newNodeType); + $expectedTetheredNodes = $this->getNodeTypeManager()->getTetheredNodesConfigurationForNodeType($newNodeType); $events = []; // find disallowed tethered nodes diff --git a/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php b/Neos.ContentRepository.Core/Classes/NodeType/Exception/TetheredNodeNotConfigured.php similarity index 77% rename from Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php rename to Neos.ContentRepository.Core/Classes/NodeType/Exception/TetheredNodeNotConfigured.php index 1bfa49cc3be..00f6c4fd3e1 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/Exception/ChildNodeNotConfigured.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/Exception/TetheredNodeNotConfigured.php @@ -5,6 +5,6 @@ /** * @api Might be encountered when childNode information is requested for a child node which was never configured. */ -class ChildNodeNotConfigured extends \DomainException +class TetheredNodeNotConfigured extends \DomainException { } diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php index 8499aa7c6ef..1eaae89553c 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeType.php @@ -14,7 +14,7 @@ * source code. */ -use Neos\ContentRepository\Core\NodeType\Exception\ChildNodeNotConfigured; +use Neos\ContentRepository\Core\NodeType\Exception\TetheredNodeNotConfigured; use Neos\ContentRepository\Core\SharedModel\Node\NodeName; use Neos\Utility\ObjectAccess; use Neos\Utility\Arrays; @@ -438,7 +438,7 @@ public function getDefaultValuesForProperties(): array /** * @return bool true if $nodeName is an autocreated child node, false otherwise */ - public function hasAutoCreatedChildNode(NodeName $nodeName): bool + public function hasTetheredNode(NodeName $nodeName): bool { $this->initialize(); foreach ($this->fullConfiguration['childNodes'] ?? [] as $rawChildNodeName => $configurationForChildNode) { @@ -452,9 +452,9 @@ public function hasAutoCreatedChildNode(NodeName $nodeName): bool } /** - * @throws ChildNodeNotConfigured if the requested childNode is not configured. Check via {@see NodeType::hasAutoCreatedChildNode()}. + * @throws TetheredNodeNotConfigured if the requested tethred node is not configured. Check via {@see NodeType::hasTetheredNode()}. */ - public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): NodeTypeName + public function getNodeTypeNameOfTetheredNode(NodeName $nodeName): NodeTypeName { $this->initialize(); foreach ($this->fullConfiguration['childNodes'] ?? [] as $rawChildNodeName => $configurationForChildNode) { @@ -464,7 +464,7 @@ public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): NodeT } } } - throw new ChildNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); + throw new TetheredNodeNotConfigured(sprintf('The child node "%s" is not configured for node type "%s"', $nodeName->value, $this->name->value), 1694786811); } /** @@ -472,7 +472,7 @@ public function getNodeTypeNameOfAutoCreatedChildNode(NodeName $nodeName): NodeT * not taking constraints of auto-created nodes into account. Thus, this method only returns * the correct result if called on NON-AUTO-CREATED nodes! * - * Otherwise, allowsGrandchildNodeType() needs to be called on the *parent node type*. + * Otherwise, isNodeTypeAllowedAsChildToTetheredNode() needs to be called on the *parent node type*. * * @return boolean true if the $nodeType is allowed as child node, false otherwise. */ diff --git a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php index c1c63167408..d01f0158edc 100644 --- a/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php +++ b/Neos.ContentRepository.Core/Classes/NodeType/NodeTypeManager.php @@ -14,7 +14,7 @@ namespace Neos\ContentRepository\Core\NodeType; -use Neos\ContentRepository\Core\NodeType\Exception\ChildNodeNotConfigured; +use Neos\ContentRepository\Core\NodeType\Exception\TetheredNodeNotConfigured; use Neos\ContentRepository\Core\SharedModel\Exception\NodeConfigurationException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeIsFinalException; use Neos\ContentRepository\Core\SharedModel\Exception\NodeTypeNotFoundException; @@ -188,14 +188,14 @@ public function overrideNodeTypes(array $completeNodeTypeConfiguration): void /** * @param NodeType $nodeType - * @param NodeName $childNodeName - * @throws ChildNodeNotConfigured if the requested childNode is not configured. Check via {@see NodeType::hasAutoCreatedChildNode()}. + * @param NodeName $tetheredNodeName * @return NodeType + *@throws TetheredNodeNotConfigured if the requested tethered node is not configured. Check via {@see NodeType::hasTetheredNode()}. */ - public function getTypeOfAutoCreatedChildNode(NodeType $nodeType, NodeName $childNodeName): NodeType + public function getTypeOfTetheredNode(NodeType $nodeType, NodeName $tetheredNodeName): NodeType { - $childNodeTypeName = $nodeType->getNodeTypeNameOfAutoCreatedChildNode($childNodeName); - return $this->getNodeType($childNodeTypeName); + $nameOfTetheredNode = $nodeType->getNodeTypeNameOfTetheredNode($tetheredNodeName); + return $this->getNodeType($nameOfTetheredNode); } /** @@ -204,41 +204,40 @@ public function getTypeOfAutoCreatedChildNode(NodeType $nodeType, NodeName $chil * @return array the key of this array is the name of the child, and the value its NodeType. * @api */ - public function getAutoCreatedChildNodesFor(NodeType $nodeType): array + public function getTetheredNodesConfigurationForNodeType(NodeType $nodeType): array { $childNodeConfiguration = $nodeType->getConfiguration('childNodes'); $autoCreatedChildNodes = []; foreach ($childNodeConfiguration ?? [] as $childNodeName => $configurationForChildNode) { if (isset($configurationForChildNode['type'])) { - $autoCreatedChildNodes[NodeName::transliterateFromString($childNodeName)->value] - = $this->getNodeType($configurationForChildNode['type']); + $autoCreatedChildNodes[NodeName::transliterateFromString($childNodeName)->value] = $this->getNodeType($configurationForChildNode['type']); } } return $autoCreatedChildNodes; } /** - * Checks if the given $nodeType is allowed as a childNode of the given $childNodeName - * (which must be auto-created in $this NodeType). + * Checks if the given $nodeType is allowed as a childNode of the given $tetheredNodeName + * (which must be tethered in $parentNodeType). * - * Only allowed to be called if $childNodeName is auto-created. + * Only allowed to be called if $tetheredNodeName is actually tethered. * * @param NodeType $parentNodeType The NodeType to check constraints based on. - * @param NodeName $childNodeName The name of a configured childNode of this NodeType + * @param NodeName $tetheredNodeName The name of a configured tethered node of this NodeType * @param NodeType $nodeType The NodeType to check constraints for. * @return bool true if the $nodeType is allowed as grandchild node, false otherwise. - * @throws \InvalidArgumentException if the requested childNode is not configured in the parent NodeType. Check via {@see NodeType::hasAutoCreatedChildNode()}. + * @throws \InvalidArgumentException if the requested tethered node is not configured in the parent NodeType. Check via {@see NodeType::hasTetheredNode()}. */ - public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $childNodeName, NodeType $nodeType): bool + public function isNodeTypeAllowedAsChildToTetheredNode(NodeType $parentNodeType, NodeName $tetheredNodeName, NodeType $nodeType): bool { try { - $childNodeType = $this->getTypeOfAutoCreatedChildNode($parentNodeType, $childNodeName); - } catch (ChildNodeNotConfigured $exception) { + $typeOfTetheredNode = $this->getTypeOfTetheredNode($parentNodeType, $tetheredNodeName); + } catch (TetheredNodeNotConfigured $exception) { throw new \InvalidArgumentException( sprintf( 'Cannot determine if grandchild is allowed in %s. Because the given child node name "%s" is not auto-created.', $parentNodeType->name->value, - $childNodeName->value + $tetheredNodeName->value ), 1403858395, $exception @@ -246,11 +245,11 @@ public function allowsGrandchildNodeType(NodeType $parentNodeType, NodeName $chi } // Constraints configured on the NodeType for the child node - $constraints = $childNodeType->getConfiguration('constraints.nodeTypes') ?: []; + $constraints = $typeOfTetheredNode->getConfiguration('constraints.nodeTypes') ?: []; // Constraints configured at the childNode configuration of the parent. try { - $childNodeConstraintConfiguration = $parentNodeType->getConfiguration('childNodes.' . $childNodeName->value . '.constraints.nodeTypes') ?? []; + $childNodeConstraintConfiguration = $parentNodeType->getConfiguration('childNodes.' . $tetheredNodeName->value . '.constraints.nodeTypes') ?? []; } catch (PropertyNotAccessibleException $exception) { // We ignore this because the configuration might just not have any constraints, if the childNode was not configured the exception above would have been thrown. $childNodeConstraintConfiguration = []; diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php index 67e372a9a18..0592c8e3d44 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeManagerTest.php @@ -349,7 +349,7 @@ public function getSubNodeTypesWithDifferentIncludeFlagValuesReturnsCorrectValue public function getAutoCreatedChildNodesReturnsLowercaseNames() { $parentNodeType = $this->nodeTypeManager->getNodeType(NodeTypeName::fromString('Neos.ContentRepository.Testing:Page2')); - $autoCreatedChildNodes = $this->nodeTypeManager->getAutoCreatedChildNodesFor($parentNodeType); + $autoCreatedChildNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($parentNodeType); // This is configured as "nodeName" above, but should be normalized to "nodename" self::assertArrayHasKey('nodename', $autoCreatedChildNodes); } diff --git a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php index ba5440ffc16..ea9ad36e5b5 100644 --- a/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php +++ b/Neos.ContentRepository.LegacyNodeMigration/Classes/NodeDataToEventsProcessor.php @@ -393,7 +393,7 @@ private function isAutoCreatedChildNode(NodeTypeName $parentNodeTypeName, NodeNa return false; } $nodeTypeOfParent = $this->nodeTypeManager->getNodeType($parentNodeTypeName); - return $nodeTypeOfParent->hasAutoCreatedChildNode($nodeName); + return $nodeTypeOfParent->hasTetheredNode($nodeName); } private function dispatch(Severity $severity, string $message, mixed ...$args): void diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index 0591d7685e5..e4a036ca3e7 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -82,7 +82,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat ) { if ($this->nodeTypeManager->hasNodeType($grandparentNode->nodeTypeName)) { if ($grandparentNodeType !== null) { - $allowedByGrandparent = $this->nodeTypeManager->allowsGrandchildNodeType( + $allowedByGrandparent = $this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( $grandparentNodeType, $parentNode->nodeName, $nodeType diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index ca76f548adb..e8bab27e6fa 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -51,7 +51,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat // In case we cannot find the expected tethered nodes, this fix cannot do anything. return; } - $expectedTetheredNodes = $this->nodeTypeManager->getAutoCreatedChildNodesFor($nodeType); + $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType); $expectedTetheredNodes = $nodeType->getAutoCreatedChildNodes(); diff --git a/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php b/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php index 6ed09eef528..b48f920c261 100644 --- a/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php +++ b/Neos.Neos/Classes/Service/NodeTypeSchemaBuilder.php @@ -105,9 +105,9 @@ protected function generateConstraints() } } - foreach ($this->nodeTypeManager->getAutoCreatedChildNodesFor($nodeType) as $key => $_x) { + foreach ($this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType) as $key => $_x) { foreach ($nodeTypes as $innerNodeTypeName => $innerNodeType) { - if ($this->nodeTypeManager->allowsGrandchildNodeType($nodeType, NodeName::fromString($key), $innerNodeType)) { + if ($this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode($nodeType, NodeName::fromString($key), $innerNodeType)) { $constraints[$nodeTypeName]['childNodes'][$key]['nodeTypes'][$innerNodeTypeName] = true; } } From 53a5ad7c17507246892b07244a012f0eb91a158d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 13 Oct 2023 18:03:02 +0200 Subject: [PATCH 10/13] Adjust new DefaultPropertyEditorPostProcessorTest --- .../DefaultPropertyEditorPostprocessorTest.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php b/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php index b0c1d52fafa..5d5723b2bec 100644 --- a/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php +++ b/Neos.Neos/Tests/Unit/NodeTypePostprocessor/DefaultPropertyEditorPostprocessorTest.php @@ -14,7 +14,6 @@ use Neos\ContentRepository\Core\NodeType\DefaultNodeLabelGeneratorFactory; use Neos\ContentRepository\Core\NodeType\NodeType; -use Neos\ContentRepository\Core\NodeType\NodeTypeManager; use Neos\ContentRepository\Core\NodeType\NodeTypeName; use Neos\Flow\Tests\UnitTestCase; use Neos\Neos\NodeTypePostprocessor\DefaultPropertyEditorPostprocessor; @@ -33,10 +32,6 @@ private function processConfiguration(array $configuration, array $dataTypesDefa NodeTypeName::fromString('Some.NodeType:Name'), [], [], - new NodeTypeManager( - fn () => [], - new DefaultNodeLabelGeneratorFactory() - ), new DefaultNodeLabelGeneratorFactory() ); $postprocessor->process($mockNodeType, $configuration, []); From c7aea4ebd7b103e5e7dbbd7020b10b412eab2eac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 13 Oct 2023 18:27:45 +0200 Subject: [PATCH 11/13] Fix syntactic error in DisallowedChildNodeAdjustment --- .../DisallowedChildNodeAdjustment.php | 64 +++++++++---------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index e4a036ca3e7..3a17cce411b 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -62,7 +62,6 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat ? $subgraph->findParentNode($parentNode->nodeAggregateId) : null; - $allowedByParent = true; $parentNodeType = null; if ($parentNode !== null) { @@ -81,44 +80,45 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat && !is_null($parentNode->nodeName) ) { if ($this->nodeTypeManager->hasNodeType($grandparentNode->nodeTypeName)) { - if ($grandparentNodeType !== null) { - $allowedByGrandparent = $this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( - $grandparentNodeType, - $parentNode->nodeName, - $nodeType - ); + if ($grandparentNodeType !== null) { + $allowedByGrandparent = $this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( + $grandparentNodeType, + $parentNode->nodeName, + $nodeType + ); + } } - } - if (!$allowedByParent && !$allowedByGrandparent) { - $node = $subgraph->findNodeById($nodeAggregate->nodeAggregateId); - if (is_null($node)) { - continue; - } + if (!$allowedByParent && !$allowedByGrandparent) { + $node = $subgraph->findNodeById($nodeAggregate->nodeAggregateId); + if (is_null($node)) { + continue; + } - $message = sprintf( - ' + $message = sprintf( + ' The parent node type "%s" is not allowing children of type "%s", and the grandparent node type "%s" is not allowing grandchildren of type "%s". Thus, the node is invalid at this location and should be removed. ', - $parentNodeType !== null ? $parentNodeType->name->value : '', - $node->nodeTypeName->value, - $grandparentNodeType !== null ? $grandparentNodeType->name->value : '', - $node->nodeTypeName->value, - ); - - yield StructureAdjustment::createForNode( - $node, - StructureAdjustment::DISALLOWED_CHILD_NODE, - $message, - function () use ($nodeAggregate, $coveredDimensionSpacePoint) { - return $this->removeNodeInSingleDimensionSpacePoint( - $nodeAggregate, - $coveredDimensionSpacePoint - ); - } - ); + $parentNodeType !== null ? $parentNodeType->name->value : '', + $node->nodeTypeName->value, + $grandparentNodeType !== null ? $grandparentNodeType->name->value : '', + $node->nodeTypeName->value, + ); + + yield StructureAdjustment::createForNode( + $node, + StructureAdjustment::DISALLOWED_CHILD_NODE, + $message, + function () use ($nodeAggregate, $coveredDimensionSpacePoint) { + return $this->removeNodeInSingleDimensionSpacePoint( + $nodeAggregate, + $coveredDimensionSpacePoint + ); + } + ); + } } } } From 6b28944253dcfb7a46f9d25448970c694adf4e1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 13 Oct 2023 18:43:11 +0200 Subject: [PATCH 12/13] Fix TetheredNodeAdjustments after wrong merge --- .../src/Adjustment/TetheredNodeAdjustments.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php index e8bab27e6fa..994672b390e 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/TetheredNodeAdjustments.php @@ -51,9 +51,7 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat // In case we cannot find the expected tethered nodes, this fix cannot do anything. return; } - $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($nodeType); - - $expectedTetheredNodes = $nodeType->getAutoCreatedChildNodes(); + $expectedTetheredNodes = $this->nodeTypeManager->getTetheredNodesConfigurationForNodeType($this->nodeTypeManager->getNodeType($nodeTypeName)); foreach ($this->projectedNodeIterator->nodeAggregatesOfType($nodeTypeName) as $nodeAggregate) { // find missing tethered nodes From 0742577b6cbba9dd7f3f42c0ef36a705d12a60e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christian=20Mu=CC=88ller?= Date: Fri, 13 Oct 2023 23:57:20 +0200 Subject: [PATCH 13/13] Fix merge error in DisallowedChildNodeAdjustment --- .../Tests/Unit/NodeType/NodeTypeTest.php | 1 - .../DisallowedChildNodeAdjustment.php | 76 +++++++++---------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php index 3554fd3bd6e..3777e0c725c 100644 --- a/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php +++ b/Neos.ContentRepository.Core/Tests/Unit/NodeType/NodeTypeTest.php @@ -22,7 +22,6 @@ * Testcase for the "NodeType" domain model */ class NodeTypeTest extends TestCase - { /** * example node types diff --git a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php index 3a17cce411b..d19b762ace8 100644 --- a/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php +++ b/Neos.ContentRepository.StructureAdjustment/src/Adjustment/DisallowedChildNodeAdjustment.php @@ -75,55 +75,55 @@ public function findAdjustmentsForNodeType(NodeTypeName $nodeTypeName): \Generat $grandparentNodeType = null; if ( $parentNode !== null - && $grandparentNode != null + && $grandparentNode !== null && $parentNode->classification->isTethered() && !is_null($parentNode->nodeName) ) { - if ($this->nodeTypeManager->hasNodeType($grandparentNode->nodeTypeName)) { - if ($grandparentNodeType !== null) { - $allowedByGrandparent = $this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( - $grandparentNodeType, - $parentNode->nodeName, - $nodeType - ); - } - } - - if (!$allowedByParent && !$allowedByGrandparent) { - $node = $subgraph->findNodeById($nodeAggregate->nodeAggregateId); - if (is_null($node)) { - continue; - } - - $message = sprintf( - ' - The parent node type "%s" is not allowing children of type "%s", - and the grandparent node type "%s" is not allowing grandchildren of type "%s". - Thus, the node is invalid at this location and should be removed. - ', - $parentNodeType !== null ? $parentNodeType->name->value : '', - $node->nodeTypeName->value, - $grandparentNodeType !== null ? $grandparentNodeType->name->value : '', - $node->nodeTypeName->value, + $grandparentNodeType = $this->nodeTypeManager->hasNodeType($grandparentNode->nodeTypeName) ? $this->nodeTypeManager->getNodeType($grandparentNode->nodeTypeName) : null; + if ($grandparentNodeType !== null) { + $allowedByGrandparent = $this->nodeTypeManager->isNodeTypeAllowedAsChildToTetheredNode( + $grandparentNodeType, + $parentNode->nodeName, + $nodeType ); + } + } - yield StructureAdjustment::createForNode( - $node, - StructureAdjustment::DISALLOWED_CHILD_NODE, - $message, - function () use ($nodeAggregate, $coveredDimensionSpacePoint) { - return $this->removeNodeInSingleDimensionSpacePoint( - $nodeAggregate, - $coveredDimensionSpacePoint - ); - } - ); + if (!$allowedByParent && !$allowedByGrandparent) { + $node = $subgraph->findNodeById($nodeAggregate->nodeAggregateId); + if (is_null($node)) { + continue; } + + $message = sprintf( + ' + The parent node type "%s" is not allowing children of type "%s", + and the grandparent node type "%s" is not allowing grandchildren of type "%s". + Thus, the node is invalid at this location and should be removed. + ', + $parentNodeType !== null ? $parentNodeType->name->value : '', + $node->nodeTypeName->value, + $grandparentNodeType !== null ? $grandparentNodeType->name->value : '', + $node->nodeTypeName->value, + ); + + yield StructureAdjustment::createForNode( + $node, + StructureAdjustment::DISALLOWED_CHILD_NODE, + $message, + function () use ($nodeAggregate, $coveredDimensionSpacePoint) { + return $this->removeNodeInSingleDimensionSpacePoint( + $nodeAggregate, + $coveredDimensionSpacePoint + ); + } + ); } } } } + private function removeNodeInSingleDimensionSpacePoint( NodeAggregate $nodeAggregate, DimensionSpacePoint $dimensionSpacePoint