1<?php
2
3/*
4 * This file is part of Twig.
5 *
6 * (c) Fabien Potencier
7 * (c) Armin Ronacher
8 *
9 * For the full copyright and license information, please view the LICENSE
10 * file that was distributed with this source code.
11 */
12
13namespace Twig;
14
15use Twig\Error\Error;
16use Twig\Error\LoaderError;
17use Twig\Error\RuntimeError;
18
19/**
20 * Default base class for compiled templates.
21 *
22 * This class is an implementation detail of how template compilation currently
23 * works, which might change. It should never be used directly. Use $twig->load()
24 * instead, which returns an instance of \Twig\TemplateWrapper.
25 *
26 * @author Fabien Potencier <fabien@symfony.com>
27 *
28 * @internal
29 */
30abstract class Template
31{
32    public const ANY_CALL = 'any';
33    public const ARRAY_CALL = 'array';
34    public const METHOD_CALL = 'method';
35
36    protected $parent;
37    protected $parents = [];
38    protected $env;
39    protected $blocks = [];
40    protected $traits = [];
41    protected $extensions = [];
42    protected $sandbox;
43
44    public function __construct(Environment $env)
45    {
46        $this->env = $env;
47        $this->extensions = $env->getExtensions();
48    }
49
50    /**
51     * @internal this method will be removed in 3.0 and is only used internally to provide an upgrade path from 1.x to 2.0
52     */
53    public function __toString()
54    {
55        return $this->getTemplateName();
56    }
57
58    /**
59     * Returns the template name.
60     *
61     * @return string The template name
62     */
63    abstract public function getTemplateName();
64
65    /**
66     * Returns debug information about the template.
67     *
68     * @return array Debug information
69     */
70    abstract public function getDebugInfo();
71
72    /**
73     * Returns information about the original template source code.
74     *
75     * @return Source
76     */
77    public function getSourceContext()
78    {
79        return new Source('', $this->getTemplateName());
80    }
81
82    /**
83     * Returns the parent template.
84     *
85     * This method is for internal use only and should never be called
86     * directly.
87     *
88     * @return Template|TemplateWrapper|false The parent template or false if there is no parent
89     */
90    public function getParent(array $context)
91    {
92        if (null !== $this->parent) {
93            return $this->parent;
94        }
95
96        try {
97            $parent = $this->doGetParent($context);
98
99            if (false === $parent) {
100                return false;
101            }
102
103            if ($parent instanceof self || $parent instanceof TemplateWrapper) {
104                return $this->parents[$parent->getSourceContext()->getName()] = $parent;
105            }
106
107            if (!isset($this->parents[$parent])) {
108                $this->parents[$parent] = $this->loadTemplate($parent);
109            }
110        } catch (LoaderError $e) {
111            $e->setSourceContext(null);
112            $e->guess();
113
114            throw $e;
115        }
116
117        return $this->parents[$parent];
118    }
119
120    protected function doGetParent(array $context)
121    {
122        return false;
123    }
124
125    public function isTraitable()
126    {
127        return true;
128    }
129
130    /**
131     * Displays a parent block.
132     *
133     * This method is for internal use only and should never be called
134     * directly.
135     *
136     * @param string $name    The block name to display from the parent
137     * @param array  $context The context
138     * @param array  $blocks  The current set of blocks
139     */
140    public function displayParentBlock($name, array $context, array $blocks = [])
141    {
142        if (isset($this->traits[$name])) {
143            $this->traits[$name][0]->displayBlock($name, $context, $blocks, false);
144        } elseif (false !== $parent = $this->getParent($context)) {
145            $parent->displayBlock($name, $context, $blocks, false);
146        } else {
147            throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext());
148        }
149    }
150
151    /**
152     * Displays a block.
153     *
154     * This method is for internal use only and should never be called
155     * directly.
156     *
157     * @param string $name      The block name to display
158     * @param array  $context   The context
159     * @param array  $blocks    The current set of blocks
160     * @param bool   $useBlocks Whether to use the current set of blocks
161     */
162    public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true, self $templateContext = null)
163    {
164        if ($useBlocks && isset($blocks[$name])) {
165            $template = $blocks[$name][0];
166            $block = $blocks[$name][1];
167        } elseif (isset($this->blocks[$name])) {
168            $template = $this->blocks[$name][0];
169            $block = $this->blocks[$name][1];
170        } else {
171            $template = null;
172            $block = null;
173        }
174
175        // avoid RCEs when sandbox is enabled
176        if (null !== $template && !$template instanceof self) {
177            throw new \LogicException('A block must be a method on a \Twig\Template instance.');
178        }
179
180        if (null !== $template) {
181            try {
182                $template->$block($context, $blocks);
183            } catch (Error $e) {
184                if (!$e->getSourceContext()) {
185                    $e->setSourceContext($template->getSourceContext());
186                }
187
188                // this is mostly useful for \Twig\Error\LoaderError exceptions
189                // see \Twig\Error\LoaderError
190                if (-1 === $e->getTemplateLine()) {
191                    $e->guess();
192                }
193
194                throw $e;
195            } catch (\Exception $e) {
196                $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e);
197                $e->guess();
198
199                throw $e;
200            }
201        } elseif (false !== $parent = $this->getParent($context)) {
202            $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false, $templateContext ?? $this);
203        } elseif (isset($blocks[$name])) {
204            throw new RuntimeError(sprintf('Block "%s" should not call parent() in "%s" as the block does not exist in the parent template "%s".', $name, $blocks[$name][0]->getTemplateName(), $this->getTemplateName()), -1, $blocks[$name][0]->getSourceContext());
205        } else {
206            throw new RuntimeError(sprintf('Block "%s" on template "%s" does not exist.', $name, $this->getTemplateName()), -1, ($templateContext ?? $this)->getSourceContext());
207        }
208    }
209
210    /**
211     * Renders a parent block.
212     *
213     * This method is for internal use only and should never be called
214     * directly.
215     *
216     * @param string $name    The block name to render from the parent
217     * @param array  $context The context
218     * @param array  $blocks  The current set of blocks
219     *
220     * @return string The rendered block
221     */
222    public function renderParentBlock($name, array $context, array $blocks = [])
223    {
224        if ($this->env->isDebug()) {
225            ob_start();
226        } else {
227            ob_start(function () { return ''; });
228        }
229        $this->displayParentBlock($name, $context, $blocks);
230
231        return ob_get_clean();
232    }
233
234    /**
235     * Renders a block.
236     *
237     * This method is for internal use only and should never be called
238     * directly.
239     *
240     * @param string $name      The block name to render
241     * @param array  $context   The context
242     * @param array  $blocks    The current set of blocks
243     * @param bool   $useBlocks Whether to use the current set of blocks
244     *
245     * @return string The rendered block
246     */
247    public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true)
248    {
249        if ($this->env->isDebug()) {
250            ob_start();
251        } else {
252            ob_start(function () { return ''; });
253        }
254        $this->displayBlock($name, $context, $blocks, $useBlocks);
255
256        return ob_get_clean();
257    }
258
259    /**
260     * Returns whether a block exists or not in the current context of the template.
261     *
262     * This method checks blocks defined in the current template
263     * or defined in "used" traits or defined in parent templates.
264     *
265     * @param string $name    The block name
266     * @param array  $context The context
267     * @param array  $blocks  The current set of blocks
268     *
269     * @return bool true if the block exists, false otherwise
270     */
271    public function hasBlock($name, array $context, array $blocks = [])
272    {
273        if (isset($blocks[$name])) {
274            return $blocks[$name][0] instanceof self;
275        }
276
277        if (isset($this->blocks[$name])) {
278            return true;
279        }
280
281        if (false !== $parent = $this->getParent($context)) {
282            return $parent->hasBlock($name, $context);
283        }
284
285        return false;
286    }
287
288    /**
289     * Returns all block names in the current context of the template.
290     *
291     * This method checks blocks defined in the current template
292     * or defined in "used" traits or defined in parent templates.
293     *
294     * @param array $context The context
295     * @param array $blocks  The current set of blocks
296     *
297     * @return array An array of block names
298     */
299    public function getBlockNames(array $context, array $blocks = [])
300    {
301        $names = array_merge(array_keys($blocks), array_keys($this->blocks));
302
303        if (false !== $parent = $this->getParent($context)) {
304            $names = array_merge($names, $parent->getBlockNames($context));
305        }
306
307        return array_unique($names);
308    }
309
310    /**
311     * @return Template|TemplateWrapper
312     */
313    protected function loadTemplate($template, $templateName = null, $line = null, $index = null)
314    {
315        try {
316            if (\is_array($template)) {
317                return $this->env->resolveTemplate($template);
318            }
319
320            if ($template instanceof self || $template instanceof TemplateWrapper) {
321                return $template;
322            }
323
324            if ($template === $this->getTemplateName()) {
325                $class = static::class;
326                if (false !== $pos = strrpos($class, '___', -1)) {
327                    $class = substr($class, 0, $pos);
328                }
329
330                return $this->env->loadClass($class, $template, $index);
331            }
332
333            return $this->env->loadTemplate($template, $index);
334        } catch (Error $e) {
335            if (!$e->getSourceContext()) {
336                $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext());
337            }
338
339            if ($e->getTemplateLine() > 0) {
340                throw $e;
341            }
342
343            if (!$line) {
344                $e->guess();
345            } else {
346                $e->setTemplateLine($line);
347            }
348
349            throw $e;
350        }
351    }
352
353    /**
354     * @internal
355     *
356     * @return Template
357     */
358    public function unwrap()
359    {
360        return $this;
361    }
362
363    /**
364     * Returns all blocks.
365     *
366     * This method is for internal use only and should never be called
367     * directly.
368     *
369     * @return array An array of blocks
370     */
371    public function getBlocks()
372    {
373        return $this->blocks;
374    }
375
376    public function display(array $context, array $blocks = [])
377    {
378        $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks));
379    }
380
381    public function render(array $context)
382    {
383        $level = ob_get_level();
384        if ($this->env->isDebug()) {
385            ob_start();
386        } else {
387            ob_start(function () { return ''; });
388        }
389        try {
390            $this->display($context);
391        } catch (\Throwable $e) {
392            while (ob_get_level() > $level) {
393                ob_end_clean();
394            }
395
396            throw $e;
397        }
398
399        return ob_get_clean();
400    }
401
402    protected function displayWithErrorHandling(array $context, array $blocks = [])
403    {
404        try {
405            $this->doDisplay($context, $blocks);
406        } catch (Error $e) {
407            if (!$e->getSourceContext()) {
408                $e->setSourceContext($this->getSourceContext());
409            }
410
411            // this is mostly useful for \Twig\Error\LoaderError exceptions
412            // see \Twig\Error\LoaderError
413            if (-1 === $e->getTemplateLine()) {
414                $e->guess();
415            }
416
417            throw $e;
418        } catch (\Exception $e) {
419            $e = new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e);
420            $e->guess();
421
422            throw $e;
423        }
424    }
425
426    /**
427     * Auto-generated method to display the template with the given context.
428     *
429     * @param array $context An array of parameters to pass to the template
430     * @param array $blocks  An array of blocks to pass to the template
431     */
432    abstract protected function doDisplay(array $context, array $blocks = []);
433}
434
435class_alias('Twig\Template', 'Twig_Template');
436