|
| 1 | +<?php |
| 2 | +namespace Flowpack\NodeTemplates; |
| 3 | + |
| 4 | +use Neos\ContentRepository\Core\ContentRepository; |
| 5 | +use Neos\ContentRepository\Core\Feature\NodeCreation\Command\CreateNodeAggregateWithNode; |
| 6 | +use Neos\ContentRepository\Core\Feature\NodeCreation\Dto\NodeAggregateIdsByNodePaths; |
| 7 | +use Neos\ContentRepository\Core\Feature\NodeModification\Command\SetNodeProperties; |
| 8 | +use Neos\ContentRepository\Core\Feature\NodeModification\Dto\PropertyValuesToWrite; |
| 9 | +use Neos\ContentRepository\Core\NodeType\NodeTypeName; |
| 10 | +use Neos\ContentRepository\Core\Projection\ContentGraph\NodePath; |
| 11 | +use Neos\ContentRepository\Core\SharedModel\Node\NodeAggregateId; |
| 12 | +use Neos\ContentRepository\Core\SharedModel\Node\NodeName; |
| 13 | + |
| 14 | +/** |
| 15 | + * TODO To use it replace the line https://github.com/neos/neos-ui/blob/a7ab78f006320ec6b58be106a5451cd24be075ac/Classes/Domain/Model/Changes/AbstractCreate.php#L118 with |
| 16 | + * `(new NeosEscrTemplateWriteAdapter())->writeTemplate($command, $contentRepository);` |
| 17 | + */ |
| 18 | +class NeosEscrTemplateWriteAdapter |
| 19 | +{ |
| 20 | + public function writeTemplate(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): void |
| 21 | + { |
| 22 | + $template = [ |
| 23 | + 'childNodes' => [ |
| 24 | + 'main' => [ |
| 25 | + 'name' => 'main', |
| 26 | + 'childNodes' => [ |
| 27 | + 'content1' => [ |
| 28 | + 'type' => "Neos.Demo:Content.Text", |
| 29 | + 'properties' => [ |
| 30 | + 'text' => "huhu", |
| 31 | + ] |
| 32 | + ] |
| 33 | + ] |
| 34 | + ], |
| 35 | + 'foo' => [ |
| 36 | + 'type' => 'Neos.Demo:Document.Page', |
| 37 | + 'childNodes' => [ |
| 38 | + 'main' => [ |
| 39 | + 'name' => 'main', |
| 40 | + 'childNodes' => [ |
| 41 | + 'content1' => [ |
| 42 | + 'type' => "Neos.Demo:Content.Text", |
| 43 | + 'properties' => [ |
| 44 | + 'text' => "textiii" |
| 45 | + ] |
| 46 | + ], |
| 47 | + 'content2' => [ |
| 48 | + 'type' => "Neos.Demo:Content.Text", |
| 49 | + 'properties' => [ |
| 50 | + 'text' => "huijkuihjnihujbn" |
| 51 | + ] |
| 52 | + ] |
| 53 | + ] |
| 54 | + ], |
| 55 | + ] |
| 56 | + ] |
| 57 | + ] |
| 58 | + ]; |
| 59 | + |
| 60 | + $createNodeCommand = $this->augmentWithTetheredDescendantNodeAggregateIds($createNodeAggregateWithNode, $contentRepository); |
| 61 | + |
| 62 | + if (isset($template['properties'])) { |
| 63 | + // documents generate uripath blabla |
| 64 | + $createNodeCommand = $createNodeCommand->initialPropertyValues->merge( |
| 65 | + PropertyValuesToWrite::fromArray($this->requireValidProperties($template['properties'])) |
| 66 | + ); |
| 67 | + } |
| 68 | + |
| 69 | + $commands = $this->createCommandsRecursivelyFromTemplateChildNodes($createNodeCommand, $template, $contentRepository); |
| 70 | + |
| 71 | + $contentRepository->handle($createNodeCommand)->block(); |
| 72 | + |
| 73 | + foreach ($commands as $command) { |
| 74 | + $contentRepository->handle($command)->block(); |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + /** |
| 79 | + * In the old CR, it was common practice to set internal or meta properties via this syntax: `_hidden` so it was also allowed in templates but not anymore. |
| 80 | + * @throws \InvalidArgumentException |
| 81 | + */ |
| 82 | + private function requireValidProperties(array $properties): array |
| 83 | + { |
| 84 | + $legacyInternalProperties = [ |
| 85 | + '_accessRoles', |
| 86 | + '_contentObject', |
| 87 | + '_hidden', |
| 88 | + '_hiddenAfterDateTime', |
| 89 | + '_hiddenBeforeDateTime', |
| 90 | + '_hiddenInIndex', |
| 91 | + '_index', |
| 92 | + '_name', |
| 93 | + '_nodeType', |
| 94 | + '_removed', |
| 95 | + '_workspace' |
| 96 | + ]; |
| 97 | + foreach ($properties as $propertyName => $propertyValue) { |
| 98 | + if (str_starts_with($propertyName, '_')) { |
| 99 | + $lowerPropertyName = strtolower($propertyName); |
| 100 | + foreach ($legacyInternalProperties as $legacyInternalProperty) { |
| 101 | + if ($lowerPropertyName === strtolower($legacyInternalProperty)) { |
| 102 | + throw new \InvalidArgumentException('Internal legacy properties are not implement.' . $propertyName); |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | + } |
| 107 | + return $properties; |
| 108 | + } |
| 109 | + |
| 110 | + private function createCommandsRecursivelyFromTemplateChildNodes(CreateNodeAggregateWithNode $createParentNodeCommand, array $template, ContentRepository $contentRepository): array |
| 111 | + { |
| 112 | + $makeCreateNodeCommand = function (NodeAggregateId $parentNodeAggregateId, array $subTemplate) use ($createParentNodeCommand, $contentRepository) { |
| 113 | + return $this->augmentWithTetheredDescendantNodeAggregateIds(new CreateNodeAggregateWithNode( |
| 114 | + contentStreamId: $createParentNodeCommand->contentStreamId, |
| 115 | + nodeAggregateId: NodeAggregateId::create(), |
| 116 | + nodeTypeName: NodeTypeName::fromString($subTemplate['type']), |
| 117 | + originDimensionSpacePoint: $createParentNodeCommand->originDimensionSpacePoint, |
| 118 | + parentNodeAggregateId: $parentNodeAggregateId, |
| 119 | + nodeName: NodeName::fromString(uniqid('node-', false)), |
| 120 | + initialPropertyValues: isset($subTemplate['properties']) |
| 121 | + ? PropertyValuesToWrite::fromArray($this->requireValidProperties($subTemplate['properties'])) |
| 122 | + : null |
| 123 | + ), $contentRepository); |
| 124 | + }; |
| 125 | + |
| 126 | + $commands = []; |
| 127 | + foreach ($template['childNodes'] ?? [] as $childNode) { |
| 128 | + if (isset($childNode['name']) && $autoCreatedNodeId = $createParentNodeCommand->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString($childNode['name']))) { |
| 129 | + if (isset($childNode['type'])) { |
| 130 | + throw new \Exception('For auto-created nodes the type cannot be qualified.'); |
| 131 | + } |
| 132 | + if (isset($childNode['properties'])) { |
| 133 | + $commands[] = new SetNodeProperties( |
| 134 | + $createParentNodeCommand->contentStreamId, |
| 135 | + $autoCreatedNodeId, |
| 136 | + $createParentNodeCommand->originDimensionSpacePoint, |
| 137 | + PropertyValuesToWrite::fromArray($this->requireValidProperties($childNode['properties'])) |
| 138 | + ); |
| 139 | + } |
| 140 | + foreach ($childNode['childNodes'] ?? [] as $innerChildNode) { |
| 141 | + $commands[] = $newParent = $makeCreateNodeCommand($autoCreatedNodeId, $innerChildNode); |
| 142 | + $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $innerChildNode, $contentRepository)]; |
| 143 | + } |
| 144 | + } else { |
| 145 | + // if is document setUriPath based on title |
| 146 | + $commands[] = $newParent = $makeCreateNodeCommand($createParentNodeCommand->nodeAggregateId, $childNode); |
| 147 | + $commands = [...$commands, ...$this->createCommandsRecursivelyFromTemplateChildNodes($newParent, $childNode, $contentRepository)]; |
| 148 | + } |
| 149 | + } |
| 150 | + return $commands; |
| 151 | + } |
| 152 | + |
| 153 | + /** |
| 154 | + * Precalculate the nodeIds for the auto-created childNodes, so that we can determine the id beforehand and use it for succeeding operations. |
| 155 | + * |
| 156 | + * ``` |
| 157 | + * $mainContentCollectionNodeId = $createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds->getNodeAggregateId(NodePath::fromString('main')) |
| 158 | + * ``` |
| 159 | + */ |
| 160 | + private function augmentWithTetheredDescendantNodeAggregateIds(CreateNodeAggregateWithNode $createNodeAggregateWithNode, ContentRepository $contentRepository): CreateNodeAggregateWithNode |
| 161 | + { |
| 162 | + $nodeType = $contentRepository->getNodeTypeManager()->getNodeType($createNodeAggregateWithNode->nodeTypeName); |
| 163 | + if (!isset($nodeType->getFullConfiguration()['childNodes'])) { |
| 164 | + return $createNodeAggregateWithNode; |
| 165 | + } |
| 166 | + $nodeAggregateIdsByNodePaths = NodeAggregateIdsByNodePaths::createEmpty(); |
| 167 | + foreach (array_keys($nodeType->getFullConfiguration()['childNodes']) as $autoCreatedNodeName) { |
| 168 | + $nodeAggregateIdsByNodePaths = $nodeAggregateIdsByNodePaths->add( |
| 169 | + NodePath::fromString($autoCreatedNodeName), |
| 170 | + NodeAggregateId::create() |
| 171 | + ); |
| 172 | + } |
| 173 | + return $createNodeAggregateWithNode->withTetheredDescendantNodeAggregateIds( |
| 174 | + $nodeAggregateIdsByNodePaths->merge($createNodeAggregateWithNode->tetheredDescendantNodeAggregateIds) |
| 175 | + ); |
| 176 | + } |
| 177 | +} |
0 commit comments