| 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 | * @package twig |
|---|
| 17 | * @author Fabien Potencier <fabien@symfony.com> |
|---|
| 18 | */ |
|---|
| 19 | abstract class Twig_Template implements Twig_TemplateInterface |
|---|
| 20 | { |
|---|
| 21 | protected static $cache = array(); |
|---|
| 22 | |
|---|
| 23 | protected $parent; |
|---|
| 24 | protected $parents; |
|---|
| 25 | protected $env; |
|---|
| 26 | protected $blocks; |
|---|
| 27 | protected $traits; |
|---|
| 28 | |
|---|
| 29 | /** |
|---|
| 30 | * Constructor. |
|---|
| 31 | * |
|---|
| 32 | * @param Twig_Environment $env A Twig_Environment instance |
|---|
| 33 | */ |
|---|
| 34 | public function __construct(Twig_Environment $env) |
|---|
| 35 | { |
|---|
| 36 | $this->env = $env; |
|---|
| 37 | $this->blocks = array(); |
|---|
| 38 | $this->traits = array(); |
|---|
| 39 | } |
|---|
| 40 | |
|---|
| 41 | /** |
|---|
| 42 | * Returns the template name. |
|---|
| 43 | * |
|---|
| 44 | * @return string The template name |
|---|
| 45 | */ |
|---|
| 46 | abstract public function getTemplateName(); |
|---|
| 47 | |
|---|
| 48 | /** |
|---|
| 49 | * {@inheritdoc} |
|---|
| 50 | */ |
|---|
| 51 | public function getEnvironment() |
|---|
| 52 | { |
|---|
| 53 | return $this->env; |
|---|
| 54 | } |
|---|
| 55 | |
|---|
| 56 | /** |
|---|
| 57 | * Returns the parent template. |
|---|
| 58 | * |
|---|
| 59 | * This method is for internal use only and should never be called |
|---|
| 60 | * directly. |
|---|
| 61 | * |
|---|
| 62 | * @return Twig_TemplateInterface|false The parent template or false if there is no parent |
|---|
| 63 | */ |
|---|
| 64 | public function getParent(array $context) |
|---|
| 65 | { |
|---|
| 66 | if (null !== $this->parent) { |
|---|
| 67 | return $this->parent; |
|---|
| 68 | } |
|---|
| 69 | |
|---|
| 70 | $parent = $this->doGetParent($context); |
|---|
| 71 | if (false === $parent) { |
|---|
| 72 | return false; |
|---|
| 73 | } elseif ($parent instanceof Twig_Template) { |
|---|
| 74 | $name = $parent->getTemplateName(); |
|---|
| 75 | $this->parents[$name] = $parent; |
|---|
| 76 | $parent = $name; |
|---|
| 77 | } elseif (!isset($this->parents[$parent])) { |
|---|
| 78 | $this->parents[$parent] = $this->env->loadTemplate($parent); |
|---|
| 79 | } |
|---|
| 80 | |
|---|
| 81 | return $this->parents[$parent]; |
|---|
| 82 | } |
|---|
| 83 | |
|---|
| 84 | protected function doGetParent(array $context) |
|---|
| 85 | { |
|---|
| 86 | return false; |
|---|
| 87 | } |
|---|
| 88 | |
|---|
| 89 | public function isTraitable() |
|---|
| 90 | { |
|---|
| 91 | return true; |
|---|
| 92 | } |
|---|
| 93 | |
|---|
| 94 | /** |
|---|
| 95 | * Displays a parent block. |
|---|
| 96 | * |
|---|
| 97 | * This method is for internal use only and should never be called |
|---|
| 98 | * directly. |
|---|
| 99 | * |
|---|
| 100 | * @param string $name The block name to display from the parent |
|---|
| 101 | * @param array $context The context |
|---|
| 102 | * @param array $blocks The current set of blocks |
|---|
| 103 | */ |
|---|
| 104 | public function displayParentBlock($name, array $context, array $blocks = array()) |
|---|
| 105 | { |
|---|
| 106 | $name = (string) $name; |
|---|
| 107 | |
|---|
| 108 | if (isset($this->traits[$name])) { |
|---|
| 109 | $this->traits[$name][0]->displayBlock($name, $context, $blocks); |
|---|
| 110 | } elseif (false !== $parent = $this->getParent($context)) { |
|---|
| 111 | $parent->displayBlock($name, $context, $blocks); |
|---|
| 112 | } else { |
|---|
| 113 | throw new Twig_Error_Runtime(sprintf('The template has no parent and no traits defining the "%s" block', $name), -1, $this->getTemplateName()); |
|---|
| 114 | } |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | /** |
|---|
| 118 | * Displays a block. |
|---|
| 119 | * |
|---|
| 120 | * This method is for internal use only and should never be called |
|---|
| 121 | * directly. |
|---|
| 122 | * |
|---|
| 123 | * @param string $name The block name to display |
|---|
| 124 | * @param array $context The context |
|---|
| 125 | * @param array $blocks The current set of blocks |
|---|
| 126 | */ |
|---|
| 127 | public function displayBlock($name, array $context, array $blocks = array()) |
|---|
| 128 | { |
|---|
| 129 | $name = (string) $name; |
|---|
| 130 | |
|---|
| 131 | if (isset($blocks[$name])) { |
|---|
| 132 | $b = $blocks; |
|---|
| 133 | unset($b[$name]); |
|---|
| 134 | call_user_func($blocks[$name], $context, $b); |
|---|
| 135 | } elseif (isset($this->blocks[$name])) { |
|---|
| 136 | call_user_func($this->blocks[$name], $context, $blocks); |
|---|
| 137 | } elseif (false !== $parent = $this->getParent($context)) { |
|---|
| 138 | $parent->displayBlock($name, $context, array_merge($this->blocks, $blocks)); |
|---|
| 139 | } |
|---|
| 140 | } |
|---|
| 141 | |
|---|
| 142 | /** |
|---|
| 143 | * Renders a parent block. |
|---|
| 144 | * |
|---|
| 145 | * This method is for internal use only and should never be called |
|---|
| 146 | * directly. |
|---|
| 147 | * |
|---|
| 148 | * @param string $name The block name to render from the parent |
|---|
| 149 | * @param array $context The context |
|---|
| 150 | * @param array $blocks The current set of blocks |
|---|
| 151 | * |
|---|
| 152 | * @return string The rendered block |
|---|
| 153 | */ |
|---|
| 154 | public function renderParentBlock($name, array $context, array $blocks = array()) |
|---|
| 155 | { |
|---|
| 156 | ob_start(); |
|---|
| 157 | $this->displayParentBlock($name, $context, $blocks); |
|---|
| 158 | |
|---|
| 159 | return ob_get_clean(); |
|---|
| 160 | } |
|---|
| 161 | |
|---|
| 162 | /** |
|---|
| 163 | * Renders a block. |
|---|
| 164 | * |
|---|
| 165 | * This method is for internal use only and should never be called |
|---|
| 166 | * directly. |
|---|
| 167 | * |
|---|
| 168 | * @param string $name The block name to render |
|---|
| 169 | * @param array $context The context |
|---|
| 170 | * @param array $blocks The current set of blocks |
|---|
| 171 | * |
|---|
| 172 | * @return string The rendered block |
|---|
| 173 | */ |
|---|
| 174 | public function renderBlock($name, array $context, array $blocks = array()) |
|---|
| 175 | { |
|---|
| 176 | ob_start(); |
|---|
| 177 | $this->displayBlock($name, $context, $blocks); |
|---|
| 178 | |
|---|
| 179 | return ob_get_clean(); |
|---|
| 180 | } |
|---|
| 181 | |
|---|
| 182 | /** |
|---|
| 183 | * Returns whether a block exists or not. |
|---|
| 184 | * |
|---|
| 185 | * This method is for internal use only and should never be called |
|---|
| 186 | * directly. |
|---|
| 187 | * |
|---|
| 188 | * This method does only return blocks defined in the current template |
|---|
| 189 | * or defined in "used" traits. |
|---|
| 190 | * |
|---|
| 191 | * It does not return blocks from parent templates as the parent |
|---|
| 192 | * template name can be dynamic, which is only known based on the |
|---|
| 193 | * current context. |
|---|
| 194 | * |
|---|
| 195 | * @param string $name The block name |
|---|
| 196 | * |
|---|
| 197 | * @return Boolean true if the block exists, false otherwise |
|---|
| 198 | */ |
|---|
| 199 | public function hasBlock($name) |
|---|
| 200 | { |
|---|
| 201 | return isset($this->blocks[(string) $name]); |
|---|
| 202 | } |
|---|
| 203 | |
|---|
| 204 | /** |
|---|
| 205 | * Returns all block names. |
|---|
| 206 | * |
|---|
| 207 | * This method is for internal use only and should never be called |
|---|
| 208 | * directly. |
|---|
| 209 | * |
|---|
| 210 | * @return array An array of block names |
|---|
| 211 | * |
|---|
| 212 | * @see hasBlock |
|---|
| 213 | */ |
|---|
| 214 | public function getBlockNames() |
|---|
| 215 | { |
|---|
| 216 | return array_keys($this->blocks); |
|---|
| 217 | } |
|---|
| 218 | |
|---|
| 219 | /** |
|---|
| 220 | * Returns all blocks. |
|---|
| 221 | * |
|---|
| 222 | * This method is for internal use only and should never be called |
|---|
| 223 | * directly. |
|---|
| 224 | * |
|---|
| 225 | * @return array An array of blocks |
|---|
| 226 | * |
|---|
| 227 | * @see hasBlock |
|---|
| 228 | */ |
|---|
| 229 | public function getBlocks() |
|---|
| 230 | { |
|---|
| 231 | return $this->blocks; |
|---|
| 232 | } |
|---|
| 233 | |
|---|
| 234 | /** |
|---|
| 235 | * {@inheritdoc} |
|---|
| 236 | */ |
|---|
| 237 | public function display(array $context, array $blocks = array()) |
|---|
| 238 | { |
|---|
| 239 | $this->displayWithErrorHandling($this->env->mergeGlobals($context), $blocks); |
|---|
| 240 | } |
|---|
| 241 | |
|---|
| 242 | /** |
|---|
| 243 | * {@inheritdoc} |
|---|
| 244 | */ |
|---|
| 245 | public function render(array $context) |
|---|
| 246 | { |
|---|
| 247 | $level = ob_get_level(); |
|---|
| 248 | ob_start(); |
|---|
| 249 | try { |
|---|
| 250 | $this->display($context); |
|---|
| 251 | } catch (Exception $e) { |
|---|
| 252 | while (ob_get_level() > $level) { |
|---|
| 253 | ob_end_clean(); |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | throw $e; |
|---|
| 257 | } |
|---|
| 258 | |
|---|
| 259 | return ob_get_clean(); |
|---|
| 260 | } |
|---|
| 261 | |
|---|
| 262 | protected function displayWithErrorHandling(array $context, array $blocks = array()) |
|---|
| 263 | { |
|---|
| 264 | try { |
|---|
| 265 | $this->doDisplay($context, $blocks); |
|---|
| 266 | } catch (Twig_Error $e) { |
|---|
| 267 | if (!$e->getTemplateFile()) { |
|---|
| 268 | $e->setTemplateFile($this->getTemplateName()); |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | // this is mostly useful for Twig_Error_Loader exceptions |
|---|
| 272 | // see Twig_Error_Loader |
|---|
| 273 | if (false === $e->getTemplateLine()) { |
|---|
| 274 | $e->setTemplateLine(-1); |
|---|
| 275 | $e->guess(); |
|---|
| 276 | } |
|---|
| 277 | |
|---|
| 278 | throw $e; |
|---|
| 279 | } catch (Exception $e) { |
|---|
| 280 | throw new Twig_Error_Runtime(sprintf('An exception has been thrown during the rendering of a template ("%s").', $e->getMessage()), -1, null, $e); |
|---|
| 281 | } |
|---|
| 282 | } |
|---|
| 283 | |
|---|
| 284 | /** |
|---|
| 285 | * Auto-generated method to display the template with the given context. |
|---|
| 286 | * |
|---|
| 287 | * @param array $context An array of parameters to pass to the template |
|---|
| 288 | * @param array $blocks An array of blocks to pass to the template |
|---|
| 289 | */ |
|---|
| 290 | abstract protected function doDisplay(array $context, array $blocks = array()); |
|---|
| 291 | |
|---|
| 292 | /** |
|---|
| 293 | * Returns a variable from the context. |
|---|
| 294 | * |
|---|
| 295 | * This method is for internal use only and should never be called |
|---|
| 296 | * directly. |
|---|
| 297 | * |
|---|
| 298 | * This method should not be overridden in a sub-class as this is an |
|---|
| 299 | * implementation detail that has been introduced to optimize variable |
|---|
| 300 | * access for versions of PHP before 5.4. This is not a way to override |
|---|
| 301 | * the way to get a variable value. |
|---|
| 302 | * |
|---|
| 303 | * @param array $context The context |
|---|
| 304 | * @param string $item The variable to return from the context |
|---|
| 305 | * @param Boolean $ignoreStrictCheck Whether to ignore the strict variable check or not |
|---|
| 306 | * |
|---|
| 307 | * @return The content of the context variable |
|---|
| 308 | * |
|---|
| 309 | * @throws Twig_Error_Runtime if the variable does not exist and Twig is running in strict mode |
|---|
| 310 | */ |
|---|
| 311 | final protected function getContext($context, $item, $ignoreStrictCheck = false) |
|---|
| 312 | { |
|---|
| 313 | if (!array_key_exists($item, $context)) { |
|---|
| 314 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
|---|
| 315 | return null; |
|---|
| 316 | } |
|---|
| 317 | |
|---|
| 318 | throw new Twig_Error_Runtime(sprintf('Variable "%s" does not exist', $item), -1, $this->getTemplateName()); |
|---|
| 319 | } |
|---|
| 320 | |
|---|
| 321 | return $context[$item]; |
|---|
| 322 | } |
|---|
| 323 | |
|---|
| 324 | /** |
|---|
| 325 | * Returns the attribute value for a given array/object. |
|---|
| 326 | * |
|---|
| 327 | * @param mixed $object The object or array from where to get the item |
|---|
| 328 | * @param mixed $item The item to get from the array or object |
|---|
| 329 | * @param array $arguments An array of arguments to pass if the item is an object method |
|---|
| 330 | * @param string $type The type of attribute (@see Twig_TemplateInterface) |
|---|
| 331 | * @param Boolean $isDefinedTest Whether this is only a defined check |
|---|
| 332 | * @param Boolean $ignoreStrictCheck Whether to ignore the strict attribute check or not |
|---|
| 333 | * |
|---|
| 334 | * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true |
|---|
| 335 | * |
|---|
| 336 | * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false |
|---|
| 337 | */ |
|---|
| 338 | protected function getAttribute($object, $item, array $arguments = array(), $type = Twig_TemplateInterface::ANY_CALL, $isDefinedTest = false, $ignoreStrictCheck = false) |
|---|
| 339 | { |
|---|
| 340 | $item = ctype_digit((string) $item) ? (int) $item : (string) $item; |
|---|
| 341 | |
|---|
| 342 | // array |
|---|
| 343 | if (Twig_TemplateInterface::METHOD_CALL !== $type) { |
|---|
| 344 | if ((is_array($object) && array_key_exists($item, $object)) |
|---|
| 345 | || ($object instanceof ArrayAccess && isset($object[$item])) |
|---|
| 346 | ) { |
|---|
| 347 | if ($isDefinedTest) { |
|---|
| 348 | return true; |
|---|
| 349 | } |
|---|
| 350 | |
|---|
| 351 | return $object[$item]; |
|---|
| 352 | } |
|---|
| 353 | |
|---|
| 354 | if (Twig_TemplateInterface::ARRAY_CALL === $type) { |
|---|
| 355 | if ($isDefinedTest) { |
|---|
| 356 | return false; |
|---|
| 357 | } |
|---|
| 358 | |
|---|
| 359 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
|---|
| 360 | return null; |
|---|
| 361 | } |
|---|
| 362 | |
|---|
| 363 | if (is_object($object)) { |
|---|
| 364 | throw new Twig_Error_Runtime(sprintf('Key "%s" in object (with ArrayAccess) of type "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); |
|---|
| 365 | } elseif (is_array($object)) { |
|---|
| 366 | throw new Twig_Error_Runtime(sprintf('Key "%s" for array with keys "%s" does not exist', $item, implode(', ', array_keys($object))), -1, $this->getTemplateName()); |
|---|
| 367 | } else { |
|---|
| 368 | throw new Twig_Error_Runtime(sprintf('Impossible to access a key ("%s") on a "%s" variable', $item, gettype($object)), -1, $this->getTemplateName()); |
|---|
| 369 | } |
|---|
| 370 | } |
|---|
| 371 | } |
|---|
| 372 | |
|---|
| 373 | if (!is_object($object)) { |
|---|
| 374 | if ($isDefinedTest) { |
|---|
| 375 | return false; |
|---|
| 376 | } |
|---|
| 377 | |
|---|
| 378 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
|---|
| 379 | return null; |
|---|
| 380 | } |
|---|
| 381 | |
|---|
| 382 | throw new Twig_Error_Runtime(sprintf('Item "%s" for "%s" does not exist', $item, is_array($object) ? 'Array' : $object), -1, $this->getTemplateName()); |
|---|
| 383 | } |
|---|
| 384 | |
|---|
| 385 | $class = get_class($object); |
|---|
| 386 | |
|---|
| 387 | // object property |
|---|
| 388 | if (Twig_TemplateInterface::METHOD_CALL !== $type) { |
|---|
| 389 | if (isset($object->$item) || array_key_exists($item, $object)) { |
|---|
| 390 | if ($isDefinedTest) { |
|---|
| 391 | return true; |
|---|
| 392 | } |
|---|
| 393 | |
|---|
| 394 | if ($this->env->hasExtension('sandbox')) { |
|---|
| 395 | $this->env->getExtension('sandbox')->checkPropertyAllowed($object, $item); |
|---|
| 396 | } |
|---|
| 397 | |
|---|
| 398 | return $object->$item; |
|---|
| 399 | } |
|---|
| 400 | } |
|---|
| 401 | |
|---|
| 402 | // object method |
|---|
| 403 | if (!isset(self::$cache[$class]['methods'])) { |
|---|
| 404 | self::$cache[$class]['methods'] = array_change_key_case(array_flip(get_class_methods($object))); |
|---|
| 405 | } |
|---|
| 406 | |
|---|
| 407 | $lcItem = strtolower($item); |
|---|
| 408 | if (isset(self::$cache[$class]['methods'][$lcItem])) { |
|---|
| 409 | $method = $item; |
|---|
| 410 | } elseif (isset(self::$cache[$class]['methods']['get'.$lcItem])) { |
|---|
| 411 | $method = 'get'.$item; |
|---|
| 412 | } elseif (isset(self::$cache[$class]['methods']['is'.$lcItem])) { |
|---|
| 413 | $method = 'is'.$item; |
|---|
| 414 | } elseif (isset(self::$cache[$class]['methods']['__call'])) { |
|---|
| 415 | $method = $item; |
|---|
| 416 | } else { |
|---|
| 417 | if ($isDefinedTest) { |
|---|
| 418 | return false; |
|---|
| 419 | } |
|---|
| 420 | |
|---|
| 421 | if ($ignoreStrictCheck || !$this->env->isStrictVariables()) { |
|---|
| 422 | return null; |
|---|
| 423 | } |
|---|
| 424 | |
|---|
| 425 | throw new Twig_Error_Runtime(sprintf('Method "%s" for object "%s" does not exist', $item, get_class($object)), -1, $this->getTemplateName()); |
|---|
| 426 | } |
|---|
| 427 | |
|---|
| 428 | if ($isDefinedTest) { |
|---|
| 429 | return true; |
|---|
| 430 | } |
|---|
| 431 | |
|---|
| 432 | if ($this->env->hasExtension('sandbox')) { |
|---|
| 433 | $this->env->getExtension('sandbox')->checkMethodAllowed($object, $method); |
|---|
| 434 | } |
|---|
| 435 | |
|---|
| 436 | $ret = call_user_func_array(array($object, $method), $arguments); |
|---|
| 437 | |
|---|
| 438 | // useful when calling a template method from a template |
|---|
| 439 | // this is not supported but unfortunately heavily used in the Symfony profiler |
|---|
| 440 | if ($object instanceof Twig_TemplateInterface) { |
|---|
| 441 | return $ret === '' ? '' : new Twig_Markup($ret, $this->env->getCharset()); |
|---|
| 442 | } |
|---|
| 443 | |
|---|
| 444 | return $ret; |
|---|
| 445 | } |
|---|
| 446 | |
|---|
| 447 | /** |
|---|
| 448 | * This method is only useful when testing Twig. Do not use it. |
|---|
| 449 | */ |
|---|
| 450 | public static function clearCache() |
|---|
| 451 | { |
|---|
| 452 | self::$cache = array(); |
|---|
| 453 | } |
|---|
| 454 | } |
|---|