[BUGFIX] Respect format when rendering partial
[Packages/TYPO3.CMS.git] / typo3 / sysext / fluid / Classes / View / AbstractTemplateView.php
1 <?php
2 namespace TYPO3\CMS\Fluid\View;
3
4 /* *
5 * This script is backported from the TYPO3 Flow package "TYPO3.Fluid". *
6 * *
7 * It is free software; you can redistribute it and/or modify it under *
8 * the terms of the GNU Lesser General Public License, either version 3 *
9 * of the License, or (at your option) any later version. *
10 * *
11 * The TYPO3 project - inspiring people to share! *
12 * */
13
14 /**
15 * Abstract Fluid Template View.
16 *
17 * Contains the fundamental methods which any Fluid based template view needs.
18 */
19 abstract class AbstractTemplateView implements \TYPO3\CMS\Extbase\Mvc\View\ViewInterface {
20
21 /**
22 * Constants defining possible rendering types
23 */
24 const RENDERING_TEMPLATE = 1;
25 const RENDERING_PARTIAL = 2;
26 const RENDERING_LAYOUT = 3;
27
28 /**
29 * @var \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext
30 */
31 protected $controllerContext;
32
33 /**
34 * @var \TYPO3\CMS\Extbase\Object\ObjectManagerInterface
35 * @inject
36 */
37 protected $objectManager;
38
39 /**
40 * @var \TYPO3\CMS\Fluid\Core\Parser\TemplateParser
41 * @inject
42 */
43 protected $templateParser;
44
45 /**
46 * @var \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler
47 */
48 protected $templateCompiler;
49
50 /**
51 * The initial rendering context for this template view.
52 * Due to the rendering stack, another rendering context might be active
53 * at certain points while rendering the template.
54 *
55 * @var \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface
56 */
57 protected $baseRenderingContext;
58
59 /**
60 * Stack containing the current rendering type, the current rendering context, and the current parsed template
61 * Do not manipulate directly, instead use the methods"getCurrent*()", "startRendering(...)" and "stopRendering()"
62 *
63 * @var array
64 */
65 protected $renderingStack = array();
66
67 /**
68 * Partial Name -> Partial Identifier cache.
69 * This is a performance optimization, effective when rendering a
70 * single partial many times.
71 *
72 * @var array
73 */
74 protected $partialIdentifierCache = array();
75
76 /**
77 * @param \TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler
78 * @return void
79 */
80 public function injectTemplateCompiler(\TYPO3\CMS\Fluid\Core\Compiler\TemplateCompiler $templateCompiler) {
81 $this->templateCompiler = $templateCompiler;
82 $this->templateCompiler->setTemplateCache(\TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Core\Cache\CacheManager::class)->getCache('fluid_template'));
83 }
84
85 /**
86 * Injects a fresh rendering context
87 *
88 * @param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
89 * @return void
90 */
91 public function setRenderingContext(\TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext) {
92 $this->baseRenderingContext = $renderingContext;
93 $this->baseRenderingContext->getViewHelperVariableContainer()->setView($this);
94 $this->controllerContext = $renderingContext->getControllerContext();
95 }
96
97 /**
98 * Sets the current controller context
99 *
100 * @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext Controller context which is available inside the view
101 * @return void
102 * @api
103 */
104 public function setControllerContext(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext) {
105 $this->controllerContext = $controllerContext;
106 }
107
108 public function initializeView() {
109 }
110 // Here, the backporter can insert the initializeView method, which is needed for Fluid v4.
111
112 /**
113 * Assign a value to the variable container.
114 *
115 * @param string $key The key of a view variable to set
116 * @param mixed $value The value of the view variable
117 * @return \TYPO3\CMS\Fluid\View\AbstractTemplateView the instance of this view to allow chaining
118 * @api
119 */
120 public function assign($key, $value) {
121 $templateVariableContainer = $this->baseRenderingContext->getTemplateVariableContainer();
122 if ($templateVariableContainer->exists($key)) {
123 $templateVariableContainer->remove($key);
124 }
125 $templateVariableContainer->add($key, $value);
126 return $this;
127 }
128
129 /**
130 * Assigns multiple values to the JSON output.
131 * However, only the key "value" is accepted.
132 *
133 * @param array $values Keys and values - only a value with key "value" is considered
134 * @return \TYPO3\CMS\Fluid\View\AbstractTemplateView the instance of this view to allow chaining
135 * @api
136 */
137 public function assignMultiple(array $values) {
138 $templateVariableContainer = $this->baseRenderingContext->getTemplateVariableContainer();
139 foreach ($values as $key => $value) {
140 if ($templateVariableContainer->exists($key)) {
141 $templateVariableContainer->remove($key);
142 }
143 $templateVariableContainer->add($key, $value);
144 }
145 return $this;
146 }
147
148 /**
149 * Loads the template source and render the template.
150 * If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout.
151 *
152 * @param string $actionName If set, the view of the specified action will be rendered instead. Default is the action specified in the Request object
153 * @return string Rendered Template
154 * @api
155 */
156 public function render($actionName = NULL) {
157 $this->baseRenderingContext->setControllerContext($this->controllerContext);
158 $this->templateParser->setConfiguration($this->buildParserConfiguration());
159
160 $templateIdentifier = $this->getTemplateIdentifier($actionName);
161 if ($this->templateCompiler->has($templateIdentifier)) {
162 $parsedTemplate = $this->templateCompiler->get($templateIdentifier);
163 } else {
164 $parsedTemplate = $this->templateParser->parse($this->getTemplateSource($actionName));
165 if ($parsedTemplate->isCompilable()) {
166 $this->templateCompiler->store($templateIdentifier, $parsedTemplate);
167 }
168 }
169
170 if ($parsedTemplate->hasLayout()) {
171 $layoutName = $parsedTemplate->getLayoutName($this->baseRenderingContext);
172 $layoutIdentifier = $this->getLayoutIdentifier($layoutName);
173 if ($this->templateCompiler->has($layoutIdentifier)) {
174 $parsedLayout = $this->templateCompiler->get($layoutIdentifier);
175 } else {
176 $parsedLayout = $this->templateParser->parse($this->getLayoutSource($layoutName));
177 if ($parsedLayout->isCompilable()) {
178 $this->templateCompiler->store($layoutIdentifier, $parsedLayout);
179 }
180 }
181 $this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext);
182 $output = $parsedLayout->render($this->baseRenderingContext);
183 $this->stopRendering();
184 } else {
185 $this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext);
186 $output = $parsedTemplate->render($this->baseRenderingContext);
187 $this->stopRendering();
188 }
189
190 return $output;
191 }
192
193 /**
194 * Renders a given section.
195 *
196 * @param string $sectionName Name of section to render
197 * @param array $variables The variables to use
198 * @param bool $ignoreUnknown Ignore an unknown section and just return an empty string
199 * @return string rendered template for the section
200 * @throws \TYPO3\CMS\Fluid\View\Exception\InvalidSectionException
201 */
202 public function renderSection($sectionName, array $variables, $ignoreUnknown = FALSE) {
203 $renderingContext = $this->getCurrentRenderingContext();
204 if ($this->getCurrentRenderingType() === self::RENDERING_LAYOUT) {
205 // in case we render a layout right now, we will render a section inside a TEMPLATE.
206 $renderingTypeOnNextLevel = self::RENDERING_TEMPLATE;
207 } else {
208 $variableContainer = $this->objectManager->get(\TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer::class, $variables);
209 $renderingContext = clone $renderingContext;
210 $renderingContext->injectTemplateVariableContainer($variableContainer);
211 $renderingTypeOnNextLevel = $this->getCurrentRenderingType();
212 }
213
214 $parsedTemplate = $this->getCurrentParsedTemplate();
215
216 if ($parsedTemplate->isCompiled()) {
217 $methodNameOfSection = 'section_' . sha1($sectionName);
218 if ($ignoreUnknown && !method_exists($parsedTemplate, $methodNameOfSection)) {
219 return '';
220 }
221 $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
222 $output = $parsedTemplate->{$methodNameOfSection}($renderingContext);
223 $this->stopRendering();
224 } else {
225 $sections = $parsedTemplate->getVariableContainer()->get('sections');
226 if (!array_key_exists($sectionName, $sections)) {
227 $controllerObjectName = $this->controllerContext->getRequest()->getControllerObjectName();
228 if ($ignoreUnknown) {
229 return '';
230 } else {
231 throw new \TYPO3\CMS\Fluid\View\Exception\InvalidSectionException(sprintf('Could not render unknown section "%s" in %s used by %s.', $sectionName, get_class($this), $controllerObjectName), 1227108982);
232 }
233 }
234 $section = $sections[$sectionName];
235
236 $renderingContext->getViewHelperVariableContainer()->add(\TYPO3\CMS\Fluid\ViewHelpers\SectionViewHelper::class, 'isCurrentlyRenderingSection', 'TRUE');
237
238 $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
239 $output = $section->evaluate($renderingContext);
240 $this->stopRendering();
241 }
242
243 return $output;
244 }
245
246 /**
247 * Renders a partial.
248 *
249 * @param string $partialName
250 * @param string $sectionName
251 * @param array $variables
252 * @param \TYPO3\CMS\Fluid\Core\ViewHelper\ViewHelperVariableContainer $viewHelperVariableContainer the View Helper Variable container to use.
253 * @return string
254 */
255 public function renderPartial($partialName, $sectionName, array $variables) {
256 $partialNameWithFormat = $partialName . '.' . $this->controllerContext->getRequest()->getFormat();
257 if (!isset($this->partialIdentifierCache[$partialNameWithFormat])) {
258 $this->partialIdentifierCache[$partialNameWithFormat] = $this->getPartialIdentifier($partialName);
259 }
260 $partialIdentifier = $this->partialIdentifierCache[$partialNameWithFormat];
261
262 if ($this->templateCompiler->has($partialIdentifier)) {
263 $parsedPartial = $this->templateCompiler->get($partialIdentifier);
264 } else {
265 $parsedPartial = $this->templateParser->parse($this->getPartialSource($partialName));
266 if ($parsedPartial->isCompilable()) {
267 $this->templateCompiler->store($partialIdentifier, $parsedPartial);
268 }
269 }
270
271 $variableContainer = $this->objectManager->get(\TYPO3\CMS\Fluid\Core\ViewHelper\TemplateVariableContainer::class, $variables);
272 $renderingContext = clone $this->getCurrentRenderingContext();
273 $renderingContext->injectTemplateVariableContainer($variableContainer);
274
275 $this->startRendering(self::RENDERING_PARTIAL, $parsedPartial, $renderingContext);
276 if ($sectionName !== NULL) {
277 $output = $this->renderSection($sectionName, $variables);
278 } else {
279 $output = $parsedPartial->render($renderingContext);
280 }
281 $this->stopRendering();
282
283 return $output;
284 }
285
286 /**
287 * Ensures the given templatePath gets the file name in UpperCamelCase
288 *
289 * @param string $templatePath A file name or a relative path
290 * @return string
291 */
292 protected function ucFileNameInPath($templatePath) {
293 if (strpos($templatePath, '/') > 0) {
294 $pathParts = explode('/', $templatePath);
295 $index = count($pathParts) - 1;
296 $pathParts[$index] = ucfirst($pathParts[$index]);
297
298 $upperCasedTemplateName = implode('/', $pathParts);
299 } else {
300 $upperCasedTemplateName = ucfirst($templatePath);
301 }
302 return $upperCasedTemplateName;
303 }
304
305 /**
306 * Wrapper method for is_file function for testing reasons
307 *
308 * @param string $filePath
309 * @return bool
310 */
311 protected function testFileExistence($filePath) {
312 return is_file($filePath);
313 }
314
315 /**
316 * Returns a unique identifier for the resolved template file.
317 * This identifier is based on the template path and last modification date
318 *
319 * @param string $actionName Name of the action. If NULL, will be taken from request.
320 * @return string template identifier
321 */
322 abstract protected function getTemplateIdentifier($actionName = NULL);
323
324 /**
325 * Resolve the template path and filename for the given action. If $actionName
326 * is NULL, looks into the current request.
327 *
328 * @param string $actionName Name of the action. If NULL, will be taken from request.
329 * @return string Full path to template
330 * @throws \TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException in case the template was not found
331 */
332 abstract protected function getTemplateSource($actionName = NULL);
333
334 /**
335 * Returns a unique identifier for the resolved layout file.
336 * This identifier is based on the template path and last modification date
337 *
338 * @param string $layoutName The name of the layout
339 * @return string layout identifier
340 */
341 abstract protected function getLayoutIdentifier($layoutName = 'Default');
342
343 /**
344 * Resolve the path and file name of the layout file, based on
345 * $this->layoutPathAndFilename and $this->layoutPathAndFilenamePattern.
346 *
347 * In case a layout has already been set with setLayoutPathAndFilename(),
348 * this method returns that path, otherwise a path and filename will be
349 * resolved using the layoutPathAndFilenamePattern.
350 *
351 * @param string $layoutName Name of the layout to use. If none given, use "Default"
352 * @return string Path and filename of layout file
353 * @throws \TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException
354 */
355 abstract protected function getLayoutSource($layoutName = 'Default');
356
357 /**
358 * Returns a unique identifier for the resolved partial file.
359 * This identifier is based on the template path and last modification date
360 *
361 * @param string $partialName The name of the partial
362 * @return string partial identifier
363 */
364 abstract protected function getPartialIdentifier($partialName);
365
366 /**
367 * Figures out which partial to use.
368 *
369 * @param string $partialName The name of the partial
370 * @return string the full path which should be used. The path definitely exists.
371 * @throws \TYPO3\CMS\Fluid\View\Exception\InvalidTemplateResourceException
372 */
373 abstract protected function getPartialSource($partialName);
374
375 /**
376 * Build parser configuration
377 *
378 * @return \TYPO3\CMS\Fluid\Core\Parser\Configuration
379 */
380 protected function buildParserConfiguration() {
381 $parserConfiguration = $this->objectManager->get(\TYPO3\CMS\Fluid\Core\Parser\Configuration::class);
382 if ($this->controllerContext->getRequest()->getFormat() === 'html') {
383 $parserConfiguration->addInterceptor($this->objectManager->get(\TYPO3\CMS\Fluid\Core\Parser\Interceptor\Escape::class));
384 }
385 return $parserConfiguration;
386 }
387
388 /**
389 * Start a new nested rendering. Pushes the given information onto the $renderingStack.
390 *
391 * @param int $type one of the RENDERING_* constants
392 * @param \TYPO3\CMS\Fluid\Core\Parser\ParsedTemplateInterface $parsedTemplate
393 * @param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
394 * @return void
395 */
396 protected function startRendering($type, \TYPO3\CMS\Fluid\Core\Parser\ParsedTemplateInterface $parsedTemplate, \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext) {
397 array_push($this->renderingStack, array('type' => $type, 'parsedTemplate' => $parsedTemplate, 'renderingContext' => $renderingContext));
398 }
399
400 /**
401 * Stops the current rendering. Removes one element from the $renderingStack. Make sure to always call this
402 * method pair-wise with startRendering().
403 *
404 * @return void
405 */
406 protected function stopRendering() {
407 array_pop($this->renderingStack);
408 }
409
410 /**
411 * Get the current rendering type.
412 *
413 * @return int one of RENDERING_* constants
414 */
415 protected function getCurrentRenderingType() {
416 $currentRendering = end($this->renderingStack);
417 return $currentRendering['type'];
418 }
419
420 /**
421 * Get the parsed template which is currently being rendered.
422 *
423 * @return \TYPO3\CMS\Fluid\Core\Parser\ParsedTemplateInterface
424 */
425 protected function getCurrentParsedTemplate() {
426 $currentRendering = end($this->renderingStack);
427 return $currentRendering['parsedTemplate'];
428 }
429
430 /**
431 * Get the rendering context which is currently used.
432 *
433 * @return \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface
434 */
435 protected function getCurrentRenderingContext() {
436 $currentRendering = end($this->renderingStack);
437 return $currentRendering['renderingContext'];
438 }
439
440 /**
441 * Tells if the view implementation can render the view for the given context.
442 *
443 * By default we assume that the view implementation can handle all kinds of
444 * contexts. Override this method if that is not the case.
445 *
446 * @param \TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext Controller context which is available inside the view
447 * @return bool TRUE if the view has something useful to display, otherwise FALSE
448 * @api
449 */
450 public function canRender(\TYPO3\CMS\Extbase\Mvc\Controller\ControllerContext $controllerContext) {
451 return TRUE;
452 }
453
454 }