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

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