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