Verzeichnisstruktur phpBB-3.3.15
- Veröffentlicht
- 28.08.2024
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 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 }
1659