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 implements \Twig_TemplateInterface 31{ 32 /** 33 * @internal 34 */ 35 protected static $cache = []; 36 37 protected $parent; 38 protected $parents = []; 39 protected $env; 40 protected $blocks = []; 41 protected $traits = []; 42 protected $sandbox; 43 44 public function __construct(Environment $env) 45 { 46 $this->env = $env; 47 } 48 49 /** 50 * @internal this method will be removed in 2.0 and is only used internally to provide an upgrade path from 1.x to 2.0 51 */ 52 public function __toString() 53 { 54 return $this->getTemplateName(); 55 } 56 57 /** 58 * Returns the template name. 59 * 60 * @return string The template name 61 */ 62 abstract public function getTemplateName(); 63 64 /** 65 * Returns debug information about the template. 66 * 67 * @return array Debug information 68 */ 69 public function getDebugInfo() 70 { 71 return []; 72 } 73 74 /** 75 * Returns the template source code. 76 * 77 * @return string The template source code 78 * 79 * @deprecated since 1.27 (to be removed in 2.0). Use getSourceContext() instead 80 */ 81 public function getSource() 82 { 83 @trigger_error('The '.__METHOD__.' method is deprecated since version 1.27 and will be removed in 2.0. Use getSourceContext() instead.', E_USER_DEPRECATED); 84 85 return ''; 86 } 87 88 /** 89 * Returns information about the original template source code. 90 * 91 * @return Source 92 */ 93 public function getSourceContext() 94 { 95 return new Source('', $this->getTemplateName()); 96 } 97 98 /** 99 * @deprecated since 1.20 (to be removed in 2.0) 100 */ 101 public function getEnvironment() 102 { 103 @trigger_error('The '.__METHOD__.' method is deprecated since version 1.20 and will be removed in 2.0.', E_USER_DEPRECATED); 104 105 return $this->env; 106 } 107 108 /** 109 * Returns the parent template. 110 * 111 * This method is for internal use only and should never be called 112 * directly. 113 * 114 * @param array $context 115 * 116 * @return \Twig_TemplateInterface|TemplateWrapper|false The parent template or false if there is no parent 117 * 118 * @internal 119 */ 120 public function getParent(array $context) 121 { 122 if (null !== $this->parent) { 123 return $this->parent; 124 } 125 126 try { 127 $parent = $this->doGetParent($context); 128 129 if (false === $parent) { 130 return false; 131 } 132 133 if ($parent instanceof self || $parent instanceof TemplateWrapper) { 134 return $this->parents[$parent->getSourceContext()->getName()] = $parent; 135 } 136 137 if (!isset($this->parents[$parent])) { 138 $this->parents[$parent] = $this->loadTemplate($parent); 139 } 140 } catch (LoaderError $e) { 141 $e->setSourceContext(null); 142 $e->guess(); 143 144 throw $e; 145 } 146 147 return $this->parents[$parent]; 148 } 149 150 protected function doGetParent(array $context) 151 { 152 return false; 153 } 154 155 public function isTraitable() 156 { 157 return true; 158 } 159 160 /** 161 * Displays a parent block. 162 * 163 * This method is for internal use only and should never be called 164 * directly. 165 * 166 * @param string $name The block name to display from the parent 167 * @param array $context The context 168 * @param array $blocks The current set of blocks 169 */ 170 public function displayParentBlock($name, array $context, array $blocks = []) 171 { 172 $name = (string) $name; 173 174 if (isset($this->traits[$name])) { 175 $this->traits[$name][0]->displayBlock($name, $context, $blocks, false); 176 } elseif (false !== $parent = $this->getParent($context)) { 177 $parent->displayBlock($name, $context, $blocks, false); 178 } else { 179 throw new RuntimeError(sprintf('The template has no parent and no traits defining the "%s" block.', $name), -1, $this->getSourceContext()); 180 } 181 } 182 183 /** 184 * Displays a block. 185 * 186 * This method is for internal use only and should never be called 187 * directly. 188 * 189 * @param string $name The block name to display 190 * @param array $context The context 191 * @param array $blocks The current set of blocks 192 * @param bool $useBlocks Whether to use the current set of blocks 193 */ 194 public function displayBlock($name, array $context, array $blocks = [], $useBlocks = true) 195 { 196 $name = (string) $name; 197 198 if ($useBlocks && isset($blocks[$name])) { 199 $template = $blocks[$name][0]; 200 $block = $blocks[$name][1]; 201 } elseif (isset($this->blocks[$name])) { 202 $template = $this->blocks[$name][0]; 203 $block = $this->blocks[$name][1]; 204 } else { 205 $template = null; 206 $block = null; 207 } 208 209 // avoid RCEs when sandbox is enabled 210 if (null !== $template && !$template instanceof self) { 211 throw new \LogicException('A block must be a method on a \Twig\Template instance.'); 212 } 213 214 if (null !== $template) { 215 try { 216 $template->$block($context, $blocks); 217 } catch (Error $e) { 218 if (!$e->getSourceContext()) { 219 $e->setSourceContext($template->getSourceContext()); 220 } 221 222 // this is mostly useful for \Twig\Error\LoaderError exceptions 223 // see \Twig\Error\LoaderError 224 if (-1 === $e->getTemplateLine()) { 225 $e->guess(); 226 } 227 228 throw $e; 229 } catch (\Exception $e) { 230 throw new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $template->getSourceContext(), $e); 231 } 232 } elseif (false !== $parent = $this->getParent($context)) { 233 $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks), false); 234 } else { 235 @trigger_error(sprintf('Silent display of undefined block "%s" in template "%s" is deprecated since version 1.29 and will throw an exception in 2.0. Use the "block(\'%s\') is defined" expression to test for block existence.', $name, $this->getTemplateName(), $name), E_USER_DEPRECATED); 236 } 237 } 238 239 /** 240 * Renders a parent block. 241 * 242 * This method is for internal use only and should never be called 243 * directly. 244 * 245 * @param string $name The block name to render from the parent 246 * @param array $context The context 247 * @param array $blocks The current set of blocks 248 * 249 * @return string The rendered block 250 */ 251 public function renderParentBlock($name, array $context, array $blocks = []) 252 { 253 ob_start(); 254 $this->displayParentBlock($name, $context, $blocks); 255 256 return ob_get_clean(); 257 } 258 259 /** 260 * Renders a block. 261 * 262 * This method is for internal use only and should never be called 263 * directly. 264 * 265 * @param string $name The block name to render 266 * @param array $context The context 267 * @param array $blocks The current set of blocks 268 * @param bool $useBlocks Whether to use the current set of blocks 269 * 270 * @return string The rendered block 271 */ 272 public function renderBlock($name, array $context, array $blocks = [], $useBlocks = true) 273 { 274 ob_start(); 275 $this->displayBlock($name, $context, $blocks, $useBlocks); 276 277 return ob_get_clean(); 278 } 279 280 /** 281 * Returns whether a block exists or not in the current context of the template. 282 * 283 * This method checks blocks defined in the current template 284 * or defined in "used" traits or defined in parent templates. 285 * 286 * @param string $name The block name 287 * @param array $context The context 288 * @param array $blocks The current set of blocks 289 * 290 * @return bool true if the block exists, false otherwise 291 */ 292 public function hasBlock($name, array $context = null, array $blocks = []) 293 { 294 if (null === $context) { 295 @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); 296 297 return isset($this->blocks[(string) $name]); 298 } 299 300 if (isset($blocks[$name])) { 301 return $blocks[$name][0] instanceof self; 302 } 303 304 if (isset($this->blocks[$name])) { 305 return true; 306 } 307 308 if (false !== $parent = $this->getParent($context)) { 309 return $parent->hasBlock($name, $context); 310 } 311 312 return false; 313 } 314 315 /** 316 * Returns all block names in the current context of the template. 317 * 318 * This method checks blocks defined in the current template 319 * or defined in "used" traits or defined in parent templates. 320 * 321 * @param array $context The context 322 * @param array $blocks The current set of blocks 323 * 324 * @return array An array of block names 325 */ 326 public function getBlockNames(array $context = null, array $blocks = []) 327 { 328 if (null === $context) { 329 @trigger_error('The '.__METHOD__.' method is internal and should never be called; calling it directly is deprecated since version 1.28 and won\'t be possible anymore in 2.0.', E_USER_DEPRECATED); 330 331 return array_keys($this->blocks); 332 } 333 334 $names = array_merge(array_keys($blocks), array_keys($this->blocks)); 335 336 if (false !== $parent = $this->getParent($context)) { 337 $names = array_merge($names, $parent->getBlockNames($context)); 338 } 339 340 return array_unique($names); 341 } 342 343 protected function loadTemplate($template, $templateName = null, $line = null, $index = null) 344 { 345 try { 346 if (\is_array($template)) { 347 return $this->env->resolveTemplate($template); 348 } 349 350 if ($template instanceof self || $template instanceof TemplateWrapper) { 351 return $template; 352 } 353 354 if ($template === $this->getTemplateName()) { 355 $class = get_class($this); 356 if (false !== $pos = strrpos($class, '___', -1)) { 357 $class = substr($class, 0, $pos); 358 } 359 360 return $this->env->loadClass($class, $template, $index); 361 } 362 363 return $this->env->loadTemplate($template, $index); 364 } catch (Error $e) { 365 if (!$e->getSourceContext()) { 366 $e->setSourceContext($templateName ? new Source('', $templateName) : $this->getSourceContext()); 367 } 368 369 if ($e->getTemplateLine() > 0) { 370 throw $e; 371 } 372 373 if (!$line) { 374 $e->guess(); 375 } else { 376 $e->setTemplateLine($line); 377 } 378 379 throw $e; 380 } 381 } 382 383 /** 384 * Returns all blocks. 385 * 386 * This method is for internal use only and should never be called 387 * directly. 388 * 389 * @return array An array of blocks 390 */ 391 public function getBlocks() 392 { 393 return $this->blocks; 394 } 395 396 public function display(array $context, array $blocks = []) 397 { 398 $this->displayWithErrorHandling($this->env->mergeGlobals($context), array_merge($this->blocks, $blocks)); 399 } 400 401 public function render(array $context) 402 { 403 $level = ob_get_level(); 404 ob_start(); 405 try { 406 $this->display($context); 407 } catch (\Exception $e) { 408 while (ob_get_level() > $level) { 409 ob_end_clean(); 410 } 411 412 throw $e; 413 } catch (\Throwable $e) { 414 while (ob_get_level() > $level) { 415 ob_end_clean(); 416 } 417 418 throw $e; 419 } 420 421 return ob_get_clean(); 422 } 423 424 protected function displayWithErrorHandling(array $context, array $blocks = []) 425 { 426 try { 427 $this->doDisplay($context, $blocks); 428 } catch (Error $e) { 429 if (!$e->getSourceContext()) { 430 $e->setSourceContext($this->getSourceContext()); 431 } 432 433 // this is mostly useful for \Twig\Error\LoaderError exceptions 434 // see \Twig\Error\LoaderError 435 if (-1 === $e->getTemplateLine()) { 436 $e->guess(); 437 } 438 439 throw $e; 440 } catch (\Exception $e) { 441 throw new RuntimeError(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, $this->getSourceContext(), $e); 442 } 443 } 444 445 /** 446 * Auto-generated method to display the template with the given context. 447 * 448 * @param array $context An array of parameters to pass to the template 449 * @param array $blocks An array of blocks to pass to the template 450 */ 451 abstract protected function doDisplay(array $context, array $blocks = []); 452 453 /** 454 * Returns a variable from the context. 455 * 456 * This method is for internal use only and should never be called 457 * directly. 458 * 459 * This method should not be overridden in a sub-class as this is an 460 * implementation detail that has been introduced to optimize variable 461 * access for versions of PHP before 5.4. This is not a way to override 462 * the way to get a variable value. 463 * 464 * @param array $context The context 465 * @param string $item The variable to return from the context 466 * @param bool $ignoreStrictCheck Whether to ignore the strict variable check or not 467 * 468 * @return mixed The content of the context variable 469 * 470 * @throws RuntimeError if the variable does not exist and Twig is running in strict mode 471 * 472 * @internal 473 */ 474 final protected function getContext($context, $item, $ignoreStrictCheck = false) 475 { 476 if (!\array_key_exists($item, $context)) { 477 if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { 478 return; 479 } 480 481 throw new RuntimeError(sprintf('Variable "%s" does not exist.', $item), -1, $this->getSourceContext()); 482 } 483 484 return $context[$item]; 485 } 486 487 /** 488 * Returns the attribute value for a given array/object. 489 * 490 * @param mixed $object The object or array from where to get the item 491 * @param mixed $item The item to get from the array or object 492 * @param array $arguments An array of arguments to pass if the item is an object method 493 * @param string $type The type of attribute (@see \Twig\Template constants) 494 * @param bool $isDefinedTest Whether this is only a defined check 495 * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not 496 * 497 * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true 498 * 499 * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false 500 * 501 * @internal 502 */ 503 protected function getAttribute($object, $item, array $arguments = [], $type = self::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) 504 { 505 // array 506 if (self::METHOD_CALL !== $type) { 507 $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; 508 509 if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, $object))) 510 || ($object instanceof \ArrayAccess && isset($object[$arrayItem])) 511 ) { 512 if ($isDefinedTest) { 513 return true; 514 } 515 516 return $object[$arrayItem]; 517 } 518 519 if (self::ARRAY_CALL === $type || !\is_object($object)) { 520 if ($isDefinedTest) { 521 return false; 522 } 523 524 if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { 525 return; 526 } 527 528 if ($object instanceof \ArrayAccess) { 529 $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); 530 } elseif (\is_object($object)) { 531 $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); 532 } elseif (\is_array($object)) { 533 if (empty($object)) { 534 $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); 535 } else { 536 $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); 537 } 538 } elseif (self::ARRAY_CALL === $type) { 539 if (null === $object) { 540 $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); 541 } else { 542 $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 543 } 544 } elseif (null === $object) { 545 $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); 546 } else { 547 $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 548 } 549 550 throw new RuntimeError($message, -1, $this->getSourceContext()); 551 } 552 } 553 554 if (!\is_object($object)) { 555 if ($isDefinedTest) { 556 return false; 557 } 558 559 if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { 560 return; 561 } 562 563 if (null === $object) { 564 $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); 565 } elseif (\is_array($object)) { 566 $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); 567 } else { 568 $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 569 } 570 571 throw new RuntimeError($message, -1, $this->getSourceContext()); 572 } 573 574 // object property 575 if (self::METHOD_CALL !== $type && !$object instanceof self) { // \Twig\Template does not have public properties, and we don't want to allow access to internal ones 576 if (isset($object->$item) || \array_key_exists((string) $item, $object)) { 577 if ($isDefinedTest) { 578 return true; 579 } 580 581 if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { 582 $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkPropertyAllowed($object, $item); 583 } 584 585 return $object->$item; 586 } 587 } 588 589 $class = \get_class($object); 590 591 // object method 592 if (!isset(self::$cache[$class])) { 593 // get_class_methods returns all methods accessible in the scope, but we only want public ones to be accessible in templates 594 if ($object instanceof self) { 595 $ref = new \ReflectionClass($class); 596 $methods = []; 597 598 foreach ($ref->getMethods(\ReflectionMethod::IS_PUBLIC) as $refMethod) { 599 // Accessing the environment from templates is forbidden to prevent untrusted changes to the environment 600 if ('getenvironment' !== strtolower($refMethod->name)) { 601 $methods[] = $refMethod->name; 602 } 603 } 604 } else { 605 $methods = get_class_methods($object); 606 } 607 // sort values to have consistent behavior, so that "get" methods win precedence over "is" methods 608 sort($methods); 609 610 $cache = []; 611 612 foreach ($methods as $method) { 613 $cache[$method] = $method; 614 $cache[$lcName = strtolower($method)] = $method; 615 616 if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { 617 $name = substr($method, 3); 618 $lcName = substr($lcName, 3); 619 } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { 620 $name = substr($method, 2); 621 $lcName = substr($lcName, 2); 622 } else { 623 continue; 624 } 625 626 // skip get() and is() methods (in which case, $name is empty) 627 if ($name) { 628 if (!isset($cache[$name])) { 629 $cache[$name] = $method; 630 } 631 if (!isset($cache[$lcName])) { 632 $cache[$lcName] = $method; 633 } 634 } 635 } 636 self::$cache[$class] = $cache; 637 } 638 639 $call = false; 640 if (isset(self::$cache[$class][$item])) { 641 $method = self::$cache[$class][$item]; 642 } elseif (isset(self::$cache[$class][$lcItem = strtolower($item)])) { 643 $method = self::$cache[$class][$lcItem]; 644 } elseif (isset(self::$cache[$class]['__call'])) { 645 $method = $item; 646 $call = true; 647 } else { 648 if ($isDefinedTest) { 649 return false; 650 } 651 652 if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { 653 return; 654 } 655 656 throw new RuntimeError(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()" or "__call()" exist and have public access in class "%2$s".', $item, $class), -1, $this->getSourceContext()); 657 } 658 659 if ($isDefinedTest) { 660 return true; 661 } 662 663 if ($this->env->hasExtension('\Twig\Extension\SandboxExtension')) { 664 $this->env->getExtension('\Twig\Extension\SandboxExtension')->checkMethodAllowed($object, $method); 665 } 666 667 // Some objects throw exceptions when they have __call, and the method we try 668 // to call is not supported. If ignoreStrictCheck is true, we should return null. 669 try { 670 if (!$arguments) { 671 $ret = $object->$method(); 672 } else { 673 $ret = \call_user_func_array([$object, $method], $arguments); 674 } 675 } catch (\BadMethodCallException $e) { 676 if ($call && ($ignoreStrictCheck || !$this->env->isStrictVariables())) { 677 return; 678 } 679 throw $e; 680 } 681 682 // @deprecated in 1.28 683 if ($object instanceof \Twig_TemplateInterface) { 684 $self = $object->getTemplateName() === $this->getTemplateName(); 685 $message = sprintf('Calling "%s" on template "%s" from template "%s" is deprecated since version 1.28 and won\'t be supported anymore in 2.0.', $item, $object->getTemplateName(), $this->getTemplateName()); 686 if ('renderBlock' === $method || 'displayBlock' === $method) { 687 $message .= sprintf(' Use block("%s"%s) instead).', $arguments[0], $self ? '' : ', template'); 688 } elseif ('hasBlock' === $method) { 689 $message .= sprintf(' Use "block("%s"%s) is defined" instead).', $arguments[0], $self ? '' : ', template'); 690 } elseif ('render' === $method || 'display' === $method) { 691 $message .= sprintf(' Use include("%s") instead).', $object->getTemplateName()); 692 } 693 @trigger_error($message, E_USER_DEPRECATED); 694 695 return '' === $ret ? '' : new Markup($ret, $this->env->getCharset()); 696 } 697 698 return $ret; 699 } 700} 701 702class_alias('Twig\Template', 'Twig_Template'); 703