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\Node;
14
15use Twig\Compiler;
16use Twig\Node\Expression\AbstractExpression;
17use Twig\Node\Expression\ConstantExpression;
18use Twig\Source;
19
20/**
21 * Represents a module node.
22 *
23 * Consider this class as being final. If you need to customize the behavior of
24 * the generated class, consider adding nodes to the following nodes: display_start,
25 * display_end, constructor_start, constructor_end, and class_end.
26 *
27 * @author Fabien Potencier <fabien@symfony.com>
28 *
29 * @final since Twig 2.4.0
30 */
31class ModuleNode extends Node
32{
33    public function __construct(Node $body, ?AbstractExpression $parent, Node $blocks, Node $macros, Node $traits, $embeddedTemplates, Source $source)
34    {
35        if (__CLASS__ !== static::class) {
36            @trigger_error('Overriding '.__CLASS__.' is deprecated since Twig 2.4.0 and the class will be final in 3.0.', \E_USER_DEPRECATED);
37        }
38
39        $nodes = [
40            'body' => $body,
41            'blocks' => $blocks,
42            'macros' => $macros,
43            'traits' => $traits,
44            'display_start' => new Node(),
45            'display_end' => new Node(),
46            'constructor_start' => new Node(),
47            'constructor_end' => new Node(),
48            'class_end' => new Node(),
49        ];
50        if (null !== $parent) {
51            $nodes['parent'] = $parent;
52        }
53
54        // embedded templates are set as attributes so that they are only visited once by the visitors
55        parent::__construct($nodes, [
56            'index' => null,
57            'embedded_templates' => $embeddedTemplates,
58        ], 1);
59
60        // populate the template name of all node children
61        $this->setSourceContext($source);
62    }
63
64    public function setIndex($index)
65    {
66        $this->setAttribute('index', $index);
67    }
68
69    public function compile(Compiler $compiler)
70    {
71        $this->compileTemplate($compiler);
72
73        foreach ($this->getAttribute('embedded_templates') as $template) {
74            $compiler->subcompile($template);
75        }
76    }
77
78    protected function compileTemplate(Compiler $compiler)
79    {
80        if (!$this->getAttribute('index')) {
81            $compiler->write('<?php');
82        }
83
84        $this->compileClassHeader($compiler);
85
86        $this->compileConstructor($compiler);
87
88        $this->compileGetParent($compiler);
89
90        $this->compileDisplay($compiler);
91
92        $compiler->subcompile($this->getNode('blocks'));
93
94        $this->compileMacros($compiler);
95
96        $this->compileGetTemplateName($compiler);
97
98        $this->compileIsTraitable($compiler);
99
100        $this->compileDebugInfo($compiler);
101
102        $this->compileGetSourceContext($compiler);
103
104        $this->compileClassFooter($compiler);
105    }
106
107    protected function compileGetParent(Compiler $compiler)
108    {
109        if (!$this->hasNode('parent')) {
110            return;
111        }
112        $parent = $this->getNode('parent');
113
114        $compiler
115            ->write("protected function doGetParent(array \$context)\n", "{\n")
116            ->indent()
117            ->addDebugInfo($parent)
118            ->write('return ')
119        ;
120
121        if ($parent instanceof ConstantExpression) {
122            $compiler->subcompile($parent);
123        } else {
124            $compiler
125                ->raw('$this->loadTemplate(')
126                ->subcompile($parent)
127                ->raw(', ')
128                ->repr($this->getSourceContext()->getName())
129                ->raw(', ')
130                ->repr($parent->getTemplateLine())
131                ->raw(')')
132            ;
133        }
134
135        $compiler
136            ->raw(";\n")
137            ->outdent()
138            ->write("}\n\n")
139        ;
140    }
141
142    protected function compileClassHeader(Compiler $compiler)
143    {
144        $compiler
145            ->write("\n\n")
146        ;
147        if (!$this->getAttribute('index')) {
148            $compiler
149                ->write("use Twig\Environment;\n")
150                ->write("use Twig\Error\LoaderError;\n")
151                ->write("use Twig\Error\RuntimeError;\n")
152                ->write("use Twig\Extension\SandboxExtension;\n")
153                ->write("use Twig\Markup;\n")
154                ->write("use Twig\Sandbox\SecurityError;\n")
155                ->write("use Twig\Sandbox\SecurityNotAllowedTagError;\n")
156                ->write("use Twig\Sandbox\SecurityNotAllowedFilterError;\n")
157                ->write("use Twig\Sandbox\SecurityNotAllowedFunctionError;\n")
158                ->write("use Twig\Source;\n")
159                ->write("use Twig\Template;\n\n")
160            ;
161        }
162        $compiler
163            // if the template name contains */, add a blank to avoid a PHP parse error
164            ->write('/* '.str_replace('*/', '* /', $this->getSourceContext()->getName())." */\n")
165            ->write('class '.$compiler->getEnvironment()->getTemplateClass($this->getSourceContext()->getName(), $this->getAttribute('index')))
166            ->raw(sprintf(" extends %s\n", $compiler->getEnvironment()->getBaseTemplateClass(false)))
167            ->write("{\n")
168            ->indent()
169            ->write("private \$source;\n")
170            ->write("private \$macros = [];\n\n")
171        ;
172    }
173
174    protected function compileConstructor(Compiler $compiler)
175    {
176        $compiler
177            ->write("public function __construct(Environment \$env)\n", "{\n")
178            ->indent()
179            ->subcompile($this->getNode('constructor_start'))
180            ->write("parent::__construct(\$env);\n\n")
181            ->write("\$this->source = \$this->getSourceContext();\n\n")
182        ;
183
184        // parent
185        if (!$this->hasNode('parent')) {
186            $compiler->write("\$this->parent = false;\n\n");
187        }
188
189        $countTraits = \count($this->getNode('traits'));
190        if ($countTraits) {
191            // traits
192            foreach ($this->getNode('traits') as $i => $trait) {
193                $node = $trait->getNode('template');
194
195                $compiler
196                    ->addDebugInfo($node)
197                    ->write(sprintf('$_trait_%s = $this->loadTemplate(', $i))
198                    ->subcompile($node)
199                    ->raw(', ')
200                    ->repr($node->getTemplateName())
201                    ->raw(', ')
202                    ->repr($node->getTemplateLine())
203                    ->raw(");\n")
204                    ->write(sprintf("if (!\$_trait_%s->isTraitable()) {\n", $i))
205                    ->indent()
206                    ->write("throw new RuntimeError('Template \"'.")
207                    ->subcompile($trait->getNode('template'))
208                    ->raw(".'\" cannot be used as a trait.', ")
209                    ->repr($node->getTemplateLine())
210                    ->raw(", \$this->source);\n")
211                    ->outdent()
212                    ->write("}\n")
213                    ->write(sprintf("\$_trait_%s_blocks = \$_trait_%s->getBlocks();\n\n", $i, $i))
214                ;
215
216                foreach ($trait->getNode('targets') as $key => $value) {
217                    $compiler
218                        ->write(sprintf('if (!isset($_trait_%s_blocks[', $i))
219                        ->string($key)
220                        ->raw("])) {\n")
221                        ->indent()
222                        ->write("throw new RuntimeError('Block ")
223                        ->string($key)
224                        ->raw(' is not defined in trait ')
225                        ->subcompile($trait->getNode('template'))
226                        ->raw(".', ")
227                        ->repr($node->getTemplateLine())
228                        ->raw(", \$this->source);\n")
229                        ->outdent()
230                        ->write("}\n\n")
231
232                        ->write(sprintf('$_trait_%s_blocks[', $i))
233                        ->subcompile($value)
234                        ->raw(sprintf('] = $_trait_%s_blocks[', $i))
235                        ->string($key)
236                        ->raw(sprintf(']; unset($_trait_%s_blocks[', $i))
237                        ->string($key)
238                        ->raw("]);\n\n")
239                    ;
240                }
241            }
242
243            if ($countTraits > 1) {
244                $compiler
245                    ->write("\$this->traits = array_merge(\n")
246                    ->indent()
247                ;
248
249                for ($i = 0; $i < $countTraits; ++$i) {
250                    $compiler
251                        ->write(sprintf('$_trait_%s_blocks'.($i == $countTraits - 1 ? '' : ',')."\n", $i))
252                    ;
253                }
254
255                $compiler
256                    ->outdent()
257                    ->write(");\n\n")
258                ;
259            } else {
260                $compiler
261                    ->write("\$this->traits = \$_trait_0_blocks;\n\n")
262                ;
263            }
264
265            $compiler
266                ->write("\$this->blocks = array_merge(\n")
267                ->indent()
268                ->write("\$this->traits,\n")
269                ->write("[\n")
270            ;
271        } else {
272            $compiler
273                ->write("\$this->blocks = [\n")
274            ;
275        }
276
277        // blocks
278        $compiler
279            ->indent()
280        ;
281
282        foreach ($this->getNode('blocks') as $name => $node) {
283            $compiler
284                ->write(sprintf("'%s' => [\$this, 'block_%s'],\n", $name, $name))
285            ;
286        }
287
288        if ($countTraits) {
289            $compiler
290                ->outdent()
291                ->write("]\n")
292                ->outdent()
293                ->write(");\n")
294            ;
295        } else {
296            $compiler
297                ->outdent()
298                ->write("];\n")
299            ;
300        }
301
302        $compiler
303            ->subcompile($this->getNode('constructor_end'))
304            ->outdent()
305            ->write("}\n\n")
306        ;
307    }
308
309    protected function compileDisplay(Compiler $compiler)
310    {
311        $compiler
312            ->write("protected function doDisplay(array \$context, array \$blocks = [])\n", "{\n")
313            ->indent()
314            ->write("\$macros = \$this->macros;\n")
315            ->subcompile($this->getNode('display_start'))
316            ->subcompile($this->getNode('body'))
317        ;
318
319        if ($this->hasNode('parent')) {
320            $parent = $this->getNode('parent');
321
322            $compiler->addDebugInfo($parent);
323            if ($parent instanceof ConstantExpression) {
324                $compiler
325                    ->write('$this->parent = $this->loadTemplate(')
326                    ->subcompile($parent)
327                    ->raw(', ')
328                    ->repr($this->getSourceContext()->getName())
329                    ->raw(', ')
330                    ->repr($parent->getTemplateLine())
331                    ->raw(");\n")
332                ;
333                $compiler->write('$this->parent');
334            } else {
335                $compiler->write('$this->getParent($context)');
336            }
337            $compiler->raw("->display(\$context, array_merge(\$this->blocks, \$blocks));\n");
338        }
339
340        $compiler
341            ->subcompile($this->getNode('display_end'))
342            ->outdent()
343            ->write("}\n\n")
344        ;
345    }
346
347    protected function compileClassFooter(Compiler $compiler)
348    {
349        $compiler
350            ->subcompile($this->getNode('class_end'))
351            ->outdent()
352            ->write("}\n")
353        ;
354    }
355
356    protected function compileMacros(Compiler $compiler)
357    {
358        $compiler->subcompile($this->getNode('macros'));
359    }
360
361    protected function compileGetTemplateName(Compiler $compiler)
362    {
363        $compiler
364            ->write("public function getTemplateName()\n", "{\n")
365            ->indent()
366            ->write('return ')
367            ->repr($this->getSourceContext()->getName())
368            ->raw(";\n")
369            ->outdent()
370            ->write("}\n\n")
371        ;
372    }
373
374    protected function compileIsTraitable(Compiler $compiler)
375    {
376        // A template can be used as a trait if:
377        //   * it has no parent
378        //   * it has no macros
379        //   * it has no body
380        //
381        // Put another way, a template can be used as a trait if it
382        // only contains blocks and use statements.
383        $traitable = !$this->hasNode('parent') && 0 === \count($this->getNode('macros'));
384        if ($traitable) {
385            if ($this->getNode('body') instanceof BodyNode) {
386                $nodes = $this->getNode('body')->getNode(0);
387            } else {
388                $nodes = $this->getNode('body');
389            }
390
391            if (!\count($nodes)) {
392                $nodes = new Node([$nodes]);
393            }
394
395            foreach ($nodes as $node) {
396                if (!\count($node)) {
397                    continue;
398                }
399
400                if ($node instanceof TextNode && ctype_space($node->getAttribute('data'))) {
401                    continue;
402                }
403
404                if ($node instanceof BlockReferenceNode) {
405                    continue;
406                }
407
408                $traitable = false;
409                break;
410            }
411        }
412
413        if ($traitable) {
414            return;
415        }
416
417        $compiler
418            ->write("public function isTraitable()\n", "{\n")
419            ->indent()
420            ->write(sprintf("return %s;\n", $traitable ? 'true' : 'false'))
421            ->outdent()
422            ->write("}\n\n")
423        ;
424    }
425
426    protected function compileDebugInfo(Compiler $compiler)
427    {
428        $compiler
429            ->write("public function getDebugInfo()\n", "{\n")
430            ->indent()
431            ->write(sprintf("return %s;\n", str_replace("\n", '', var_export(array_reverse($compiler->getDebugInfo(), true), true))))
432            ->outdent()
433            ->write("}\n\n")
434        ;
435    }
436
437    protected function compileGetSourceContext(Compiler $compiler)
438    {
439        $compiler
440            ->write("public function getSourceContext()\n", "{\n")
441            ->indent()
442            ->write('return new Source(')
443            ->string($compiler->getEnvironment()->isDebug() ? $this->getSourceContext()->getCode() : '')
444            ->raw(', ')
445            ->string($this->getSourceContext()->getName())
446            ->raw(', ')
447            ->string($this->getSourceContext()->getPath())
448            ->raw(");\n")
449            ->outdent()
450            ->write("}\n")
451        ;
452    }
453
454    protected function compileLoadTemplate(Compiler $compiler, $node, $var)
455    {
456        if ($node instanceof ConstantExpression) {
457            $compiler
458                ->write(sprintf('%s = $this->loadTemplate(', $var))
459                ->subcompile($node)
460                ->raw(', ')
461                ->repr($node->getTemplateName())
462                ->raw(', ')
463                ->repr($node->getTemplateLine())
464                ->raw(");\n")
465            ;
466        } else {
467            throw new \LogicException('Trait templates can only be constant nodes.');
468        }
469    }
470}
471
472class_alias('Twig\Node\ModuleNode', 'Twig_Node_Module');
473