1
|
|
|
<?php |
2
|
|
|
declare(strict_types=1); |
3
|
|
|
namespace TYPO3Fluid\Fluid\Core\Rendering; |
4
|
|
|
|
5
|
|
|
use TYPO3Fluid\Fluid\Component\ComponentInterface; |
6
|
|
|
use TYPO3Fluid\Fluid\Component\Error\ChildNotFoundException; |
7
|
|
|
use TYPO3Fluid\Fluid\Core\Parser\PassthroughSourceException; |
8
|
|
|
use TYPO3Fluid\Fluid\View\Exception; |
9
|
|
|
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException; |
10
|
|
|
|
11
|
|
|
class FluidRenderer |
12
|
|
|
{ |
13
|
|
|
/** |
14
|
|
|
* Constants defining possible rendering types |
15
|
|
|
*/ |
16
|
|
|
const RENDERING_TEMPLATE = 1; |
17
|
|
|
const RENDERING_PARTIAL = 2; |
18
|
|
|
const RENDERING_LAYOUT = 3; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* The initial rendering context for this template view. |
22
|
|
|
* Due to the rendering stack, another rendering context might be active |
23
|
|
|
* at certain points while rendering the template. |
24
|
|
|
* |
25
|
|
|
* @var RenderingContextInterface |
26
|
|
|
*/ |
27
|
|
|
protected $baseRenderingContext; |
28
|
|
|
|
29
|
|
|
/** |
30
|
|
|
* Stack containing the current rendering type, the current rendering context, and the current parsed template |
31
|
|
|
* Do not manipulate directly, instead use the methods"getCurrent*()", "startRendering(...)" and "stopRendering()" |
32
|
|
|
* |
33
|
|
|
* @var array |
34
|
|
|
*/ |
35
|
|
|
protected $renderingStack = []; |
36
|
|
|
|
37
|
|
|
/** |
38
|
|
|
* @var callable|null |
39
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
40
|
|
|
*/ |
41
|
|
|
protected $baseTemplateClosure; |
42
|
|
|
|
43
|
|
|
/** |
44
|
|
|
* @var callable|null |
45
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
46
|
|
|
*/ |
47
|
|
|
protected $baseIdentifierClosure; |
48
|
|
|
|
49
|
|
|
public function __construct(RenderingContextInterface $renderingContext) |
50
|
|
|
{ |
51
|
|
|
$this->baseRenderingContext = $renderingContext; |
52
|
|
|
} |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* @param callable|null $baseTemplateClosure |
56
|
|
|
* @return FluidRenderer |
57
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
58
|
|
|
*/ |
59
|
|
|
public function setBaseTemplateClosure(?callable $baseTemplateClosure): self |
60
|
|
|
{ |
61
|
|
|
$this->baseTemplateClosure = $baseTemplateClosure; |
|
|
|
|
62
|
|
|
return $this; |
63
|
|
|
} |
64
|
|
|
|
65
|
|
|
/** |
66
|
|
|
* @param callable|null $baseIdentifierClosure |
67
|
|
|
* @return FluidRenderer |
68
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
69
|
|
|
*/ |
70
|
|
|
public function setBaseIdentifierClosure(?callable $baseIdentifierClosure): self |
71
|
|
|
{ |
72
|
|
|
$this->baseIdentifierClosure = $baseIdentifierClosure; |
|
|
|
|
73
|
|
|
return $this; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
public function getRenderingContext(): RenderingContextInterface |
77
|
|
|
{ |
78
|
|
|
return $this->baseRenderingContext; |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
public function setRenderingContext(RenderingContextInterface $renderingContext): void |
82
|
|
|
{ |
83
|
|
|
$this->baseRenderingContext = $renderingContext; |
84
|
|
|
} |
85
|
|
|
|
86
|
|
|
public function renderSource(string $source) |
87
|
|
|
{ |
88
|
|
|
$renderingContext = $this->getCurrentRenderingContext(); |
89
|
|
|
$renderingContext->getTemplatePaths()->setTemplateSource($source); |
|
|
|
|
90
|
|
|
$templateParser = $renderingContext->getTemplateParser(); |
91
|
|
|
$templatePaths = $renderingContext->getTemplatePaths(); |
|
|
|
|
92
|
|
|
try { |
93
|
|
|
$parsedTemplate = $templateParser->getOrParseAndStoreTemplate( |
94
|
|
|
sha1($source), |
95
|
|
|
function() use ($source): string { return $source; } |
96
|
|
|
); |
97
|
|
|
$parsedTemplate->getArguments()->setRenderingContext($renderingContext); |
98
|
|
|
} catch (PassthroughSourceException $error) { |
99
|
|
|
return $error->getSource(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
try { |
103
|
|
|
$layoutNameNode = $parsedTemplate->getNamedChild('layoutName'); |
104
|
|
|
$layoutName = $layoutNameNode->getArguments()->setRenderingContext($renderingContext)['name']; |
105
|
|
|
} catch (ChildNotFoundException $exception) { |
106
|
|
|
$layoutName = null; |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
if ($layoutName) { |
110
|
|
|
try { |
111
|
|
|
$parsedLayout = $templateParser->getOrParseAndStoreTemplate( |
112
|
|
|
$templatePaths->getLayoutIdentifier($layoutName), |
113
|
|
|
function(RenderingContextInterface $renderingContext) use ($layoutName): string { |
114
|
|
|
return $renderingContext->getTemplatePaths()->getLayoutSource($layoutName); |
|
|
|
|
115
|
|
|
} |
116
|
|
|
); |
117
|
|
|
$parsedLayout->getArguments()->setRenderingContext($renderingContext); |
118
|
|
|
} catch (PassthroughSourceException $error) { |
119
|
|
|
return $error->getSource(); |
120
|
|
|
} |
121
|
|
|
$this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext); |
122
|
|
|
$output = $parsedLayout->evaluate($this->baseRenderingContext); |
123
|
|
|
$this->stopRendering(); |
124
|
|
|
} else { |
125
|
|
|
$this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext); |
126
|
|
|
$output = $parsedTemplate->evaluate($this->baseRenderingContext); |
127
|
|
|
$this->stopRendering(); |
128
|
|
|
} |
129
|
|
|
return $output; |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* Loads the template source and render the template. |
134
|
|
|
* If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout. |
135
|
|
|
* |
136
|
|
|
* @param string $filePathAndName |
137
|
|
|
* @return mixed Rendered Template |
138
|
|
|
*/ |
139
|
|
|
public function renderFile(string $filePathAndName) |
140
|
|
|
{ |
141
|
|
|
$this->baseRenderingContext->getTemplatePaths()->setTemplatePathAndFilename($filePathAndName); |
|
|
|
|
142
|
|
|
$output = $this->renderSource(file_get_contents($filePathAndName)); |
143
|
|
|
$this->baseRenderingContext->getTemplatePaths()->setTemplatePathAndFilename(null); |
|
|
|
|
144
|
|
|
return $output; |
145
|
|
|
} |
146
|
|
|
|
147
|
|
|
/** |
148
|
|
|
* Renders a given section. |
149
|
|
|
* |
150
|
|
|
* Deprecated in favor of the Atoms concept which can be accessed through the ViewHelperResolver. |
151
|
|
|
* |
152
|
|
|
* A section can be rendered by resolving the appropriate (template, layout or partial-like) |
153
|
|
|
* Atom and using either getTypedChildren() or getNamedChild() to extract the desired section |
154
|
|
|
* and render it via the Component interface the return value implements. |
155
|
|
|
* |
156
|
|
|
* @param string $sectionName Name of section to render |
157
|
|
|
* @param array $variables The variables to use |
158
|
|
|
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string |
159
|
|
|
* @return mixed rendered template for the section |
160
|
|
|
* @throws ChildNotFoundException |
161
|
|
|
* @throws InvalidTemplateResourceException |
162
|
|
|
* @throws Exception |
163
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
164
|
|
|
*/ |
165
|
|
|
public function renderSection(string $sectionName, array $variables = [], bool $ignoreUnknown = false) |
166
|
|
|
{ |
167
|
|
|
if ($this->getCurrentRenderingType() === self::RENDERING_LAYOUT) { |
168
|
|
|
// in case we render a layout right now, we will render a section inside a TEMPLATE. |
169
|
|
|
$renderingTypeOnNextLevel = self::RENDERING_TEMPLATE; |
170
|
|
|
$renderingContext = $this->getCurrentRenderingContext(); |
171
|
|
|
} else { |
172
|
|
|
$renderingTypeOnNextLevel = $this->getCurrentRenderingType(); |
173
|
|
|
$renderingContext = clone $this->getCurrentRenderingContext(); |
174
|
|
|
$renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables)); |
175
|
|
|
} |
176
|
|
|
|
177
|
|
|
try { |
178
|
|
|
$parsedTemplate = $this->getCurrentParsedTemplate(); |
179
|
|
|
$section = $parsedTemplate->getNamedChild($sectionName); |
180
|
|
|
} catch (PassthroughSourceException $error) { |
181
|
|
|
return $error->getSource(); |
182
|
|
|
} catch (InvalidTemplateResourceException $error) { |
183
|
|
|
if (!$ignoreUnknown) { |
184
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
185
|
|
|
} |
186
|
|
|
return ''; |
187
|
|
|
} catch (ChildNotFoundException $error) { |
188
|
|
|
if (!$ignoreUnknown) { |
189
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
190
|
|
|
} |
191
|
|
|
return ''; |
192
|
|
|
} catch (Exception $error) { |
193
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
$this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext); |
197
|
|
|
$output = $section->evaluate($renderingContext); |
198
|
|
|
$this->stopRendering(); |
199
|
|
|
|
200
|
|
|
return $output; |
201
|
|
|
} |
202
|
|
|
|
203
|
|
|
/** |
204
|
|
|
* Renders a partial. |
205
|
|
|
* |
206
|
|
|
* Deprecated in favor of Atoms concept which can be accessed through the |
207
|
|
|
* ViewHelperResolver to fetch and render a (partial-like) Atom directly. |
208
|
|
|
* |
209
|
|
|
* @param string $partialName |
210
|
|
|
* @param string|null $sectionName |
211
|
|
|
* @param array $variables |
212
|
|
|
* @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string |
213
|
|
|
* @return mixed |
214
|
|
|
* @throws ChildNotFoundException |
215
|
|
|
* @throws InvalidTemplateResourceException |
216
|
|
|
* @throws Exception |
217
|
|
|
* @deprecated Will be removed in Fluid 4.0 |
218
|
|
|
*/ |
219
|
|
|
public function renderPartial(string $partialName, ?string $sectionName, array $variables, bool $ignoreUnknown = false) |
220
|
|
|
{ |
221
|
|
|
$templatePaths = $this->baseRenderingContext->getTemplatePaths(); |
|
|
|
|
222
|
|
|
$renderingContext = clone $this->getCurrentRenderingContext(); |
223
|
|
|
try { |
224
|
|
|
$parsedPartial = $renderingContext->getTemplateParser()->getOrParseAndStoreTemplate( |
225
|
|
|
$templatePaths->getPartialIdentifier($partialName), |
226
|
|
|
function (RenderingContextInterface $renderingContext) use ($partialName): string { |
227
|
|
|
return $renderingContext->getTemplatePaths()->getPartialSource($partialName); |
|
|
|
|
228
|
|
|
} |
229
|
|
|
); |
230
|
|
|
$parsedPartial->getArguments()->setRenderingContext($renderingContext); |
231
|
|
|
} catch (PassthroughSourceException $error) { |
232
|
|
|
return $error->getSource(); |
233
|
|
|
} catch (InvalidTemplateResourceException $error) { |
234
|
|
|
if (!$ignoreUnknown) { |
235
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
236
|
|
|
} |
237
|
|
|
return ''; |
238
|
|
|
} catch (ChildNotFoundException $error) { |
239
|
|
|
if (!$ignoreUnknown) { |
240
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
241
|
|
|
} |
242
|
|
|
return ''; |
243
|
|
|
} catch (Exception $error) { |
244
|
|
|
return $renderingContext->getErrorHandler()->handleViewError($error); |
|
|
|
|
245
|
|
|
} |
246
|
|
|
$this->startRendering(self::RENDERING_PARTIAL, $parsedPartial, $renderingContext); |
247
|
|
|
if ($sectionName !== null) { |
248
|
|
|
$output = $this->renderSection($sectionName, $variables, $ignoreUnknown); |
|
|
|
|
249
|
|
|
} else { |
250
|
|
|
$renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables)); |
251
|
|
|
$output = $parsedPartial->evaluate($renderingContext); |
252
|
|
|
} |
253
|
|
|
$this->stopRendering(); |
254
|
|
|
return $output; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Start a new nested rendering. Pushes the given information onto the $renderingStack. |
259
|
|
|
* |
260
|
|
|
* @param integer $type one of the RENDERING_* constants |
261
|
|
|
* @param ComponentInterface $template |
262
|
|
|
* @param RenderingContextInterface $context |
263
|
|
|
* @return void |
264
|
|
|
*/ |
265
|
|
|
protected function startRendering(int $type, ComponentInterface $template, RenderingContextInterface $context): void |
266
|
|
|
{ |
267
|
|
|
array_push($this->renderingStack, ['type' => $type, 'parsedTemplate' => $template, 'renderingContext' => $context]); |
268
|
|
|
} |
269
|
|
|
|
270
|
|
|
/** |
271
|
|
|
* Stops the current rendering. Removes one element from the $renderingStack. Make sure to always call this |
272
|
|
|
* method pair-wise with startRendering(). |
273
|
|
|
* |
274
|
|
|
* @return void |
275
|
|
|
*/ |
276
|
|
|
protected function stopRendering(): void |
277
|
|
|
{ |
278
|
|
|
array_pop($this->renderingStack); |
279
|
|
|
} |
280
|
|
|
|
281
|
|
|
protected function getCurrentRenderingType(): int |
282
|
|
|
{ |
283
|
|
|
$currentRendering = end($this->renderingStack); |
284
|
|
|
return $currentRendering['type'] ? $currentRendering['type'] : self::RENDERING_TEMPLATE; |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
protected function getCurrentParsedTemplate(): ComponentInterface |
288
|
|
|
{ |
289
|
|
|
$currentRendering = end($this->renderingStack); |
290
|
|
|
$renderingContext = $this->getCurrentRenderingContext(); |
291
|
|
|
$parsedTemplate = $currentRendering['parsedTemplate'] ?? null; |
292
|
|
|
if ($parsedTemplate) { |
293
|
|
|
return $parsedTemplate; |
294
|
|
|
} |
295
|
|
|
$templatePaths = $renderingContext->getTemplatePaths(); |
|
|
|
|
296
|
|
|
$templateParser = $renderingContext->getTemplateParser(); |
297
|
|
|
|
298
|
|
|
// Retrieve the current parsed template, which happens if the renderSection() method was called as first entry |
299
|
|
|
// method (as opposed to rendering through renderFile / renderSource which causes stack entries which in turn |
300
|
|
|
// causes this method to return early). |
301
|
|
|
// Support for the closures will be removed in Fluid 4.0 since they are a temporary measure. |
302
|
|
|
$parsedTemplate = $templateParser->getOrParseAndStoreTemplate( |
303
|
|
|
$this->baseIdentifierClosure ? call_user_func($this->baseIdentifierClosure) : $templatePaths->getTemplateIdentifier('Default', 'Default'), |
|
|
|
|
304
|
|
|
$this->baseTemplateClosure ?? function(RenderingContextInterface $renderingContext): string { |
|
|
|
|
305
|
|
|
return $renderingContext->getTemplatePaths()->getTemplateSource('Default', 'Default'); |
|
|
|
|
306
|
|
|
} |
307
|
|
|
); |
308
|
|
|
return $parsedTemplate; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
protected function getCurrentRenderingContext(): RenderingContextInterface |
312
|
|
|
{ |
313
|
|
|
$currentRendering = end($this->renderingStack); |
314
|
|
|
return $currentRendering['renderingContext'] ? $currentRendering['renderingContext'] : $this->baseRenderingContext; |
315
|
|
|
} |
316
|
|
|
} |
This property has been deprecated. The supplier of the class has supplied an explanatory message.
The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.