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