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