vendor/twig/twig/src/Extension/CoreExtension.php line 1764

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\Environment;
  12. use Twig\Error\LoaderError;
  13. use Twig\Error\RuntimeError;
  14. use Twig\ExpressionParser;
  15. use Twig\Markup;
  16. use Twig\Node\Expression\Binary\AddBinary;
  17. use Twig\Node\Expression\Binary\AndBinary;
  18. use Twig\Node\Expression\Binary\BitwiseAndBinary;
  19. use Twig\Node\Expression\Binary\BitwiseOrBinary;
  20. use Twig\Node\Expression\Binary\BitwiseXorBinary;
  21. use Twig\Node\Expression\Binary\ConcatBinary;
  22. use Twig\Node\Expression\Binary\DivBinary;
  23. use Twig\Node\Expression\Binary\EndsWithBinary;
  24. use Twig\Node\Expression\Binary\EqualBinary;
  25. use Twig\Node\Expression\Binary\FloorDivBinary;
  26. use Twig\Node\Expression\Binary\GreaterBinary;
  27. use Twig\Node\Expression\Binary\GreaterEqualBinary;
  28. use Twig\Node\Expression\Binary\HasEveryBinary;
  29. use Twig\Node\Expression\Binary\HasSomeBinary;
  30. use Twig\Node\Expression\Binary\InBinary;
  31. use Twig\Node\Expression\Binary\LessBinary;
  32. use Twig\Node\Expression\Binary\LessEqualBinary;
  33. use Twig\Node\Expression\Binary\MatchesBinary;
  34. use Twig\Node\Expression\Binary\ModBinary;
  35. use Twig\Node\Expression\Binary\MulBinary;
  36. use Twig\Node\Expression\Binary\NotEqualBinary;
  37. use Twig\Node\Expression\Binary\NotInBinary;
  38. use Twig\Node\Expression\Binary\OrBinary;
  39. use Twig\Node\Expression\Binary\PowerBinary;
  40. use Twig\Node\Expression\Binary\RangeBinary;
  41. use Twig\Node\Expression\Binary\SpaceshipBinary;
  42. use Twig\Node\Expression\Binary\StartsWithBinary;
  43. use Twig\Node\Expression\Binary\SubBinary;
  44. use Twig\Node\Expression\Filter\DefaultFilter;
  45. use Twig\Node\Expression\NullCoalesceExpression;
  46. use Twig\Node\Expression\Test\ConstantTest;
  47. use Twig\Node\Expression\Test\DefinedTest;
  48. use Twig\Node\Expression\Test\DivisiblebyTest;
  49. use Twig\Node\Expression\Test\EvenTest;
  50. use Twig\Node\Expression\Test\NullTest;
  51. use Twig\Node\Expression\Test\OddTest;
  52. use Twig\Node\Expression\Test\SameasTest;
  53. use Twig\Node\Expression\Unary\NegUnary;
  54. use Twig\Node\Expression\Unary\NotUnary;
  55. use Twig\Node\Expression\Unary\PosUnary;
  56. use Twig\NodeVisitor\MacroAutoImportNodeVisitor;
  57. use Twig\Sandbox\SecurityNotAllowedMethodError;
  58. use Twig\Sandbox\SecurityNotAllowedPropertyError;
  59. use Twig\Source;
  60. use Twig\Template;
  61. use Twig\TemplateWrapper;
  62. use Twig\TokenParser\ApplyTokenParser;
  63. use Twig\TokenParser\BlockTokenParser;
  64. use Twig\TokenParser\DeprecatedTokenParser;
  65. use Twig\TokenParser\DoTokenParser;
  66. use Twig\TokenParser\EmbedTokenParser;
  67. use Twig\TokenParser\ExtendsTokenParser;
  68. use Twig\TokenParser\FlushTokenParser;
  69. use Twig\TokenParser\ForTokenParser;
  70. use Twig\TokenParser\FromTokenParser;
  71. use Twig\TokenParser\IfTokenParser;
  72. use Twig\TokenParser\ImportTokenParser;
  73. use Twig\TokenParser\IncludeTokenParser;
  74. use Twig\TokenParser\MacroTokenParser;
  75. use Twig\TokenParser\SetTokenParser;
  76. use Twig\TokenParser\UseTokenParser;
  77. use Twig\TokenParser\WithTokenParser;
  78. use Twig\TwigFilter;
  79. use Twig\TwigFunction;
  80. use Twig\TwigTest;
  81. final class CoreExtension extends AbstractExtension
  82. {
  83.     public const ARRAY_LIKE_CLASSES = [
  84.         'ArrayIterator',
  85.         'ArrayObject',
  86.         'CachingIterator',
  87.         'RecursiveArrayIterator',
  88.         'RecursiveCachingIterator',
  89.         'SplDoublyLinkedList',
  90.         'SplFixedArray',
  91.         'SplObjectStorage',
  92.         'SplQueue',
  93.         'SplStack',
  94.         'WeakMap',
  95.     ];
  96.     private $dateFormats = ['F j, Y H:i''%d days'];
  97.     private $numberFormat = [0'.'','];
  98.     private $timezone null;
  99.     /**
  100.      * Sets the default format to be used by the date filter.
  101.      *
  102.      * @param string|null $format             The default date format string
  103.      * @param string|null $dateIntervalFormat The default date interval format string
  104.      */
  105.     public function setDateFormat($format null$dateIntervalFormat null)
  106.     {
  107.         if (null !== $format) {
  108.             $this->dateFormats[0] = $format;
  109.         }
  110.         if (null !== $dateIntervalFormat) {
  111.             $this->dateFormats[1] = $dateIntervalFormat;
  112.         }
  113.     }
  114.     /**
  115.      * Gets the default format to be used by the date filter.
  116.      *
  117.      * @return array The default date format string and the default date interval format string
  118.      */
  119.     public function getDateFormat()
  120.     {
  121.         return $this->dateFormats;
  122.     }
  123.     /**
  124.      * Sets the default timezone to be used by the date filter.
  125.      *
  126.      * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object
  127.      */
  128.     public function setTimezone($timezone)
  129.     {
  130.         $this->timezone $timezone instanceof \DateTimeZone $timezone : new \DateTimeZone($timezone);
  131.     }
  132.     /**
  133.      * Gets the default timezone to be used by the date filter.
  134.      *
  135.      * @return \DateTimeZone The default timezone currently in use
  136.      */
  137.     public function getTimezone()
  138.     {
  139.         if (null === $this->timezone) {
  140.             $this->timezone = new \DateTimeZone(date_default_timezone_get());
  141.         }
  142.         return $this->timezone;
  143.     }
  144.     /**
  145.      * Sets the default format to be used by the number_format filter.
  146.      *
  147.      * @param int    $decimal      the number of decimal places to use
  148.      * @param string $decimalPoint the character(s) to use for the decimal point
  149.      * @param string $thousandSep  the character(s) to use for the thousands separator
  150.      */
  151.     public function setNumberFormat($decimal$decimalPoint$thousandSep)
  152.     {
  153.         $this->numberFormat = [$decimal$decimalPoint$thousandSep];
  154.     }
  155.     /**
  156.      * Get the default format used by the number_format filter.
  157.      *
  158.      * @return array The arguments for number_format()
  159.      */
  160.     public function getNumberFormat()
  161.     {
  162.         return $this->numberFormat;
  163.     }
  164.     public function getTokenParsers(): array
  165.     {
  166.         return [
  167.             new ApplyTokenParser(),
  168.             new ForTokenParser(),
  169.             new IfTokenParser(),
  170.             new ExtendsTokenParser(),
  171.             new IncludeTokenParser(),
  172.             new BlockTokenParser(),
  173.             new UseTokenParser(),
  174.             new MacroTokenParser(),
  175.             new ImportTokenParser(),
  176.             new FromTokenParser(),
  177.             new SetTokenParser(),
  178.             new FlushTokenParser(),
  179.             new DoTokenParser(),
  180.             new EmbedTokenParser(),
  181.             new WithTokenParser(),
  182.             new DeprecatedTokenParser(),
  183.         ];
  184.     }
  185.     public function getFilters(): array
  186.     {
  187.         return [
  188.             // formatting filters
  189.             new TwigFilter('date', [$this'formatDate']),
  190.             new TwigFilter('date_modify', [$this'modifyDate']),
  191.             new TwigFilter('format', [self::class, 'sprintf']),
  192.             new TwigFilter('replace', [self::class, 'replace']),
  193.             new TwigFilter('number_format', [$this'formatNumber']),
  194.             new TwigFilter('abs''abs'),
  195.             new TwigFilter('round', [self::class, 'round']),
  196.             // encoding
  197.             new TwigFilter('url_encode', [self::class, 'urlencode']),
  198.             new TwigFilter('json_encode''json_encode'),
  199.             new TwigFilter('convert_encoding', [self::class, 'convertEncoding']),
  200.             // string filters
  201.             new TwigFilter('title', [self::class, 'titleCase'], ['needs_charset' => true]),
  202.             new TwigFilter('capitalize', [self::class, 'capitalize'], ['needs_charset' => true]),
  203.             new TwigFilter('upper', [self::class, 'upper'], ['needs_charset' => true]),
  204.             new TwigFilter('lower', [self::class, 'lower'], ['needs_charset' => true]),
  205.             new TwigFilter('striptags', [self::class, 'striptags']),
  206.             new TwigFilter('trim', [self::class, 'trim']),
  207.             new TwigFilter('nl2br', [self::class, 'nl2br'], ['pre_escape' => 'html''is_safe' => ['html']]),
  208.             new TwigFilter('spaceless', [self::class, 'spaceless'], ['is_safe' => ['html']]),
  209.             // array helpers
  210.             new TwigFilter('join', [self::class, 'join']),
  211.             new TwigFilter('split', [self::class, 'split'], ['needs_charset' => true]),
  212.             new TwigFilter('sort', [self::class, 'sort'], ['needs_environment' => true]),
  213.             new TwigFilter('merge', [self::class, 'merge']),
  214.             new TwigFilter('batch', [self::class, 'batch']),
  215.             new TwigFilter('column', [self::class, 'column']),
  216.             new TwigFilter('filter', [self::class, 'filter'], ['needs_environment' => true]),
  217.             new TwigFilter('map', [self::class, 'map'], ['needs_environment' => true]),
  218.             new TwigFilter('reduce', [self::class, 'reduce'], ['needs_environment' => true]),
  219.             new TwigFilter('find', [self::class, 'find'], ['needs_environment' => true]),
  220.             // string/array filters
  221.             new TwigFilter('reverse', [self::class, 'reverse'], ['needs_charset' => true]),
  222.             new TwigFilter('shuffle', [self::class, 'shuffle'], ['needs_charset' => true]),
  223.             new TwigFilter('length', [self::class, 'length'], ['needs_charset' => true]),
  224.             new TwigFilter('slice', [self::class, 'slice'], ['needs_charset' => true]),
  225.             new TwigFilter('first', [self::class, 'first'], ['needs_charset' => true]),
  226.             new TwigFilter('last', [self::class, 'last'], ['needs_charset' => true]),
  227.             // iteration and runtime
  228.             new TwigFilter('default', [self::class, 'default'], ['node_class' => DefaultFilter::class]),
  229.             new TwigFilter('keys', [self::class, 'keys']),
  230.         ];
  231.     }
  232.     public function getFunctions(): array
  233.     {
  234.         return [
  235.             new TwigFunction('max''max'),
  236.             new TwigFunction('min''min'),
  237.             new TwigFunction('range''range'),
  238.             new TwigFunction('constant', [self::class, 'constant']),
  239.             new TwigFunction('cycle', [self::class, 'cycle']),
  240.             new TwigFunction('random', [self::class, 'random'], ['needs_charset' => true]),
  241.             new TwigFunction('date', [$this'convertDate']),
  242.             new TwigFunction('include', [self::class, 'include'], ['needs_environment' => true'needs_context' => true'is_safe' => ['all']]),
  243.             new TwigFunction('source', [self::class, 'source'], ['needs_environment' => true'is_safe' => ['all']]),
  244.         ];
  245.     }
  246.     public function getTests(): array
  247.     {
  248.         return [
  249.             new TwigTest('even'null, ['node_class' => EvenTest::class]),
  250.             new TwigTest('odd'null, ['node_class' => OddTest::class]),
  251.             new TwigTest('defined'null, ['node_class' => DefinedTest::class]),
  252.             new TwigTest('same as'null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]),
  253.             new TwigTest('none'null, ['node_class' => NullTest::class]),
  254.             new TwigTest('null'null, ['node_class' => NullTest::class]),
  255.             new TwigTest('divisible by'null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),
  256.             new TwigTest('constant'null, ['node_class' => ConstantTest::class]),
  257.             new TwigTest('empty', [self::class, 'testEmpty']),
  258.             new TwigTest('iterable''is_iterable'),
  259.             new TwigTest('sequence', [self::class, 'testSequence']),
  260.             new TwigTest('mapping', [self::class, 'testMapping']),
  261.         ];
  262.     }
  263.     public function getNodeVisitors(): array
  264.     {
  265.         return [new MacroAutoImportNodeVisitor()];
  266.     }
  267.     public function getOperators(): array
  268.     {
  269.         return [
  270.             [
  271.                 'not' => ['precedence' => 50'class' => NotUnary::class],
  272.                 '-' => ['precedence' => 500'class' => NegUnary::class],
  273.                 '+' => ['precedence' => 500'class' => PosUnary::class],
  274.             ],
  275.             [
  276.                 'or' => ['precedence' => 10'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  277.                 'and' => ['precedence' => 15'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  278.                 'b-or' => ['precedence' => 16'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  279.                 'b-xor' => ['precedence' => 17'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  280.                 'b-and' => ['precedence' => 18'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  281.                 '==' => ['precedence' => 20'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  282.                 '!=' => ['precedence' => 20'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  283.                 '<=>' => ['precedence' => 20'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  284.                 '<' => ['precedence' => 20'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  285.                 '>' => ['precedence' => 20'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  286.                 '>=' => ['precedence' => 20'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  287.                 '<=' => ['precedence' => 20'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  288.                 'not in' => ['precedence' => 20'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  289.                 'in' => ['precedence' => 20'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  290.                 'matches' => ['precedence' => 20'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  291.                 'starts with' => ['precedence' => 20'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  292.                 'ends with' => ['precedence' => 20'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  293.                 'has some' => ['precedence' => 20'class' => HasSomeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  294.                 'has every' => ['precedence' => 20'class' => HasEveryBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  295.                 '..' => ['precedence' => 25'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  296.                 '+' => ['precedence' => 30'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  297.                 '-' => ['precedence' => 30'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  298.                 '~' => ['precedence' => 40'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  299.                 '*' => ['precedence' => 60'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  300.                 '/' => ['precedence' => 60'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  301.                 '//' => ['precedence' => 60'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  302.                 '%' => ['precedence' => 60'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
  303.                 'is' => ['precedence' => 100'associativity' => ExpressionParser::OPERATOR_LEFT],
  304.                 'is not' => ['precedence' => 100'associativity' => ExpressionParser::OPERATOR_LEFT],
  305.                 '**' => ['precedence' => 200'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  306.                 '??' => ['precedence' => 300'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
  307.             ],
  308.         ];
  309.     }
  310.     /**
  311.      * Cycles over a value.
  312.      *
  313.      * @param \ArrayAccess|array $values
  314.      * @param int                $position The cycle position
  315.      *
  316.      * @return string The next value in the cycle
  317.      *
  318.      * @internal
  319.      */
  320.     public static function cycle($values$position): string
  321.     {
  322.         if (!\is_array($values) && !$values instanceof \ArrayAccess) {
  323.             return $values;
  324.         }
  325.         if (!\count($values)) {
  326.             throw new RuntimeError('The "cycle" function does not work on empty sequences/mappings.');
  327.         }
  328.         return $values[$position % \count($values)];
  329.     }
  330.     /**
  331.      * Returns a random value depending on the supplied parameter type:
  332.      * - a random item from a \Traversable or array
  333.      * - a random character from a string
  334.      * - a random integer between 0 and the integer parameter.
  335.      *
  336.      * @param \Traversable|array|int|float|string $values The values to pick a random item from
  337.      * @param int|null                            $max    Maximum value used when $values is an int
  338.      *
  339.      * @return mixed A random value from the given sequence
  340.      *
  341.      * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
  342.      *
  343.      * @internal
  344.      */
  345.     public static function random(string $charset$values null$max null)
  346.     {
  347.         if (null === $values) {
  348.             return null === $max mt_rand() : mt_rand(0, (int) $max);
  349.         }
  350.         if (\is_int($values) || \is_float($values)) {
  351.             if (null === $max) {
  352.                 if ($values 0) {
  353.                     $max 0;
  354.                     $min $values;
  355.                 } else {
  356.                     $max $values;
  357.                     $min 0;
  358.                 }
  359.             } else {
  360.                 $min $values;
  361.             }
  362.             return mt_rand((int) $min, (int) $max);
  363.         }
  364.         if (\is_string($values)) {
  365.             if ('' === $values) {
  366.                 return '';
  367.             }
  368.             if ('UTF-8' !== $charset) {
  369.                 $values self::convertEncoding($values'UTF-8'$charset);
  370.             }
  371.             // unicode version of str_split()
  372.             // split at all positions, but not after the start and not before the end
  373.             $values preg_split('/(?<!^)(?!$)/u'$values);
  374.             if ('UTF-8' !== $charset) {
  375.                 foreach ($values as $i => $value) {
  376.                     $values[$i] = self::convertEncoding($value$charset'UTF-8');
  377.                 }
  378.             }
  379.         }
  380.         if (!is_iterable($values)) {
  381.             return $values;
  382.         }
  383.         $values self::toArray($values);
  384.         if (=== \count($values)) {
  385.             throw new RuntimeError('The random function cannot pick from an empty sequence/mapping.');
  386.         }
  387.         return $values[array_rand($values1)];
  388.     }
  389.     /**
  390.      * Formats a date.
  391.      *
  392.      *   {{ post.published_at|date("m/d/Y") }}
  393.      *
  394.      * @param \DateTimeInterface|\DateInterval|string $date     A date
  395.      * @param string|null                             $format   The target format, null to use the default
  396.      * @param \DateTimeZone|string|false|null         $timezone The target timezone, null to use the default, false to leave unchanged
  397.      */
  398.     public function formatDate($date$format null$timezone null): string
  399.     {
  400.         if (null === $format) {
  401.             $formats $this->getDateFormat();
  402.             $format $date instanceof \DateInterval $formats[1] : $formats[0];
  403.         }
  404.         if ($date instanceof \DateInterval) {
  405.             return $date->format($format);
  406.         }
  407.         return $this->convertDate($date$timezone)->format($format);
  408.     }
  409.     /**
  410.      * Returns a new date object modified.
  411.      *
  412.      *   {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
  413.      *
  414.      * @param \DateTimeInterface|string $date     A date
  415.      * @param string                    $modifier A modifier string
  416.      *
  417.      * @return \DateTime|\DateTimeImmutable
  418.      *
  419.      * @internal
  420.      */
  421.     public function modifyDate($date$modifier)
  422.     {
  423.         return $this->convertDate($datefalse)->modify($modifier);
  424.     }
  425.     /**
  426.      * Returns a formatted string.
  427.      *
  428.      * @param string|null $format
  429.      * @param ...$values
  430.      *
  431.      * @internal
  432.      */
  433.     public static function sprintf($format, ...$values): string
  434.     {
  435.         return \sprintf($format ?? '', ...$values);
  436.     }
  437.     /**
  438.      * @internal
  439.      */
  440.     public static function dateConverter(Environment $env$date$format null$timezone null): string
  441.     {
  442.         return $env->getExtension(self::class)->formatDate($date$format$timezone);
  443.     }
  444.     /**
  445.      * Converts an input to a \DateTime instance.
  446.      *
  447.      *    {% if date(user.created_at) < date('+2days') %}
  448.      *      {# do something #}
  449.      *    {% endif %}
  450.      *
  451.      * @param \DateTimeInterface|string|null  $date     A date or null to use the current time
  452.      * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
  453.      *
  454.      * @return \DateTime|\DateTimeImmutable
  455.      */
  456.     public function convertDate($date null$timezone null)
  457.     {
  458.         // determine the timezone
  459.         if (false !== $timezone) {
  460.             if (null === $timezone) {
  461.                 $timezone $this->getTimezone();
  462.             } elseif (!$timezone instanceof \DateTimeZone) {
  463.                 $timezone = new \DateTimeZone($timezone);
  464.             }
  465.         }
  466.         // immutable dates
  467.         if ($date instanceof \DateTimeImmutable) {
  468.             return false !== $timezone $date->setTimezone($timezone) : $date;
  469.         }
  470.         if ($date instanceof \DateTime) {
  471.             $date = clone $date;
  472.             if (false !== $timezone) {
  473.                 $date->setTimezone($timezone);
  474.             }
  475.             return $date;
  476.         }
  477.         if (null === $date || 'now' === $date) {
  478.             if (null === $date) {
  479.                 $date 'now';
  480.             }
  481.             return new \DateTime($datefalse !== $timezone $timezone $this->getTimezone());
  482.         }
  483.         $asString = (string) $date;
  484.         if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString1)))) {
  485.             $date = new \DateTime('@'.$date);
  486.         } else {
  487.             $date = new \DateTime($date$this->getTimezone());
  488.         }
  489.         if (false !== $timezone) {
  490.             $date->setTimezone($timezone);
  491.         }
  492.         return $date;
  493.     }
  494.     /**
  495.      * Replaces strings within a string.
  496.      *
  497.      * @param string|null        $str  String to replace in
  498.      * @param array|\Traversable $from Replace values
  499.      *
  500.      * @internal
  501.      */
  502.     public static function replace($str$from): string
  503.     {
  504.         if (!is_iterable($from)) {
  505.             throw new RuntimeError(\sprintf('The "replace" filter expects a sequence/mapping or "Traversable" as replace values, got "%s".', \is_object($from) ? \get_class($from) : \gettype($from)));
  506.         }
  507.         return strtr($str ?? ''self::toArray($from));
  508.     }
  509.     /**
  510.      * Rounds a number.
  511.      *
  512.      * @param int|float|string|null $value     The value to round
  513.      * @param int|float             $precision The rounding precision
  514.      * @param string                $method    The method to use for rounding
  515.      *
  516.      * @return int|float The rounded number
  517.      *
  518.      * @internal
  519.      */
  520.     public static function round($value$precision 0$method 'common')
  521.     {
  522.         $value = (float) $value;
  523.         if ('common' === $method) {
  524.             return round($value$precision);
  525.         }
  526.         if ('ceil' !== $method && 'floor' !== $method) {
  527.             throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.');
  528.         }
  529.         return $method($value 10 ** $precision) / 10 ** $precision;
  530.     }
  531.     /**
  532.      * Formats a number.
  533.      *
  534.      * All of the formatting options can be left null, in that case the defaults will
  535.      * be used. Supplying any of the parameters will override the defaults set in the
  536.      * environment object.
  537.      *
  538.      * @param mixed       $number       A float/int/string of the number to format
  539.      * @param int|null    $decimal      the number of decimal points to display
  540.      * @param string|null $decimalPoint the character(s) to use for the decimal point
  541.      * @param string|null $thousandSep  the character(s) to use for the thousands separator
  542.      */
  543.     public function formatNumber($number$decimal null$decimalPoint null$thousandSep null): string
  544.     {
  545.         $defaults $this->getNumberFormat();
  546.         if (null === $decimal) {
  547.             $decimal $defaults[0];
  548.         }
  549.         if (null === $decimalPoint) {
  550.             $decimalPoint $defaults[1];
  551.         }
  552.         if (null === $thousandSep) {
  553.             $thousandSep $defaults[2];
  554.         }
  555.         return number_format((float) $number$decimal$decimalPoint$thousandSep);
  556.     }
  557.     /**
  558.      * URL encodes (RFC 3986) a string as a path segment or an array as a query string.
  559.      *
  560.      * @param string|array|null $url A URL or an array of query parameters
  561.      *
  562.      * @internal
  563.      */
  564.     public static function urlencode($url): string
  565.     {
  566.         if (\is_array($url)) {
  567.             return http_build_query($url'''&', \PHP_QUERY_RFC3986);
  568.         }
  569.         return rawurlencode($url ?? '');
  570.     }
  571.     /**
  572.      * Merges any number of arrays or Traversable objects.
  573.      *
  574.      *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
  575.      *
  576.      *  {% set items = items|merge({ 'peugeot': 'car' }, { 'banana': 'fruit' }) %}
  577.      *
  578.      *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car', 'banana': 'fruit' } #}
  579.      *
  580.      * @param array|\Traversable ...$arrays Any number of arrays or Traversable objects to merge
  581.      *
  582.      * @internal
  583.      */
  584.     public static function merge(...$arrays): array
  585.     {
  586.         $result = [];
  587.         foreach ($arrays as $argNumber => $array) {
  588.             if (!is_iterable($array)) {
  589.                 throw new RuntimeError(\sprintf('The merge filter only works with sequences/mappings or "Traversable", got "%s" for argument %d.', \gettype($array), $argNumber 1));
  590.             }
  591.             $result array_merge($resultself::toArray($array));
  592.         }
  593.         return $result;
  594.     }
  595.     /**
  596.      * Slices a variable.
  597.      *
  598.      * @param mixed $item         A variable
  599.      * @param int   $start        Start of the slice
  600.      * @param int   $length       Size of the slice
  601.      * @param bool  $preserveKeys Whether to preserve key or not (when the input is an array)
  602.      *
  603.      * @return mixed The sliced variable
  604.      *
  605.      * @internal
  606.      */
  607.     public static function slice(string $charset$item$start$length null$preserveKeys false)
  608.     {
  609.         if ($item instanceof \Traversable) {
  610.             while ($item instanceof \IteratorAggregate) {
  611.                 $item $item->getIterator();
  612.             }
  613.             if ($start >= && $length >= && $item instanceof \Iterator) {
  614.                 try {
  615.                     return iterator_to_array(new \LimitIterator($item$start$length ?? -1), $preserveKeys);
  616.                 } catch (\OutOfBoundsException $e) {
  617.                     return [];
  618.                 }
  619.             }
  620.             $item iterator_to_array($item$preserveKeys);
  621.         }
  622.         if (\is_array($item)) {
  623.             return \array_slice($item$start$length$preserveKeys);
  624.         }
  625.         return mb_substr((string) $item$start$length$charset);
  626.     }
  627.     /**
  628.      * Returns the first element of the item.
  629.      *
  630.      * @param mixed $item A variable
  631.      *
  632.      * @return mixed The first element of the item
  633.      *
  634.      * @internal
  635.      */
  636.     public static function first(string $charset$item)
  637.     {
  638.         $elements self::slice($charset$item01false);
  639.         return \is_string($elements) ? $elements current($elements);
  640.     }
  641.     /**
  642.      * Returns the last element of the item.
  643.      *
  644.      * @param mixed $item A variable
  645.      *
  646.      * @return mixed The last element of the item
  647.      *
  648.      * @internal
  649.      */
  650.     public static function last(string $charset$item)
  651.     {
  652.         $elements self::slice($charset$item, -11false);
  653.         return \is_string($elements) ? $elements current($elements);
  654.     }
  655.     /**
  656.      * Joins the values to a string.
  657.      *
  658.      * The separators between elements are empty strings per default, you can define them with the optional parameters.
  659.      *
  660.      *  {{ [1, 2, 3]|join(', ', ' and ') }}
  661.      *  {# returns 1, 2 and 3 #}
  662.      *
  663.      *  {{ [1, 2, 3]|join('|') }}
  664.      *  {# returns 1|2|3 #}
  665.      *
  666.      *  {{ [1, 2, 3]|join }}
  667.      *  {# returns 123 #}
  668.      *
  669.      * @param array       $value An array
  670.      * @param string      $glue  The separator
  671.      * @param string|null $and   The separator for the last pair
  672.      *
  673.      * @internal
  674.      */
  675.     public static function join($value$glue ''$and null): string
  676.     {
  677.         if (!is_iterable($value)) {
  678.             $value = (array) $value;
  679.         }
  680.         $value self::toArray($valuefalse);
  681.         if (=== \count($value)) {
  682.             return '';
  683.         }
  684.         if (null === $and || $and === $glue) {
  685.             return implode($glue$value);
  686.         }
  687.         if (=== \count($value)) {
  688.             return $value[0];
  689.         }
  690.         return implode($glue, \array_slice($value0, -1)).$and.$value[\count($value) - 1];
  691.     }
  692.     /**
  693.      * Splits the string into an array.
  694.      *
  695.      *  {{ "one,two,three"|split(',') }}
  696.      *  {# returns [one, two, three] #}
  697.      *
  698.      *  {{ "one,two,three,four,five"|split(',', 3) }}
  699.      *  {# returns [one, two, "three,four,five"] #}
  700.      *
  701.      *  {{ "123"|split('') }}
  702.      *  {# returns [1, 2, 3] #}
  703.      *
  704.      *  {{ "aabbcc"|split('', 2) }}
  705.      *  {# returns [aa, bb, cc] #}
  706.      *
  707.      * @param string|null $value     A string
  708.      * @param string      $delimiter The delimiter
  709.      * @param int|null    $limit     The limit
  710.      *
  711.      * @internal
  712.      */
  713.     public static function split(string $charset$value$delimiter$limit null): array
  714.     {
  715.         $value $value ?? '';
  716.         if ('' !== $delimiter) {
  717.             return null === $limit explode($delimiter$value) : explode($delimiter$value$limit);
  718.         }
  719.         if ($limit <= 1) {
  720.             return preg_split('/(?<!^)(?!$)/u'$value);
  721.         }
  722.         $length mb_strlen($value$charset);
  723.         if ($length $limit) {
  724.             return [$value];
  725.         }
  726.         $r = [];
  727.         for ($i 0$i $length$i += $limit) {
  728.             $r[] = mb_substr($value$i$limit$charset);
  729.         }
  730.         return $r;
  731.     }
  732.     // The '_default' filter is used internally to avoid using the ternary operator
  733.     // which costs a lot for big contexts (before PHP 5.4). So, on average,
  734.     // a function call is cheaper.
  735.     /**
  736.      * @internal
  737.      */
  738.     public static function default($value$default '')
  739.     {
  740.         if (self::testEmpty($value)) {
  741.             return $default;
  742.         }
  743.         return $value;
  744.     }
  745.     /**
  746.      * Returns the keys for the given array.
  747.      *
  748.      * It is useful when you want to iterate over the keys of an array:
  749.      *
  750.      *  {% for key in array|keys %}
  751.      *      {# ... #}
  752.      *  {% endfor %}
  753.      *
  754.      * @internal
  755.      */
  756.     public static function keys($array): array
  757.     {
  758.         if ($array instanceof \Traversable) {
  759.             while ($array instanceof \IteratorAggregate) {
  760.                 $array $array->getIterator();
  761.             }
  762.             $keys = [];
  763.             if ($array instanceof \Iterator) {
  764.                 $array->rewind();
  765.                 while ($array->valid()) {
  766.                     $keys[] = $array->key();
  767.                     $array->next();
  768.                 }
  769.                 return $keys;
  770.             }
  771.             foreach ($array as $key => $item) {
  772.                 $keys[] = $key;
  773.             }
  774.             return $keys;
  775.         }
  776.         if (!\is_array($array)) {
  777.             return [];
  778.         }
  779.         return array_keys($array);
  780.     }
  781.     /**
  782.      * Reverses a variable.
  783.      *
  784.      * @param array|\Traversable|string|null $item         An array, a \Traversable instance, or a string
  785.      * @param bool                           $preserveKeys Whether to preserve key or not
  786.      *
  787.      * @return mixed The reversed input
  788.      *
  789.      * @internal
  790.      */
  791.     public static function reverse(string $charset$item$preserveKeys false)
  792.     {
  793.         if ($item instanceof \Traversable) {
  794.             return array_reverse(iterator_to_array($item), $preserveKeys);
  795.         }
  796.         if (\is_array($item)) {
  797.             return array_reverse($item$preserveKeys);
  798.         }
  799.         $string = (string) $item;
  800.         if ('UTF-8' !== $charset) {
  801.             $string self::convertEncoding($string'UTF-8'$charset);
  802.         }
  803.         preg_match_all('/./us'$string$matches);
  804.         $string implode(''array_reverse($matches[0]));
  805.         if ('UTF-8' !== $charset) {
  806.             $string self::convertEncoding($string$charset'UTF-8');
  807.         }
  808.         return $string;
  809.     }
  810.     /**
  811.      * Shuffles an array, a \Traversable instance, or a string.
  812.      * The function does not preserve keys.
  813.      *
  814.      * @param array|\Traversable|string|null $item
  815.      *
  816.      * @return mixed
  817.      *
  818.      * @internal
  819.      */
  820.     public static function shuffle(string $charset$item)
  821.     {
  822.         if (\is_string($item)) {
  823.             if ('UTF-8' !== $charset) {
  824.                 $item self::convertEncoding($item'UTF-8'$charset);
  825.             }
  826.             $item preg_split('/(?<!^)(?!$)/u'$item, -1);
  827.             shuffle($item);
  828.             $item implode(''$item);
  829.             if ('UTF-8' !== $charset) {
  830.                 $item self::convertEncoding($item$charset'UTF-8');
  831.             }
  832.             return $item;
  833.         }
  834.         if (is_iterable($item)) {
  835.             $item self::toArray($itemfalse);
  836.             shuffle($item);
  837.         }
  838.         return $item;
  839.     }
  840.     /**
  841.      * Sorts an array.
  842.      *
  843.      * @param array|\Traversable $array
  844.      *
  845.      * @internal
  846.      */
  847.     public static function sort(Environment $env$array$arrow null): array
  848.     {
  849.         if ($array instanceof \Traversable) {
  850.             $array iterator_to_array($array);
  851.         } elseif (!\is_array($array)) {
  852.             throw new RuntimeError(\sprintf('The sort filter only works with sequences/mappings or "Traversable", got "%s".', \gettype($array)));
  853.         }
  854.         if (null !== $arrow) {
  855.             self::checkArrowInSandbox($env$arrow'sort''filter');
  856.             uasort($array$arrow);
  857.         } else {
  858.             asort($array);
  859.         }
  860.         return $array;
  861.     }
  862.     /**
  863.      * @internal
  864.      */
  865.     public static function inFilter($value$compare)
  866.     {
  867.         if ($value instanceof Markup) {
  868.             $value = (string) $value;
  869.         }
  870.         if ($compare instanceof Markup) {
  871.             $compare = (string) $compare;
  872.         }
  873.         if (\is_string($compare)) {
  874.             if (\is_string($value) || \is_int($value) || \is_float($value)) {
  875.                 return '' === $value || str_contains($compare, (string) $value);
  876.             }
  877.             return false;
  878.         }
  879.         if (!is_iterable($compare)) {
  880.             return false;
  881.         }
  882.         if (\is_object($value) || \is_resource($value)) {
  883.             if (!\is_array($compare)) {
  884.                 foreach ($compare as $item) {
  885.                     if ($item === $value) {
  886.                         return true;
  887.                     }
  888.                 }
  889.                 return false;
  890.             }
  891.             return \in_array($value$comparetrue);
  892.         }
  893.         foreach ($compare as $item) {
  894.             if (=== self::compare($value$item)) {
  895.                 return true;
  896.             }
  897.         }
  898.         return false;
  899.     }
  900.     /**
  901.      * Compares two values using a more strict version of the PHP non-strict comparison operator.
  902.      *
  903.      * @see https://wiki.php.net/rfc/string_to_number_comparison
  904.      * @see https://wiki.php.net/rfc/trailing_whitespace_numerics
  905.      *
  906.      * @internal
  907.      */
  908.     public static function compare($a$b)
  909.     {
  910.         // int <=> string
  911.         if (\is_int($a) && \is_string($b)) {
  912.             $bTrim trim($b" \t\n\r\v\f");
  913.             if (!is_numeric($bTrim)) {
  914.                 return (string) $a <=> $b;
  915.             }
  916.             if ((int) $bTrim == $bTrim) {
  917.                 return $a <=> (int) $bTrim;
  918.             } else {
  919.                 return (float) $a <=> (float) $bTrim;
  920.             }
  921.         }
  922.         if (\is_string($a) && \is_int($b)) {
  923.             $aTrim trim($a" \t\n\r\v\f");
  924.             if (!is_numeric($aTrim)) {
  925.                 return $a <=> (string) $b;
  926.             }
  927.             if ((int) $aTrim == $aTrim) {
  928.                 return (int) $aTrim <=> $b;
  929.             } else {
  930.                 return (float) $aTrim <=> (float) $b;
  931.             }
  932.         }
  933.         // float <=> string
  934.         if (\is_float($a) && \is_string($b)) {
  935.             if (is_nan($a)) {
  936.                 return 1;
  937.             }
  938.             $bTrim trim($b" \t\n\r\v\f");
  939.             if (!is_numeric($bTrim)) {
  940.                 return (string) $a <=> $b;
  941.             }
  942.             return $a <=> (float) $bTrim;
  943.         }
  944.         if (\is_string($a) && \is_float($b)) {
  945.             if (is_nan($b)) {
  946.                 return 1;
  947.             }
  948.             $aTrim trim($a" \t\n\r\v\f");
  949.             if (!is_numeric($aTrim)) {
  950.                 return $a <=> (string) $b;
  951.             }
  952.             return (float) $aTrim <=> $b;
  953.         }
  954.         // fallback to <=>
  955.         return $a <=> $b;
  956.     }
  957.     /**
  958.      * @throws RuntimeError When an invalid pattern is used
  959.      *
  960.      * @internal
  961.      */
  962.     public static function matches(string $regexp, ?string $str): int
  963.     {
  964.         set_error_handler(function ($t$m) use ($regexp) {
  965.             throw new RuntimeError(\sprintf('Regexp "%s" passed to "matches" is not valid'$regexp).substr($m12));
  966.         });
  967.         try {
  968.             return preg_match($regexp$str ?? '');
  969.         } finally {
  970.             restore_error_handler();
  971.         }
  972.     }
  973.     /**
  974.      * Returns a trimmed string.
  975.      *
  976.      * @param string|null $string
  977.      * @param string|null $characterMask
  978.      * @param string      $side
  979.      *
  980.      * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
  981.      *
  982.      * @internal
  983.      */
  984.     public static function trim($string$characterMask null$side 'both'): string
  985.     {
  986.         if (null === $characterMask) {
  987.             $characterMask " \t\n\r\0\x0B";
  988.         }
  989.         switch ($side) {
  990.             case 'both':
  991.                 return trim($string ?? ''$characterMask);
  992.             case 'left':
  993.                 return ltrim($string ?? ''$characterMask);
  994.             case 'right':
  995.                 return rtrim($string ?? ''$characterMask);
  996.             default:
  997.                 throw new RuntimeError('Trimming side must be "left", "right" or "both".');
  998.         }
  999.     }
  1000.     /**
  1001.      * Inserts HTML line breaks before all newlines in a string.
  1002.      *
  1003.      * @param string|null $string
  1004.      *
  1005.      * @internal
  1006.      */
  1007.     public static function nl2br($string): string
  1008.     {
  1009.         return nl2br($string ?? '');
  1010.     }
  1011.     /**
  1012.      * Removes whitespaces between HTML tags.
  1013.      *
  1014.      * @param string|null $content
  1015.      *
  1016.      * @internal
  1017.      */
  1018.     public static function spaceless($content): string
  1019.     {
  1020.         return trim(preg_replace('/>\s+</''><'$content ?? ''));
  1021.     }
  1022.     /**
  1023.      * @param string|null $string
  1024.      * @param string      $to
  1025.      * @param string      $from
  1026.      *
  1027.      * @internal
  1028.      */
  1029.     public static function convertEncoding($string$to$from): string
  1030.     {
  1031.         if (!\function_exists('iconv')) {
  1032.             throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
  1033.         }
  1034.         return iconv($from$to$string ?? '');
  1035.     }
  1036.     /**
  1037.      * Returns the length of a variable.
  1038.      *
  1039.      * @param mixed $thing A variable
  1040.      *
  1041.      * @internal
  1042.      */
  1043.     public static function length(string $charset$thing): int
  1044.     {
  1045.         if (null === $thing) {
  1046.             return 0;
  1047.         }
  1048.         if (\is_scalar($thing)) {
  1049.             return mb_strlen($thing$charset);
  1050.         }
  1051.         if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) {
  1052.             return \count($thing);
  1053.         }
  1054.         if ($thing instanceof \Traversable) {
  1055.             return iterator_count($thing);
  1056.         }
  1057.         if (method_exists($thing'__toString')) {
  1058.             return mb_strlen((string) $thing$charset);
  1059.         }
  1060.         return 1;
  1061.     }
  1062.     /**
  1063.      * Converts a string to uppercase.
  1064.      *
  1065.      * @param string|null $string A string
  1066.      *
  1067.      * @internal
  1068.      */
  1069.     public static function upper(string $charset$string): string
  1070.     {
  1071.         return mb_strtoupper($string ?? ''$charset);
  1072.     }
  1073.     /**
  1074.      * Converts a string to lowercase.
  1075.      *
  1076.      * @param string|null $string A string
  1077.      *
  1078.      * @internal
  1079.      */
  1080.     public static function lower(string $charset$string): string
  1081.     {
  1082.         return mb_strtolower($string ?? ''$charset);
  1083.     }
  1084.     /**
  1085.      * Strips HTML and PHP tags from a string.
  1086.      *
  1087.      * @param string|null          $string
  1088.      * @param string[]|string|null $allowable_tags
  1089.      *
  1090.      * @internal
  1091.      */
  1092.     public static function striptags($string$allowable_tags null): string
  1093.     {
  1094.         return strip_tags($string ?? ''$allowable_tags);
  1095.     }
  1096.     /**
  1097.      * Returns a titlecased string.
  1098.      *
  1099.      * @param string|null $string A string
  1100.      *
  1101.      * @internal
  1102.      */
  1103.     public static function titleCase(string $charset$string): string
  1104.     {
  1105.         return mb_convert_case($string ?? '', \MB_CASE_TITLE$charset);
  1106.     }
  1107.     /**
  1108.      * Returns a capitalized string.
  1109.      *
  1110.      * @param string|null $string A string
  1111.      *
  1112.      * @internal
  1113.      */
  1114.     public static function capitalize(string $charset$string): string
  1115.     {
  1116.         return mb_strtoupper(mb_substr($string ?? ''01$charset), $charset).mb_strtolower(mb_substr($string ?? ''1null$charset), $charset);
  1117.     }
  1118.     /**
  1119.      * @internal
  1120.      */
  1121.     public static function callMacro(Template $templatestring $method, array $argsint $lineno, array $contextSource $source)
  1122.     {
  1123.         if (!method_exists($template$method)) {
  1124.             $parent $template;
  1125.             while ($parent $parent->getParent($context)) {
  1126.                 if (method_exists($parent$method)) {
  1127.                     return $parent->$method(...$args);
  1128.                 }
  1129.             }
  1130.             throw new RuntimeError(\sprintf('Macro "%s" is not defined in template "%s".'substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno$source);
  1131.         }
  1132.         return $template->$method(...$args);
  1133.     }
  1134.     /**
  1135.      * @internal
  1136.      */
  1137.     public static function ensureTraversable($seq)
  1138.     {
  1139.         if (is_iterable($seq)) {
  1140.             return $seq;
  1141.         }
  1142.         return [];
  1143.     }
  1144.     /**
  1145.      * @internal
  1146.      */
  1147.     public static function toArray($seq$preserveKeys true)
  1148.     {
  1149.         if ($seq instanceof \Traversable) {
  1150.             return iterator_to_array($seq$preserveKeys);
  1151.         }
  1152.         if (!\is_array($seq)) {
  1153.             return $seq;
  1154.         }
  1155.         return $preserveKeys $seq array_values($seq);
  1156.     }
  1157.     /**
  1158.      * Checks if a variable is empty.
  1159.      *
  1160.      *    {# evaluates to true if the foo variable is null, false, or the empty string #}
  1161.      *    {% if foo is empty %}
  1162.      *        {# ... #}
  1163.      *    {% endif %}
  1164.      *
  1165.      * @param mixed $value A variable
  1166.      *
  1167.      * @internal
  1168.      */
  1169.     public static function testEmpty($value): bool
  1170.     {
  1171.         if ($value instanceof \Countable) {
  1172.             return === \count($value);
  1173.         }
  1174.         if ($value instanceof \Traversable) {
  1175.             return !iterator_count($value);
  1176.         }
  1177.         if (\is_object($value) && method_exists($value'__toString')) {
  1178.             return '' === (string) $value;
  1179.         }
  1180.         return '' === $value || false === $value || null === $value || [] === $value;
  1181.     }
  1182.     /**
  1183.      * Checks if a variable is a sequence.
  1184.      *
  1185.      *    {# evaluates to true if the foo variable is a sequence #}
  1186.      *    {% if foo is sequence %}
  1187.      *        {# ... #}
  1188.      *    {% endif %}
  1189.      *
  1190.      * @param mixed $value
  1191.      *
  1192.      * @internal
  1193.      */
  1194.     public static function testSequence($value): bool
  1195.     {
  1196.         if ($value instanceof \ArrayObject) {
  1197.             $value $value->getArrayCopy();
  1198.         }
  1199.         if ($value instanceof \Traversable) {
  1200.             $value iterator_to_array($value);
  1201.         }
  1202.         return \is_array($value) && array_is_list($value);
  1203.     }
  1204.     /**
  1205.      * Checks if a variable is a mapping.
  1206.      *
  1207.      *    {# evaluates to true if the foo variable is a mapping #}
  1208.      *    {% if foo is mapping %}
  1209.      *        {# ... #}
  1210.      *    {% endif %}
  1211.      *
  1212.      * @param mixed $value
  1213.      *
  1214.      * @internal
  1215.      */
  1216.     public static function testMapping($value): bool
  1217.     {
  1218.         if ($value instanceof \ArrayObject) {
  1219.             $value $value->getArrayCopy();
  1220.         }
  1221.         if ($value instanceof \Traversable) {
  1222.             $value iterator_to_array($value);
  1223.         }
  1224.         return (\is_array($value) && !array_is_list($value)) || \is_object($value);
  1225.     }
  1226.     /**
  1227.      * Renders a template.
  1228.      *
  1229.      * @param array                        $context
  1230.      * @param string|array|TemplateWrapper $template      The template to render or an array of templates to try consecutively
  1231.      * @param array                        $variables     The variables to pass to the template
  1232.      * @param bool                         $withContext
  1233.      * @param bool                         $ignoreMissing Whether to ignore missing templates or not
  1234.      * @param bool                         $sandboxed     Whether to sandbox the template or not
  1235.      *
  1236.      * @internal
  1237.      */
  1238.     public static function include(Environment $env$context$template$variables = [], $withContext true$ignoreMissing false$sandboxed false): string
  1239.     {
  1240.         $alreadySandboxed false;
  1241.         $sandbox null;
  1242.         if ($withContext) {
  1243.             $variables array_merge($context$variables);
  1244.         }
  1245.         if ($isSandboxed $sandboxed && $env->hasExtension(SandboxExtension::class)) {
  1246.             $sandbox $env->getExtension(SandboxExtension::class);
  1247.             if (!$alreadySandboxed $sandbox->isSandboxed()) {
  1248.                 $sandbox->enableSandbox();
  1249.             }
  1250.         }
  1251.         try {
  1252.             $loaded null;
  1253.             try {
  1254.                 $loaded $env->resolveTemplate($template);
  1255.             } catch (LoaderError $e) {
  1256.                 if (!$ignoreMissing) {
  1257.                     throw $e;
  1258.                 }
  1259.             }
  1260.             if ($isSandboxed && $loaded) {
  1261.                 $loaded->unwrap()->checkSecurity();
  1262.             }
  1263.             return $loaded $loaded->render($variables) : '';
  1264.         } finally {
  1265.             if ($isSandboxed && !$alreadySandboxed) {
  1266.                 $sandbox->disableSandbox();
  1267.             }
  1268.         }
  1269.     }
  1270.     /**
  1271.      * Returns a template content without rendering it.
  1272.      *
  1273.      * @param string $name          The template name
  1274.      * @param bool   $ignoreMissing Whether to ignore missing templates or not
  1275.      *
  1276.      * @internal
  1277.      */
  1278.     public static function source(Environment $env$name$ignoreMissing false): string
  1279.     {
  1280.         $loader $env->getLoader();
  1281.         try {
  1282.             return $loader->getSourceContext($name)->getCode();
  1283.         } catch (LoaderError $e) {
  1284.             if (!$ignoreMissing) {
  1285.                 throw $e;
  1286.             }
  1287.             return '';
  1288.         }
  1289.     }
  1290.     /**
  1291.      * Provides the ability to get constants from instances as well as class/global constants.
  1292.      *
  1293.      * @param string      $constant The name of the constant
  1294.      * @param object|null $object   The object to get the constant from
  1295.      *
  1296.      * @return mixed Class constants can return many types like scalars, arrays, and
  1297.      *               objects depending on the PHP version (\BackedEnum, \UnitEnum, etc.)
  1298.      *
  1299.      * @internal
  1300.      */
  1301.     public static function constant($constant$object null)
  1302.     {
  1303.         if (null !== $object) {
  1304.             if ('class' === $constant) {
  1305.                 return \get_class($object);
  1306.             }
  1307.             $constant = \get_class($object).'::'.$constant;
  1308.         }
  1309.         if (!\defined($constant)) {
  1310.             if ('::class' === strtolower(substr($constant, -7))) {
  1311.                 throw new RuntimeError(\sprintf('You cannot use the Twig function "constant()" to access "%s". You could provide an object and call constant("class", $object) or use the class name directly as a string.'$constant));
  1312.             }
  1313.             throw new RuntimeError(\sprintf('Constant "%s" is undefined.'$constant));
  1314.         }
  1315.         return \constant($constant);
  1316.     }
  1317.     /**
  1318.      * Checks if a constant exists.
  1319.      *
  1320.      * @param string      $constant The name of the constant
  1321.      * @param object|null $object   The object to get the constant from
  1322.      *
  1323.      * @internal
  1324.      */
  1325.     public static function constantIsDefined($constant$object null): bool
  1326.     {
  1327.         if (null !== $object) {
  1328.             if ('class' === $constant) {
  1329.                 return true;
  1330.             }
  1331.             $constant = \get_class($object).'::'.$constant;
  1332.         }
  1333.         return \defined($constant);
  1334.     }
  1335.     /**
  1336.      * Batches item.
  1337.      *
  1338.      * @param array $items An array of items
  1339.      * @param int   $size  The size of the batch
  1340.      * @param mixed $fill  A value used to fill missing items
  1341.      *
  1342.      * @internal
  1343.      */
  1344.     public static function batch($items$size$fill null$preserveKeys true): array
  1345.     {
  1346.         if (!is_iterable($items)) {
  1347.             throw new RuntimeError(\sprintf('The "batch" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items)));
  1348.         }
  1349.         $size = (int) ceil($size);
  1350.         $result array_chunk(self::toArray($items$preserveKeys), $size$preserveKeys);
  1351.         if (null !== $fill && $result) {
  1352.             $last = \count($result) - 1;
  1353.             if ($fillCount $size - \count($result[$last])) {
  1354.                 for ($i 0$i $fillCount; ++$i) {
  1355.                     $result[$last][] = $fill;
  1356.                 }
  1357.             }
  1358.         }
  1359.         return $result;
  1360.     }
  1361.     /**
  1362.      * Returns the attribute value for a given array/object.
  1363.      *
  1364.      * @param mixed  $object            The object or array from where to get the item
  1365.      * @param mixed  $item              The item to get from the array or object
  1366.      * @param array  $arguments         An array of arguments to pass if the item is an object method
  1367.      * @param string $type              The type of attribute (@see \Twig\Template constants)
  1368.      * @param bool   $isDefinedTest     Whether this is only a defined check
  1369.      * @param bool   $ignoreStrictCheck Whether to ignore the strict attribute check or not
  1370.      * @param int    $lineno            The template line where the attribute was called
  1371.      *
  1372.      * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
  1373.      *
  1374.      * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
  1375.      *
  1376.      * @internal
  1377.      */
  1378.     public static function getAttribute(Environment $envSource $source$object$item, array $arguments = [], $type /* Template::ANY_CALL */ 'any'$isDefinedTest false$ignoreStrictCheck false$sandboxed falseint $lineno = -1)
  1379.     {
  1380.         $propertyNotAllowedError null;
  1381.         // array
  1382.         if (/* Template::METHOD_CALL */ 'method' !== $type) {
  1383.             $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item $item;
  1384.             if ($sandboxed && $object instanceof \ArrayAccess && !\in_array(get_class($object), self::ARRAY_LIKE_CLASSEStrue)) {
  1385.                 try {
  1386.                     $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object$arrayItem$lineno$source);
  1387.                 } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {
  1388.                     goto methodCheck;
  1389.                 }
  1390.             }
  1391.             if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object)))
  1392.                 || ($object instanceof \ArrayAccess && isset($object[$arrayItem]))
  1393.             ) {
  1394.                 if ($isDefinedTest) {
  1395.                     return true;
  1396.                 }
  1397.                 return $object[$arrayItem];
  1398.             }
  1399.             if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) {
  1400.                 if ($isDefinedTest) {
  1401.                     return false;
  1402.                 }
  1403.                 if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1404.                     return;
  1405.                 }
  1406.                 if ($object instanceof \ArrayAccess) {
  1407.                     $message = \sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.'$arrayItem, \get_class($object));
  1408.                 } elseif (\is_object($object)) {
  1409.                     $message = \sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.'$item, \get_class($object));
  1410.                 } elseif (\is_array($object)) {
  1411.                     if (empty($object)) {
  1412.                         $message = \sprintf('Key "%s" does not exist as the sequence/mapping is empty.'$arrayItem);
  1413.                     } else {
  1414.                         $message = \sprintf('Key "%s" for sequence/mapping with keys "%s" does not exist.'$arrayItemimplode(', 'array_keys($object)));
  1415.                     }
  1416.                 } elseif (/* Template::ARRAY_CALL */ 'array' === $type) {
  1417.                     if (null === $object) {
  1418.                         $message = \sprintf('Impossible to access a key ("%s") on a null variable.'$item);
  1419.                     } else {
  1420.                         $message = \sprintf('Impossible to access a key ("%s") on a %s variable ("%s").'$item, \gettype($object), $object);
  1421.                     }
  1422.                 } elseif (null === $object) {
  1423.                     $message = \sprintf('Impossible to access an attribute ("%s") on a null variable.'$item);
  1424.                 } else {
  1425.                     $message = \sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").'$item, \gettype($object), $object);
  1426.                 }
  1427.                 throw new RuntimeError($message$lineno$source);
  1428.             }
  1429.         }
  1430.         if (!\is_object($object)) {
  1431.             if ($isDefinedTest) {
  1432.                 return false;
  1433.             }
  1434.             if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1435.                 return;
  1436.             }
  1437.             if (null === $object) {
  1438.                 $message = \sprintf('Impossible to invoke a method ("%s") on a null variable.'$item);
  1439.             } elseif (\is_array($object)) {
  1440.                 $message = \sprintf('Impossible to invoke a method ("%s") on a sequence/mapping.'$item);
  1441.             } else {
  1442.                 $message = \sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").'$item, \gettype($object), $object);
  1443.             }
  1444.             throw new RuntimeError($message$lineno$source);
  1445.         }
  1446.         if ($object instanceof Template) {
  1447.             throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.'$lineno$source);
  1448.         }
  1449.         // object property
  1450.         if (/* Template::METHOD_CALL */ 'method' !== $type) {
  1451.             if ($sandboxed) {
  1452.                 try {
  1453.                     $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object$item$lineno$source);
  1454.                 } catch (SecurityNotAllowedPropertyError $propertyNotAllowedError) {
  1455.                     goto methodCheck;
  1456.                 }
  1457.             }
  1458.             if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) {
  1459.                 if ($isDefinedTest) {
  1460.                     return true;
  1461.                 }
  1462.                 return $object->$item;
  1463.             }
  1464.         }
  1465.         methodCheck:
  1466.         static $cache = [];
  1467.         $class = \get_class($object);
  1468.         // object method
  1469.         // precedence: getXxx() > isXxx() > hasXxx()
  1470.         if (!isset($cache[$class])) {
  1471.             $methods get_class_methods($object);
  1472.             sort($methods);
  1473.             $lcMethods array_map(function ($value) { return strtr($value'ABCDEFGHIJKLMNOPQRSTUVWXYZ''abcdefghijklmnopqrstuvwxyz'); }, $methods);
  1474.             $classCache = [];
  1475.             foreach ($methods as $i => $method) {
  1476.                 $classCache[$method] = $method;
  1477.                 $classCache[$lcName $lcMethods[$i]] = $method;
  1478.                 if ('g' === $lcName[0] && str_starts_with($lcName'get')) {
  1479.                     $name substr($method3);
  1480.                     $lcName substr($lcName3);
  1481.                 } elseif ('i' === $lcName[0] && str_starts_with($lcName'is')) {
  1482.                     $name substr($method2);
  1483.                     $lcName substr($lcName2);
  1484.                 } elseif ('h' === $lcName[0] && str_starts_with($lcName'has')) {
  1485.                     $name substr($method3);
  1486.                     $lcName substr($lcName3);
  1487.                     if (\in_array('is'.$lcName$lcMethods)) {
  1488.                         continue;
  1489.                     }
  1490.                 } else {
  1491.                     continue;
  1492.                 }
  1493.                 // skip get() and is() methods (in which case, $name is empty)
  1494.                 if ($name) {
  1495.                     if (!isset($classCache[$name])) {
  1496.                         $classCache[$name] = $method;
  1497.                     }
  1498.                     if (!isset($classCache[$lcName])) {
  1499.                         $classCache[$lcName] = $method;
  1500.                     }
  1501.                 }
  1502.             }
  1503.             $cache[$class] = $classCache;
  1504.         }
  1505.         $call false;
  1506.         if (isset($cache[$class][$item])) {
  1507.             $method $cache[$class][$item];
  1508.         } elseif (isset($cache[$class][$lcItem strtr($item'ABCDEFGHIJKLMNOPQRSTUVWXYZ''abcdefghijklmnopqrstuvwxyz')])) {
  1509.             $method $cache[$class][$lcItem];
  1510.         } elseif (isset($cache[$class]['__call'])) {
  1511.             $method $item;
  1512.             $call true;
  1513.         } else {
  1514.             if ($isDefinedTest) {
  1515.                 return false;
  1516.             }
  1517.             if ($propertyNotAllowedError) {
  1518.                 throw $propertyNotAllowedError;
  1519.             }
  1520.             if ($ignoreStrictCheck || !$env->isStrictVariables()) {
  1521.                 return;
  1522.             }
  1523.             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), $lineno$source);
  1524.         }
  1525.         if ($sandboxed) {
  1526.             try {
  1527.                 $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object$method$lineno$source);
  1528.             } catch (SecurityNotAllowedMethodError $e) {
  1529.                 if ($isDefinedTest) {
  1530.                     return false;
  1531.                 }
  1532.                 if ($propertyNotAllowedError) {
  1533.                     throw $propertyNotAllowedError;
  1534.                 }
  1535.                 throw $e;
  1536.             }
  1537.         }
  1538.         if ($isDefinedTest) {
  1539.             return true;
  1540.         }
  1541.         // Some objects throw exceptions when they have __call, and the method we try
  1542.         // to call is not supported. If ignoreStrictCheck is true, we should return null.
  1543.         try {
  1544.             $ret $object->$method(...$arguments);
  1545.         } catch (\BadMethodCallException $e) {
  1546.             if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {
  1547.                 return;
  1548.             }
  1549.             throw $e;
  1550.         }
  1551.         return $ret;
  1552.     }
  1553.     /**
  1554.      * Returns the values from a single column in the input array.
  1555.      *
  1556.      * <pre>
  1557.      *  {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
  1558.      *
  1559.      *  {% set fruits = items|column('fruit') %}
  1560.      *
  1561.      *  {# fruits now contains ['apple', 'orange'] #}
  1562.      * </pre>
  1563.      *
  1564.      * @param array|\Traversable $array An array
  1565.      * @param int|string         $name  The column name
  1566.      * @param int|string|null    $index The column to use as the index/keys for the returned array
  1567.      *
  1568.      * @return array The array of values
  1569.      *
  1570.      * @internal
  1571.      */
  1572.     public static function column($array$name$index null): array
  1573.     {
  1574.         if ($array instanceof \Traversable) {
  1575.             $array iterator_to_array($array);
  1576.         } elseif (!\is_array($array)) {
  1577.             throw new RuntimeError(\sprintf('The column filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array)));
  1578.         }
  1579.         return array_column($array$name$index);
  1580.     }
  1581.     /**
  1582.      * @internal
  1583.      */
  1584.     public static function filter(Environment $env$array$arrow)
  1585.     {
  1586.         if (!is_iterable($array)) {
  1587.             throw new RuntimeError(\sprintf('The "filter" filter expects a sequence/mapping or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
  1588.         }
  1589.         self::checkArrowInSandbox($env$arrow'filter''filter');
  1590.         if (\is_array($array)) {
  1591.             return array_filter($array$arrow, \ARRAY_FILTER_USE_BOTH);
  1592.         }
  1593.         // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
  1594.         return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
  1595.     }
  1596.     /**
  1597.      * @internal
  1598.      */
  1599.     public static function find(Environment $env$array$arrow)
  1600.     {
  1601.         self::checkArrowInSandbox($env$arrow'find''filter');
  1602.         foreach ($array as $k => $v) {
  1603.             if ($arrow($v$k)) {
  1604.                 return $v;
  1605.             }
  1606.         }
  1607.         return null;
  1608.     }
  1609.     /**
  1610.      * @internal
  1611.      */
  1612.     public static function map(Environment $env$array$arrow)
  1613.     {
  1614.         self::checkArrowInSandbox($env$arrow'map''filter');
  1615.         $r = [];
  1616.         foreach ($array as $k => $v) {
  1617.             $r[$k] = $arrow($v$k);
  1618.         }
  1619.         return $r;
  1620.     }
  1621.     /**
  1622.      * @internal
  1623.      */
  1624.     public static function reduce(Environment $env$array$arrow$initial null)
  1625.     {
  1626.         self::checkArrowInSandbox($env$arrow'reduce''filter');
  1627.         if (!\is_array($array) && !$array instanceof \Traversable) {
  1628.             throw new RuntimeError(\sprintf('The "reduce" filter only works with sequences/mappings or "Traversable", got "%s" as first argument.', \gettype($array)));
  1629.         }
  1630.         $accumulator $initial;
  1631.         foreach ($array as $key => $value) {
  1632.             $accumulator $arrow($accumulator$value$key);
  1633.         }
  1634.         return $accumulator;
  1635.     }
  1636.     /**
  1637.      * @internal
  1638.      */
  1639.     public static function arraySome(Environment $env$array$arrow)
  1640.     {
  1641.         self::checkArrowInSandbox($env$arrow'has some''operator');
  1642.         foreach ($array as $k => $v) {
  1643.             if ($arrow($v$k)) {
  1644.                 return true;
  1645.             }
  1646.         }
  1647.         return false;
  1648.     }
  1649.     /**
  1650.      * @internal
  1651.      */
  1652.     public static function arrayEvery(Environment $env$array$arrow)
  1653.     {
  1654.         self::checkArrowInSandbox($env$arrow'has every''operator');
  1655.         foreach ($array as $k => $v) {
  1656.             if (!$arrow($v$k)) {
  1657.                 return false;
  1658.             }
  1659.         }
  1660.         return true;
  1661.     }
  1662.     /**
  1663.      * @internal
  1664.      */
  1665.     public static function checkArrowInSandbox(Environment $env$arrow$thing$type)
  1666.     {
  1667.         if (!$arrow instanceof \Closure && $env->hasExtension(SandboxExtension::class) && $env->getExtension(SandboxExtension::class)->isSandboxed()) {
  1668.             throw new RuntimeError(\sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.'$thing$type));
  1669.         }
  1670.     }
  1671.     /**
  1672.      * @internal to be removed in Twig 4
  1673.      */
  1674.     public static function captureOutput(iterable $body): string
  1675.     {
  1676.         $output '';
  1677.         $level ob_get_level();
  1678.         ob_start();
  1679.         try {
  1680.             foreach ($body as $data) {
  1681.                 if (ob_get_length()) {
  1682.                     $output .= ob_get_clean();
  1683.                     ob_start();
  1684.                 }
  1685.                 $output .= $data;
  1686.             }
  1687.             if (ob_get_length()) {
  1688.                 $output .= ob_get_clean();
  1689.             }
  1690.         } finally {
  1691.             while (ob_get_level() > $level) {
  1692.                 ob_end_clean();
  1693.             }
  1694.         }
  1695.         return $output;
  1696.     }
  1697. }