[ Index ] |
PHP Cross Reference of phpBB-3.3.2-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', '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', 'strip_tags'), 233 new TwigFilter('trim', 'twig_trim_filter'), 234 new TwigFilter('nl2br', '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'), 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]), 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]), 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 353 /** 354 * Cycles over a value. 355 * 356 * @param \ArrayAccess|array $values 357 * @param int $position The cycle position 358 * 359 * @return string The next value in the cycle 360 */ 361 function twig_cycle($values, $position) 362 { 363 if (!\is_array($values) && !$values instanceof \ArrayAccess) { 364 return $values; 365 } 366 367 return $values[$position % \count($values)]; 368 } 369 370 /** 371 * Returns a random value depending on the supplied parameter type: 372 * - a random item from a \Traversable or array 373 * - a random character from a string 374 * - a random integer between 0 and the integer parameter. 375 * 376 * @param \Traversable|array|int|float|string $values The values to pick a random item from 377 * @param int|null $max Maximum value used when $values is an int 378 * 379 * @throws RuntimeError when $values is an empty array (does not apply to an empty string which is returned as is) 380 * 381 * @return mixed A random value from the given sequence 382 */ 383 function twig_random(Environment $env, $values = null, $max = null) 384 { 385 if (null === $values) { 386 return null === $max ? mt_rand() : mt_rand(0, $max); 387 } 388 389 if (\is_int($values) || \is_float($values)) { 390 if (null === $max) { 391 if ($values < 0) { 392 $max = 0; 393 $min = $values; 394 } else { 395 $max = $values; 396 $min = 0; 397 } 398 } else { 399 $min = $values; 400 $max = $max; 401 } 402 403 return mt_rand($min, $max); 404 } 405 406 if (\is_string($values)) { 407 if ('' === $values) { 408 return ''; 409 } 410 411 $charset = $env->getCharset(); 412 413 if ('UTF-8' !== $charset) { 414 $values = twig_convert_encoding($values, 'UTF-8', $charset); 415 } 416 417 // unicode version of str_split() 418 // split at all positions, but not after the start and not before the end 419 $values = preg_split('/(?<!^)(?!$)/u', $values); 420 421 if ('UTF-8' !== $charset) { 422 foreach ($values as $i => $value) { 423 $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); 424 } 425 } 426 } 427 428 if (!twig_test_iterable($values)) { 429 return $values; 430 } 431 432 $values = twig_to_array($values); 433 434 if (0 === \count($values)) { 435 throw new RuntimeError('The random function cannot pick from an empty array.'); 436 } 437 438 return $values[array_rand($values, 1)]; 439 } 440 441 /** 442 * Converts a date to the given format. 443 * 444 * {{ post.published_at|date("m/d/Y") }} 445 * 446 * @param \DateTimeInterface|\DateInterval|string $date A date 447 * @param string|null $format The target format, null to use the default 448 * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged 449 * 450 * @return string The formatted date 451 */ 452 function twig_date_format_filter(Environment $env, $date, $format = null, $timezone = null) 453 { 454 if (null === $format) { 455 $formats = $env->getExtension(CoreExtension::class)->getDateFormat(); 456 $format = $date instanceof \DateInterval ? $formats[1] : $formats[0]; 457 } 458 459 if ($date instanceof \DateInterval) { 460 return $date->format($format); 461 } 462 463 return twig_date_converter($env, $date, $timezone)->format($format); 464 } 465 466 /** 467 * Returns a new date object modified. 468 * 469 * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} 470 * 471 * @param \DateTimeInterface|string $date A date 472 * @param string $modifier A modifier string 473 * 474 * @return \DateTimeInterface 475 */ 476 function twig_date_modify_filter(Environment $env, $date, $modifier) 477 { 478 $date = twig_date_converter($env, $date, false); 479 480 return $date->modify($modifier); 481 } 482 483 /** 484 * Converts an input to a \DateTime instance. 485 * 486 * {% if date(user.created_at) < date('+2days') %} 487 * {# do something #} 488 * {% endif %} 489 * 490 * @param \DateTimeInterface|string|null $date A date or null to use the current time 491 * @param \DateTimeZone|string|false|null $timezone The target timezone, null to use the default, false to leave unchanged 492 * 493 * @return \DateTimeInterface 494 */ 495 function twig_date_converter(Environment $env, $date = null, $timezone = null) 496 { 497 // determine the timezone 498 if (false !== $timezone) { 499 if (null === $timezone) { 500 $timezone = $env->getExtension(CoreExtension::class)->getTimezone(); 501 } elseif (!$timezone instanceof \DateTimeZone) { 502 $timezone = new \DateTimeZone($timezone); 503 } 504 } 505 506 // immutable dates 507 if ($date instanceof \DateTimeImmutable) { 508 return false !== $timezone ? $date->setTimezone($timezone) : $date; 509 } 510 511 if ($date instanceof \DateTimeInterface) { 512 $date = clone $date; 513 if (false !== $timezone) { 514 $date->setTimezone($timezone); 515 } 516 517 return $date; 518 } 519 520 if (null === $date || 'now' === $date) { 521 return new \DateTime($date, false !== $timezone ? $timezone : $env->getExtension(CoreExtension::class)->getTimezone()); 522 } 523 524 $asString = (string) $date; 525 if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { 526 $date = new \DateTime('@'.$date); 527 } else { 528 $date = new \DateTime($date, $env->getExtension(CoreExtension::class)->getTimezone()); 529 } 530 531 if (false !== $timezone) { 532 $date->setTimezone($timezone); 533 } 534 535 return $date; 536 } 537 538 /** 539 * Replaces strings within a string. 540 * 541 * @param string $str String to replace in 542 * @param array|\Traversable $from Replace values 543 * 544 * @return string 545 */ 546 function twig_replace_filter($str, $from) 547 { 548 if (!twig_test_iterable($from)) { 549 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))); 550 } 551 552 return strtr($str, twig_to_array($from)); 553 } 554 555 /** 556 * Rounds a number. 557 * 558 * @param int|float $value The value to round 559 * @param int|float $precision The rounding precision 560 * @param string $method The method to use for rounding 561 * 562 * @return int|float The rounded number 563 */ 564 function twig_round($value, $precision = 0, $method = 'common') 565 { 566 if ('common' === $method) { 567 return round($value, $precision); 568 } 569 570 if ('ceil' !== $method && 'floor' !== $method) { 571 throw new RuntimeError('The round filter only supports the "common", "ceil", and "floor" methods.'); 572 } 573 574 return $method($value * pow(10, $precision)) / pow(10, $precision); 575 } 576 577 /** 578 * Number format filter. 579 * 580 * All of the formatting options can be left null, in that case the defaults will 581 * be used. Supplying any of the parameters will override the defaults set in the 582 * environment object. 583 * 584 * @param mixed $number A float/int/string of the number to format 585 * @param int $decimal the number of decimal points to display 586 * @param string $decimalPoint the character(s) to use for the decimal point 587 * @param string $thousandSep the character(s) to use for the thousands separator 588 * 589 * @return string The formatted number 590 */ 591 function twig_number_format_filter(Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) 592 { 593 $defaults = $env->getExtension(CoreExtension::class)->getNumberFormat(); 594 if (null === $decimal) { 595 $decimal = $defaults[0]; 596 } 597 598 if (null === $decimalPoint) { 599 $decimalPoint = $defaults[1]; 600 } 601 602 if (null === $thousandSep) { 603 $thousandSep = $defaults[2]; 604 } 605 606 return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); 607 } 608 609 /** 610 * URL encodes (RFC 3986) a string as a path segment or an array as a query string. 611 * 612 * @param string|array $url A URL or an array of query parameters 613 * 614 * @return string The URL encoded value 615 */ 616 function twig_urlencode_filter($url) 617 { 618 if (\is_array($url)) { 619 return http_build_query($url, '', '&', PHP_QUERY_RFC3986); 620 } 621 622 return rawurlencode($url); 623 } 624 625 /** 626 * Merges an array with another one. 627 * 628 * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} 629 * 630 * {% set items = items|merge({ 'peugeot': 'car' }) %} 631 * 632 * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} 633 * 634 * @param array|\Traversable $arr1 An array 635 * @param array|\Traversable $arr2 An array 636 * 637 * @return array The merged array 638 */ 639 function twig_array_merge($arr1, $arr2) 640 { 641 if (!twig_test_iterable($arr1)) { 642 throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($arr1))); 643 } 644 645 if (!twig_test_iterable($arr2)) { 646 throw new RuntimeError(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', \gettype($arr2))); 647 } 648 649 return array_merge(twig_to_array($arr1), twig_to_array($arr2)); 650 } 651 652 /** 653 * Slices a variable. 654 * 655 * @param mixed $item A variable 656 * @param int $start Start of the slice 657 * @param int $length Size of the slice 658 * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) 659 * 660 * @return mixed The sliced variable 661 */ 662 function twig_slice(Environment $env, $item, $start, $length = null, $preserveKeys = false) 663 { 664 if ($item instanceof \Traversable) { 665 while ($item instanceof \IteratorAggregate) { 666 $item = $item->getIterator(); 667 } 668 669 if ($start >= 0 && $length >= 0 && $item instanceof \Iterator) { 670 try { 671 return iterator_to_array(new \LimitIterator($item, $start, null === $length ? -1 : $length), $preserveKeys); 672 } catch (\OutOfBoundsException $e) { 673 return []; 674 } 675 } 676 677 $item = iterator_to_array($item, $preserveKeys); 678 } 679 680 if (\is_array($item)) { 681 return \array_slice($item, $start, $length, $preserveKeys); 682 } 683 684 $item = (string) $item; 685 686 return (string) mb_substr($item, $start, $length, $env->getCharset()); 687 } 688 689 /** 690 * Returns the first element of the item. 691 * 692 * @param mixed $item A variable 693 * 694 * @return mixed The first element of the item 695 */ 696 function twig_first(Environment $env, $item) 697 { 698 $elements = twig_slice($env, $item, 0, 1, false); 699 700 return \is_string($elements) ? $elements : current($elements); 701 } 702 703 /** 704 * Returns the last element of the item. 705 * 706 * @param mixed $item A variable 707 * 708 * @return mixed The last element of the item 709 */ 710 function twig_last(Environment $env, $item) 711 { 712 $elements = twig_slice($env, $item, -1, 1, false); 713 714 return \is_string($elements) ? $elements : current($elements); 715 } 716 717 /** 718 * Joins the values to a string. 719 * 720 * The separators between elements are empty strings per default, you can define them with the optional parameters. 721 * 722 * {{ [1, 2, 3]|join(', ', ' and ') }} 723 * {# returns 1, 2 and 3 #} 724 * 725 * {{ [1, 2, 3]|join('|') }} 726 * {# returns 1|2|3 #} 727 * 728 * {{ [1, 2, 3]|join }} 729 * {# returns 123 #} 730 * 731 * @param array $value An array 732 * @param string $glue The separator 733 * @param string|null $and The separator for the last pair 734 * 735 * @return string The concatenated string 736 */ 737 function twig_join_filter($value, $glue = '', $and = null) 738 { 739 if (!twig_test_iterable($value)) { 740 $value = (array) $value; 741 } 742 743 $value = twig_to_array($value, false); 744 745 if (0 === \count($value)) { 746 return ''; 747 } 748 749 if (null === $and || $and === $glue) { 750 return implode($glue, $value); 751 } 752 753 if (1 === \count($value)) { 754 return $value[0]; 755 } 756 757 return implode($glue, \array_slice($value, 0, -1)).$and.$value[\count($value) - 1]; 758 } 759 760 /** 761 * Splits the string into an array. 762 * 763 * {{ "one,two,three"|split(',') }} 764 * {# returns [one, two, three] #} 765 * 766 * {{ "one,two,three,four,five"|split(',', 3) }} 767 * {# returns [one, two, "three,four,five"] #} 768 * 769 * {{ "123"|split('') }} 770 * {# returns [1, 2, 3] #} 771 * 772 * {{ "aabbcc"|split('', 2) }} 773 * {# returns [aa, bb, cc] #} 774 * 775 * @param string $value A string 776 * @param string $delimiter The delimiter 777 * @param int $limit The limit 778 * 779 * @return array The split string as an array 780 */ 781 function twig_split_filter(Environment $env, $value, $delimiter, $limit = null) 782 { 783 if (\strlen($delimiter) > 0) { 784 return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); 785 } 786 787 if ($limit <= 1) { 788 return preg_split('/(?<!^)(?!$)/u', $value); 789 } 790 791 $length = mb_strlen($value, $env->getCharset()); 792 if ($length < $limit) { 793 return [$value]; 794 } 795 796 $r = []; 797 for ($i = 0; $i < $length; $i += $limit) { 798 $r[] = mb_substr($value, $i, $limit, $env->getCharset()); 799 } 800 801 return $r; 802 } 803 804 // The '_default' filter is used internally to avoid using the ternary operator 805 // which costs a lot for big contexts (before PHP 5.4). So, on average, 806 // a function call is cheaper. 807 /** 808 * @internal 809 */ 810 function _twig_default_filter($value, $default = '') 811 { 812 if (twig_test_empty($value)) { 813 return $default; 814 } 815 816 return $value; 817 } 818 819 /** 820 * Returns the keys for the given array. 821 * 822 * It is useful when you want to iterate over the keys of an array: 823 * 824 * {% for key in array|keys %} 825 * {# ... #} 826 * {% endfor %} 827 * 828 * @param array $array An array 829 * 830 * @return array The keys 831 */ 832 function twig_get_array_keys_filter($array) 833 { 834 if ($array instanceof \Traversable) { 835 while ($array instanceof \IteratorAggregate) { 836 $array = $array->getIterator(); 837 } 838 839 if ($array instanceof \Iterator) { 840 $keys = []; 841 $array->rewind(); 842 while ($array->valid()) { 843 $keys[] = $array->key(); 844 $array->next(); 845 } 846 847 return $keys; 848 } 849 850 $keys = []; 851 foreach ($array as $key => $item) { 852 $keys[] = $key; 853 } 854 855 return $keys; 856 } 857 858 if (!\is_array($array)) { 859 return []; 860 } 861 862 return array_keys($array); 863 } 864 865 /** 866 * Reverses a variable. 867 * 868 * @param array|\Traversable|string $item An array, a \Traversable instance, or a string 869 * @param bool $preserveKeys Whether to preserve key or not 870 * 871 * @return mixed The reversed input 872 */ 873 function twig_reverse_filter(Environment $env, $item, $preserveKeys = false) 874 { 875 if ($item instanceof \Traversable) { 876 return array_reverse(iterator_to_array($item), $preserveKeys); 877 } 878 879 if (\is_array($item)) { 880 return array_reverse($item, $preserveKeys); 881 } 882 883 $string = (string) $item; 884 885 $charset = $env->getCharset(); 886 887 if ('UTF-8' !== $charset) { 888 $item = twig_convert_encoding($string, 'UTF-8', $charset); 889 } 890 891 preg_match_all('/./us', $item, $matches); 892 893 $string = implode('', array_reverse($matches[0])); 894 895 if ('UTF-8' !== $charset) { 896 $string = twig_convert_encoding($string, $charset, 'UTF-8'); 897 } 898 899 return $string; 900 } 901 902 /** 903 * Sorts an array. 904 * 905 * @param array|\Traversable $array 906 * 907 * @return array 908 */ 909 function twig_sort_filter($array, $arrow = null) 910 { 911 if ($array instanceof \Traversable) { 912 $array = iterator_to_array($array); 913 } elseif (!\is_array($array)) { 914 throw new RuntimeError(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', \gettype($array))); 915 } 916 917 if (null !== $arrow) { 918 uasort($array, $arrow); 919 } else { 920 asort($array); 921 } 922 923 return $array; 924 } 925 926 /** 927 * @internal 928 */ 929 function twig_in_filter($value, $compare) 930 { 931 if ($value instanceof Markup) { 932 $value = (string) $value; 933 } 934 if ($compare instanceof Markup) { 935 $compare = (string) $compare; 936 } 937 938 if (\is_array($compare)) { 939 return \in_array($value, $compare, \is_object($value) || \is_resource($value)); 940 } elseif (\is_string($compare) && (\is_string($value) || \is_int($value) || \is_float($value))) { 941 return '' === $value || false !== strpos($compare, (string) $value); 942 } elseif ($compare instanceof \Traversable) { 943 if (\is_object($value) || \is_resource($value)) { 944 foreach ($compare as $item) { 945 if ($item === $value) { 946 return true; 947 } 948 } 949 } else { 950 foreach ($compare as $item) { 951 if ($item == $value) { 952 return true; 953 } 954 } 955 } 956 957 return false; 958 } 959 960 return false; 961 } 962 963 /** 964 * Returns a trimmed string. 965 * 966 * @return string 967 * 968 * @throws RuntimeError When an invalid trimming side is used (not a string or not 'left', 'right', or 'both') 969 */ 970 function twig_trim_filter($string, $characterMask = null, $side = 'both') 971 { 972 if (null === $characterMask) { 973 $characterMask = " \t\n\r\0\x0B"; 974 } 975 976 switch ($side) { 977 case 'both': 978 return trim($string, $characterMask); 979 case 'left': 980 return ltrim($string, $characterMask); 981 case 'right': 982 return rtrim($string, $characterMask); 983 default: 984 throw new RuntimeError('Trimming side must be "left", "right" or "both".'); 985 } 986 } 987 988 /** 989 * Removes whitespaces between HTML tags. 990 * 991 * @return string 992 */ 993 function twig_spaceless($content) 994 { 995 return trim(preg_replace('/>\s+</', '><', $content)); 996 } 997 998 function twig_convert_encoding($string, $to, $from) 999 { 1000 if (!\function_exists('iconv')) { 1001 throw new RuntimeError('Unable to convert encoding: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); 1002 } 1003 1004 return iconv($from, $to, $string); 1005 } 1006 1007 /** 1008 * Returns the length of a variable. 1009 * 1010 * @param mixed $thing A variable 1011 * 1012 * @return int The length of the value 1013 */ 1014 function twig_length_filter(Environment $env, $thing) 1015 { 1016 if (null === $thing) { 1017 return 0; 1018 } 1019 1020 if (is_scalar($thing)) { 1021 return mb_strlen($thing, $env->getCharset()); 1022 } 1023 1024 if ($thing instanceof \Countable || \is_array($thing) || $thing instanceof \SimpleXMLElement) { 1025 return \count($thing); 1026 } 1027 1028 if ($thing instanceof \Traversable) { 1029 return iterator_count($thing); 1030 } 1031 1032 if (method_exists($thing, '__toString') && !$thing instanceof \Countable) { 1033 return mb_strlen((string) $thing, $env->getCharset()); 1034 } 1035 1036 return 1; 1037 } 1038 1039 /** 1040 * Converts a string to uppercase. 1041 * 1042 * @param string $string A string 1043 * 1044 * @return string The uppercased string 1045 */ 1046 function twig_upper_filter(Environment $env, $string) 1047 { 1048 return mb_strtoupper($string, $env->getCharset()); 1049 } 1050 1051 /** 1052 * Converts a string to lowercase. 1053 * 1054 * @param string $string A string 1055 * 1056 * @return string The lowercased string 1057 */ 1058 function twig_lower_filter(Environment $env, $string) 1059 { 1060 return mb_strtolower($string, $env->getCharset()); 1061 } 1062 1063 /** 1064 * Returns a titlecased string. 1065 * 1066 * @param string $string A string 1067 * 1068 * @return string The titlecased string 1069 */ 1070 function twig_title_string_filter(Environment $env, $string) 1071 { 1072 if (null !== $charset = $env->getCharset()) { 1073 return mb_convert_case($string, MB_CASE_TITLE, $charset); 1074 } 1075 1076 return ucwords(strtolower($string)); 1077 } 1078 1079 /** 1080 * Returns a capitalized string. 1081 * 1082 * @param string $string A string 1083 * 1084 * @return string The capitalized string 1085 */ 1086 function twig_capitalize_string_filter(Environment $env, $string) 1087 { 1088 $charset = $env->getCharset(); 1089 1090 return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, null, $charset), $charset); 1091 } 1092 1093 /** 1094 * @internal 1095 */ 1096 function twig_call_macro(Template $template, string $method, array $args, int $lineno, array $context, Source $source) 1097 { 1098 if (!method_exists($template, $method)) { 1099 $parent = $template; 1100 while ($parent = $parent->getParent($context)) { 1101 if (method_exists($parent, $method)) { 1102 return $parent->$method(...$args); 1103 } 1104 } 1105 1106 throw new RuntimeError(sprintf('Macro "%s" is not defined in template "%s".', substr($method, \strlen('macro_')), $template->getTemplateName()), $lineno, $source); 1107 } 1108 1109 return $template->$method(...$args); 1110 } 1111 1112 /** 1113 * @internal 1114 */ 1115 function twig_ensure_traversable($seq) 1116 { 1117 if ($seq instanceof \Traversable || \is_array($seq)) { 1118 return $seq; 1119 } 1120 1121 return []; 1122 } 1123 1124 /** 1125 * @internal 1126 */ 1127 function twig_to_array($seq, $preserveKeys = true) 1128 { 1129 if ($seq instanceof \Traversable) { 1130 return iterator_to_array($seq, $preserveKeys); 1131 } 1132 1133 if (!\is_array($seq)) { 1134 return $seq; 1135 } 1136 1137 return $preserveKeys ? $seq : array_values($seq); 1138 } 1139 1140 /** 1141 * Checks if a variable is empty. 1142 * 1143 * {# evaluates to true if the foo variable is null, false, or the empty string #} 1144 * {% if foo is empty %} 1145 * {# ... #} 1146 * {% endif %} 1147 * 1148 * @param mixed $value A variable 1149 * 1150 * @return bool true if the value is empty, false otherwise 1151 */ 1152 function twig_test_empty($value) 1153 { 1154 if ($value instanceof \Countable) { 1155 return 0 === \count($value); 1156 } 1157 1158 if ($value instanceof \Traversable) { 1159 return !iterator_count($value); 1160 } 1161 1162 if (\is_object($value) && method_exists($value, '__toString')) { 1163 return '' === (string) $value; 1164 } 1165 1166 return '' === $value || false === $value || null === $value || [] === $value; 1167 } 1168 1169 /** 1170 * Checks if a variable is traversable. 1171 * 1172 * {# evaluates to true if the foo variable is an array or a traversable object #} 1173 * {% if foo is iterable %} 1174 * {# ... #} 1175 * {% endif %} 1176 * 1177 * @param mixed $value A variable 1178 * 1179 * @return bool true if the value is traversable 1180 */ 1181 function twig_test_iterable($value) 1182 { 1183 return $value instanceof \Traversable || \is_array($value); 1184 } 1185 1186 /** 1187 * Renders a template. 1188 * 1189 * @param array $context 1190 * @param string|array $template The template to render or an array of templates to try consecutively 1191 * @param array $variables The variables to pass to the template 1192 * @param bool $withContext 1193 * @param bool $ignoreMissing Whether to ignore missing templates or not 1194 * @param bool $sandboxed Whether to sandbox the template or not 1195 * 1196 * @return string The rendered template 1197 */ 1198 function twig_include(Environment $env, $context, $template, $variables = [], $withContext = true, $ignoreMissing = false, $sandboxed = false) 1199 { 1200 $alreadySandboxed = false; 1201 $sandbox = null; 1202 if ($withContext) { 1203 $variables = array_merge($context, $variables); 1204 } 1205 1206 if ($isSandboxed = $sandboxed && $env->hasExtension(SandboxExtension::class)) { 1207 $sandbox = $env->getExtension(SandboxExtension::class); 1208 if (!$alreadySandboxed = $sandbox->isSandboxed()) { 1209 $sandbox->enableSandbox(); 1210 } 1211 } 1212 1213 try { 1214 $loaded = null; 1215 try { 1216 $loaded = $env->resolveTemplate($template); 1217 } catch (LoaderError $e) { 1218 if (!$ignoreMissing) { 1219 throw $e; 1220 } 1221 } 1222 1223 return $loaded ? $loaded->render($variables) : ''; 1224 } finally { 1225 if ($isSandboxed && !$alreadySandboxed) { 1226 $sandbox->disableSandbox(); 1227 } 1228 } 1229 } 1230 1231 /** 1232 * Returns a template content without rendering it. 1233 * 1234 * @param string $name The template name 1235 * @param bool $ignoreMissing Whether to ignore missing templates or not 1236 * 1237 * @return string The template source 1238 */ 1239 function twig_source(Environment $env, $name, $ignoreMissing = false) 1240 { 1241 $loader = $env->getLoader(); 1242 try { 1243 return $loader->getSourceContext($name)->getCode(); 1244 } catch (LoaderError $e) { 1245 if (!$ignoreMissing) { 1246 throw $e; 1247 } 1248 } 1249 } 1250 1251 /** 1252 * Provides the ability to get constants from instances as well as class/global constants. 1253 * 1254 * @param string $constant The name of the constant 1255 * @param object|null $object The object to get the constant from 1256 * 1257 * @return string 1258 */ 1259 function twig_constant($constant, $object = null) 1260 { 1261 if (null !== $object) { 1262 $constant = \get_class($object).'::'.$constant; 1263 } 1264 1265 return \constant($constant); 1266 } 1267 1268 /** 1269 * Checks if a constant exists. 1270 * 1271 * @param string $constant The name of the constant 1272 * @param object|null $object The object to get the constant from 1273 * 1274 * @return bool 1275 */ 1276 function twig_constant_is_defined($constant, $object = null) 1277 { 1278 if (null !== $object) { 1279 $constant = \get_class($object).'::'.$constant; 1280 } 1281 1282 return \defined($constant); 1283 } 1284 1285 /** 1286 * Batches item. 1287 * 1288 * @param array $items An array of items 1289 * @param int $size The size of the batch 1290 * @param mixed $fill A value used to fill missing items 1291 * 1292 * @return array 1293 */ 1294 function twig_array_batch($items, $size, $fill = null, $preserveKeys = true) 1295 { 1296 if (!twig_test_iterable($items)) { 1297 throw new RuntimeError(sprintf('The "batch" filter expects an array or "Traversable", got "%s".', \is_object($items) ? \get_class($items) : \gettype($items))); 1298 } 1299 1300 $size = ceil($size); 1301 1302 $result = array_chunk(twig_to_array($items, $preserveKeys), $size, $preserveKeys); 1303 1304 if (null !== $fill && $result) { 1305 $last = \count($result) - 1; 1306 if ($fillCount = $size - \count($result[$last])) { 1307 for ($i = 0; $i < $fillCount; ++$i) { 1308 $result[$last][] = $fill; 1309 } 1310 } 1311 } 1312 1313 return $result; 1314 } 1315 1316 /** 1317 * Returns the attribute value for a given array/object. 1318 * 1319 * @param mixed $object The object or array from where to get the item 1320 * @param mixed $item The item to get from the array or object 1321 * @param array $arguments An array of arguments to pass if the item is an object method 1322 * @param string $type The type of attribute (@see \Twig\Template constants) 1323 * @param bool $isDefinedTest Whether this is only a defined check 1324 * @param bool $ignoreStrictCheck Whether to ignore the strict attribute check or not 1325 * @param int $lineno The template line where the attribute was called 1326 * 1327 * @return mixed The attribute value, or a Boolean when $isDefinedTest is true, or null when the attribute is not set and $ignoreStrictCheck is true 1328 * 1329 * @throws RuntimeError if the attribute does not exist and Twig is running in strict mode and $isDefinedTest is false 1330 * 1331 * @internal 1332 */ 1333 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) 1334 { 1335 // array 1336 if (/* Template::METHOD_CALL */ 'method' !== $type) { 1337 $arrayItem = \is_bool($item) || \is_float($item) ? (int) $item : $item; 1338 1339 if (((\is_array($object) || $object instanceof \ArrayObject) && (isset($object[$arrayItem]) || \array_key_exists($arrayItem, (array) $object))) 1340 || ($object instanceof ArrayAccess && isset($object[$arrayItem])) 1341 ) { 1342 if ($isDefinedTest) { 1343 return true; 1344 } 1345 1346 return $object[$arrayItem]; 1347 } 1348 1349 if (/* Template::ARRAY_CALL */ 'array' === $type || !\is_object($object)) { 1350 if ($isDefinedTest) { 1351 return false; 1352 } 1353 1354 if ($ignoreStrictCheck || !$env->isStrictVariables()) { 1355 return; 1356 } 1357 1358 if ($object instanceof ArrayAccess) { 1359 $message = sprintf('Key "%s" in object with ArrayAccess of class "%s" does not exist.', $arrayItem, \get_class($object)); 1360 } elseif (\is_object($object)) { 1361 $message = sprintf('Impossible to access a key "%s" on an object of class "%s" that does not implement ArrayAccess interface.', $item, \get_class($object)); 1362 } elseif (\is_array($object)) { 1363 if (empty($object)) { 1364 $message = sprintf('Key "%s" does not exist as the array is empty.', $arrayItem); 1365 } else { 1366 $message = sprintf('Key "%s" for array with keys "%s" does not exist.', $arrayItem, implode(', ', array_keys($object))); 1367 } 1368 } elseif (/* Template::ARRAY_CALL */ 'array' === $type) { 1369 if (null === $object) { 1370 $message = sprintf('Impossible to access a key ("%s") on a null variable.', $item); 1371 } else { 1372 $message = sprintf('Impossible to access a key ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 1373 } 1374 } elseif (null === $object) { 1375 $message = sprintf('Impossible to access an attribute ("%s") on a null variable.', $item); 1376 } else { 1377 $message = sprintf('Impossible to access an attribute ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 1378 } 1379 1380 throw new RuntimeError($message, $lineno, $source); 1381 } 1382 } 1383 1384 if (!\is_object($object)) { 1385 if ($isDefinedTest) { 1386 return false; 1387 } 1388 1389 if ($ignoreStrictCheck || !$env->isStrictVariables()) { 1390 return; 1391 } 1392 1393 if (null === $object) { 1394 $message = sprintf('Impossible to invoke a method ("%s") on a null variable.', $item); 1395 } elseif (\is_array($object)) { 1396 $message = sprintf('Impossible to invoke a method ("%s") on an array.', $item); 1397 } else { 1398 $message = sprintf('Impossible to invoke a method ("%s") on a %s variable ("%s").', $item, \gettype($object), $object); 1399 } 1400 1401 throw new RuntimeError($message, $lineno, $source); 1402 } 1403 1404 if ($object instanceof Template) { 1405 throw new RuntimeError('Accessing \Twig\Template attributes is forbidden.', $lineno, $source); 1406 } 1407 1408 // object property 1409 if (/* Template::METHOD_CALL */ 'method' !== $type) { 1410 if (isset($object->$item) || \array_key_exists((string) $item, (array) $object)) { 1411 if ($isDefinedTest) { 1412 return true; 1413 } 1414 1415 if ($sandboxed) { 1416 $env->getExtension(SandboxExtension::class)->checkPropertyAllowed($object, $item, $lineno, $source); 1417 } 1418 1419 return $object->$item; 1420 } 1421 } 1422 1423 static $cache = []; 1424 1425 $class = \get_class($object); 1426 1427 // object method 1428 // precedence: getXxx() > isXxx() > hasXxx() 1429 if (!isset($cache[$class])) { 1430 $methods = get_class_methods($object); 1431 sort($methods); 1432 $lcMethods = array_map(function ($value) { return strtr($value, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'); }, $methods); 1433 $classCache = []; 1434 foreach ($methods as $i => $method) { 1435 $classCache[$method] = $method; 1436 $classCache[$lcName = $lcMethods[$i]] = $method; 1437 1438 if ('g' === $lcName[0] && 0 === strpos($lcName, 'get')) { 1439 $name = substr($method, 3); 1440 $lcName = substr($lcName, 3); 1441 } elseif ('i' === $lcName[0] && 0 === strpos($lcName, 'is')) { 1442 $name = substr($method, 2); 1443 $lcName = substr($lcName, 2); 1444 } elseif ('h' === $lcName[0] && 0 === strpos($lcName, 'has')) { 1445 $name = substr($method, 3); 1446 $lcName = substr($lcName, 3); 1447 if (\in_array('is'.$lcName, $lcMethods)) { 1448 continue; 1449 } 1450 } else { 1451 continue; 1452 } 1453 1454 // skip get() and is() methods (in which case, $name is empty) 1455 if ($name) { 1456 if (!isset($classCache[$name])) { 1457 $classCache[$name] = $method; 1458 } 1459 1460 if (!isset($classCache[$lcName])) { 1461 $classCache[$lcName] = $method; 1462 } 1463 } 1464 } 1465 $cache[$class] = $classCache; 1466 } 1467 1468 $call = false; 1469 if (isset($cache[$class][$item])) { 1470 $method = $cache[$class][$item]; 1471 } elseif (isset($cache[$class][$lcItem = strtr($item, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz')])) { 1472 $method = $cache[$class][$lcItem]; 1473 } elseif (isset($cache[$class]['__call'])) { 1474 $method = $item; 1475 $call = true; 1476 } else { 1477 if ($isDefinedTest) { 1478 return false; 1479 } 1480 1481 if ($ignoreStrictCheck || !$env->isStrictVariables()) { 1482 return; 1483 } 1484 1485 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); 1486 } 1487 1488 if ($isDefinedTest) { 1489 return true; 1490 } 1491 1492 if ($sandboxed) { 1493 $env->getExtension(SandboxExtension::class)->checkMethodAllowed($object, $method, $lineno, $source); 1494 } 1495 1496 // Some objects throw exceptions when they have __call, and the method we try 1497 // to call is not supported. If ignoreStrictCheck is true, we should return null. 1498 try { 1499 $ret = $object->$method(...$arguments); 1500 } catch (\BadMethodCallException $e) { 1501 if ($call && ($ignoreStrictCheck || !$env->isStrictVariables())) { 1502 return; 1503 } 1504 throw $e; 1505 } 1506 1507 return $ret; 1508 } 1509 1510 /** 1511 * Returns the values from a single column in the input array. 1512 * 1513 * <pre> 1514 * {% set items = [{ 'fruit' : 'apple'}, {'fruit' : 'orange' }] %} 1515 * 1516 * {% set fruits = items|column('fruit') %} 1517 * 1518 * {# fruits now contains ['apple', 'orange'] #} 1519 * </pre> 1520 * 1521 * @param array|Traversable $array An array 1522 * @param mixed $name The column name 1523 * @param mixed $index The column to use as the index/keys for the returned array 1524 * 1525 * @return array The array of values 1526 */ 1527 function twig_array_column($array, $name, $index = null): array 1528 { 1529 if ($array instanceof Traversable) { 1530 $array = iterator_to_array($array); 1531 } elseif (!\is_array($array)) { 1532 throw new RuntimeError(sprintf('The column filter only works with arrays or "Traversable", got "%s" as first argument.', \gettype($array))); 1533 } 1534 1535 return array_column($array, $name, $index); 1536 } 1537 1538 function twig_array_filter(Environment $env, $array, $arrow) 1539 { 1540 if (!twig_test_iterable($array)) { 1541 throw new RuntimeError(sprintf('The "filter" filter expects an array or "Traversable", got "%s".', \is_object($array) ? \get_class($array) : \gettype($array))); 1542 } 1543 1544 if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { 1545 throw new RuntimeError('The callable passed to "filter" filter must be a Closure in sandbox mode.'); 1546 } 1547 1548 if (\is_array($array)) { 1549 return array_filter($array, $arrow, \ARRAY_FILTER_USE_BOTH); 1550 } 1551 1552 // the IteratorIterator wrapping is needed as some internal PHP classes are \Traversable but do not implement \Iterator 1553 return new \CallbackFilterIterator(new \IteratorIterator($array), $arrow); 1554 } 1555 1556 function twig_array_map(Environment $env, $array, $arrow) 1557 { 1558 if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { 1559 throw new RuntimeError('The callable passed to the "map" filter must be a Closure in sandbox mode.'); 1560 } 1561 1562 $r = []; 1563 foreach ($array as $k => $v) { 1564 $r[$k] = $arrow($v, $k); 1565 } 1566 1567 return $r; 1568 } 1569 1570 function twig_array_reduce(Environment $env, $array, $arrow, $initial = null) 1571 { 1572 if (!$arrow instanceof Closure && $env->hasExtension('\Twig\Extension\SandboxExtension') && $env->getExtension('\Twig\Extension\SandboxExtension')->isSandboxed()) { 1573 throw new RuntimeError('The callable passed to the "reduce" filter must be a Closure in sandbox mode.'); 1574 } 1575 1576 if (!\is_array($array)) { 1577 $array = iterator_to_array($array); 1578 } 1579 1580 return array_reduce($array, $arrow, $initial); 1581 } 1582 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Wed Nov 11 20:28:18 2020 | Cross-referenced by PHPXref 0.7.1 |