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