1010namespace TYPO3Fluid \Fluid \Core \Component ;
1111
1212use TYPO3Fluid \Fluid \Core \Compiler \TemplateCompiler ;
13+ use TYPO3Fluid \Fluid \Core \Parser \Exception as ParserException ;
1314use TYPO3Fluid \Fluid \Core \Parser \SyntaxTree \NodeInterface ;
1415use TYPO3Fluid \Fluid \Core \Parser \SyntaxTree \ViewHelperNode ;
16+ use TYPO3Fluid \Fluid \Core \Rendering \RenderingContext ;
1517use TYPO3Fluid \Fluid \Core \Rendering \RenderingContextInterface ;
1618use TYPO3Fluid \Fluid \Core \ViewHelper \ArgumentDefinition ;
1719use TYPO3Fluid \Fluid \Core \ViewHelper \Exception ;
1820use TYPO3Fluid \Fluid \Core \ViewHelper \ViewHelperInterface ;
1921use TYPO3Fluid \Fluid \Core \ViewHelper \ViewHelperResolverDelegateInterface ;
22+ use TYPO3Fluid \Fluid \ViewHelpers \FragmentViewHelper ;
2023use TYPO3Fluid \Fluid \ViewHelpers \SlotViewHelper ;
2124
2225/**
@@ -125,18 +128,43 @@ public function initializeArgumentsAndRender(): mixed
125128 return $ this ->getComponentDefinitionProvider ()->getComponentRenderer ()->renderComponent (
126129 $ this ->viewHelperNode ->getName (),
127130 $ this ->arguments ,
128- $ this ->viewHelperNode ->getChildNodes () !== []
129- ? [SlotViewHelper::DEFAULT_SLOT => $ this ->buildRenderChildrenClosure ()]
130- : [],
131+ $ this ->buildSlotClosures (),
131132 $ this ->renderingContext ,
132133 );
133134 }
134135
135- private function buildRenderChildrenClosure (): callable
136+ /**
137+ * @return \Closure[]
138+ */
139+ private function buildSlotClosures (): array
140+ {
141+ if ($ this ->viewHelperNode ->getChildNodes () === []) {
142+ return [];
143+ }
144+ $ slotClosures = [];
145+ foreach ($ this ->extractFragmentViewHelperNodes ($ this ->viewHelperNode ) as $ fragmentNode ) {
146+ $ fragmentName = $ this ->extractFragmentName ($ fragmentNode );
147+ if (isset ($ slotClosures [$ fragmentName ])) {
148+ throw new ParserException (sprintf (
149+ 'Fragment "%s" for <%s:%s> is defined multiple times. ' ,
150+ $ fragmentName ,
151+ $ this ->viewHelperNode ->getNamespace (),
152+ $ this ->viewHelperNode ->getName (),
153+ ), 1750865701 );
154+ }
155+ $ slotClosures [$ fragmentName ] = $ this ->buildSlotClosure ($ fragmentNode );
156+ }
157+ if ($ slotClosures === []) {
158+ $ slotClosures [SlotViewHelper::DEFAULT_SLOT ] = $ this ->buildSlotClosure ($ this ->viewHelperNode );
159+ }
160+ return $ slotClosures ;
161+ }
162+
163+ private function buildSlotClosure (ViewHelperNode $ viewHelperNode ): \Closure
136164 {
137- return function (): mixed {
165+ return function () use ( $ viewHelperNode ) : mixed {
138166 $ this ->renderingContextStack [] = $ this ->renderingContext ;
139- $ result = $ this -> viewHelperNode ->evaluateChildNodes ($ this ->renderingContext );
167+ $ result = $ viewHelperNode ->evaluateChildNodes ($ this ->renderingContext );
140168 $ this ->setRenderingContext (array_pop ($ this ->renderingContextStack ));
141169 return $ result ;
142170 };
@@ -152,7 +180,7 @@ public function convert(TemplateCompiler $templateCompiler): array
152180 $ initializationPhpCode = '// Rendering Component ' . $ this ->viewHelperNode ->getNamespace () . ': ' . $ this ->viewHelperNode ->getName () . chr (10 );
153181
154182 $ argumentsVariableName = $ templateCompiler ->variableName ('arguments ' );
155- $ renderChildrenClosureVariableName = $ templateCompiler ->variableName ('renderChildrenClosure ' );
183+ $ slotClosuresVariableName = $ templateCompiler ->variableName ('slotClosures ' );
156184
157185 // Similarly to Fluid's ViewHelper processing, the responsible ViewHelper resolver delegate
158186 // is resolved, validated early and "baked in" to the cache to improve rendering times for
@@ -163,9 +191,7 @@ public function convert(TemplateCompiler $templateCompiler): array
163191 var_export ($ resolverDelegate ->getNamespace (), true ),
164192 var_export ($ this ->viewHelperNode ->getName (), true ),
165193 $ argumentsVariableName ,
166- $ this ->viewHelperNode ->getChildNodes () !== []
167- ? '[ \'' . SlotViewHelper::DEFAULT_SLOT . '\' => ' . $ renderChildrenClosureVariableName . '] '
168- : '[] ' ,
194+ $ slotClosuresVariableName ,
169195 );
170196
171197 $ accumulatedArgumentInitializationCode = '' ;
@@ -194,11 +220,25 @@ public function convert(TemplateCompiler $templateCompiler): array
194220
195221 $ argumentInitializationCode .= ']; ' . chr (10 );
196222
223+ $ slotClosures = [];
224+ if ($ this ->viewHelperNode ->getChildNodes () !== []) {
225+ foreach ($ this ->extractFragmentViewHelperNodes ($ this ->viewHelperNode ) as $ fragmentNode ) {
226+ $ fragmentName = $ this ->extractFragmentName ($ fragmentNode );
227+ $ slotClosures [$ fragmentName ] = $ templateCompiler ->wrapChildNodesInClosure ($ fragmentNode );
228+ }
229+ if ($ slotClosures === []) {
230+ $ slotClosures [SlotViewHelper::DEFAULT_SLOT ] = $ templateCompiler ->wrapChildNodesInClosure ($ this ->viewHelperNode );
231+ }
232+ }
233+
197234 // Build up closure which renders the child nodes
235+ foreach ($ slotClosures as $ name => $ closureCode ) {
236+ $ slotClosures [$ name ] = var_export ($ name , true ) . ' => ' . $ closureCode ;
237+ }
198238 $ initializationPhpCode .= sprintf (
199- '%s = %s ; ' . chr (10 ),
200- $ renderChildrenClosureVariableName ,
201- $ templateCompiler -> wrapChildNodesInClosure ( $ this -> viewHelperNode ),
239+ '%s = [ ' . chr ( 10 ) . ' %s ' . chr ( 10 ) . ' ] ; ' . chr (10 ),
240+ $ slotClosuresVariableName ,
241+ implode ( ' , ' . chr ( 10 ), $ slotClosures ),
202242 );
203243
204244 $ initializationPhpCode .= $ accumulatedArgumentInitializationCode . chr (10 ) . $ argumentInitializationCode ;
@@ -264,4 +304,22 @@ private function getComponentDefinitionProvider(): ViewHelperResolverDelegateInt
264304 }
265305 return $ this ->viewHelperNode ->getResolverDelegate ();
266306 }
307+
308+ /**
309+ * @return ViewHelperNode[]
310+ */
311+ private function extractFragmentViewHelperNodes (ViewHelperNode $ viewHelperNode ): array
312+ {
313+ return array_filter (
314+ $ viewHelperNode ->getChildNodes (),
315+ fn (NodeInterface $ node ): bool => $ node instanceof ViewHelperNode && $ node ->getUninitializedViewHelper () instanceof FragmentViewHelper,
316+ );
317+ }
318+
319+ private function extractFragmentName (ViewHelperNode $ fragmentNode ): string
320+ {
321+ return isset ($ fragmentNode ->getArguments ()['name ' ])
322+ ? (string )$ fragmentNode ->getArguments ()['name ' ]->evaluate (new RenderingContext ())
323+ : SlotViewHelper::DEFAULT_SLOT ;
324+ }
267325}
0 commit comments