vendor/twig/twig/lib/Twig/Template.php line 403

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