44
55namespace Flowpack \NodeTemplates \Domain \NodeTemplateDumper ;
66
7- use Neos \ContentRepository \Domain \Model \ArrayPropertyCollection ;
8- use Neos \ContentRepository \Domain \Model \NodeInterface ;
9- use Neos \ContentRepository \Domain \Projection \Content \PropertyCollectionInterface ;
7+ use Neos \ContentRepository \Core \ContentRepository ;
8+ use Neos \ContentRepository \Core \NodeType \NodeType ;
9+ use Neos \ContentRepository \Core \Projection \ContentGraph \ContentSubgraphInterface ;
10+ use Neos \ContentRepository \Core \Projection \ContentGraph \Filter \FindChildNodesFilter ;
11+ use Neos \ContentRepository \Core \Projection \ContentGraph \Filter \FindReferencesFilter ;
12+ use Neos \ContentRepository \Core \Projection \ContentGraph \Node ;
13+ use Neos \ContentRepository \Core \Projection \ContentGraph \Nodes ;
1014use Neos \Flow \Annotations as Flow ;
1115use Neos \Flow \I18n \EelHelper \TranslationHelper ;
16+ use Neos \Neos \Domain \NodeLabel \NodeLabelGeneratorInterface ;
1217use Symfony \Component \Yaml \Yaml ;
1318
1419/** @Flow\Scope("singleton") */
@@ -20,28 +25,36 @@ class NodeTemplateDumper
2025 */
2126 protected $ translationHelper ;
2227
28+ /**
29+ * @var NodeLabelGeneratorInterface
30+ * @Flow\Inject
31+ */
32+ protected $ nodeLabelGenerator ;
33+
2334 /**
2435 * Dump the node tree structure into a NodeTemplate YAML structure.
2536 * References to Nodes and non-primitive property values are commented out in the YAML.
2637 *
27- * @param NodeInterface $startingNode specified root node of the node tree to dump
38+ * @param Node $startingNode specified root node of the node tree to dump
2839 * @return string YAML representation of the node template
2940 */
30- public function createNodeTemplateYamlDumpFromSubtree (NodeInterface $ startingNode ): string
41+ public function createNodeTemplateYamlDumpFromSubtree (Node $ startingNode, ContentRepository $ contentRepository ): string
3142 {
3243 $ comments = Comments::empty ();
3344
34- $ nodeType = $ startingNode -> getNodeType ();
45+ $ nodeType = $ contentRepository -> getNodeTypeManager ()-> getNodeType ($ startingNode -> nodeTypeName );
3546
3647 if (
37- !$ nodeType ->isOfType ('Neos.Neos:Document ' )
38- && !$ nodeType ->isOfType ('Neos.Neos:Content ' )
39- && !$ nodeType ->isOfType ('Neos.Neos:ContentCollection ' )
48+ !$ nodeType || (
49+ !$ nodeType ->isOfType ('Neos.Neos:Document ' )
50+ && !$ nodeType ->isOfType ('Neos.Neos:Content ' )
51+ && !$ nodeType ->isOfType ('Neos.Neos:ContentCollection ' )
52+ )
4053 ) {
41- throw new \InvalidArgumentException ("Node {$ startingNode ->getIdentifier () } must be one of Neos.Neos:Document,Neos.Neos:Content,Neos.Neos:ContentCollection. " );
54+ throw new \InvalidArgumentException ("Node {$ startingNode ->aggregateId -> value } must be one of Neos.Neos:Document,Neos.Neos:Content,Neos.Neos:ContentCollection. " );
4255 }
4356
44- $ template = $ this ->nodeTemplateFromNodes ([$ startingNode ], $ comments );
57+ $ template = $ this ->nodeTemplateFromNodes (Nodes:: fromArray ( [$ startingNode ]) , $ comments, $ contentRepository );
4558
4659 $ firstEntry = null ;
4760 foreach ($ template as $ firstEntry ) {
@@ -53,7 +66,7 @@ public function createNodeTemplateYamlDumpFromSubtree(NodeInterface $startingNod
5366
5467
5568 $ templateInNodeTypeOptions = [
56- $ nodeType ->getName () => [
69+ $ nodeType ->name -> value => [
5770 'options ' => [
5871 'template ' => array_filter ([
5972 'properties ' => $ properties ,
@@ -68,21 +81,32 @@ public function createNodeTemplateYamlDumpFromSubtree(NodeInterface $startingNod
6881 return $ comments ->renderCommentsInYamlDump ($ yamlWithSerializedComments );
6982 }
7083
71- /** @param array<NodeInterface> $nodes */
72- private function nodeTemplateFromNodes (array $ nodes , Comments $ comments ): array
84+ /** @return array<string, array<string, mixed>> */
85+ private function nodeTemplateFromNodes (Nodes $ nodes , Comments $ comments, ContentRepository $ contentRepository ): array
7386 {
87+ $ subgraph = null ;
88+
7489 $ documentNodeTemplates = [];
7590 $ contentNodeTemplates = [];
7691 foreach ($ nodes as $ index => $ node ) {
77- assert ($ node instanceof NodeInterface);
78- $ nodeType = $ node ->getNodeType ();
92+ $ subgraph ??= $ contentRepository ->getContentGraph ($ node ->workspaceName )->getSubgraph (
93+ $ node ->dimensionSpacePoint ,
94+ $ node ->visibilityConstraints
95+ );
96+
97+ $ nodeType = $ contentRepository ->getNodeTypeManager ()->getNodeType ($ node ->nodeTypeName );
98+ if (!$ nodeType ) {
99+ throw new \RuntimeException ("NodeType {$ node ->nodeTypeName ->value } of Node {$ node ->aggregateId ->value } doesnt exist. " );
100+ }
101+
79102 $ isDocumentNode = $ nodeType ->isOfType ('Neos.Neos:Document ' );
80103
81104 $ templatePart = array_filter ([
82- 'properties ' => $ this ->nonDefaultConfiguredNodeProperties ($ node , $ comments ),
105+ 'properties ' => $ this ->nonDefaultConfiguredNodeProperties ($ node , $ nodeType , $ comments, $ subgraph ),
83106 'childNodes ' => $ this ->nodeTemplateFromNodes (
84- $ node ->getChildNodes ('Neos.Neos:Node ' ),
85- $ comments
107+ $ subgraph ->findChildNodes ($ node ->aggregateId , FindChildNodesFilter::create ('Neos.Neos:Node ' )),
108+ $ comments ,
109+ $ contentRepository
86110 )
87111 ]);
88112
@@ -91,38 +115,44 @@ private function nodeTemplateFromNodes(array $nodes, Comments $comments): array
91115 }
92116
93117 if ($ isDocumentNode ) {
94- if ($ node ->isTethered ()) {
95- $ documentNodeTemplates [$ node ->getLabel () ?: $ node ->getName ()] = array_merge ([
96- 'name ' => $ node ->getName ()
118+ if ($ node ->classification ->isTethered ()) {
119+ $ tetheredName = $ node ->name ;
120+ assert ($ tetheredName !== null );
121+
122+ $ documentNodeTemplates [$ this ->nodeLabelGenerator ->getLabel ($ node ) ?: $ tetheredName ->value ] = array_merge ([
123+ 'name ' => $ tetheredName ->value
97124 ], $ templatePart );
98125 continue ;
99126 }
100127
101128 $ documentNodeTemplates ["page $ index " ] = array_merge ([
102- 'type ' => $ node ->getNodeType ()-> getName ()
129+ 'type ' => $ node ->nodeTypeName -> value
103130 ], $ templatePart );
104131 continue ;
105132 }
106133
107- if ($ node ->isTethered ()) {
108- $ contentNodeTemplates [$ node ->getLabel () ?: $ node ->getName ()] = array_merge ([
109- 'name ' => $ node ->getName ()
134+ if ($ node ->classification ->isTethered ()) {
135+ $ tetheredName = $ node ->name ;
136+ assert ($ tetheredName !== null );
137+
138+ $ contentNodeTemplates [$ this ->nodeLabelGenerator ->getLabel ($ node ) ?: $ tetheredName ->value ] = array_merge ([
139+ 'name ' => $ tetheredName ->value
110140 ], $ templatePart );
111141 continue ;
112142 }
113143
114144 $ contentNodeTemplates ["content $ index " ] = array_merge ([
115- 'type ' => $ node ->getNodeType ()-> getName ()
145+ 'type ' => $ node ->nodeTypeName -> value
116146 ], $ templatePart );
117147 }
118148
119149 return array_merge ($ contentNodeTemplates , $ documentNodeTemplates );
120150 }
121151
122- private function nonDefaultConfiguredNodeProperties (NodeInterface $ node , Comments $ comments ): array
152+ /** @return array<string, string> */
153+ private function nonDefaultConfiguredNodeProperties (Node $ node , NodeType $ nodeType , Comments $ comments , ContentSubgraphInterface $ subgraph ): array
123154 {
124- $ nodeType = $ node ->getNodeType ();
125- $ nodeProperties = $ node ->getProperties ();
155+ $ nodeProperties = $ node ->properties ;
126156
127157 $ filteredProperties = [];
128158 foreach ($ nodeType ->getProperties () as $ propertyName => $ configuration ) {
@@ -170,22 +200,12 @@ function ($indentation, $propertyName) use ($dataSourceIdentifier, $propertyValu
170200 continue ;
171201 }
172202
173- if (($ configuration ['type ' ] ?? null ) === 'reference ' ) {
174- $ nodeTypesInReference = $ configuration ['ui ' ]['inspector ' ]['editorOptions ' ]['nodeTypes ' ] ?? ['Neos.Neos:Document ' ];
175- $ filteredProperties [$ propertyName ] = $ comments ->addCommentAndGetMarker ($ augmentCommentWithLabel (Comment::fromRenderer (
176- function ($ indentation , $ propertyName ) use ($ nodeTypesInReference , $ propertyValue ) {
177- return $ indentation . '# ' . $ propertyName . ' -> Reference of NodeTypes ( ' . join (', ' , $ nodeTypesInReference ) . ') with value ' . $ this ->valueToDebugString ($ propertyValue );
178- }
179- )));
180- continue ;
181- }
182-
183203 if (($ configuration ['ui ' ]['inspector ' ]['editor ' ] ?? null ) === 'Neos.Neos/Inspector/Editors/SelectBoxEditor ' ) {
184204 $ selectBoxValues = array_keys ($ configuration ['ui ' ]['inspector ' ]['editorOptions ' ]['values ' ] ?? []);
185205 $ filteredProperties [$ propertyName ] = $ comments ->addCommentAndGetMarker ($ augmentCommentWithLabel (Comment::fromRenderer (
186206 function ($ indentation , $ propertyName ) use ($ selectBoxValues , $ propertyValue ) {
187207 return $ indentation . '# ' . $ propertyName . ' -> SelectBox of '
188- . mb_strimwidth (json_encode ($ selectBoxValues ), 0 , 60 , ' ...] ' )
208+ . mb_strimwidth (json_encode ($ selectBoxValues, JSON_THROW_ON_ERROR ), 0 , 60 , ' ...] ' )
189209 . ' with value ' . $ this ->valueToDebugString ($ propertyValue );
190210 }
191211 )));
@@ -208,36 +228,71 @@ function ($indentation, $propertyName) use ($propertyValue) {
208228 )));
209229 }
210230
231+ if ($ nodeType ->getReferences () === []) {
232+ return $ filteredProperties ;
233+ }
234+
235+ $ references = $ subgraph ->findReferences ($ node ->aggregateId , FindReferencesFilter::create ());
236+ $ referencesArray = [];
237+ foreach ($ references as $ reference ) {
238+ if (!isset ($ referencesArray [$ reference ->name ->value ])) {
239+ $ referencesArray [$ reference ->name ->value ] = $ reference ->node ->aggregateId ->value ;
240+ continue ;
241+ }
242+ $ referencesArray [$ reference ->name ->value ] .= ', ' . $ reference ->node ->aggregateId ->value ;
243+ }
244+
245+ foreach ($ nodeType ->getReferences () as $ referenceName => $ configuration ) {
246+ $ referenceValue = $ referencesArray [$ referenceName ] ?? null ;
247+ if (!$ referenceValue ) {
248+ continue ;
249+ }
250+
251+ $ label = $ configuration ['ui ' ]['label ' ] ?? null ;
252+ $ augmentCommentWithLabel = fn (Comment $ comment ) => $ comment ;
253+ if ($ label ) {
254+ $ label = $ this ->translationHelper ->translate ($ label );
255+ $ augmentCommentWithLabel = fn (Comment $ comment ) => Comment::fromRenderer (
256+ function ($ indentation , $ propertyName ) use ($ comment , $ label ) {
257+ return $ indentation . '# ' . $ label . "\n" .
258+ $ comment ->toYamlComment ($ indentation , $ propertyName );
259+ }
260+ );
261+ }
262+
263+ if (($ configuration ['constraints ' ]['maxItems ' ] ?? null ) === 1 ) {
264+ $ nodeTypesInReference = $ configuration ['ui ' ]['inspector ' ]['editorOptions ' ]['nodeTypes ' ] ?? ['Neos.Neos:Document ' ];
265+ $ filteredProperties [$ referenceName ] = $ comments ->addCommentAndGetMarker ($ augmentCommentWithLabel (Comment::fromRenderer (
266+ function ($ indentation , $ propertyName ) use ($ nodeTypesInReference , $ referenceValue ) {
267+ return $ indentation . '# ' . $ propertyName . ' -> Reference of NodeTypes ( ' . join (', ' , $ nodeTypesInReference ) . ') with Node: ' . $ referenceValue ;
268+ }
269+ )));
270+ continue ;
271+ }
272+
273+ $ filteredProperties [$ referenceName ] = $ comments ->addCommentAndGetMarker ($ augmentCommentWithLabel (Comment::fromRenderer (
274+ function ($ indentation , $ propertyName ) use ($ referenceValue ) {
275+ return $ indentation . '# ' . $ propertyName . ' -> References with Nodes: ' . $ referenceValue ;
276+ }
277+ )));
278+ }
279+
211280 return $ filteredProperties ;
212281 }
213282
214- private function valueToDebugString ($ value ): string
283+ private function valueToDebugString (mixed $ value ): string
215284 {
216- if ($ value instanceof NodeInterface) {
217- return 'Node( ' . $ value ->getIdentifier () . ') ' ;
218- }
219285 if (is_iterable ($ value )) {
220- $ name = null ;
221286 $ entries = [];
222287 foreach ($ value as $ key => $ item ) {
223- if ($ item instanceof NodeInterface) {
224- if ($ name === null || $ name === 'Nodes ' ) {
225- $ name = 'Nodes ' ;
226- } else {
227- $ name = 'array ' ;
228- }
229- $ entries [$ key ] = $ item ->getIdentifier ();
230- continue ;
231- }
232- $ name = 'array ' ;
233288 $ entries [$ key ] = is_object ($ item ) ? get_class ($ item ) : json_encode ($ item );
234289 }
235- return $ name . ' ( ' . join (', ' , $ entries ) . ') ' ;
290+ return ' array ( ' . join (', ' , $ entries ) . ') ' ;
236291 }
237292
238293 if (is_object ($ value )) {
239294 return 'object( ' . get_class ($ value ) . ') ' ;
240295 }
241- return json_encode ($ value );
296+ return json_encode ($ value, JSON_THROW_ON_ERROR );
242297 }
243298}
0 commit comments