Verzeichnisstruktur phpBB-3.3.16
- Veröffentlicht
- 27.04.2026
So funktioniert es
|
|
Auf das letzte Element klicken. Dies geht jeweils ein Schritt zurück |
Auf das Icon klicken, dies öffnet das Verzeichnis. Nochmal klicken schließt das Verzeichnis. |
|
|
(Beispiel Datei-Icons)
|
Auf das Icon klicken um den Quellcode anzuzeigen |
CoreExtension.php
0001 <?php
0002
0003 /*
0004 * This file is part of Twig.
0005 *
0006 * (c) Fabien Potencier
0007 *
0008 * For the full copyright and license information, please view the LICENSE
0009 * file that was distributed with this source code.
0010 */
0011
0012 namespace Twig\Extension {
0013 use Twig\ExpressionParser;
0014 use Twig\Node\Expression\Binary\AddBinary;
0015 use Twig\Node\Expression\Binary\AndBinary;
0016 use Twig\Node\Expression\Binary\BitwiseAndBinary;
0017 use Twig\Node\Expression\Binary\BitwiseOrBinary;
0018 use Twig\Node\Expression\Binary\BitwiseXorBinary;
0019 use Twig\Node\Expression\Binary\ConcatBinary;
0020 use Twig\Node\Expression\Binary\DivBinary;
0021 use Twig\Node\Expression\Binary\EndsWithBinary;
0022 use Twig\Node\Expression\Binary\EqualBinary;
0023 use Twig\Node\Expression\Binary\FloorDivBinary;
0024 use Twig\Node\Expression\Binary\GreaterBinary;
0025 use Twig\Node\Expression\Binary\GreaterEqualBinary;
0026 use Twig\Node\Expression\Binary\InBinary;
0027 use Twig\Node\Expression\Binary\LessBinary;
0028 use Twig\Node\Expression\Binary\LessEqualBinary;
0029 use Twig\Node\Expression\Binary\MatchesBinary;
0030 use Twig\Node\Expression\Binary\ModBinary;
0031 use Twig\Node\Expression\Binary\MulBinary;
0032 use Twig\Node\Expression\Binary\NotEqualBinary;
0033 use Twig\Node\Expression\Binary\NotInBinary;
0034 use Twig\Node\Expression\Binary\OrBinary;
0035 use Twig\Node\Expression\Binary\PowerBinary;
0036 use Twig\Node\Expression\Binary\RangeBinary;
0037 use Twig\Node\Expression\Binary\SpaceshipBinary;
0038 use Twig\Node\Expression\Binary\StartsWithBinary;
0039 use Twig\Node\Expression\Binary\SubBinary;
0040 use Twig\Node\Expression\Filter\DefaultFilter;
0041 use Twig\Node\Expression\NullCoalesceExpression;
0042 use Twig\Node\Expression\Test\ConstantTest;
0043 use Twig\Node\Expression\Test\DefinedTest;
0044 use Twig\Node\Expression\Test\DivisiblebyTest;
0045 use Twig\Node\Expression\Test\EvenTest;
0046 use Twig\Node\Expression\Test\NullTest;
0047 use Twig\Node\Expression\Test\OddTest;
0048 use Twig\Node\Expression\Test\SameasTest;
0049 use Twig\Node\Expression\Unary\NegUnary;
0050 use Twig\Node\Expression\Unary\NotUnary;
0051 use Twig\Node\Expression\Unary\PosUnary;
0052 use Twig\NodeVisitor\MacroAutoImportNodeVisitor;
0053 use Twig\TokenParser\ApplyTokenParser;
0054 use Twig\TokenParser\BlockTokenParser;
0055 use Twig\TokenParser\DeprecatedTokenParser;
0056 use Twig\TokenParser\DoTokenParser;
0057 use Twig\TokenParser\EmbedTokenParser;
0058 use Twig\TokenParser\ExtendsTokenParser;
0059 use Twig\TokenParser\FilterTokenParser;
0060 use Twig\TokenParser\FlushTokenParser;
0061 use Twig\TokenParser\ForTokenParser;
0062 use Twig\TokenParser\FromTokenParser;
0063 use Twig\TokenParser\IfTokenParser;
0064 use Twig\TokenParser\ImportTokenParser;
0065 use Twig\TokenParser\IncludeTokenParser;
0066 use Twig\TokenParser\MacroTokenParser;
0067 use Twig\TokenParser\SetTokenParser;
0068 use Twig\TokenParser\SpacelessTokenParser;
0069 use Twig\TokenParser\UseTokenParser;
0070 use Twig\TokenParser\WithTokenParser;
0071 use Twig\TwigFilter;
0072 use Twig\TwigFunction;
0073 use Twig\TwigTest;
0074
0075 final class CoreExtension extends AbstractExtension
0076 {
0077 private $dateFormats = ['F j, Y H:i', '%d days'];
0078 private $numberFormat = [0, '.', ','];
0079 private $timezone = null;
0080 private $escapers = [];
0081
0082 /**
0083 * Defines a new escaper to be used via the escape filter.
0084 *
0085 * @param string $strategy The strategy name that should be used as a strategy in the escape call
0086 * @param callable $callable A valid PHP callable
0087 *
0088 * @deprecated since Twig 2.11, to be removed in 3.0; use the same method on EscaperExtension instead
0089 */
0090 public function setEscaper($strategy, callable $callable)
0091 {
0092 @trigger_error(sprintf('The "%s" method is deprecated since Twig 2.11; use "%s::setEscaper" instead.', __METHOD__, EscaperExtension::class), \E_USER_DEPRECATED);
0093
0094 $this->escapers[$strategy] = $callable;
0095 }
0096
0097 /**
0098 * Gets all defined escapers.
0099 *
0100 * @return callable[] An array of escapers
0101 *
0102 * @deprecated since Twig 2.11, to be removed in 3.0; use the same method on EscaperExtension instead
0103 */
0104 public function getEscapers(/* $triggerDeprecation = true */)
0105 {
0106 if (0 === \func_num_args() || \func_get_arg(0)) {
0107 @trigger_error(sprintf('The "%s" method is deprecated since Twig 2.11; use "%s::getEscapers" instead.', __METHOD__, EscaperExtension::class), \E_USER_DEPRECATED);
0108 }
0109
0110 return $this->escapers;
0111 }
0112
0113 /**
0114 * Sets the default format to be used by the date filter.
0115 *
0116 * @param string $format The default date format string
0117 * @param string $dateIntervalFormat The default date interval format string
0118 */
0119 public function setDateFormat($format = null, $dateIntervalFormat = null)
0120 {
0121 if (null !== $format) {
0122 $this->dateFormats[0] = $format;
0123 }
0124
0125 if (null !== $dateIntervalFormat) {
0126 $this->dateFormats[1] = $dateIntervalFormat;
0127 }
0128 }
0129
0130 /**
0131 * Gets the default format to be used by the date filter.
0132 *
0133 * @return array The default date format string and the default date interval format string
0134 */
0135 public function getDateFormat()
0136 {
0137 return $this->dateFormats;
0138 }
0139
0140 /**
0141 * Sets the default timezone to be used by the date filter.
0142 *
0143 * @param \DateTimeZone|string $timezone The default timezone string or a \DateTimeZone object
0144 */
0145 public function setTimezone($timezone)
0146 {
0147 $this->timezone = $timezone instanceof \DateTimeZone ? $timezone : new \DateTimeZone($timezone);
0148 }
0149
0150 /**
0151 * Gets the default timezone to be used by the date filter.
0152 *
0153 * @return \DateTimeZone The default timezone currently in use
0154 */
0155 public function getTimezone()
0156 {
0157 if (null === $this->timezone) {
0158 $this->timezone = new \DateTimeZone(date_default_timezone_get());
0159 }
0160
0161 return $this->timezone;
0162 }
0163
0164 /**
0165 * Sets the default format to be used by the number_format filter.
0166 *
0167 * @param int $decimal the number of decimal places to use
0168 * @param string $decimalPoint the character(s) to use for the decimal point
0169 * @param string $thousandSep the character(s) to use for the thousands separator
0170 */
0171 public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
0172 {
0173 $this->numberFormat = [$decimal, $decimalPoint, $thousandSep];
0174 }
0175
0176 /**
0177 * Get the default format used by the number_format filter.
0178 *
0179 * @return array The arguments for number_format()
0180 */
0181 public function getNumberFormat()
0182 {
0183 return $this->numberFormat;
0184 }
0185
0186 public function getTokenParsers()
0187 {
0188 return [
0189 new ApplyTokenParser(),
0190 new ForTokenParser(),
0191 new IfTokenParser(),
0192 new ExtendsTokenParser(),
0193 new IncludeTokenParser(),
0194 new BlockTokenParser(),
0195 new UseTokenParser(),
0196 new FilterTokenParser(),
0197 new MacroTokenParser(),
0198 new ImportTokenParser(),
0199 new FromTokenParser(),
0200 new SetTokenParser(),
0201 new SpacelessTokenParser(),
0202 new FlushTokenParser(),
0203 new DoTokenParser(),
0204 new EmbedTokenParser(),
0205 new WithTokenParser(),
0206 new DeprecatedTokenParser(),
0207 ];
0208 }
0209
0210 public function getFilters()
0211 {
0212 return [
0213 // formatting filters
0214 new TwigFilter('date', 'twig_date_format_filter', ['needs_environment' => true]),
0215 new TwigFilter('date_modify', 'twig_date_modify_filter', ['needs_environment' => true]),
0216 new TwigFilter('format', 'twig_sprintf'),
0217 new TwigFilter('replace', 'twig_replace_filter'),
0218 new TwigFilter('number_format', 'twig_number_format_filter', ['needs_environment' => true]),
0219 new TwigFilter('abs', 'abs'),
0220 new TwigFilter('round', 'twig_round'),
0221
0222 // encoding
0223 new TwigFilter('url_encode', 'twig_urlencode_filter'),
0224 new TwigFilter('json_encode', 'json_encode'),
0225 new TwigFilter('convert_encoding', 'twig_convert_encoding'),
0226
0227 // string filters
0228 new TwigFilter('title', 'twig_title_string_filter', ['needs_environment' => true]),
0229 new TwigFilter('capitalize', 'twig_capitalize_string_filter', ['needs_environment' => true]),
0230 new TwigFilter('upper', 'twig_upper_filter', ['needs_environment' => true]),
0231 new TwigFilter('lower', 'twig_lower_filter', ['needs_environment' => true]),
0232 new TwigFilter('striptags', 'twig_striptags'),
0233 new TwigFilter('trim', 'twig_trim_filter'),
0234 new TwigFilter('nl2br', 'twig_nl2br', ['pre_escape' => 'html', 'is_safe' => ['html']]),
0235 new TwigFilter('spaceless', 'twig_spaceless', ['is_safe' => ['html']]),
0236
0237 // array helpers
0238 new TwigFilter('join', 'twig_join_filter'),
0239 new TwigFilter('split', 'twig_split_filter', ['needs_environment' => true]),
0240 new TwigFilter('sort', 'twig_sort_filter', ['needs_environment' => true]),
0241 new TwigFilter('merge', 'twig_array_merge'),
0242 new TwigFilter('batch', 'twig_array_batch'),
0243 new TwigFilter('column', 'twig_array_column'),
0244 new TwigFilter('filter', 'twig_array_filter', ['needs_environment' => true]),
0245 new TwigFilter('map', 'twig_array_map', ['needs_environment' => true]),
0246 new TwigFilter('reduce', 'twig_array_reduce', ['needs_environment' => true]),
0247
0248 // string/array filters
0249 new TwigFilter('reverse', 'twig_reverse_filter', ['needs_environment' => true]),
0250 new TwigFilter('length', 'twig_length_filter', ['needs_environment' => true]),
0251 new TwigFilter('slice', 'twig_slice', ['needs_environment' => true]),
0252 new TwigFilter('first', 'twig_first', ['needs_environment' => true]),
0253 new TwigFilter('last', 'twig_last', ['needs_environment' => true]),
0254
0255 // iteration and runtime
0256 new TwigFilter('default', '_twig_default_filter', ['node_class' => DefaultFilter::class]),
0257 new TwigFilter('keys', 'twig_get_array_keys_filter'),
0258 ];
0259 }
0260
0261 public function getFunctions()
0262 {
0263 return [
0264 new TwigFunction('max', 'max'),
0265 new TwigFunction('min', 'min'),
0266 new TwigFunction('range', 'range'),
0267 new TwigFunction('constant', 'twig_constant'),
0268 new TwigFunction('cycle', 'twig_cycle'),
0269 new TwigFunction('random', 'twig_random', ['needs_environment' => true]),
0270 new TwigFunction('date', 'twig_date_converter', ['needs_environment' => true]),
0271 new TwigFunction('include', 'twig_include', ['needs_environment' => true, 'needs_context' => true, 'is_safe' => ['all']]),
0272 new TwigFunction('source', 'twig_source', ['needs_environment' => true, 'is_safe' => ['all']]),
0273 ];
0274 }
0275
0276 public function getTests()
0277 {
0278 return [
0279 new TwigTest('even', null, ['node_class' => EvenTest::class]),
0280 new TwigTest('odd', null, ['node_class' => OddTest::class]),
0281 new TwigTest('defined', null, ['node_class' => DefinedTest::class]),
0282 new TwigTest('same as', null, ['node_class' => SameasTest::class, 'one_mandatory_argument' => true]),
0283 new TwigTest('none', null, ['node_class' => NullTest::class]),
0284 new TwigTest('null', null, ['node_class' => NullTest::class]),
0285 new TwigTest('divisible by', null, ['node_class' => DivisiblebyTest::class, 'one_mandatory_argument' => true]),
0286 new TwigTest('constant', null, ['node_class' => ConstantTest::class]),
0287 new TwigTest('empty', 'twig_test_empty'),
0288 new TwigTest('iterable', 'twig_test_iterable'),
0289 ];
0290 }
0291
0292 public function getNodeVisitors()
0293 {
0294 return [new MacroAutoImportNodeVisitor()];
0295 }
0296
0297 public function getOperators()
0298 {
0299 return [
0300 [
0301 'not' => ['precedence' => 50, 'class' => NotUnary::class],
0302 '-' => ['precedence' => 500, 'class' => NegUnary::class],
0303 '+' => ['precedence' => 500, 'class' => PosUnary::class],
0304 ],
0305 [
0306 'or' => ['precedence' => 10, 'class' => OrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0307 'and' => ['precedence' => 15, 'class' => AndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0308 'b-or' => ['precedence' => 16, 'class' => BitwiseOrBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0309 'b-xor' => ['precedence' => 17, 'class' => BitwiseXorBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0310 'b-and' => ['precedence' => 18, 'class' => BitwiseAndBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0311 '==' => ['precedence' => 20, 'class' => EqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0312 '!=' => ['precedence' => 20, 'class' => NotEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0313 '<=>' => ['precedence' => 20, 'class' => SpaceshipBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0314 '<' => ['precedence' => 20, 'class' => LessBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0315 '>' => ['precedence' => 20, 'class' => GreaterBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0316 '>=' => ['precedence' => 20, 'class' => GreaterEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0317 '<=' => ['precedence' => 20, 'class' => LessEqualBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0318 'not in' => ['precedence' => 20, 'class' => NotInBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0319 'in' => ['precedence' => 20, 'class' => InBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0320 'matches' => ['precedence' => 20, 'class' => MatchesBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0321 'starts with' => ['precedence' => 20, 'class' => StartsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0322 'ends with' => ['precedence' => 20, 'class' => EndsWithBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0323 '..' => ['precedence' => 25, 'class' => RangeBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0324 '+' => ['precedence' => 30, 'class' => AddBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0325 '-' => ['precedence' => 30, 'class' => SubBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0326 '~' => ['precedence' => 40, 'class' => ConcatBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0327 '*' => ['precedence' => 60, 'class' => MulBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0328 '/' => ['precedence' => 60, 'class' => DivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0329 '//' => ['precedence' => 60, 'class' => FloorDivBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0330 '%' => ['precedence' => 60, 'class' => ModBinary::class, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0331 'is' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0332 'is not' => ['precedence' => 100, 'associativity' => ExpressionParser::OPERATOR_LEFT],
0333 '**' => ['precedence' => 200, 'class' => PowerBinary::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
0334 '??' => ['precedence' => 300, 'class' => NullCoalesceExpression::class, 'associativity' => ExpressionParser::OPERATOR_RIGHT],
0335 ],
0336 ];
0337 }
0338 }
0339
0340 class_alias('Twig\Extension\CoreExtension', 'Twig_Extension_Core');
0341 }
0342
0343 namespace {
0344 use Twig\Environment;
0345 use Twig\Error\LoaderError;
0346 use Twig\Error\RuntimeError;
0347 use Twig\Extension\CoreExtension;
0348 use Twig\Extension\SandboxExtension;
0349 use Twig\Markup;
0350 use Twig\Source;
0351 use Twig\Template;
0352 use Twig\TemplateWrapper;
0353
0354 /**
0355 * Cycles over a value.
0356 *
0357 * @param \ArrayAccess|array $values
0358 * @param int $position The cycle position
0359 *
0360 * @return string The next value in the cycle
0361 */
0362 function twig_cycle($values, $position)
0363 {
0364 if (!\is_array($values) && !$values instanceof \ArrayAccess) {
0365 return $values;
0366 }
0367
0368 return $values[$position % \count($values)];
0369 }
0370
0371 /**
0372 * Returns a random value depending on the supplied parameter type:
0373 * - a random item from a \Traversable or array
0374 * - a random character from a string
0375 * - a random integer between 0 and the integer parameter.
0376 *
0377 * @param \Traversable|array|int|float|string $values The values to pick a random item from
0378 * @param int|null $max Maximum value used when $values is an int
0379 *
0380 * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is)
0381 *
0382 * @return mixed A random value from the given sequence
0383 */
0384 function twig_random(Environment $env, $values = null, $max = null)
0385 {
0386 if (null === $values) {
0387 return null === $max ? mt_rand() : mt_rand(0, (int) $max);
0388 }
0389
0390 if (\is_int($values) || \is_float($values)) {
0391 if (null === $max) {
0392 if ($values < 0) {
0393 $max = 0;
0394 $min = $values;
0395 } else {
0396 $max = $values;
0397 $min = 0;
0398 }
0399 } else {
0400 $min = $values;
0401 $max = $max;
0402 }
0403
0404 return mt_rand((int) $min, (int) $max);
0405 }
0406
0407 if (\is_string($values)) {
0408 if ('' === $values) {
0409 return '';
0410 }
0411
0412 $charset = $env->getCharset();
0413
0414 if ('UTF-8' !== $charset) {
0415 $values = twig_convert_encoding($values, 'UTF-8', $charset);
0416 }
0417
0418 // unicode version of str_split()
0419 // split at all positions, but not after the start and not before the end
0420 $values = preg_split('/(?<!^)(?!$)/u', $values);
0421
0422 if ('UTF-8' !== $charset) {
0423 foreach ($values as $i => $value) {
0424 $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
0425 }
0426 }
0427 }
0428
0429 if (!twig_test_iterable($values)) {
0430 return $values;
0431 }
0432
0433 $values = twig_to_array($values);
0434
0435 if (0 === \count($values)) {
0436 throw new RuntimeError('The random function cannot pick from an empty array.');
0437 }
0438
0439 return $values[array_rand($values, 1)];
0440 }
0441
0442 /**
0443 * Converts a date to the given format.
0444 *
0445 * {{ post.published_at|date("m/d/Y") }}
0446 *
0447 * @param \DateTimeInterface|\DateInterval|string $date A date
0448 * @param string|null $format The target format, null to use the default
0449 * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
0450 *
0451 * @return string The formatted date
0452 */
0453 function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null)
0454 {
0455 if (null === $format) {
0456 $formats = $env->getExtension(CoreExtension::class)->getDateFormat();
0457 $format = $date instanceof \DateInterval ? $formats[1] : $formats[0];
0458 }
0459
0460 if ($date instanceof \DateInterval) {
0461 return $date->format($format);
0462 }
0463
0464 return twig_date_converter($env, $date, $timezone)->format($format);
0465 }
0466
0467 /**
0468 * Returns a new date object modified.
0469 *
0470 * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }}
0471 *
0472 * @param \DateTimeInterface|string $date A date
0473 * @param string $modifier A modifier string
0474 *
0475 * @return \DateTimeInterface
0476 */
0477 function twig_date_modify_filter(Environment $env, $date, $modifier)
0478 {
0479 $date = twig_date_converter($env, $date, false);
0480
0481 return $date->modify($modifier);
0482 }
0483
0484 /**
0485 * Returns a formatted string.
0486 *
0487 * @param string|null $format
0488 * @param ...$values
0489 *
0490 * @return string
0491 */
0492 function twig_sprintf($format, ...$values)
0493 {
0494 return sprintf($format ?? '', ...$values);
0495 }
0496
0497 /**
0498 * Converts an input to a \DateTime instance.
0499 *
0500 * {% if date(user.created_at) < date('+2days') %}
0501 * {# do something #}
0502 * {% endif %}
0503 *
0504 * @param \DateTimeInterface|string|null $date A date or null to use the current time
0505 * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged
0506 *
0507 * @return \DateTimeInterface
0508 */
0509 function twig_date_converter(Environment $env, $date = null, $timezone = null)
0510 {
0511 // determine the timezone
0512 if (false !== $timezone) {
0513 if (null === $timezone) {
0514 $timezone = $env->getExtension(CoreExtension::class)->getTimezone();
0515 } elseif (!$timezone instanceof \DateTimeZone) {
0516 $timezone = new \DateTimeZone($timezone);
0517 }
0518 }
0519
0520 // immutable dates
0521 if ($date instanceof \DateTimeImmutable) {
0522 return false !== $timezone ? $date->setTimezone($timezone) : $date;
0523 }
0524
0525 if ($date instanceof \DateTimeInterface) {
0526 $date = clone $date;
0527 if (false !== $timezone) {
0528 $date->setTimezone($timezone);
0529 }
0530
0531 return $date;
0532 }
0533
0534 if (null === $date || 'now' === $date) {
0535 if (null === $date) {
0536 $date = 'now';
0537 }
0538
0539 return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone());
0540 }
0541
0542 $asString = (string) $date;
0543 if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
0544 $date = new \DateTime('@'.$date);
0545 } else {
0546 $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone());
0547 }
0548
0549 if (false !== $timezone) {
0550 $date->setTimezone($timezone);
0551 }
0552
0553 return $date;
0554 }
0555
0556 /**
0557 * Replaces strings within a string.
0558 *
0559 * @param string|null $str String to replace in
0560 * @param array|\Traversable $from Replace values
0561 *
0562 * @return string
0563 */
0564 function twig_replace_filter($str, $from)
0565 {
0566 if (!twig_test_iterable($from)) {
0567 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)));
0568 }
0569
0570 return strtr($str ?? '', twig_to_array($from));
0571 }
0572
0573 /**
0574 * Rounds a number.
0575 *
0576 * @param int|float|string|null $value The value to round
0577 * @param int|float $precision The rounding precision
0578 * @param string $method The method to use for rounding
0579 *
0580 * @return int|float The rounded number
0581 */
0582 function twig_round($value, $precision = 0, $method = 'common')
0583 {
0584 $value = (float) $value;
0585
0586 if ('common' === $method) {
0587 return round($value, $precision);
0588 }
0589
0590 if ('ceil' !== $method && 'floor' !== $method) {
0591 throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.');
0592 }
0593
0594 return $method($value * 10 ** $precision) / 10 ** $precision;
0595 }
0596
0597 /**
0598 * Number format filter.
0599 *
0600 * All of the formatting options can be left null, in that case the defaults will
0601 * be used. Supplying any of the parameters will override the defaults set in the
0602 * environment object.
0603 *
0604 * @param mixed $number A float/int/string of the number to format
0605 * @param int $decimal the number of decimal points to display
0606 * @param string $decimalPoint the character(s) to use for the decimal point
0607 * @param string $thousandSep the character(s) to use for the thousands separator
0608 *
0609 * @return string The formatted number
0610 */
0611 function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
0612 {
0613 $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat();
0614 if (null === $decimal) {
0615 $decimal = $defaults[0];
0616 }
0617
0618 if (null === $decimalPoint) {
0619 $decimalPoint = $defaults[1];
0620 }
0621
0622 if (null === $thousandSep) {
0623 $thousandSep = $defaults[2];
0624 }
0625
0626 return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
0627 }
0628
0629 /**
0630 * URL encodes (RFC 3986) a string as a path segment or an array as a query string.
0631 *
0632 * @param string|array|null $url A URL or an array of query parameters
0633 *
0634 * @return string The URL encoded value
0635 */
0636 function twig_urlencode_filter($url)
0637 {
0638 if (\is_array($url)) {
0639 return http_build_query($url, '', '&', \PHP_QUERY_RFC3986);
0640 }
0641
0642 return rawurlencode($url ?? '');
0643 }
0644
0645 /**
0646 * Merges an array with another one.
0647 *
0648 * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
0649 *
0650 * {% set items = items|merge({ 'peugeot': 'car' }) %}
0651 *
0652 * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
0653 *
0654 * @param array|\Traversable $arr1 An array
0655 * @param array|\Traversable $arr2 An array
0656 *
0657 * @return array The merged array
0658 */
0659 function twig_array_merge($arr1, $arr2)
0660 {
0661 if (!twig_test_iterable($arr1)) {
0662 throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1)));
0663 }
0664
0665 if (!twig_test_iterable($arr2)) {
0666 throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2)));
0667 }
0668
0669 return array_merge(twig_to_array($arr1), twig_to_array($arr2));
0670 }
0671
0672 /**
0673 * Slices a variable.
0674 *
0675 * @param mixed $item A variable
0676 * @param int $start Start of the slice
0677 * @param int $length Size of the slice
0678 * @param bool $preserveKeys Whether to preserve key or not (when the input is an array)
0679 *
0680 * @return mixed The sliced variable
0681 */
0682 function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false)
0683 {
0684 if ($item instanceof \Traversable) {
0685 while ($item instanceof \IteratorAggregate) {
0686 $item = $item->getIterator();
0687 }
0688
0689 if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) {
0690 try {
0691 return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys);
0692 } catch (\OutOfBoundsException $e) {
0693 return [];
0694 }
0695 }
0696
0697 $item = iterator_to_array($item, $preserveKeys);
0698 }
0699
0700 if (\is_array($item)) {
0701 return \array_slice($item, $start, $length, $preserveKeys);
0702 }
0703
0704 return (string) mb_substr((string) $item, $start, $length, $env->getCharset());
0705 }
0706
0707 /**
0708 * Returns the first element of the item.
0709 *
0710 * @param mixed $item A variable
0711 *
0712 * @return mixed The first element of the item
0713 */
0714 function twig_first(Environment $env, $item)
0715 {
0716 $elements = twig_slice($env, $item, 0, 1, false);
0717
0718 return \is_string($elements) ? $elements : current($elements);
0719 }
0720
0721 /**
0722 * Returns the last element of the item.
0723 *
0724 * @param mixed $item A variable
0725 *
0726 * @return mixed The last element of the item
0727 */
0728 function twig_last(Environment $env, $item)
0729 {
0730 $elements = twig_slice($env, $item, -1, 1, false);
0731
0732 return \is_string($elements) ? $elements : current($elements);
0733 }
0734
0735 /**
0736 * Joins the values to a string.
0737 *
0738 * The separators between elements are empty strings per default, you can define them with the optional parameters.
0739 *
0740 * {{ [1, 2, 3]|join(', ', ' and ') }}
0741 * {# returns 1, 2 and 3 #}
0742 *
0743 * {{ [1, 2, 3]|join('|') }}
0744 * {# returns 1|2|3 #}
0745 *
0746 * {{ [1, 2, 3]|join }}
0747 * {# returns 123 #}
0748 *
0749 * @param array $value An array
0750 * @param string $glue The separator
0751 * @param string|null $and The separator for the last pair
0752 *
0753 * @return string The concatenated string
0754 */
0755 function twig_join_filter($value, $glue = '', $and = null)
0756 {
0757 if (!twig_test_iterable($value)) {
0758 $value = (array) $value;
0759 }
0760
0761 $value = twig_to_array($value, false);
0762
0763 if (0 === \count($value)) {
0764 return '';
0765 }
0766
0767 if (null === $and || $and === $glue) {
0768 return implode($glue, $value);
0769 }
0770
0771 if (1 === \count($value)) {
0772 return $value[0];
0773 }
0774
0775 return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1];
0776 }
0777
0778 /**
0779 * Splits the string into an array.
0780 *
0781 * {{ "one,two,three"|split(',') }}
0782 * {# returns [one, two, three] #}
0783 *
0784 * {{ "one,two,three,four,five"|split(',', 3) }}
0785 * {# returns [one, two, "three,four,five"] #}
0786 *
0787 * {{ "123"|split('') }}
0788 * {# returns [1, 2, 3] #}
0789 *
0790 * {{ "aabbcc"|split('', 2) }}
0791 * {# returns [aa, bb, cc] #}
0792 *
0793 * @param string|null $value A string
0794 * @param string $delimiter The delimiter
0795 * @param int $limit The limit
0796 *
0797 * @return array The split string as an array
0798 */
0799 function twig_split_filter(Environment $env, $value, $delimiter, $limit = null)
0800 {
0801 $value = $value ?? '';
0802
0803 if (\strlen($delimiter) > 0) {
0804 return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
0805 }
0806
0807 if ($limit <= 1) {
0808 return preg_split('/(?<!^)(?!$)/u', $value);
0809 }
0810
0811 $length = mb_strlen($value, $env->getCharset());
0812 if ($length < $limit) {
0813 return [$value];
0814 }
0815
0816 $r = [];
0817 for ($i = 0; $i < $length; $i += $limit) {
0818 $r[] = mb_substr($value, $i, $limit, $env->getCharset());
0819 }
0820
0821 return $r;
0822 }
0823
0824 // The '_default' filter is used internally to avoid using the ternary operator
0825 // which costs a lot for big contexts (before PHP 5.4). So, on average,
0826 // a function call is cheaper.
0827 /**
0828 * @internal
0829 */
0830 function _twig_default_filter($value, $default = '')
0831 {
0832 if (twig_test_empty($value)) {
0833 return $default;
0834 }
0835
0836 return $value;
0837 }
0838
0839 /**
0840 * Returns the keys for the given array.
0841 *
0842 * It is useful when you want to iterate over the keys of an array:
0843 *
0844 * {% for key in array|keys %}
0845 * {# ... #}
0846 * {% endfor %}
0847 *
0848 * @param array $array An array
0849 *
0850 * @return array The keys
0851 */
0852 function twig_get_array_keys_filter($array)
0853 {
0854 if ($array instanceof \Traversable) {
0855 while ($array instanceof \IteratorAggregate) {
0856 $array = $array->getIterator();
0857 }
0858
0859 if ($array instanceof \Iterator) {
0860 $keys = [];
0861 $array->rewind();
0862 while ($array->valid()) {
0863 $keys[] = $array->key();
0864 $array->next();
0865 }
0866
0867 return $keys;
0868 }
0869
0870 $keys = [];
0871 foreach ($array as $key => $item) {
0872 $keys[] = $key;
0873 }
0874
0875 return $keys;
0876 }
0877
0878 if (!\is_array($array)) {
0879 return [];
0880 }
0881
0882 return array_keys($array);
0883 }
0884
0885 /**
0886 * Reverses a variable.
0887 *
0888 * @param array|\Traversable|string|null $item An array, a \Traversable instance, or a string
0889 * @param bool $preserveKeys Whether to preserve key or not
0890 *
0891 * @return mixed The reversed input
0892 */
0893 function twig_reverse_filter(Environment $env, $item, $preserveKeys = false)
0894 {
0895 if ($item instanceof \Traversable) {
0896 return array_reverse(iterator_to_array($item), $preserveKeys);
0897 }
0898
0899 if (\is_array($item)) {
0900 return array_reverse($item, $preserveKeys);
0901 }
0902
0903 $string = (string) $item;
0904
0905 $charset = $env->getCharset();
0906
0907 if ('UTF-8' !== $charset) {
0908 $string = twig_convert_encoding($string, 'UTF-8', $charset);
0909 }
0910
0911 preg_match_all('/./us', $string, $matches);
0912
0913 $string = implode('', array_reverse($matches[0]));
0914
0915 if ('UTF-8' !== $charset) {
0916 $string = twig_convert_encoding($string, $charset, 'UTF-8');
0917 }
0918
0919 return $string;
0920 }
0921
0922 /**
0923 * Sorts an array.
0924 *
0925 * @param array|\Traversable $array
0926 *
0927 * @return array
0928 */
0929 function twig_sort_filter(Environment $env, $array, $arrow = null)
0930 {
0931 if ($array instanceof \Traversable) {
0932 $array = iterator_to_array($array);
0933 } elseif (!\is_array($array)) {
0934 throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array)));
0935 }
0936
0937 if (null !== $arrow) {
0938 twig_check_arrow_in_sandbox($env, $arrow, 'sort', 'filter');
0939
0940 uasort($array, $arrow);
0941 } else {
0942 asort($array);
0943 }
0944
0945 return $array;
0946 }
0947
0948 /**
0949 * @internal
0950 */
0951 function twig_in_filter($value, $compare)
0952 {
0953 if ($value instanceof Markup) {
0954 $value = (string) $value;
0955 }
0956 if ($compare instanceof Markup) {
0957 $compare = (string) $compare;
0958 }
0959
0960 if (\is_array($compare)) {
0961 return \in_array($value, $compare, \is_object($value) || \is_resource($value));
0962 } elseif (\is_string($compare) && (\is_string($value) || \is_int($value) || \is_float($value))) {
0963 return '' === $value || false !== strpos($compare, (string) $value);
0964 } elseif ($compare instanceof \Traversable) {
0965 if (\is_object($value) || \is_resource($value)) {
0966 foreach ($compare as $item) {
0967 if ($item === $value) {
0968 return true;
0969 }
0970 }
0971 } else {
0972 foreach ($compare as $item) {
0973 if ($item == $value) {
0974 return true;
0975 }
0976 }
0977 }
0978
0979 return false;
0980 }
0981
0982 return false;
0983 }
0984
0985 /**
0986 * Returns a trimmed string.
0987 *
0988 * @param string|null $string
0989 * @param string|null $characterMask
0990 * @param string $side
0991 *
0992 * @return string
0993 *
0994 * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
0995 */
0996 function twig_trim_filter($string, $characterMask = null, $side = 'both')
0997 {
0998 if (null === $characterMask) {
0999 $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
1273 try {
1274 $loaded = null;
1275 try {
1276 $loaded = $env->resolveTemplate($template);
1277 } catch (LoaderError $e) {
1278 if (!$ignoreMissing) {
1279 throw $e;
1280 }
1281 }
1282
1283 if ($isSandboxed && $loaded) {
1284 $loaded->unwrap()->checkSecurity();
1285 }
1286
1287 return $loaded ? $loaded->render($variables) : '';
1288 } finally {
1289 if ($isSandboxed && !$alreadySandboxed) {
1290 $sandbox->disableSandbox();
1291 }
1292 }
1293 }
1294
1295 /**
1296 * Returns a template content without rendering it.
1297 *
1298 * @param string $name The template name
1299 * @param bool $ignoreMissing Whether to ignore missing templates or not
1300 *
1301 * @return string The template source
1302 */
1303 function twig_source(Environment $env, $name, $ignoreMissing = false)
1304 {
1305 $loader = $env->getLoader();
1306 try {
1307 return $loader->getSourceContext($name)->getCode();
1308 } catch (LoaderError $e) {
1309 if (!$ignoreMissing) {
1310 throw $e;
1311 }
1312 }
1313 }
1314
1315 /**
1316 * Provides the ability to get constants from instances as well as class/global constants.
1317 *
1318 * @param string $constant The name of the constant
1319 * @param object|null $object The object to get the constant from
1320 *
1321 * @return string
1322 */
1323 function twig_constant($constant, $object = null)
1324 {
1325 if (null !== $object) {
1326 $constant = \get_class($object).'::'.$constant;
1327 }
1328
1329 if (!\defined($constant)) {
1330 throw new RuntimeError(sprintf('Constant "%s" is undefined.', $constant));
1331 }
1332
1333 return \constant($constant);
1334 }
1335
1336 /**
1337 * Checks if a constant exists.
1338 *
1339 * @param string $constant The name of the constant
1340 * @param object|null $object The object to get the constant from
1341 *
1342 * @return bool
1343 */
1344 function twig_constant_is_defined($constant, $object = null)
1345 {
1346 if (null !== $object) {
1347 $constant = \get_class($object).'::'.$constant;
1348 }
1349
1350 return \defined($constant);
1351 }
1352
1353 /**
1354 * Batches item.
1355 *
1356 * @param array $items An array of items
1357 * @param int $size The size of the batch
1358 * @param mixed $fill A value used to fill missing items
1359 *
1360 * @return array
1361 */
1362 function twig_array_batch($items, $size, $fill = null, $preserveKeys = true)
1363 {
1364 if (!twig_test_iterable($items)) {
1365 throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items)));
1366 }
1367
1368 $size = ceil($size);
1369
1370 $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys);
1371
1372 if (null !== $fill && $result) {
1373 $last = \count($result) - 1;
1374 if ($fillCount = $size - \count($result[$last])) {
1375 for ($i = 0; $i < $fillCount; ++$i) {
1376 $result[$last][] = $fill;
1377 }
1378 }
1379 }
1380
1381 return $result;
1382 }
1383
1384 /**
1385 * Returns the attribute value for a given array/object.
1386 *
1387 * @param mixed $object The object or array from where to get the item
1388 * @param mixed $item The item to get from the array or object
1389 * @param array $arguments An array of arguments to pass if the item is an object method
1390 * @param string $type The type of attribute (@see \Twig\Template constants)
1391 * @param bool $isDefinedTest Whether this is only a defined check
1392 * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not
1393 * @param int $lineno The template line where the attribute was called
1394 *
1395 * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true
1396 *
1397 * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false
1398 *
1399 * @internal
1400 */
1401 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)
1402 {
1403 // array
1404 if (/* Template::METHOD_CALL */ 'method' !== $type) {
1405 $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item;
1406
1407 if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object)))
1408 || ($object instanceof ArrayAccess && isset($object[$arrayItem]))
1409 ) {
1410 if ($isDefinedTest) {
1411 return true;
1412 }
1413
1414 return $object[$arrayItem];
1415 }
1416
1417 if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) {
1418 if ($isDefinedTest) {
1419 return false;
1420 }
1421
1422 if ($ignoreStrictCheck || !$env->isStrictVariables()) {
1423 return;
1424 }
1425
1426 if ($object instanceof ArrayAccess) {
1427 $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object));
1428 } elseif (\is_object($object)) {
1429 $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object));
1430 } elseif (\is_array($object)) {
1431 if (empty($object)) {
1432 $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem);
1433 } else {
1434 $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object)));
1435 }
1436 } elseif (/* Template::ARRAY_CALL */ 'array' === $type) {
1437 if (null === $object) {
1438 $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item);
1439 } else {
1440 $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
1441 }
1442 } elseif (null === $object) {
1443 $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item);
1444 } else {
1445 $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
1446 }
1447
1448 throw new RuntimeError($message, $lineno, $source);
1449 }
1450 }
1451
1452 if (!\is_object($object)) {
1453 if ($isDefinedTest) {
1454 return false;
1455 }
1456
1457 if ($ignoreStrictCheck || !$env->isStrictVariables()) {
1458 return;
1459 }
1460
1461 if (null === $object) {
1462 $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item);
1463 } elseif (\is_array($object)) {
1464 $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item);
1465 } else {
1466 $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object);
1467 }
1468
1469 throw new RuntimeError($message, $lineno, $source);
1470 }
1471
1472 if ($object instanceof Template) {
1473 throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source);
1474 }
1475
1476 // object property
1477 if (/* Template::METHOD_CALL */ 'method' !== $type) {
1478 if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) {
1479 if ($isDefinedTest) {
1480 return true;
1481 }
1482
1483 if ($sandboxed) {
1484 $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source);
1485 }
1486
1487 return $object->$item;
1488 }
1489 }
1490
1491 static $cache = [];
1492
1493 $class = \get_class($object);
1494
1495 // object method
1496 // precedence: getXxx() > isXxx() > hasXxx()
1497 if (!isset($cache[$class])) {
1498 $methods = get_class_methods($object);
1499 sort($methods);
1500 $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods);
1501 $classCache = [];
1502 foreach ($methods as $i => $method) {
1503 $classCache[$method] = $method;
1504 $classCache[$lcName = $lcMethods[$i]] = $method;
1505
1506 if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) {
1507 $name = substr($method, 3);
1508 $lcName = substr($lcName, 3);
1509 } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) {
1510 $name = substr($method, 2);
1511 $lcName = substr($lcName, 2);
1512 } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) {
1513 $name = substr($method, 3);
1514 $lcName = substr($lcName, 3);
1515 if (\in_array('is'.$lcName, $lcMethods)) {
1516 continue;
1517 }
1518 } else {
1519 continue;
1520 }
1521
1522 // skip get() and is() methods (in which case, $name is empty)
1523 if ($name) {
1524 if (!isset($classCache[$name])) {
1525 $classCache[$name] = $method;
1526 }
1527
1528 if (!isset($classCache[$lcName])) {
1529 $classCache[$lcName] = $method;
1530 }
1531 }
1532 }
1533 $cache[$class] = $classCache;
1534 }
1535
1536 $call = false;
1537 if (isset($cache[$class][$item])) {
1538 $method = $cache[$class][$item];
1539 } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) {
1540 $method = $cache[$class][$lcItem];
1541 } elseif (isset($cache[$class]['__call'])) {
1542 $method = $item;
1543 $call = true;
1544 } else {
1545 if ($isDefinedTest) {
1546 return false;
1547 }
1548
1549 if ($ignoreStrictCheck || !$env->isStrictVariables()) {
1550 return;
1551 }
1552
1553 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);
1554 }
1555
1556 if ($isDefinedTest) {
1557 return true;
1558 }
1559
1560 if ($sandboxed) {
1561 $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source);
1562 }
1563
1564 // Some objects throw exceptions when they have __call, and the method we try
1565 // to call is not supported. If ignoreStrictCheck is true, we should return null.
1566 try {
1567 $ret = $object->$method(...$arguments);
1568 } catch (\BadMethodCallException $e) {
1569 if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) {
1570 return;
1571 }
1572 throw $e;
1573 }
1574
1575 return $ret;
1576 }
1577
1578 /**
1579 * Returns the values from a single column in the input array.
1580 *
1581 * <pre>
1582 * {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %}
1583 *
1584 * {% set fruits = items|column('fruit') %}
1585 *
1586 * {# fruits now contains ['apple', 'orange'] #}
1587 * </pre>
1588 *
1589 * @param array|Traversable $array An array
1590 * @param mixed $name The column name
1591 * @param mixed $index The column to use as the index/keys for the returned array
1592 *
1593 * @return array The array of values
1594 */
1595 function twig_array_column($array, $name, $index = null): array
1596 {
1597 if ($array instanceof Traversable) {
1598 $array = iterator_to_array($array);
1599 } elseif (!\is_array($array)) {
1600 throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
1601 }
1602
1603 return array_column($array, $name, $index);
1604 }
1605
1606 function twig_array_filter(Environment $env, $array, $arrow)
1607 {
1608 if (!twig_test_iterable($array)) {
1609 throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array)));
1610 }
1611
1612 twig_check_arrow_in_sandbox($env, $arrow, 'filter', 'filter');
1613
1614 if (\is_array($array)) {
1615 return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH);
1616 }
1617
1618 // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator
1619 return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow);
1620 }
1621
1622 function twig_array_map(Environment $env, $array, $arrow)
1623 {
1624 twig_check_arrow_in_sandbox($env, $arrow, 'map', 'filter');
1625
1626 $r = [];
1627 foreach ($array as $k => $v) {
1628 $r[$k] = $arrow($v, $k);
1629 }
1630
1631 return $r;
1632 }
1633
1634 function twig_array_reduce(Environment $env, $array, $arrow, $initial = null)
1635 {
1636 twig_check_arrow_in_sandbox($env, $arrow, 'reduce', 'filter');
1637
1638 if (!\is_array($array)) {
1639 if (!$array instanceof \Traversable) {
1640 throw new RuntimeError(sprintf('The "reduce" filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array)));
1641 }
1642
1643 $array = iterator_to_array($array);
1644 }
1645
1646 return array_reduce($array, $arrow, $initial);
1647 }
1648
1649 function twig_check_arrow_in_sandbox(Environment $env, $arrow, $thing, $type)
1650 {
1651 if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) {
1652 throw new RuntimeError(sprintf('The callable passed to the "%s" %s must be a Closure in sandbox mode.', $thing, $type));
1653 }
1654 }
1655 }
1656