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