Completed
Pull Request — master (#347)
by Claus
01:49
created

AbstractTemplateView::renderPartial()   C

Complexity

Conditions 8
Paths 8

Size

Total Lines 36
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 28
nc 8
nop 4
dl 0
loc 36
rs 5.3846
c 0
b 0
f 0
1
<?php
2
namespace TYPO3Fluid\Fluid\View;
3
4
/*
5
 * This file belongs to the package "TYPO3 Fluid".
6
 * See LICENSE.txt that was shipped with this package.
7
 */
8
9
use TYPO3Fluid\Fluid\Core\Cache\FluidCacheInterface;
10
use TYPO3Fluid\Fluid\Core\Parser\ParsedTemplateInterface;
11
use TYPO3Fluid\Fluid\Core\Parser\PassthroughSourceException;
12
use TYPO3Fluid\Fluid\Core\Parser\SyntaxTree\ViewHelperNode;
13
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContext;
14
use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
15
use TYPO3Fluid\Fluid\Core\ViewHelper\ViewHelperResolver;
16
use TYPO3Fluid\Fluid\View\Exception\InvalidSectionException;
17
use TYPO3Fluid\Fluid\View\Exception\InvalidTemplateResourceException;
18
use TYPO3Fluid\Fluid\ViewHelpers\SectionViewHelper;
19
20
/**
21
 * Abstract Fluid Template View.
22
 *
23
 * Contains the fundamental methods which any Fluid based template view needs.
24
 */
25
abstract class AbstractTemplateView extends AbstractView
26
{
27
28
    /**
29
     * Constants defining possible rendering types
30
     */
31
    const RENDERING_TEMPLATE = 1;
32
    const RENDERING_PARTIAL = 2;
33
    const RENDERING_LAYOUT = 3;
34
35
    /**
36
     * The initial rendering context for this template view.
37
     * Due to the rendering stack, another rendering context might be active
38
     * at certain points while rendering the template.
39
     *
40
     * @var RenderingContextInterface
41
     */
42
    protected $baseRenderingContext;
43
44
    /**
45
     * Stack containing the current rendering type, the current rendering context, and the current parsed template
46
     * Do not manipulate directly, instead use the methods"getCurrent*()", "startRendering(...)" and "stopRendering()"
47
     *
48
     * @var array
49
     */
50
    protected $renderingStack = [];
51
52
    /**
53
     * Constructor
54
     *
55
     * @param null|RenderingContextInterface $context
56
     */
57
    public function __construct(RenderingContextInterface $context = null)
58
    {
59
        if (!$context) {
60
            $context = new RenderingContext($this);
61
            $context->setControllerName('Default');
62
            $context->setControllerAction('Default');
63
        }
64
        $this->setRenderingContext($context);
65
    }
66
67
    /**
68
     * Initialize the RenderingContext. This method can be overridden in your
69
     * View implementation to manipulate the rendering context *before* it is
70
     * passed during rendering.
71
     */
72
    public function initializeRenderingContext()
73
    {
74
        $this->baseRenderingContext->getViewHelperVariableContainer()->setView($this);
75
    }
76
77
    /**
78
     * Sets the cache to use in RenderingContext.
79
     *
80
     * @param FluidCacheInterface $cache
81
     * @return void
82
     */
83
    public function setCache(FluidCacheInterface $cache)
84
    {
85
        $this->baseRenderingContext->setCache($cache);
86
    }
87
88
    /**
89
     * Gets the TemplatePaths instance from RenderingContext
90
     *
91
     * @return TemplatePaths
92
     */
93
    public function getTemplatePaths()
94
    {
95
        return $this->baseRenderingContext->getTemplatePaths();
96
    }
97
98
    /**
99
     * Gets the ViewHelperResolver instance from RenderingContext
100
     *
101
     * @return ViewHelperResolver
102
     */
103
    public function getViewHelperResolver()
104
    {
105
        return $this->baseRenderingContext->getViewHelperResolver();
106
    }
107
108
    /**
109
     * Gets the RenderingContext used by the View
110
     *
111
     * @return RenderingContextInterface
112
     */
113
    public function getRenderingContext()
114
    {
115
        return $this->baseRenderingContext;
116
    }
117
118
    /**
119
     * Injects a fresh rendering context
120
     *
121
     * @param RenderingContextInterface $renderingContext
122
     * @return void
123
     */
124
    public function setRenderingContext(RenderingContextInterface $renderingContext)
125
    {
126
        $this->baseRenderingContext = $renderingContext;
127
        $this->initializeRenderingContext();
128
    }
129
130
    /**
131
     * Assign a value to the variable container.
132
     *
133
     * @param string $key The key of a view variable to set
134
     * @param mixed $value The value of the view variable
135
     * @return $this
136
     * @api
137
     */
138
    public function assign($key, $value)
139
    {
140
        $this->baseRenderingContext->getVariableProvider()->add($key, $value);
141
        return $this;
142
    }
143
144
    /**
145
     * Assigns multiple values to the JSON output.
146
     * However, only the key "value" is accepted.
147
     *
148
     * @param array $values Keys and values - only a value with key "value" is considered
149
     * @return $this
150
     * @api
151
     */
152
    public function assignMultiple(array $values)
153
    {
154
        $templateVariableContainer = $this->baseRenderingContext->getVariableProvider();
155
        foreach ($values as $key => $value) {
156
            $templateVariableContainer->add($key, $value);
157
        }
158
        return $this;
159
    }
160
161
    /**
162
     * Loads the template source and render the template.
163
     * If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout.
164
     *
165
     * @param string|null $actionName If set, this action's template will be rendered instead of the one defined in the context.
166
     * @return string Rendered Template
167
     * @api
168
     */
169
    public function render($actionName = null)
170
    {
171
        $renderingContext = $this->getCurrentRenderingContext();
172
        $templateParser = $renderingContext->getTemplateParser();
173
        $templatePaths = $renderingContext->getTemplatePaths();
174
        if ($actionName) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $actionName of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
175
            $actionName = ucfirst($actionName);
176
            $renderingContext->setControllerAction($actionName);
177
        }
178
        try {
179
            $parsedTemplate = $this->getCurrentParsedTemplate();
180
        } catch (PassthroughSourceException $error) {
181
            return $error->getSource();
182
        }
183
184
        if (!$parsedTemplate->hasLayout()) {
185
            $this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext);
186
            $output = $parsedTemplate->render($this->baseRenderingContext);
187
            $this->stopRendering();
188
        } else {
189
            $layoutName = $parsedTemplate->getLayoutName($this->baseRenderingContext);
190
            try {
191
                $parsedLayout = $templateParser->getOrParseAndStoreTemplate(
192
                    $templatePaths->getLayoutIdentifier($layoutName),
193
                    function($parent, TemplatePaths $paths) use ($layoutName) {
194
                        return $paths->getLayoutSource($layoutName);
195
                    }
196
                );
197
            } catch (PassthroughSourceException $error) {
198
                return $error->getSource();
199
            }
200
            $this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext);
201
            $output = $parsedLayout->render($this->baseRenderingContext);
202
            $this->stopRendering();
203
        }
204
205
        return $output;
206
    }
207
208
    /**
209
     * Renders a given section.
210
     *
211
     * @param string $sectionName Name of section to render
212
     * @param array $variables The variables to use
213
     * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
214
     * @return string rendered template for the section
215
     * @throws InvalidSectionException
216
     */
217
    public function renderSection($sectionName, array $variables = [], $ignoreUnknown = false)
218
    {
219
        $renderingContext = $this->getCurrentRenderingContext();
220
221
        if ($this->getCurrentRenderingType() === self::RENDERING_LAYOUT) {
222
            // in case we render a layout right now, we will render a section inside a TEMPLATE.
223
            $renderingTypeOnNextLevel = self::RENDERING_TEMPLATE;
224
        } else {
225
            $renderingContext = clone $renderingContext;
226
            $renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables));
227
            $renderingTypeOnNextLevel = $this->getCurrentRenderingType();
228
        }
229
230
        try {
231
            $parsedTemplate = $this->getCurrentParsedTemplate();
232
        } catch (PassthroughSourceException $error) {
233
            return $error->getSource();
234
        } catch (InvalidTemplateResourceException $error) {
235
            if (!$ignoreUnknown) {
236
                return $renderingContext->getErrorHandler()->handleViewError($error);
237
            }
238
            return '';
239
        } catch (InvalidSectionException $error) {
240
            if (!$ignoreUnknown) {
241
                return $renderingContext->getErrorHandler()->handleViewError($error);
242
            }
243
            return '';
244
        } catch (Exception $error) {
245
            return $renderingContext->getErrorHandler()->handleViewError($error);
246
        }
247
248
        if ($parsedTemplate->isCompiled()) {
249
            $methodNameOfSection = 'section_' . sha1($sectionName);
250 View Code Duplication
            if (!method_exists($parsedTemplate, $methodNameOfSection)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
251
                if ($ignoreUnknown) {
252
                    return '';
253
                } else {
254
                    return $renderingContext->getErrorHandler()->handleViewError(
255
                        new InvalidSectionException('Section "' . $sectionName . '" does not exist.')
256
                    );
257
                }
258
            }
259
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
260
            $output = $parsedTemplate->$methodNameOfSection($renderingContext);
261
            $this->stopRendering();
262
        } else {
263
            $sections = $parsedTemplate->getVariableContainer()->get('1457379500_sections');
264 View Code Duplication
            if (!isset($sections[$sectionName])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
265
                if ($ignoreUnknown) {
266
                    return '';
267
                }
268
                return $renderingContext->getErrorHandler()->handleViewError(
269
                    new InvalidSectionException('Section "' . $sectionName . '" does not exist.')
270
                );
271
            }
272
            /** @var $section ViewHelperNode */
273
            $section = $sections[$sectionName];
274
275
            $renderingContext->getViewHelperVariableContainer()->add(
276
                SectionViewHelper::class,
277
                'isCurrentlyRenderingSection',
278
                true
279
            );
280
281
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
282
            $output = $section->evaluate($renderingContext);
283
            $this->stopRendering();
284
        }
285
286
        return $output;
287
    }
288
289
    /**
290
     * Renders a partial.
291
     *
292
     * @param string $partialName
293
     * @param string $sectionName
294
     * @param array $variables
295
     * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
296
     * @return string
297
     */
298
    public function renderPartial($partialName, $sectionName, array $variables, $ignoreUnknown = false)
299
    {
300
        $templatePaths = $this->baseRenderingContext->getTemplatePaths();
301
        $renderingContext = clone $this->getCurrentRenderingContext();
302
        try {
303
            $parsedPartial = $renderingContext->getTemplateParser()->getOrParseAndStoreTemplate(
304
                $templatePaths->getPartialIdentifier($partialName),
305
                function ($parent, TemplatePaths $paths) use ($partialName) {
306
                    return $paths->getPartialSource($partialName);
307
                }
308
            );
309
        } catch (PassthroughSourceException $error) {
310
            return $error->getSource();
311
        } catch (InvalidTemplateResourceException $error) {
312
            if (!$ignoreUnknown) {
313
                return $renderingContext->getErrorHandler()->handleViewError($error);
314
            }
315
            return '';
316
        } catch (InvalidSectionException $error) {
317
            if (!$ignoreUnknown) {
318
                return $renderingContext->getErrorHandler()->handleViewError($error);
319
            }
320
            return '';
321
        } catch (Exception $error) {
322
            return $renderingContext->getErrorHandler()->handleViewError($error);
323
        }
324
        $renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables));
325
        $this->startRendering(self::RENDERING_PARTIAL, $parsedPartial, $renderingContext);
326
        if ($sectionName !== null) {
327
            $output = $this->renderSection($sectionName, $variables, $ignoreUnknown);
328
        } else {
329
            $output = $parsedPartial->render($renderingContext);
330
        }
331
        $this->stopRendering();
332
        return $output;
333
    }
334
335
    /**
336
     * Start a new nested rendering. Pushes the given information onto the $renderingStack.
337
     *
338
     * @param integer $type one of the RENDERING_* constants
339
     * @param ParsedTemplateInterface $template
340
     * @param RenderingContextInterface $context
341
     * @return void
342
     */
343
    protected function startRendering($type, ParsedTemplateInterface $template, RenderingContextInterface $context)
344
    {
345
        array_push($this->renderingStack, ['type' => $type, 'parsedTemplate' => $template, 'renderingContext' => $context]);
346
    }
347
348
    /**
349
     * Stops the current rendering. Removes one element from the $renderingStack. Make sure to always call this
350
     * method pair-wise with startRendering().
351
     *
352
     * @return void
353
     */
354
    protected function stopRendering()
355
    {
356
        $this->getCurrentRenderingContext()->getTemplateCompiler()->reset();
357
        array_pop($this->renderingStack);
358
    }
359
360
    /**
361
     * Get the current rendering type.
362
     *
363
     * @return integer one of RENDERING_* constants
364
     */
365
    protected function getCurrentRenderingType()
366
    {
367
        $currentRendering = end($this->renderingStack);
368
        return $currentRendering['type'] ? $currentRendering['type'] : self::RENDERING_TEMPLATE;
369
    }
370
371
    /**
372
     * Get the parsed template which is currently being rendered or compiled.
373
     *
374
     * @return ParsedTemplateInterface
375
     */
376
    protected function getCurrentParsedTemplate()
377
    {
378
        $currentRendering = end($this->renderingStack);
379
        $renderingContext = $this->getCurrentRenderingContext();
380
        $parsedTemplate = $currentRendering['parsedTemplate'] ? $currentRendering['parsedTemplate'] : $renderingContext->getTemplateCompiler()->getCurrentlyProcessingState();
381
        if ($parsedTemplate) {
382
            return $parsedTemplate;
383
        }
384
        $templatePaths = $renderingContext->getTemplatePaths();
385
        $templateParser = $renderingContext->getTemplateParser();
386
        $controllerName = $renderingContext->getControllerName();
387
        $actionName = $renderingContext->getControllerAction();
388
        $parsedTemplate = $templateParser->getOrParseAndStoreTemplate(
389
            $templatePaths->getTemplateIdentifier($controllerName, $actionName),
390
            function($parent, TemplatePaths $paths) use ($controllerName, $actionName, $renderingContext) {
391
                return $paths->getTemplateSource($controllerName, $actionName);
392
            }
393
        );
394
        if ($parsedTemplate->isCompiled()) {
395
            $parsedTemplate->addCompiledNamespaces($this->baseRenderingContext);
396
        }
397
        return $parsedTemplate;
398
    }
399
400
    /**
401
     * Get the rendering context which is currently used.
402
     *
403
     * @return RenderingContextInterface
404
     */
405
    protected function getCurrentRenderingContext()
406
    {
407
        $currentRendering = end($this->renderingStack);
408
        return $currentRendering['renderingContext'] ? $currentRendering['renderingContext'] : $this->baseRenderingContext;
409
    }
410
}
411