vendor/twig/twig/lib/Twig/Extension/Core.php line 578

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of Twig.
  4.  *
  5.  * (c) Fabien Potencier
  6.  *
  7.  * For the full copyright and license information, please view the LICENSE
  8.  * file that was distributed with this source code.
  9.  */
  10. final class Twig_Extension_Core extends Twig_Extension
  11. {
  12.     private $dateFormats = ['F j, Y H:i''%d days'];
  13.     private $numberFormat = [0'.'','];
  14.     private $timezone null;
  15.     private $escapers = [];
  16.     /**
  17.      * Defines a new escaper to be used via the escape filter.
  18.      *
  19.      * @param string   $strategy The strategy name that should be used as a strategy in the escape call
  20.      * @param callable $callable A valid PHP callable
  21.      */
  22.     public function setEscaper($strategy, callable $callable)
  23.     {
  24.         $this->escapers[$strategy] = $callable;
  25.     }
  26.     /**
  27.      * Gets all defined escapers.
  28.      *
  29.      * @return callable[] An array of escapers
  30.      */
  31.     public function getEscapers()
  32.     {
  33.         return $this->escapers;
  34.     }
  35.     /**
  36.      * Sets the default format to be used by the date filter.
  37.      *
  38.      * @param string $format             The default date format string
  39.      * @param string $dateIntervalFormat The default date interval format string
  40.      */
  41.     public function setDateFormat($format null$dateIntervalFormat null)
  42.     {
  43.         if (null !== $format) {
  44.             $this->dateFormats[0] = $format;
  45.         }
  46.         if (null !== $dateIntervalFormat) {
  47.             $this->dateFormats[1] = $dateIntervalFormat;
  48.         }
  49.     }
  50.     /**
  51.      * Gets the default format to be used by the date filter.
  52.      *
  53.      * @return array The default date format string and the default date interval format string
  54.      */
  55.     public function getDateFormat()
  56.     {
  57.         return $this->dateFormats;
  58.     }
  59.     /**
  60.      * Sets the default timezone to be used by the date filter.
  61.      *
  62.      * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
  63.      */
  64.     public function setTimezone($timezone)
  65.     {
  66.         $this->timezone $timezone instanceof DateTimeZone $timezone : new DateTimeZone($timezone);
  67.     }
  68.     /**
  69.      * Gets the default timezone to be used by the date filter.
  70.      *
  71.      * @return DateTimeZone The default timezone currently in use
  72.      */
  73.     public function getTimezone()
  74.     {
  75.         if (null === $this->timezone) {
  76.             $this->timezone = new DateTimeZone(date_default_timezone_get());
  77.         }
  78.         return $this->timezone;
  79.     }
  80.     /**
  81.      * Sets the default format to be used by the number_format filter.
  82.      *
  83.      * @param int    $decimal      the number of decimal places to use
  84.      * @param string $decimalPoint the character(s) to use for the decimal point
  85.      * @param string $thousandSep  the character(s) to use for the thousands separator
  86.      */
  87.     public function setNumberFormat($decimal$decimalPoint$thousandSep)
  88.     {
  89.         $this->numberFormat = [$decimal$decimalPoint$thousandSep];
  90.     }
  91.     /**
  92.      * Get the default format used by the number_format filter.
  93.      *
  94.      * @return array The arguments for number_format()
  95.      */
  96.     public function getNumberFormat()
  97.     {
  98.         return $this->numberFormat;
  99.     }
  100.     public function getTokenParsers()
  101.     {
  102.         return [
  103.             new Twig_TokenParser_For(),
  104.             new Twig_TokenParser_If(),
  105.             new Twig_TokenParser_Extends(),
  106.             new Twig_TokenParser_Include(),
  107.             new Twig_TokenParser_Block(),
  108.             new Twig_TokenParser_Use(),
  109.             new Twig_TokenParser_Filter(),
  110.             new Twig_TokenParser_Macro(),
  111.             new Twig_TokenParser_Import(),
  112.             new Twig_TokenParser_From(),
  113.             new Twig_TokenParser_Set(),
  114.             new Twig_TokenParser_Spaceless(),
  115.             new Twig_TokenParser_Flush(),
  116.             new Twig_TokenParser_Do(),
  117.             new Twig_TokenParser_Embed(),
  118.             new Twig_TokenParser_With(),
  119.             new Twig_TokenParser_Deprecated(),
  120.         ];
  121.     }
  122.     public function getFilters()
  123.     {
  124.         return [
  125.             // formatting filters
  126.             new Twig_Filter('date''twig_date_format_filter', ['needs_environment' => true]),
  127.             new Twig_Filter('date_modify''twig_date_modify_filter', ['needs_environment' => true]),
  128.             new Twig_Filter('format''sprintf'),
  129.             new Twig_Filter('replace''twig_replace_filter'),
  130.             new Twig_Filter('number_format''twig_number_format_filter', ['needs_environment' => true]),
  131.             new Twig_Filter('abs''abs'),
  132.             new Twig_Filter('round''twig_round'),
  133.             // encoding
  134.             new Twig_Filter('url_encode''twig_urlencode_filter'),
  135.             new Twig_Filter('json_encode''json_encode'),
  136.             new Twig_Filter('convert_encoding''twig_convert_encoding'),
  137.             // string filters
  138.             new Twig_Filter('title''twig_title_string_filter', ['needs_environment' => true]),
  139.             new Twig_Filter('capitalize''twig_capitalize_string_filter', ['needs_environment' => true]),
  140.             new Twig_Filter('upper''twig_upper_filter', ['needs_environment' => true]),
  141.             new Twig_Filter('lower''twig_lower_filter', ['needs_environment' => true]),
  142.             new Twig_Filter('striptags''strip_tags'),
  143.             new Twig_Filter('trim''twig_trim_filter'),
  144.             new Twig_Filter('nl2br''nl2br', ['pre_escape' => 'html''is_safe' => ['html']]),
  145.             // array helpers
  146.             new Twig_Filter('join''twig_join_filter'),
  147.             new Twig_Filter('split''twig_split_filter', ['needs_environment' => true]),
  148.             new Twig_Filter('sort''twig_sort_filter'),
  149.             new Twig_Filter('merge''twig_array_merge'),
  150.             new Twig_Filter('batch''twig_array_batch'),
  151.             // string/array filters
  152.             new Twig_Filter('reverse''twig_reverse_filter', ['needs_environment' => true]),
  153.             new Twig_Filter('length''twig_length_filter', ['needs_environment' => true]),
  154.             new Twig_Filter('slice''twig_slice', ['needs_environment' => true]),
  155.             new Twig_Filter('first''twig_first', ['needs_environment' => true]),
  156.             new Twig_Filter('last''twig_last', ['needs_environment' => true]),
  157.             // iteration and runtime
  158.             new Twig_Filter('default''_twig_default_filter', ['node_class' => 'Twig_Node_Expression_Filter_Default']),
  159.             new Twig_Filter('keys''twig_get_array_keys_filter'),
  160.             // escaping
  161.             new Twig_Filter('escape''twig_escape_filter', ['needs_environment' => true'is_safe_callback' => 'twig_escape_filter_is_safe']),
  162.             new Twig_Filter('e''twig_escape_filter', ['needs_environment' => true'is_safe_callback' => 'twig_escape_filter_is_safe']),
  163.         ];
  164.     }
  165.     public function getFunctions()
  166.     {
  167.         return [
  168.             new Twig_Function('max''max'),
  169.             new Twig_Function('min''min'),
  170.             new Twig_Function('range''range'),
  171.             new Twig_Function('constant''twig_constant'),
  172.             new Twig_Function('cycle''twig_cycle'),
  173.             new Twig_Function('random''twig_random', ['needs_environment' => true]),
  174.             new Twig_Function('date''twig_date_converter', ['needs_environment' => true]),
  175.             new Twig_Function('include''twig_include', ['needs_environment' => true'needs_context' => true'is_safe' => ['all']]),
  176.             new Twig_Function('source''twig_source', ['needs_environment' => true'is_safe' => ['all']]),
  177.         ];
  178.     }
  179.     public function getTests()
  180.     {
  181.         return [
  182.             new Twig_Test('even'null, ['node_class' => 'Twig_Node_Expression_Test_Even']),
  183.             new Twig_Test('odd'null, ['node_class' => 'Twig_Node_Expression_Test_Odd']),
  184.             new Twig_Test('defined'null, ['node_class' => 'Twig_Node_Expression_Test_Defined']),
  185.             new Twig_Test('same as'null, ['node_class' => 'Twig_Node_Expression_Test_Sameas']),
  186.             new Twig_Test('none'null, ['node_class' => 'Twig_Node_Expression_Test_Null']),
  187.             new Twig_Test('null'null, ['node_class' => 'Twig_Node_Expression_Test_Null']),
  188.             new Twig_Test('divisible by'null, ['node_class' => 'Twig_Node_Expression_Test_Divisibleby']),
  189.             new Twig_Test('constant'null, ['node_class' => 'Twig_Node_Expression_Test_Constant']),
  190.             new Twig_Test('empty''twig_test_empty'),
  191.             new Twig_Test('iterable''twig_test_iterable'),
  192.         ];
  193.     }
  194.     public function getOperators()
  195.     {
  196.         return [
  197.             [
  198.                 'not' => ['precedence' => 50'class' => 'Twig_Node_Expression_Unary_Not'],
  199.                 '-' => ['precedence' => 500'class' => 'Twig_Node_Expression_Unary_Neg'],
  200.                 '+' => ['precedence' => 500'class' => 'Twig_Node_Expression_Unary_Pos'],
  201.             ],
  202.             [
  203.                 'or' => ['precedence' => 10'class' => 'Twig_Node_Expression_Binary_Or''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  204.                 'and' => ['precedence' => 15'class' => 'Twig_Node_Expression_Binary_And''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  205.                 'b-or' => ['precedence' => 16'class' => 'Twig_Node_Expression_Binary_BitwiseOr''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  206.                 'b-xor' => ['precedence' => 17'class' => 'Twig_Node_Expression_Binary_BitwiseXor''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  207.                 'b-and' => ['precedence' => 18'class' => 'Twig_Node_Expression_Binary_BitwiseAnd''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  208.                 '==' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_Equal''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  209.                 '!=' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_NotEqual''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  210.                 '<' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_Less''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  211.                 '>' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_Greater''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  212.                 '>=' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_GreaterEqual''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  213.                 '<=' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_LessEqual''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  214.                 'not in' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_NotIn''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  215.                 'in' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_In''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  216.                 'matches' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_Matches''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  217.                 'starts with' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_StartsWith''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  218.                 'ends with' => ['precedence' => 20'class' => 'Twig_Node_Expression_Binary_EndsWith''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  219.                 '..' => ['precedence' => 25'class' => 'Twig_Node_Expression_Binary_Range''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  220.                 '+' => ['precedence' => 30'class' => 'Twig_Node_Expression_Binary_Add''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  221.                 '-' => ['precedence' => 30'class' => 'Twig_Node_Expression_Binary_Sub''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  222.                 '~' => ['precedence' => 40'class' => 'Twig_Node_Expression_Binary_Concat''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  223.                 '*' => ['precedence' => 60'class' => 'Twig_Node_Expression_Binary_Mul''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  224.                 '/' => ['precedence' => 60'class' => 'Twig_Node_Expression_Binary_Div''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  225.                 '//' => ['precedence' => 60'class' => 'Twig_Node_Expression_Binary_FloorDiv''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  226.                 '%' => ['precedence' => 60'class' => 'Twig_Node_Expression_Binary_Mod''associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  227.                 'is' => ['precedence' => 100'associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  228.                 'is not' => ['precedence' => 100'associativity' => Twig_ExpressionParser::OPERATOR_LEFT],
  229.                 '**' => ['precedence' => 200'class' => 'Twig_Node_Expression_Binary_Power''associativity' => Twig_ExpressionParser::OPERATOR_RIGHT],
  230.                 '??' => ['precedence' => 300'class' => 'Twig_Node_Expression_NullCoalesce''associativity' => Twig_ExpressionParser::OPERATOR_RIGHT],
  231.             ],
  232.         ];
  233.     }
  234. }
  235. /**
  236.  * Cycles over a value.
  237.  *
  238.  * @param ArrayAccess|array $values
  239.  * @param int               $position The cycle position
  240.  *
  241.  * @return string The next value in the cycle
  242.  */
  243. function twig_cycle($values$position)
  244. {
  245.     if (!is_array($values) && !$values instanceof ArrayAccess) {
  246.         return $values;
  247.     }
  248.     return $values[$position count($values)];
  249. }
  250. /**
  251.  * Returns a random value depending on the supplied parameter type:
  252.  * - a random item from a Traversable or array
  253.  * - a random character from a string
  254.  * - a random integer between 0 and the integer parameter.
  255.  *
  256.  * @param Twig_Environment                   $env
  257.  * @param Traversable|array|int|float|string $values The values to pick a random item from
  258.  *
  259.  * @throws Twig_Error_Runtime when $values is an empty array (does not apply to an empty string which is returned as is)
  260.  *
  261.  * @return mixed A random value from the given sequence
  262.  */
  263. function twig_random(Twig_Environment $env$values null)
  264. {
  265.     if (null === $values) {
  266.         return mt_rand();
  267.     }
  268.     if (is_int($values) || is_float($values)) {
  269.         return $values mt_rand($values0) : mt_rand(0$values);
  270.     }
  271.     if ($values instanceof Traversable) {
  272.         $values iterator_to_array($values);
  273.     } elseif (is_string($values)) {
  274.         if ('' === $values) {
  275.             return '';
  276.         }
  277.         $charset $env->getCharset();
  278.         if ('UTF-8' !== $charset) {
  279.             $values iconv($charset'UTF-8'$values);
  280.         }
  281.         // unicode version of str_split()
  282.         // split at all positions, but not after the start and not before the end
  283.         $values preg_split('/(?<!^)(?!$)/u'$values);
  284.         if ('UTF-8' !== $charset) {
  285.             foreach ($values as $i => $value) {
  286.                 $values[$i] = iconv('UTF-8'$charset$value);
  287.             }
  288.         }
  289.     }
  290.     if (!is_array($values)) {
  291.         return $values;
  292.     }
  293.     if (=== count($values)) {
  294.         throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
  295.     }
  296.     return $values[array_rand($values1)];
  297. }
  298. /**
  299.  * Converts a date to the given format.
  300.  *
  301.  * <pre>
  302.  *   {{ post.published_at|date("m/d/Y") }}
  303.  * </pre>
  304.  *
  305.  * @param Twig_Environment                      $env
  306.  * @param DateTimeInterface|DateInterval|string $date     A date
  307.  * @param string|null                           $format   The target format, null to use the default
  308.  * @param DateTimeZone|string|false|null        $timezone The target timezone, null to use the default, false to leave unchanged
  309.  *
  310.  * @return string The formatted date
  311.  */
  312. function twig_date_format_filter(Twig_Environment $env$date$format null$timezone null)
  313. {
  314.     if (null === $format) {
  315.         $formats $env->getExtension('Twig_Extension_Core')->getDateFormat();
  316.         $format $date instanceof DateInterval $formats[1] : $formats[0];
  317.     }
  318.     if ($date instanceof DateInterval) {
  319.         return $date->format($format);
  320.     }
  321.     return twig_date_converter($env$date$timezone)->format($format);
  322. }
  323. /**
  324.  * Returns a new date object modified.
  325.  *
  326.  * <pre>
  327.  *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
  328.  * </pre>
  329.  *
  330.  * @param Twig_Environment         $env
  331.  * @param DateTimeInterface|string $date     A date
  332.  * @param string                   $modifier A modifier string
  333.  *
  334.  * @return DateTimeInterface A new date object
  335.  */
  336. function twig_date_modify_filter(Twig_Environment $env$date$modifier)
  337. {
  338.     $date twig_date_converter($env$datefalse);
  339.     return $date->modify($modifier);
  340. }
  341. /**
  342.  * Converts an input to a DateTime instance.
  343.  *
  344.  * <pre>
  345.  *    {% if date(user.created_at) < date('+2days') %}
  346.  *      {# do something #}
  347.  *    {% endif %}
  348.  * </pre>
  349.  *
  350.  * @param Twig_Environment               $env
  351.  * @param DateTimeInterface|string|null  $date     A date or null to use the current time
  352.  * @param DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
  353.  *
  354.  * @return DateTime A DateTime instance
  355.  */
  356. function twig_date_converter(Twig_Environment $env$date null$timezone null)
  357. {
  358.     // determine the timezone
  359.     if (false !== $timezone) {
  360.         if (null === $timezone) {
  361.             $timezone $env->getExtension('Twig_Extension_Core')->getTimezone();
  362.         } elseif (!$timezone instanceof DateTimeZone) {
  363.             $timezone = new DateTimeZone($timezone);
  364.         }
  365.     }
  366.     // immutable dates
  367.     if ($date instanceof DateTimeImmutable) {
  368.         return false !== $timezone $date->setTimezone($timezone) : $date;
  369.     }
  370.     if ($date instanceof DateTimeInterface) {
  371.         $date = clone $date;
  372.         if (false !== $timezone) {
  373.             $date->setTimezone($timezone);
  374.         }
  375.         return $date;
  376.     }
  377.     if (null === $date || 'now' === $date) {
  378.         return new DateTime($datefalse !== $timezone $timezone $env->getExtension('Twig_Extension_Core')->getTimezone());
  379.     }
  380.     $asString = (string) $date;
  381.     if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString1)))) {
  382.         $date = new DateTime('@'.$date);
  383.     } else {
  384.         $date = new DateTime($date$env->getExtension('Twig_Extension_Core')->getTimezone());
  385.     }
  386.     if (false !== $timezone) {
  387.         $date->setTimezone($timezone);
  388.     }
  389.     return $date;
  390. }
  391. /**
  392.  * Replaces strings within a string.
  393.  *
  394.  * @param string            $str  String to replace in
  395.  * @param array|Traversable $from Replace values
  396.  *
  397.  * @return string
  398.  */
  399. function twig_replace_filter($str$from)
  400. {
  401.     if ($from instanceof Traversable) {
  402.         $from iterator_to_array($from);
  403.     } elseif (!is_array($from)) {
  404.         throw new Twig_Error_Runtime(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".'is_object($from) ? get_class($from) : gettype($from)));
  405.     }
  406.     return strtr($str$from);
  407. }
  408. /**
  409.  * Rounds a number.
  410.  *
  411.  * @param int|float $value     The value to round
  412.  * @param int|float $precision The rounding precision
  413.  * @param string    $method    The method to use for rounding
  414.  *
  415.  * @return int|float The rounded number
  416.  */
  417. function twig_round($value$precision 0$method 'common')
  418. {
  419.     if ('common' == $method) {
  420.         return round($value$precision);
  421.     }
  422.     if ('ceil' != $method && 'floor' != $method) {
  423.         throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.');
  424.     }
  425.     return $method($value pow(10$precision)) / pow(10$precision);
  426. }
  427. /**
  428.  * Number format filter.
  429.  *
  430.  * All of the formatting options can be left null, in that case the defaults will
  431.  * be used.  Supplying any of the parameters will override the defaults set in the
  432.  * environment object.
  433.  *
  434.  * @param Twig_Environment $env
  435.  * @param mixed            $number       A float/int/string of the number to format
  436.  * @param int              $decimal      the number of decimal points to display
  437.  * @param string           $decimalPoint the character(s) to use for the decimal point
  438.  * @param string           $thousandSep  the character(s) to use for the thousands separator
  439.  *
  440.  * @return string The formatted number
  441.  */
  442. function twig_number_format_filter(Twig_Environment $env$number$decimal null$decimalPoint null$thousandSep null)
  443. {
  444.     $defaults $env->getExtension('Twig_Extension_Core')->getNumberFormat();
  445.     if (null === $decimal) {
  446.         $decimal $defaults[0];
  447.     }
  448.     if (null === $decimalPoint) {
  449.         $decimalPoint $defaults[1];
  450.     }
  451.     if (null === $thousandSep) {
  452.         $thousandSep $defaults[2];
  453.     }
  454.     return number_format((float) $number$decimal$decimalPoint$thousandSep);
  455. }
  456. /**
  457.  * URL encodes (RFC 3986) a string as a path segment or an array as a query string.
  458.  *
  459.  * @param string|array $url A URL or an array of query parameters
  460.  *
  461.  * @return string The URL encoded value
  462.  */
  463. function twig_urlencode_filter($url)
  464. {
  465.     if (is_array($url)) {
  466.         return http_build_query($url'''&'PHP_QUERY_RFC3986);
  467.     }
  468.     return rawurlencode($url);
  469. }
  470. /**
  471.  * Merges an array with another one.
  472.  *
  473.  * <pre>
  474.  *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
  475.  *
  476.  *  {% set items = items|merge({ 'peugeot': 'car' }) %}
  477.  *
  478.  *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
  479.  * </pre>
  480.  *
  481.  * @param array|Traversable $arr1 An array
  482.  * @param array|Traversable $arr2 An array
  483.  *
  484.  * @return array The merged array
  485.  */
  486. function twig_array_merge($arr1$arr2)
  487. {
  488.     if ($arr1 instanceof Traversable) {
  489.         $arr1 iterator_to_array($arr1);
  490.     } elseif (!is_array($arr1)) {
  491.         throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.'gettype($arr1)));
  492.     }
  493.     if ($arr2 instanceof Traversable) {
  494.         $arr2 iterator_to_array($arr2);
  495.     } elseif (!is_array($arr2)) {
  496.         throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.'gettype($arr2)));
  497.     }
  498.     return array_merge($arr1$arr2);
  499. }
  500. /**
  501.  * Slices a variable.
  502.  *
  503.  * @param Twig_Environment $env
  504.  * @param mixed            $item         A variable
  505.  * @param int              $start        Start of the slice
  506.  * @param int              $length       Size of the slice
  507.  * @param bool             $preserveKeys Whether to preserve key or not (when the input is an array)
  508.  *
  509.  * @return mixed The sliced variable
  510.  */
  511. function twig_slice(Twig_Environment $env$item$start$length null$preserveKeys false)
  512. {
  513.     if ($item instanceof Traversable) {
  514.         while ($item instanceof IteratorAggregate) {
  515.             $item $item->getIterator();
  516.         }
  517.         if ($start >= && $length >= && $item instanceof Iterator) {
  518.             try {
  519.                 return iterator_to_array(new LimitIterator($item$startnull === $length ? -$length), $preserveKeys);
  520.             } catch (OutOfBoundsException $exception) {
  521.                 return [];
  522.             }
  523.         }
  524.         $item iterator_to_array($item$preserveKeys);
  525.     }
  526.     if (is_array($item)) {
  527.         return array_slice($item$start$length$preserveKeys);
  528.     }
  529.     $item = (string) $item;
  530.     return (string) mb_substr($item$start$length$env->getCharset());
  531. }
  532. /**
  533.  * Returns the first element of the item.
  534.  *
  535.  * @param Twig_Environment $env
  536.  * @param mixed            $item A variable
  537.  *
  538.  * @return mixed The first element of the item
  539.  */
  540. function twig_first(Twig_Environment $env$item)
  541. {
  542.     $elements twig_slice($env$item01false);
  543.     return is_string($elements) ? $elements current($elements);
  544. }
  545. /**
  546.  * Returns the last element of the item.
  547.  *
  548.  * @param Twig_Environment $env
  549.  * @param mixed            $item A variable
  550.  *
  551.  * @return mixed The last element of the item
  552.  */
  553. function twig_last(Twig_Environment $env$item)
  554. {
  555.     $elements twig_slice($env$item, -11false);
  556.     return is_string($elements) ? $elements current($elements);
  557. }
  558. /**
  559.  * Joins the values to a string.
  560.  *
  561.  * The separators between elements are empty strings per default, you can define them with the optional parameters.
  562.  *
  563.  * <pre>
  564.  *  {{ [1, 2, 3]|join(', ', ' and ') }}
  565.  *  {# returns 1, 2 and 3 #}
  566.  *
  567.  *  {{ [1, 2, 3]|join('|') }}
  568.  *  {# returns 1|2|3 #}
  569.  *
  570.  *  {{ [1, 2, 3]|join }}
  571.  *  {# returns 123 #}
  572.  * </pre>
  573.  *
  574.  * @param array       $value An array
  575.  * @param string      $glue  The separator
  576.  * @param string|null $and   The separator for the last pair
  577.  *
  578.  * @return string The concatenated string
  579.  */
  580. function twig_join_filter($value$glue ''$and null)
  581. {
  582.     if ($value instanceof Traversable) {
  583.         $value iterator_to_array($valuefalse);
  584.     } else {
  585.         $value = (array) $value;
  586.     }
  587.     if (=== count($value)) {
  588.         return '';
  589.     }
  590.     if (null === $and || $and === $glue) {
  591.         return implode($glue$value);
  592.     }
  593.     $v array_values($value);
  594.     if (=== count($v)) {
  595.         return $v[0];
  596.     }
  597.     return implode($gluearray_slice($value0, -1)).$and.$v[count($v) - 1];
  598. }
  599. /**
  600.  * Splits the string into an array.
  601.  *
  602.  * <pre>
  603.  *  {{ "one,two,three"|split(',') }}
  604.  *  {# returns [one, two, three] #}
  605.  *
  606.  *  {{ "one,two,three,four,five"|split(',', 3) }}
  607.  *  {# returns [one, two, "three,four,five"] #}
  608.  *
  609.  *  {{ "123"|split('') }}
  610.  *  {# returns [1, 2, 3] #}
  611.  *
  612.  *  {{ "aabbcc"|split('', 2) }}
  613.  *  {# returns [aa, bb, cc] #}
  614.  * </pre>
  615.  *
  616.  * @param Twig_Environment $env
  617.  * @param string           $value     A string
  618.  * @param string           $delimiter The delimiter
  619.  * @param int              $limit     The limit
  620.  *
  621.  * @return array The split string as an array
  622.  */
  623. function twig_split_filter(Twig_Environment $env$value$delimiter$limit null)
  624. {
  625.     if (!empty($delimiter)) {
  626.         return null === $limit explode($delimiter$value) : explode($delimiter$value$limit);
  627.     }
  628.     if ($limit <= 1) {
  629.         return preg_split('/(?<!^)(?!$)/u'$value);
  630.     }
  631.     $length mb_strlen($value$env->getCharset());
  632.     if ($length $limit) {
  633.         return [$value];
  634.     }
  635.     $r = [];
  636.     for ($i 0$i $length$i += $limit) {
  637.         $r[] = mb_substr($value$i$limit$env->getCharset());
  638.     }
  639.     return $r;
  640. }
  641. // The '_default' filter is used internally to avoid using the ternary operator
  642. // which costs a lot for big contexts (before PHP 5.4). So, on average,
  643. // a function call is cheaper.
  644. /**
  645.  * @internal
  646.  */
  647. function _twig_default_filter($value$default '')
  648. {
  649.     if (twig_test_empty($value)) {
  650.         return $default;
  651.     }
  652.     return $value;
  653. }
  654. /**
  655.  * Returns the keys for the given array.
  656.  *
  657.  * It is useful when you want to iterate over the keys of an array:
  658.  *
  659.  * <pre>
  660.  *  {% for key in array|keys %}
  661.  *      {# ... #}
  662.  *  {% endfor %}
  663.  * </pre>
  664.  *
  665.  * @param array $array An array
  666.  *
  667.  * @return array The keys
  668.  */
  669. function twig_get_array_keys_filter($array)
  670. {
  671.     if ($array instanceof Traversable) {
  672.         while ($array instanceof IteratorAggregate) {
  673.             $array $array->getIterator();
  674.         }
  675.         if ($array instanceof Iterator) {
  676.             $keys = [];
  677.             $array->rewind();
  678.             while ($array->valid()) {
  679.                 $keys[] = $array->key();
  680.                 $array->next();
  681.             }
  682.             return $keys;
  683.         }
  684.         $keys = [];
  685.         foreach ($array as $key => $item) {
  686.             $keys[] = $key;
  687.         }
  688.         return $keys;
  689.     }
  690.     if (!is_array($array)) {
  691.         return [];
  692.     }
  693.     return array_keys($array);
  694. }
  695. /**
  696.  * Reverses a variable.
  697.  *
  698.  * @param Twig_Environment         $env
  699.  * @param array|Traversable|string $item         An array, a Traversable instance, or a string
  700.  * @param bool                     $preserveKeys Whether to preserve key or not
  701.  *
  702.  * @return mixed The reversed input
  703.  */
  704. function twig_reverse_filter(Twig_Environment $env$item$preserveKeys false)
  705. {
  706.     if ($item instanceof Traversable) {
  707.         return array_reverse(iterator_to_array($item), $preserveKeys);
  708.     }
  709.     if (is_array($item)) {
  710.         return array_reverse($item$preserveKeys);
  711.     }
  712.     $string = (string) $item;
  713.     $charset $env->getCharset();
  714.     if ('UTF-8' !== $charset) {
  715.         $item iconv($charset'UTF-8'$string);
  716.     }
  717.     preg_match_all('/./us'$item$matches);
  718.     $string implode(''array_reverse($matches[0]));
  719.     if ('UTF-8' !== $charset) {
  720.         $string iconv('UTF-8'$charset$string);
  721.     }
  722.     return $string;
  723. }
  724. /**
  725.  * Sorts an array.
  726.  *
  727.  * @param array|Traversable $array
  728.  *
  729.  * @return array
  730.  */
  731. function twig_sort_filter($array)
  732. {
  733.     if ($array instanceof Traversable) {
  734.         $array iterator_to_array($array);
  735.     } elseif (!is_array($array)) {
  736.         throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".'gettype($array)));
  737.     }
  738.     asort($array);
  739.     return $array;
  740. }
  741. /**
  742.  * @internal
  743.  */
  744. function twig_in_filter($value$compare)
  745. {
  746.     if (is_array($compare)) {
  747.         return in_array($value$compareis_object($value) || is_resource($value));
  748.     } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
  749.         return '' === $value || false !== strpos($compare, (string) $value);
  750.     } elseif ($compare instanceof Traversable) {
  751.         if (is_object($value) || is_resource($value)) {
  752.             foreach ($compare as $item) {
  753.                 if ($item === $value) {
  754.                     return true;
  755.                 }
  756.             }
  757.         } else {
  758.             foreach ($compare as $item) {
  759.                 if ($item == $value) {
  760.                     return true;
  761.                 }
  762.             }
  763.         }
  764.         return false;
  765.     }
  766.     return false;
  767. }
  768. /**
  769.  * Returns a trimmed string.
  770.  *
  771.  * @return string
  772.  *
  773.  * @throws Twig_Error_Runtime When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
  774.  */
  775. function twig_trim_filter($string$characterMask null$side 'both')
  776. {
  777.     if (null === $characterMask) {
  778.         $characterMask " \t\n\r\0\x0B";
  779.     }
  780.     switch ($side) {
  781.         case 'both':
  782.             return trim($string$characterMask);
  783.         case 'left':
  784.             return ltrim($string$characterMask);
  785.         case 'right':
  786.             return rtrim($string$characterMask);
  787.         default:
  788.             throw new Twig_Error_Runtime('Trimming side must be "left", "right" or "both".');
  789.     }
  790. }
  791. /**
  792.  * Escapes a string.
  793.  *
  794.  * @param Twig_Environment $env
  795.  * @param mixed            $string     The value to be escaped
  796.  * @param string           $strategy   The escaping strategy
  797.  * @param string           $charset    The charset
  798.  * @param bool             $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
  799.  *
  800.  * @return string
  801.  */
  802. function twig_escape_filter(Twig_Environment $env$string$strategy 'html'$charset null$autoescape false)
  803. {
  804.     if ($autoescape && $string instanceof Twig_Markup) {
  805.         return $string;
  806.     }
  807.     if (!is_string($string)) {
  808.         if (is_object($string) && method_exists($string'__toString')) {
  809.             $string = (string) $string;
  810.         } elseif (in_array($strategy, ['html''js''css''html_attr''url'])) {
  811.             return $string;
  812.         }
  813.     }
  814.     if ('' === $string) {
  815.         return '';
  816.     }
  817.     if (null === $charset) {
  818.         $charset $env->getCharset();
  819.     }
  820.     switch ($strategy) {
  821.         case 'html':
  822.             // see https://secure.php.net/htmlspecialchars
  823.             // Using a static variable to avoid initializing the array
  824.             // each time the function is called. Moving the declaration on the
  825.             // top of the function slow downs other escaping strategies.
  826.             static $htmlspecialcharsCharsets = [
  827.                 'ISO-8859-1' => true'ISO8859-1' => true,
  828.                 'ISO-8859-15' => true'ISO8859-15' => true,
  829.                 'utf-8' => true'UTF-8' => true,
  830.                 'CP866' => true'IBM866' => true'866' => true,
  831.                 'CP1251' => true'WINDOWS-1251' => true'WIN-1251' => true,
  832.                 '1251' => true,
  833.                 'CP1252' => true'WINDOWS-1252' => true'1252' => true,
  834.                 'KOI8-R' => true'KOI8-RU' => true'KOI8R' => true,
  835.                 'BIG5' => true'950' => true,
  836.                 'GB2312' => true'936' => true,
  837.                 'BIG5-HKSCS' => true,
  838.                 'SHIFT_JIS' => true'SJIS' => true'932' => true,
  839.                 'EUC-JP' => true'EUCJP' => true,
  840.                 'ISO8859-5' => true'ISO-8859-5' => true'MACROMAN' => true,
  841.             ];
  842.             if (isset($htmlspecialcharsCharsets[$charset])) {
  843.                 return htmlspecialchars($stringENT_QUOTES ENT_SUBSTITUTE$charset);
  844.             }
  845.             if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
  846.                 // cache the lowercase variant for future iterations
  847.                 $htmlspecialcharsCharsets[$charset] = true;
  848.                 return htmlspecialchars($stringENT_QUOTES ENT_SUBSTITUTE$charset);
  849.             }
  850.             $string iconv($charset'UTF-8'$string);
  851.             $string htmlspecialchars($stringENT_QUOTES ENT_SUBSTITUTE'UTF-8');
  852.             return iconv('UTF-8'$charset$string);
  853.         case 'js':
  854.             // escape all non-alphanumeric characters
  855.             // into their \x or \uHHHH representations
  856.             if ('UTF-8' !== $charset) {
  857.                 $string iconv($charset'UTF-8'$string);
  858.             }
  859.             if (!preg_match('//u'$string)) {
  860.                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
  861.             }
  862.             $string preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', function ($matches) {
  863.                 $char $matches[0];
  864.                 /*
  865.                  * A few characters have short escape sequences in JSON and JavaScript.
  866.                  * Escape sequences supported only by JavaScript, not JSON, are ommitted.
  867.                  * \" is also supported but omitted, because the resulting string is not HTML safe.
  868.                  */
  869.                 static $shortMap = [
  870.                     '\\' => '\\\\',
  871.                     '/' => '\\/',
  872.                     "\x08" => '\b',
  873.                     "\x0C" => '\f',
  874.                     "\x0A" => '\n',
  875.                     "\x0D" => '\r',
  876.                     "\x09" => '\t',
  877.                 ];
  878.                 if (isset($shortMap[$char])) {
  879.                     return $shortMap[$char];
  880.                 }
  881.                 // \uHHHH
  882.                 $char twig_convert_encoding($char'UTF-16BE''UTF-8');
  883.                 $char strtoupper(bin2hex($char));
  884.                 if (>= strlen($char)) {
  885.                     return sprintf('\u%04s'$char);
  886.                 }
  887.                 return sprintf('\u%04s\u%04s'substr($char0, -4), substr($char, -4));
  888.             }, $string);
  889.             if ('UTF-8' !== $charset) {
  890.                 $string iconv('UTF-8'$charset$string);
  891.             }
  892.             return $string;
  893.         case 'css':
  894.             if ('UTF-8' !== $charset) {
  895.                 $string iconv($charset'UTF-8'$string);
  896.             }
  897.             if (!preg_match('//u'$string)) {
  898.                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
  899.             }
  900.             $string preg_replace_callback('#[^a-zA-Z0-9]#Su', function ($matches) {
  901.                 $char $matches[0];
  902.                 return sprintf('\\%X '=== strlen($char) ? ord($char) : mb_ord($char'UTF-8'));
  903.             }, $string);
  904.             if ('UTF-8' !== $charset) {
  905.                 $string iconv('UTF-8'$charset$string);
  906.             }
  907.             return $string;
  908.         case 'html_attr':
  909.             if ('UTF-8' !== $charset) {
  910.                 $string iconv($charset'UTF-8'$string);
  911.             }
  912.             if (!preg_match('//u'$string)) {
  913.                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
  914.             }
  915.             $string preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', function ($matches) {
  916.                 /**
  917.                  * This function is adapted from code coming from Zend Framework.
  918.                  *
  919.                  * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (https://www.zend.com)
  920.                  * @license   https://framework.zend.com/license/new-bsd New BSD License
  921.                  */
  922.                 $chr $matches[0];
  923.                 $ord ord($chr);
  924.                 /*
  925.                  * The following replaces characters undefined in HTML with the
  926.                  * hex entity for the Unicode replacement character.
  927.                  */
  928.                 if (($ord <= 0x1f && "\t" != $chr && "\n" != $chr && "\r" != $chr) || ($ord >= 0x7f && $ord <= 0x9f)) {
  929.                     return '&#xFFFD;';
  930.                 }
  931.                 /*
  932.                  * Check if the current character to escape has a name entity we should
  933.                  * replace it with while grabbing the hex value of the character.
  934.                  */
  935.                 if (=== strlen($chr)) {
  936.                     /*
  937.                      * While HTML supports far more named entities, the lowest common denominator
  938.                      * has become HTML5's XML Serialisation which is restricted to the those named
  939.                      * entities that XML supports. Using HTML entities would result in this error:
  940.                      *     XML Parsing Error: undefined entity
  941.                      */
  942.                     static $entityMap = [
  943.                         34 => '&quot;'/* quotation mark */
  944.                         38 => '&amp;',  /* ampersand */
  945.                         60 => '&lt;',   /* less-than sign */
  946.                         62 => '&gt;',   /* greater-than sign */
  947.                     ];
  948.                     if (isset($entityMap[$ord])) {
  949.                         return $entityMap[$ord];
  950.                     }
  951.                     return sprintf('&#x%02X;'$ord);
  952.                 }
  953.                 /*
  954.                  * Per OWASP recommendations, we'll use hex entities for any other
  955.                  * characters where a named entity does not exist.
  956.                  */
  957.                 return sprintf('&#x%04X;'mb_ord($chr'UTF-8'));
  958.             }, $string);
  959.             if ('UTF-8' !== $charset) {
  960.                 $string iconv('UTF-8'$charset$string);
  961.             }
  962.             return $string;
  963.         case 'url':
  964.             return rawurlencode($string);
  965.         default:
  966.             static $escapers;
  967.             if (null === $escapers) {
  968.                 $escapers $env->getExtension('Twig_Extension_Core')->getEscapers();
  969.             }
  970.             if (isset($escapers[$strategy])) {
  971.                 return $escapers[$strategy]($env$string$charset);
  972.             }
  973.             $validStrategies implode(', 'array_merge(['html''js''url''css''html_attr'], array_keys($escapers)));
  974.             throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).'$strategy$validStrategies));
  975.     }
  976. }
  977. /**
  978.  * @internal
  979.  */
  980. function twig_escape_filter_is_safe(Twig_Node $filterArgs)
  981. {
  982.     foreach ($filterArgs as $arg) {
  983.         if ($arg instanceof Twig_Node_Expression_Constant) {
  984.             return [$arg->getAttribute('value')];
  985.         }
  986.         return [];
  987.     }
  988.     return ['html'];
  989. }
  990. function twig_convert_encoding($string$to$from)
  991. {
  992.     return iconv($from$to$string);
  993. }
  994. /**
  995.  * Returns the length of a variable.
  996.  *
  997.  * @param Twig_Environment $env   A Twig_Environment instance
  998.  * @param mixed            $thing A variable
  999.  *
  1000.  * @return int The length of the value
  1001.  */
  1002. function twig_length_filter(Twig_Environment $env$thing)
  1003. {
  1004.     if (null === $thing) {
  1005.         return 0;
  1006.     }
  1007.     if (is_scalar($thing)) {
  1008.         return mb_strlen($thing$env->getCharset());
  1009.     }
  1010.     if ($thing instanceof \SimpleXMLElement) {
  1011.         return count($thing);
  1012.     }
  1013.     if (method_exists($thing'__toString') && !$thing instanceof \Countable) {
  1014.         return mb_strlen((string) $thing$env->getCharset());
  1015.     }
  1016.     if ($thing instanceof \Countable || is_array($thing)) {
  1017.         return count($thing);
  1018.     }
  1019.     if ($thing instanceof \IteratorAggregate) {
  1020.         return iterator_count($thing);
  1021.     }
  1022.     return 1;
  1023. }
  1024. /**
  1025.  * Converts a string to uppercase.
  1026.  *
  1027.  * @param Twig_Environment $env
  1028.  * @param string           $string A string
  1029.  *
  1030.  * @return string The uppercased string
  1031.  */
  1032. function twig_upper_filter(Twig_Environment $env$string)
  1033. {
  1034.     return mb_strtoupper($string$env->getCharset());
  1035. }
  1036. /**
  1037.  * Converts a string to lowercase.
  1038.  *
  1039.  * @param Twig_Environment $env
  1040.  * @param string           $string A string
  1041.  *
  1042.  * @return string The lowercased string
  1043.  */
  1044. function twig_lower_filter(Twig_Environment $env$string)
  1045. {
  1046.     return mb_strtolower($string$env->getCharset());
  1047. }
  1048. /**
  1049.  * Returns a titlecased string.
  1050.  *
  1051.  * @param Twig_Environment $env
  1052.  * @param string           $string A string
  1053.  *
  1054.  * @return string The titlecased string
  1055.  */
  1056. function twig_title_string_filter(Twig_Environment $env$string)
  1057. {
  1058.     if (null !== $charset $env->getCharset()) {
  1059.         return mb_convert_case($stringMB_CASE_TITLE$charset);
  1060.     }
  1061.     return ucwords(strtolower($string));
  1062. }
  1063. /**
  1064.  * Returns a capitalized string.
  1065.  *
  1066.  * @param Twig_Environment $env
  1067.  * @param string           $string A string
  1068.  *
  1069.  * @return string The capitalized string
  1070.  */
  1071. function twig_capitalize_string_filter(Twig_Environment $env$string)
  1072. {
  1073.     $charset $env->getCharset();
  1074.     return mb_strtoupper(mb_substr($string01$charset), $charset).mb_strtolower(mb_substr($string1null$charset), $charset);
  1075. }
  1076. /**
  1077.  * @internal
  1078.  */
  1079. function twig_ensure_traversable($seq)
  1080. {
  1081.     if ($seq instanceof Traversable || is_array($seq)) {
  1082.         return $seq;
  1083.     }
  1084.     return [];
  1085. }
  1086. /**
  1087.  * Checks if a variable is empty.
  1088.  *
  1089.  * <pre>
  1090.  * {# evaluates to true if the foo variable is null, false, or the empty string #}
  1091.  * {% if foo is empty %}
  1092.  *     {# ... #}
  1093.  * {% endif %}
  1094.  * </pre>
  1095.  *
  1096.  * @param mixed $value A variable
  1097.  *
  1098.  * @return bool true if the value is empty, false otherwise
  1099.  */
  1100. function twig_test_empty($value)
  1101. {
  1102.     if ($value instanceof Countable) {
  1103.         return == count($value);
  1104.     }
  1105.     if (is_object($value) && method_exists($value'__toString')) {
  1106.         return '' === (string) $value;
  1107.     }
  1108.     return '' === $value || false === $value || null === $value || [] === $value;
  1109. }
  1110. /**
  1111.  * Checks if a variable is traversable.
  1112.  *
  1113.  * <pre>
  1114.  * {# evaluates to true if the foo variable is an array or a traversable object #}
  1115.  * {% if foo is iterable %}
  1116.  *     {# ... #}
  1117.  * {% endif %}
  1118.  * </pre>
  1119.  *
  1120.  * @param mixed $value A variable
  1121.  *
  1122.  * @return bool true if the value is traversable
  1123.  */
  1124. function twig_test_iterable($value)
  1125. {
  1126.     return $value instanceof Traversable || is_array($value);
  1127. }
  1128. /**
  1129.  * Renders a template.
  1130.  *
  1131.  * @param Twig_Environment $env
  1132.  * @param array            $context
  1133.  * @param string|array     $template      The template to render or an array of templates to try consecutively
  1134.  * @param array            $variables     The variables to pass to the template
  1135.  * @param bool             $withContext
  1136.  * @param bool             $ignoreMissing Whether to ignore missing templates or not
  1137.  * @param bool             $sandboxed     Whether to sandbox the template or not
  1138.  *
  1139.  * @return string The rendered template
  1140.  */
  1141. function twig_include(Twig_Environment $env$context$template$variables = [], $withContext true$ignoreMissing false$sandboxed false)
  1142. {
  1143.     $alreadySandboxed false;
  1144.     $sandbox null;
  1145.     if ($withContext) {
  1146.         $variables array_merge($context$variables);
  1147.     }
  1148.     if ($isSandboxed $sandboxed && $env->hasExtension('Twig_Extension_Sandbox')) {
  1149.         $sandbox $env->getExtension('Twig_Extension_Sandbox');
  1150.         if (!$alreadySandboxed $sandbox->isSandboxed()) {
  1151.             $sandbox->enableSandbox();
  1152.         }
  1153.     }
  1154.     $result '';
  1155.     try {
  1156.         $result $env->resolveTemplate($template)->render($variables);
  1157.     } catch (Twig_Error_Loader $e) {
  1158.         if (!$ignoreMissing) {
  1159.             if ($isSandboxed && !$alreadySandboxed) {
  1160.                 $sandbox->disableSandbox();
  1161.             }
  1162.             throw $e;
  1163.         }
  1164.     } catch (Throwable $e) {
  1165.         if ($isSandboxed && !$alreadySandboxed) {
  1166.             $sandbox->disableSandbox();
  1167.         }
  1168.         throw $e;
  1169.     }
  1170.     if ($isSandboxed && !$alreadySandboxed) {
  1171.         $sandbox->disableSandbox();
  1172.     }
  1173.     return $result;
  1174. }
  1175. /**
  1176.  * Returns a template content without rendering it.
  1177.  *
  1178.  * @param Twig_Environment $env
  1179.  * @param string           $name          The template name
  1180.  * @param bool             $ignoreMissing Whether to ignore missing templates or not
  1181.  *
  1182.  * @return string The template source
  1183.  */
  1184. function twig_source(Twig_Environment $env$name$ignoreMissing false)
  1185. {
  1186.     $loader $env->getLoader();
  1187.     try {
  1188.         return $loader->getSourceContext($name)->getCode();
  1189.     } catch (Twig_Error_Loader $e) {
  1190.         if (!$ignoreMissing) {
  1191.             throw $e;
  1192.         }
  1193.     }
  1194. }
  1195. /**
  1196.  * Provides the ability to get constants from instances as well as class/global constants.
  1197.  *
  1198.  * @param string      $constant The name of the constant
  1199.  * @param object|null $object   The object to get the constant from
  1200.  *
  1201.  * @return string
  1202.  */
  1203. function twig_constant($constant$object null)
  1204. {
  1205.     if (null !== $object) {
  1206.         $constant get_class($object).'::'.$constant;
  1207.     }
  1208.     return constant($constant);
  1209. }
  1210. /**
  1211.  * Checks if a constant exists.
  1212.  *
  1213.  * @param string      $constant The name of the constant
  1214.  * @param object|null $object   The object to get the constant from
  1215.  *
  1216.  * @return bool
  1217.  */
  1218. function twig_constant_is_defined($constant$object null)
  1219. {
  1220.     if (null !== $object) {
  1221.         $constant get_class($object).'::'.$constant;
  1222.     }
  1223.     return defined($constant);
  1224. }
  1225. /**
  1226.  * Batches item.
  1227.  *
  1228.  * @param array $items An array of items
  1229.  * @param int   $size  The size of the batch
  1230.  * @param mixed $fill  A value used to fill missing items
  1231.  *
  1232.  * @return array
  1233.  */
  1234. function twig_array_batch($items$size$fill null)
  1235. {
  1236.     if ($items instanceof Traversable) {
  1237.         $items iterator_to_array($itemsfalse);
  1238.     }
  1239.     $size ceil($size);
  1240.     $result array_chunk($items$sizetrue);
  1241.     if (null !== $fill && !empty($result)) {
  1242.         $last count($result) - 1;
  1243.         if ($fillCount $size count($result[$last])) {
  1244.             $result[$last] = array_merge(
  1245.                 $result[$last],
  1246.                 array_fill(0$fillCount$fill)
  1247.             );
  1248.         }
  1249.     }
  1250.     return $result;
  1251. }
  1252. /**
  1253.  * Returns the attribute value for a given array/object.
  1254.  *
  1255.  * @param mixed  $object            The object or array from where to get the item
  1256.  * @param mixed  $item              The item to get from the array or object
  1257.  * @param array  $arguments         An array of arguments to pass if the item is an object method
  1258.  * @param string $type              The type of attribute (@see Twig_Template constants)
  1259.  * @param bool   $isDefinedTest     Whether this is only a defined check
  1260.  * @param bool   $ignoreStrictCheck Whether to ignore the strict attribute check or not
  1261.  *
  1262.  * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
  1263.  *
  1264.  * @throws Twig_Error_Runtime if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
  1265.  *
  1266.  * @internal
  1267.  */
  1268. function twig_get_attribute(Twig_Environment $envTwig_Source $source$object$item, array $arguments = [], $type /* Twig_Template::ANY_CALL */ 'any'$isDefinedTest false$ignoreStrictCheck false$sandboxed false)
  1269. {
  1270.     // array
  1271.     if (/* Twig_Template::METHOD_CALL */ 'method' !== $type) {
  1272.         $arrayItem is_bool($item) || is_float($item) ? (int) $item $item;
  1273.         if (((is_array($object) || $object instanceof ArrayObject) && (isset($object[$arrayItem]) || array_key_exists($arrayItem$object)))
  1274.             || ($object instanceof ArrayAccess && isset($object[$arrayItem]))
  1275.         ) {
  1276.             if ($isDefinedTest) {
  1277.                 return true;
  1278.             }
  1279.             return $object[$arrayItem];
  1280.         }
  1281.         if (/* Twig_Template::ARRAY_CALL */ 'array' === $type || !is_object($object)) {
  1282.             if ($isDefinedTest) {
  1283.                 return false;
  1284.             }
  1285.             if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1286.                 return;
  1287.             }
  1288.             if ($object instanceof ArrayAccess) {
  1289.                 $message sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.'$arrayItemget_class($object));
  1290.             } elseif (is_object($object)) {
  1291.                 $message sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.'$itemget_class($object));
  1292.             } elseif (is_array($object)) {
  1293.                 if (empty($object)) {
  1294.                     $message sprintf('Key "%s" does not exist as the array is empty.'$arrayItem);
  1295.                 } else {
  1296.                     $message sprintf('Key "%s" for array with keys "%s" does not exist.'$arrayItemimplode(', 'array_keys($object)));
  1297.                 }
  1298.             } elseif (/* Twig_Template::ARRAY_CALL */ 'array' === $type) {
  1299.                 if (null === $object) {
  1300.                     $message sprintf('Impossible to access a key ("%s") on a null variable.'$item);
  1301.                 } else {
  1302.                     $message sprintf('Impossible to access a key ("%s") on a %s variable ("%s").'$itemgettype($object), $object);
  1303.                 }
  1304.             } elseif (null === $object) {
  1305.                 $message sprintf('Impossible to access an attribute ("%s") on a null variable.'$item);
  1306.             } else {
  1307.                 $message sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").'$itemgettype($object), $object);
  1308.             }
  1309.             throw new Twig_Error_Runtime($message, -1$source);
  1310.         }
  1311.     }
  1312.     if (!is_object($object)) {
  1313.         if ($isDefinedTest) {
  1314.             return false;
  1315.         }
  1316.         if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1317.             return;
  1318.         }
  1319.         if (null === $object) {
  1320.             $message sprintf('Impossible to invoke a method ("%s") on a null variable.'$item);
  1321.         } elseif (is_array($object)) {
  1322.             $message sprintf('Impossible to invoke a method ("%s") on an array.'$item);
  1323.         } else {
  1324.             $message sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").'$itemgettype($object), $object);
  1325.         }
  1326.         throw new Twig_Error_Runtime($message, -1$source);
  1327.     }
  1328.     if ($object instanceof Twig_Template) {
  1329.         throw new Twig_Error_Runtime('Accessing Twig_Template attributes is forbidden.');
  1330.     }
  1331.     // object property
  1332.     if (/* Twig_Template::METHOD_CALL */ 'method' !== $type) {
  1333.         if (isset($object->$item) || array_key_exists((string) $item$object)) {
  1334.             if ($isDefinedTest) {
  1335.                 return true;
  1336.             }
  1337.             if ($sandboxed) {
  1338.                 $env->getExtension('Twig_Extension_Sandbox')->checkPropertyAllowed($object$item);
  1339.             }
  1340.             return $object->$item;
  1341.         }
  1342.     }
  1343.     static $cache = [];
  1344.     $class get_class($object);
  1345.     // object method
  1346.     // precedence: getXxx() > isXxx() > hasXxx()
  1347.     if (!isset($cache[$class])) {
  1348.         $methods get_class_methods($object);
  1349.         sort($methods);
  1350.         $lcMethods array_map('strtolower'$methods);
  1351.         $classCache = [];
  1352.         foreach ($methods as $i => $method) {
  1353.             $classCache[$method] = $method;
  1354.             $classCache[$lcName $lcMethods[$i]] = $method;
  1355.             if ('g' === $lcName[0] && === strpos($lcName'get')) {
  1356.                 $name substr($method3);
  1357.                 $lcName substr($lcName3);
  1358.             } elseif ('i' === $lcName[0] && === strpos($lcName'is')) {
  1359.                 $name substr($method2);
  1360.                 $lcName substr($lcName2);
  1361.             } elseif ('h' === $lcName[0] && === strpos($lcName'has')) {
  1362.                 $name substr($method3);
  1363.                 $lcName substr($lcName3);
  1364.                 if (in_array('is'.$lcName$lcMethods)) {
  1365.                     continue;
  1366.                 }
  1367.             } else {
  1368.                 continue;
  1369.             }
  1370.             // skip get() and is() methods (in which case, $name is empty)
  1371.             if ($name) {
  1372.                 if (!isset($classCache[$name])) {
  1373.                     $classCache[$name] = $method;
  1374.                 }
  1375.                 if (!isset($classCache[$lcName])) {
  1376.                     $classCache[$lcName] = $method;
  1377.                 }
  1378.             }
  1379.         }
  1380.         $cache[$class] = $classCache;
  1381.     }
  1382.     $call false;
  1383.     if (isset($cache[$class][$item])) {
  1384.         $method $cache[$class][$item];
  1385.     } elseif (isset($cache[$class][$lcItem strtolower($item)])) {
  1386.         $method $cache[$class][$lcItem];
  1387.     } elseif (isset($cache[$class]['__call'])) {
  1388.         $method $item;
  1389.         $call true;
  1390.     } else {
  1391.         if ($isDefinedTest) {
  1392.             return false;
  1393.         }
  1394.         if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1395.             return;
  1396.         }
  1397.         throw new Twig_Error_Runtime(sprintf('Neither the property "%1$s" nor one of the methods "%1$s()", "get%1$s()"/"is%1$s()"/"has%1$s()" or "__call()" exist and have public access in class "%2$s".'$item$class), -1$source);
  1398.     }
  1399.     if ($isDefinedTest) {
  1400.         return true;
  1401.     }
  1402.     if ($sandboxed) {
  1403.         $env->getExtension('Twig_Extension_Sandbox')->checkMethodAllowed($object$method);
  1404.     }
  1405.     // Some objects throw exceptions when they have __call, and the method we try
  1406.     // to call is not supported. If ignoreStrictCheck is true, we should return null.
  1407.     try {
  1408.         $ret $object->$method(...$arguments);
  1409.     } catch (BadMethodCallException $e) {
  1410.         if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {
  1411.             return;
  1412.         }
  1413.         throw $e;
  1414.     }
  1415.     return $ret;
  1416. }
  1417. class_alias('Twig_Extension_Core''Twig\Extension\CoreExtension'false);