vendor/twig/twig/src/Extension/CoreExtension.php line1412

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