[ Index ]

PHP Cross Reference of phpBB-3.3.12-deutsch

title

Body

[close]

/vendor/twig/twig/src/Extension/ -> CoreExtension.php (source)

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


Generated: Sun Jun 23 12:25:44 2024 Cross-referenced by PHPXref 0.7.1