Completed
Push — master ( 255113...79ec2a )
by Marc
06:17 queued 03:31
created

AbstractTemplateView   B

Complexity

Total Complexity 37

Size/Duplication

Total Lines 358
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Importance

Changes 0
Metric Value
dl 0
loc 358
rs 8.6
c 0
b 0
f 0
wmc 37
lcom 1
cbo 8

17 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 9 2
A initializeRenderingContext() 0 4 1
A setCache() 0 4 1
A getTemplatePaths() 0 4 1
A getViewHelperResolver() 0 4 1
A getRenderingContext() 0 4 1
A setRenderingContext() 0 5 1
A assign() 0 5 1
A assignMultiple() 0 8 2
B render() 0 38 5
B renderSection() 0 55 8
B renderPartial() 0 24 3
A startRendering() 0 4 1
A stopRendering() 0 4 1
A getCurrentRenderingType() 0 5 2
B getCurrentParsedTemplate() 0 24 4
A getCurrentRenderingContext() 0 5 2
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\ViewHelpers\SectionViewHelper;
17
use TYPO3Fluid\Fluid\View\Exception\InvalidSectionException;
18
19
/**
20
 * Abstract Fluid Template View.
21
 *
22
 * Contains the fundamental methods which any Fluid based template view needs.
23
 */
24
abstract class AbstractTemplateView extends AbstractView
25
{
26
27
    /**
28
     * Constants defining possible rendering types
29
     */
30
    const RENDERING_TEMPLATE = 1;
31
    const RENDERING_PARTIAL = 2;
32
    const RENDERING_LAYOUT = 3;
33
34
    /**
35
     * The initial rendering context for this template view.
36
     * Due to the rendering stack, another rendering context might be active
37
     * at certain points while rendering the template.
38
     *
39
     * @var RenderingContextInterface
40
     */
41
    protected $baseRenderingContext;
42
43
    /**
44
     * Stack containing the current rendering type, the current rendering context, and the current parsed template
45
     * Do not manipulate directly, instead use the methods"getCurrent*()", "startRendering(...)" and "stopRendering()"
46
     *
47
     * @var array
48
     */
49
    protected $renderingStack = [];
50
51
    /**
52
     * Constructor
53
     *
54
     * @param null|RenderingContextInterface $context
55
     */
56
    public function __construct(RenderingContextInterface $context = null)
57
    {
58
        if (!$context) {
59
            $context = new RenderingContext($this);
60
            $context->setControllerName('Default');
61
            $context->setControllerAction('Default');
62
        }
63
        $this->setRenderingContext($context);
64
    }
65
66
    /**
67
     * Initialize the RenderingContext. This method can be overridden in your
68
     * View implementation to manipulate the rendering context *before* it is
69
     * passed during rendering.
70
     */
71
    public function initializeRenderingContext()
72
    {
73
        $this->baseRenderingContext->getViewHelperVariableContainer()->setView($this);
74
    }
75
76
    /**
77
     * Sets the cache to use in RenderingContext.
78
     *
79
     * @param FluidCacheInterface $cache
80
     * @return void
81
     */
82
    public function setCache(FluidCacheInterface $cache)
83
    {
84
        $this->baseRenderingContext->setCache($cache);
85
    }
86
87
    /**
88
     * Gets the TemplatePaths instance from RenderingContext
89
     *
90
     * @return TemplatePaths
91
     */
92
    public function getTemplatePaths()
93
    {
94
        return $this->baseRenderingContext->getTemplatePaths();
95
    }
96
97
    /**
98
     * Gets the ViewHelperResolver instance from RenderingContext
99
     *
100
     * @return ViewHelperResolver
101
     */
102
    public function getViewHelperResolver()
103
    {
104
        return $this->baseRenderingContext->getViewHelperResolver();
105
    }
106
107
    /**
108
     * Gets the RenderingContext used by the View
109
     *
110
     * @return RenderingContextInterface
111
     */
112
    public function getRenderingContext()
113
    {
114
        return $this->baseRenderingContext;
115
    }
116
117
    /**
118
     * Injects a fresh rendering context
119
     *
120
     * @param RenderingContextInterface $renderingContext
121
     * @return void
122
     */
123
    public function setRenderingContext(RenderingContextInterface $renderingContext)
124
    {
125
        $this->baseRenderingContext = $renderingContext;
126
        $this->initializeRenderingContext();
127
    }
128
129
    /**
130
     * Assign a value to the variable container.
131
     *
132
     * @param string $key The key of a view variable to set
133
     * @param mixed $value The value of the view variable
134
     * @return $this
135
     * @api
136
     */
137
    public function assign($key, $value)
138
    {
139
        $this->baseRenderingContext->getVariableProvider()->add($key, $value);
140
        return $this;
141
    }
142
143
    /**
144
     * Assigns multiple values to the JSON output.
145
     * However, only the key "value" is accepted.
146
     *
147
     * @param array $values Keys and values - only a value with key "value" is considered
148
     * @return $this
149
     * @api
150
     */
151
    public function assignMultiple(array $values)
152
    {
153
        $templateVariableContainer = $this->baseRenderingContext->getVariableProvider();
154
        foreach ($values as $key => $value) {
155
            $templateVariableContainer->add($key, $value);
156
        }
157
        return $this;
158
    }
159
160
    /**
161
     * Loads the template source and render the template.
162
     * If "layoutName" is set in a PostParseFacet callback, it will render the file with the given layout.
163
     *
164
     * @param string|null $actionName If set, this action's template will be rendered instead of the one defined in the context.
165
     * @return string Rendered Template
166
     * @api
167
     */
168
    public function render($actionName = null)
169
    {
170
        $renderingContext = $this->getCurrentRenderingContext();
171
        $templateParser = $renderingContext->getTemplateParser();
172
        $templatePaths = $renderingContext->getTemplatePaths();
173
        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...
174
            $actionName = ucfirst($actionName);
175
            $renderingContext->setControllerAction($actionName);
176
        }
177
        try {
178
            $parsedTemplate = $this->getCurrentParsedTemplate();
179
        } catch (PassthroughSourceException $error) {
180
            return $error->getSource();
181
        }
182
183
        if (!$parsedTemplate->hasLayout()) {
184
            $this->startRendering(self::RENDERING_TEMPLATE, $parsedTemplate, $this->baseRenderingContext);
185
            $output = $parsedTemplate->render($this->baseRenderingContext);
186
            $this->stopRendering();
187
        } else {
188
            $layoutName = $parsedTemplate->getLayoutName($this->baseRenderingContext);
189
            try {
190
                $parsedLayout = $templateParser->getOrParseAndStoreTemplate(
191
                    $templatePaths->getLayoutIdentifier($layoutName),
192
                    function($parent, TemplatePaths $paths) use ($layoutName) {
193
                        return $paths->getLayoutSource($layoutName);
194
                    }
195
                );
196
            } catch (PassthroughSourceException $error) {
197
                return $error->getSource();
198
            }
199
            $this->startRendering(self::RENDERING_LAYOUT, $parsedTemplate, $this->baseRenderingContext);
200
            $output = $parsedLayout->render($this->baseRenderingContext);
201
            $this->stopRendering();
202
        }
203
204
        return $output;
205
    }
206
207
    /**
208
     * Renders a given section.
209
     *
210
     * @param string $sectionName Name of section to render
211
     * @param array $variables The variables to use
212
     * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
213
     * @return string rendered template for the section
214
     * @throws InvalidSectionException
215
     */
216
    public function renderSection($sectionName, array $variables = [], $ignoreUnknown = false)
217
    {
218
        $renderingContext = $this->getCurrentRenderingContext();
219
220
        if ($this->getCurrentRenderingType() === self::RENDERING_LAYOUT) {
221
            // in case we render a layout right now, we will render a section inside a TEMPLATE.
222
            $renderingTypeOnNextLevel = self::RENDERING_TEMPLATE;
223
        } else {
224
            $renderingContext = clone $renderingContext;
225
            $renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables));
226
            $renderingTypeOnNextLevel = $this->getCurrentRenderingType();
227
        }
228
229
        try {
230
            $parsedTemplate = $this->getCurrentParsedTemplate();
231
        } catch (PassthroughSourceException $error) {
232
            return $error->getSource();
233
        }
234
235
        if ($parsedTemplate->isCompiled()) {
236
            $methodNameOfSection = 'section_' . sha1($sectionName);
237
            if (!method_exists($parsedTemplate, $methodNameOfSection)) {
238
                if ($ignoreUnknown) {
239
                    return '';
240
                } else {
241
                    throw new InvalidSectionException('Section "' . $sectionName . '" does not exist.');
242
                }
243
            }
244
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
245
            $output = $parsedTemplate->$methodNameOfSection($renderingContext);
246
            $this->stopRendering();
247
        } else {
248
            $sections = $parsedTemplate->getVariableContainer()->get('1457379500_sections');
249
            if (!isset($sections[$sectionName])) {
250
                if ($ignoreUnknown) {
251
                    return '';
252
                }
253
                throw new InvalidSectionException('Section "' . $sectionName . '" does not exist.');
254
            }
255
            /** @var $section ViewHelperNode */
256
            $section = $sections[$sectionName];
257
258
            $renderingContext->getViewHelperVariableContainer()->add(
259
                SectionViewHelper::class,
260
                'isCurrentlyRenderingSection',
261
                true
262
            );
263
264
            $this->startRendering($renderingTypeOnNextLevel, $parsedTemplate, $renderingContext);
265
            $output = $section->evaluate($renderingContext);
266
            $this->stopRendering();
267
        }
268
269
        return $output;
270
    }
271
272
    /**
273
     * Renders a partial.
274
     *
275
     * @param string $partialName
276
     * @param string $sectionName
277
     * @param array $variables
278
     * @param boolean $ignoreUnknown Ignore an unknown section and just return an empty string
279
     * @return string
280
     */
281
    public function renderPartial($partialName, $sectionName, array $variables, $ignoreUnknown = false)
282
    {
283
        $templatePaths = $this->baseRenderingContext->getTemplatePaths();
284
        try {
285
            $parsedPartial = $this->baseRenderingContext->getTemplateParser()->getOrParseAndStoreTemplate(
286
                $templatePaths->getPartialIdentifier($partialName),
287
                function ($parent, TemplatePaths $paths) use ($partialName) {
288
                    return $paths->getPartialSource($partialName);
289
                }
290
            );
291
        } catch (PassthroughSourceException $error) {
292
            return $error->getSource();
293
        }
294
        $renderingContext = clone $this->getCurrentRenderingContext();
295
        $renderingContext->setVariableProvider($renderingContext->getVariableProvider()->getScopeCopy($variables));
296
        $this->startRendering(self::RENDERING_PARTIAL, $parsedPartial, $renderingContext);
297
        if ($sectionName !== null) {
298
            $output = $this->renderSection($sectionName, $variables, $ignoreUnknown);
299
        } else {
300
            $output = $parsedPartial->render($renderingContext);
301
        }
302
        $this->stopRendering();
303
        return $output;
304
    }
305
306
    /**
307
     * Start a new nested rendering. Pushes the given information onto the $renderingStack.
308
     *
309
     * @param integer $type one of the RENDERING_* constants
310
     * @param ParsedTemplateInterface $template
311
     * @param RenderingContextInterface $context
312
     * @return void
313
     */
314
    protected function startRendering($type, ParsedTemplateInterface $template, RenderingContextInterface $context)
315
    {
316
        array_push($this->renderingStack, ['type' => $type, 'parsedTemplate' => $template, 'renderingContext' => $context]);
317
    }
318
319
    /**
320
     * Stops the current rendering. Removes one element from the $renderingStack. Make sure to always call this
321
     * method pair-wise with startRendering().
322
     *
323
     * @return void
324
     */
325
    protected function stopRendering()
326
    {
327
        array_pop($this->renderingStack);
328
    }
329
330
    /**
331
     * Get the current rendering type.
332
     *
333
     * @return integer one of RENDERING_* constants
334
     */
335
    protected function getCurrentRenderingType()
336
    {
337
        $currentRendering = end($this->renderingStack);
338
        return $currentRendering['type'] ? $currentRendering['type'] : self::RENDERING_TEMPLATE;
339
    }
340
341
    /**
342
     * Get the parsed template which is currently being rendered or compiled.
343
     *
344
     * @return ParsedTemplateInterface
345
     */
346
    protected function getCurrentParsedTemplate()
347
    {
348
        $currentRendering = end($this->renderingStack);
349
        $renderingContext = $this->getCurrentRenderingContext();
350
        $parsedTemplate = $currentRendering['parsedTemplate'] ? $currentRendering['parsedTemplate'] : $renderingContext->getTemplateCompiler()->getCurrentlyProcessingState();
351
        if ($parsedTemplate) {
352
            return $parsedTemplate;
353
        }
354
        $templatePaths = $renderingContext->getTemplatePaths();
355
        $templateParser = $renderingContext->getTemplateParser();
356
        $controllerName = $renderingContext->getControllerName();
357
        $actionName = $renderingContext->getControllerAction();
358
        $parsedTemplate = $templateParser->getOrParseAndStoreTemplate(
359
            $templatePaths->getTemplateIdentifier($controllerName, $actionName),
360
            function($parent, TemplatePaths $paths) use ($controllerName, $actionName) {
361
                return $paths->getTemplateSource($controllerName, $actionName);
362
            }
363
        );
364
        if ($parsedTemplate->isCompiled()) {
365
            $parsedTemplate->addCompiledNamespaces($this->baseRenderingContext);
366
        }
367
        $renderingContext->getTemplateCompiler()->reset();
368
        return $parsedTemplate;
369
    }
370
371
    /**
372
     * Get the rendering context which is currently used.
373
     *
374
     * @return RenderingContextInterface
375
     */
376
    protected function getCurrentRenderingContext()
377
    {
378
        $currentRendering = end($this->renderingStack);
379
        return $currentRendering['renderingContext'] ? $currentRendering['renderingContext'] : $this->baseRenderingContext;
380
    }
381
}
382