Completed
Push — master ( b4adf1...7cbf62 )
by Marc
02:01
created

AbstractTemplateView::getCurrentRenderingContext()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 5
rs 9.4285
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\InvalidTemplateResourceException;
17
use TYPO3Fluid\Fluid\ViewHelpers\SectionViewHelper;
18
use TYPO3Fluid\Fluid\View\Exception\InvalidSectionException;
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 (Exception $error) {
235
            return $renderingContext->getErrorHandler()->handleViewError($error);
236
        }
237
238
        if ($parsedTemplate->isCompiled()) {
239
            $methodNameOfSection = 'section_' . sha1($sectionName);
240 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...
241
                if ($ignoreUnknown) {
242
                    return '';
243
                } else {
244
                    return $renderingContext->getErrorHandler()->handleViewError(
245
                        new InvalidSectionException('Section "' . $sectionName . '" does not exist.')
246
                    );
247
                }
248
            }
249
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
250
            $output = $parsedTemplate->$methodNameOfSection($renderingContext);
251
            $this->stopRendering();
252
        } else {
253
            $sections = $parsedTemplate->getVariableContainer()->get('1457379500_sections');
254 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...
255
                if ($ignoreUnknown) {
256
                    return '';
257
                }
258
                return $renderingContext->getErrorHandler()->handleViewError(
259
                    new InvalidSectionException('Section "' . $sectionName . '" does not exist.')
260
                );
261
            }
262
            /** @var $section ViewHelperNode */
263
            $section = $sections[$sectionName];
264
265
            $renderingContext->getViewHelperVariableContainer()->add(
266
                SectionViewHelper::class,
267
                'isCurrentlyRenderingSection',
268
                true
269
            );
270
271
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
272
            $output = $section->evaluate($renderingContext);
273
            $this->stopRendering();
274
        }
275
276
        return $output;
277
    }
278
279
    /**
280
     * Renders a partial.
281
     *
282
     * @param string $partialName
283
     * @param string $sectionName
284
     * @param array $variables
285
     * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
286
     * @return string
287
     */
288
    public function renderPartial($partialName, $sectionName, array $variables, $ignoreUnknown = false)
289
    {
290
        $templatePaths = $this->baseRenderingContext->getTemplatePaths();
291
        $renderingContext = clone $this->getCurrentRenderingContext();
292
        try {
293
            $parsedPartial = $renderingContext->getTemplateParser()->getOrParseAndStoreTemplate(
294
                $templatePaths->getPartialIdentifier($partialName),
295
                function ($parent, TemplatePaths $paths) use ($partialName) {
296
                    return $paths->getPartialSource($partialName);
297
                }
298
            );
299
        } catch (PassthroughSourceException $error) {
300
            return $error->getSource();
301
        } catch (Exception $error) {
302
            return $renderingContext->getErrorHandler()->handleViewError($error);
303
        }
304
        $renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables));
305
        $this->startRendering(self::RENDERING_PARTIAL, $parsedPartial, $renderingContext);
306
        if ($sectionName !== null) {
307
            $output = $this->renderSection($sectionName, $variables, $ignoreUnknown);
308
        } else {
309
            $output = $parsedPartial->render($renderingContext);
310
        }
311
        $this->stopRendering();
312
        return $output;
313
    }
314
315
    /**
316
     * Start a new nested rendering. Pushes the given information onto the $renderingStack.
317
     *
318
     * @param integer $type one of the RENDERING_* constants
319
     * @param ParsedTemplateInterface $template
320
     * @param RenderingContextInterface $context
321
     * @return void
322
     */
323
    protected function startRendering($type, ParsedTemplateInterface $template, RenderingContextInterface $context)
324
    {
325
        array_push($this->renderingStack, ['type' => $type, 'parsedTemplate' => $template, 'renderingContext' => $context]);
326
    }
327
328
    /**
329
     * Stops the current rendering. Removes one element from the $renderingStack. Make sure to always call this
330
     * method pair-wise with startRendering().
331
     *
332
     * @return void
333
     */
334
    protected function stopRendering()
335
    {
336
        $this->getCurrentRenderingContext()->getTemplateCompiler()->reset();
337
        array_pop($this->renderingStack);
338
    }
339
340
    /**
341
     * Get the current rendering type.
342
     *
343
     * @return integer one of RENDERING_* constants
344
     */
345
    protected function getCurrentRenderingType()
346
    {
347
        $currentRendering = end($this->renderingStack);
348
        return $currentRendering['type'] ? $currentRendering['type'] : self::RENDERING_TEMPLATE;
349
    }
350
351
    /**
352
     * Get the parsed template which is currently being rendered or compiled.
353
     *
354
     * @return ParsedTemplateInterface
355
     */
356
    protected function getCurrentParsedTemplate()
357
    {
358
        $currentRendering = end($this->renderingStack);
359
        $renderingContext = $this->getCurrentRenderingContext();
360
        $parsedTemplate = $currentRendering['parsedTemplate'] ? $currentRendering['parsedTemplate'] : $renderingContext->getTemplateCompiler()->getCurrentlyProcessingState();
361
        if ($parsedTemplate) {
362
            return $parsedTemplate;
363
        }
364
        $templatePaths = $renderingContext->getTemplatePaths();
365
        $templateParser = $renderingContext->getTemplateParser();
366
        $controllerName = $renderingContext->getControllerName();
367
        $actionName = $renderingContext->getControllerAction();
368
        $parsedTemplate = $templateParser->getOrParseAndStoreTemplate(
369
            $templatePaths->getTemplateIdentifier($controllerName, $actionName),
370
            function($parent, TemplatePaths $paths) use ($controllerName, $actionName, $renderingContext) {
371
                return $paths->getTemplateSource($controllerName, $actionName);
372
            }
373
        );
374
        if ($parsedTemplate->isCompiled()) {
375
            $parsedTemplate->addCompiledNamespaces($this->baseRenderingContext);
376
        }
377
        return $parsedTemplate;
378
    }
379
380
    /**
381
     * Get the rendering context which is currently used.
382
     *
383
     * @return RenderingContextInterface
384
     */
385
    protected function getCurrentRenderingContext()
386
    {
387
        $currentRendering = end($this->renderingStack);
388
        return $currentRendering['renderingContext'] ? $currentRendering['renderingContext'] : $this->baseRenderingContext;
389
    }
390
}
391