Core.php (54773B)
1 <?php 2 3 if (!defined('ENT_SUBSTITUTE')) { 4 // use 0 as hhvm does not support several flags yet 5 define('ENT_SUBSTITUTE', 0); 6 } 7 8 /* 9 * This file is part of Twig. 10 * 11 * (c) 2009 Fabien Potencier 12 * 13 * For the full copyright and license information, please view the LICENSE 14 * file that was distributed with this source code. 15 */ 16 class Twig_Extension_Core extends Twig_Extension 17 { 18 protected $dateFormats = array('F j, Y H:i', '%d days'); 19 protected $numberFormat = array(0, '.', ','); 20 protected $timezone = null; 21 protected $escapers = array(); 22 23 /** 24 * Defines a new escaper to be used via the escape filter. 25 * 26 * @param string $strategy The strategy name that should be used as a strategy in the escape call 27 * @param callable $callable A valid PHP callable 28 */ 29 public function setEscaper($strategy, $callable) 30 { 31 $this->escapers[$strategy] = $callable; 32 } 33 34 /** 35 * Gets all defined escapers. 36 * 37 * @return array An array of escapers 38 */ 39 public function getEscapers() 40 { 41 return $this->escapers; 42 } 43 44 /** 45 * Sets the default format to be used by the date filter. 46 * 47 * @param string $format The default date format string 48 * @param string $dateIntervalFormat The default date interval format string 49 */ 50 public function setDateFormat($format = null, $dateIntervalFormat = null) 51 { 52 if (null !== $format) { 53 $this->dateFormats[0] = $format; 54 } 55 56 if (null !== $dateIntervalFormat) { 57 $this->dateFormats[1] = $dateIntervalFormat; 58 } 59 } 60 61 /** 62 * Gets the default format to be used by the date filter. 63 * 64 * @return array The default date format string and the default date interval format string 65 */ 66 public function getDateFormat() 67 { 68 return $this->dateFormats; 69 } 70 71 /** 72 * Sets the default timezone to be used by the date filter. 73 * 74 * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object 75 */ 76 public function setTimezone($timezone) 77 { 78 $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone); 79 } 80 81 /** 82 * Gets the default timezone to be used by the date filter. 83 * 84 * @return DateTimeZone The default timezone currently in use 85 */ 86 public function getTimezone() 87 { 88 if (null === $this->timezone) { 89 $this->timezone = new DateTimeZone(date_default_timezone_get()); 90 } 91 92 return $this->timezone; 93 } 94 95 /** 96 * Sets the default format to be used by the number_format filter. 97 * 98 * @param int $decimal The number of decimal places to use. 99 * @param string $decimalPoint The character(s) to use for the decimal point. 100 * @param string $thousandSep The character(s) to use for the thousands separator. 101 */ 102 public function setNumberFormat($decimal, $decimalPoint, $thousandSep) 103 { 104 $this->numberFormat = array($decimal, $decimalPoint, $thousandSep); 105 } 106 107 /** 108 * Get the default format used by the number_format filter. 109 * 110 * @return array The arguments for number_format() 111 */ 112 public function getNumberFormat() 113 { 114 return $this->numberFormat; 115 } 116 117 public function getTokenParsers() 118 { 119 return array( 120 new Twig_TokenParser_For(), 121 new Twig_TokenParser_If(), 122 new Twig_TokenParser_Extends(), 123 new Twig_TokenParser_Include(), 124 new Twig_TokenParser_Block(), 125 new Twig_TokenParser_Use(), 126 new Twig_TokenParser_Filter(), 127 new Twig_TokenParser_Macro(), 128 new Twig_TokenParser_Import(), 129 new Twig_TokenParser_From(), 130 new Twig_TokenParser_Set(), 131 new Twig_TokenParser_Spaceless(), 132 new Twig_TokenParser_Flush(), 133 new Twig_TokenParser_Do(), 134 new Twig_TokenParser_Embed(), 135 ); 136 } 137 138 public function getFilters() 139 { 140 $filters = array( 141 // formatting filters 142 new Twig_SimpleFilter('date', 'twig_date_format_filter', array('needs_environment' => true)), 143 new Twig_SimpleFilter('date_modify', 'twig_date_modify_filter', array('needs_environment' => true)), 144 new Twig_SimpleFilter('format', 'sprintf'), 145 new Twig_SimpleFilter('replace', 'twig_replace_filter'), 146 new Twig_SimpleFilter('number_format', 'twig_number_format_filter', array('needs_environment' => true)), 147 new Twig_SimpleFilter('abs', 'abs'), 148 new Twig_SimpleFilter('round', 'twig_round'), 149 150 // encoding 151 new Twig_SimpleFilter('url_encode', 'twig_urlencode_filter'), 152 new Twig_SimpleFilter('json_encode', 'twig_jsonencode_filter'), 153 new Twig_SimpleFilter('convert_encoding', 'twig_convert_encoding'), 154 155 // string filters 156 new Twig_SimpleFilter('title', 'twig_title_string_filter', array('needs_environment' => true)), 157 new Twig_SimpleFilter('capitalize', 'twig_capitalize_string_filter', array('needs_environment' => true)), 158 new Twig_SimpleFilter('upper', 'strtoupper'), 159 new Twig_SimpleFilter('lower', 'strtolower'), 160 new Twig_SimpleFilter('striptags', 'strip_tags'), 161 new Twig_SimpleFilter('trim', 'trim'), 162 new Twig_SimpleFilter('nl2br', 'nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))), 163 164 // array helpers 165 new Twig_SimpleFilter('join', 'twig_join_filter'), 166 new Twig_SimpleFilter('split', 'twig_split_filter', array('needs_environment' => true)), 167 new Twig_SimpleFilter('sort', 'twig_sort_filter'), 168 new Twig_SimpleFilter('merge', 'twig_array_merge'), 169 new Twig_SimpleFilter('batch', 'twig_array_batch'), 170 171 // string/array filters 172 new Twig_SimpleFilter('reverse', 'twig_reverse_filter', array('needs_environment' => true)), 173 new Twig_SimpleFilter('length', 'twig_length_filter', array('needs_environment' => true)), 174 new Twig_SimpleFilter('slice', 'twig_slice', array('needs_environment' => true)), 175 new Twig_SimpleFilter('first', 'twig_first', array('needs_environment' => true)), 176 new Twig_SimpleFilter('last', 'twig_last', array('needs_environment' => true)), 177 178 // iteration and runtime 179 new Twig_SimpleFilter('default', '_twig_default_filter', array('node_class' => 'Twig_Node_Expression_Filter_Default')), 180 new Twig_SimpleFilter('keys', 'twig_get_array_keys_filter'), 181 182 // escaping 183 new Twig_SimpleFilter('escape', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), 184 new Twig_SimpleFilter('e', 'twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')), 185 ); 186 187 if (function_exists('mb_get_info')) { 188 $filters[] = new Twig_SimpleFilter('upper', 'twig_upper_filter', array('needs_environment' => true)); 189 $filters[] = new Twig_SimpleFilter('lower', 'twig_lower_filter', array('needs_environment' => true)); 190 } 191 192 return $filters; 193 } 194 195 public function getFunctions() 196 { 197 return array( 198 new Twig_SimpleFunction('max', 'max'), 199 new Twig_SimpleFunction('min', 'min'), 200 new Twig_SimpleFunction('range', 'range'), 201 new Twig_SimpleFunction('constant', 'twig_constant'), 202 new Twig_SimpleFunction('cycle', 'twig_cycle'), 203 new Twig_SimpleFunction('random', 'twig_random', array('needs_environment' => true)), 204 new Twig_SimpleFunction('date', 'twig_date_converter', array('needs_environment' => true)), 205 new Twig_SimpleFunction('include', 'twig_include', array('needs_environment' => true, 'needs_context' => true, 'is_safe' => array('all'))), 206 new Twig_SimpleFunction('source', 'twig_source', array('needs_environment' => true, 'is_safe' => array('all'))), 207 ); 208 } 209 210 public function getTests() 211 { 212 return array( 213 new Twig_SimpleTest('even', null, array('node_class' => 'Twig_Node_Expression_Test_Even')), 214 new Twig_SimpleTest('odd', null, array('node_class' => 'Twig_Node_Expression_Test_Odd')), 215 new Twig_SimpleTest('defined', null, array('node_class' => 'Twig_Node_Expression_Test_Defined')), 216 new Twig_SimpleTest('sameas', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas', 'deprecated' => '1.21', 'alternative' => 'same as')), 217 new Twig_SimpleTest('same as', null, array('node_class' => 'Twig_Node_Expression_Test_Sameas')), 218 new Twig_SimpleTest('none', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), 219 new Twig_SimpleTest('null', null, array('node_class' => 'Twig_Node_Expression_Test_Null')), 220 new Twig_SimpleTest('divisibleby', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby', 'deprecated' => '1.21', 'alternative' => 'divisible by')), 221 new Twig_SimpleTest('divisible by', null, array('node_class' => 'Twig_Node_Expression_Test_Divisibleby')), 222 new Twig_SimpleTest('constant', null, array('node_class' => 'Twig_Node_Expression_Test_Constant')), 223 new Twig_SimpleTest('empty', 'twig_test_empty'), 224 new Twig_SimpleTest('iterable', 'twig_test_iterable'), 225 ); 226 } 227 228 public function getOperators() 229 { 230 return array( 231 array( 232 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'), 233 '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'), 234 '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'), 235 ), 236 array( 237 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 238 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 239 'b-or' => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 240 'b-xor' => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 241 'b-and' => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 242 '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 243 '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 244 '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 245 '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 246 '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 247 '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 248 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 249 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 250 'matches' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Matches', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 251 'starts with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_StartsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 252 'ends with' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_EndsWith', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 253 '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 254 '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 255 '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 256 '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 257 '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 258 '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 259 '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 260 '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 261 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 262 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT), 263 '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), 264 '??' => array('precedence' => 300, 'class' => 'Twig_Node_Expression_NullCoalesce', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT), 265 ), 266 ); 267 } 268 269 public function parseNotTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) 270 { 271 return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine()); 272 } 273 274 public function parseTestExpression(Twig_Parser $parser, Twig_NodeInterface $node) 275 { 276 $stream = $parser->getStream(); 277 list($name, $test) = $this->getTest($parser, $node->getLine()); 278 279 if ($test instanceof Twig_SimpleTest && $test->isDeprecated()) { 280 $message = sprintf('Twig Test "%s" is deprecated', $name); 281 if (!is_bool($test->getDeprecatedVersion())) { 282 $message .= sprintf(' since version %s', $test->getDeprecatedVersion()); 283 } 284 if ($test->getAlternative()) { 285 $message .= sprintf('. Use "%s" instead', $test->getAlternative()); 286 } 287 $message .= sprintf(' in %s at line %d.', $stream->getFilename(), $stream->getCurrent()->getLine()); 288 289 @trigger_error($message, E_USER_DEPRECATED); 290 } 291 292 $class = $this->getTestNodeClass($parser, $test); 293 $arguments = null; 294 if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) { 295 $arguments = $parser->getExpressionParser()->parseArguments(true); 296 } 297 298 return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine()); 299 } 300 301 protected function getTest(Twig_Parser $parser, $line) 302 { 303 $stream = $parser->getStream(); 304 $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue(); 305 $env = $parser->getEnvironment(); 306 307 if ($test = $env->getTest($name)) { 308 return array($name, $test); 309 } 310 311 if ($stream->test(Twig_Token::NAME_TYPE)) { 312 // try 2-words tests 313 $name = $name.' '.$parser->getCurrentToken()->getValue(); 314 315 if ($test = $env->getTest($name)) { 316 $parser->getStream()->next(); 317 318 return array($name, $test); 319 } 320 } 321 322 $e = new Twig_Error_Syntax(sprintf('Unknown "%s" test.', $name), $line, $parser->getFilename()); 323 $e->addSuggestions($name, array_keys($env->getTests())); 324 325 throw $e; 326 } 327 328 protected function getTestNodeClass(Twig_Parser $parser, $test) 329 { 330 if ($test instanceof Twig_SimpleTest) { 331 return $test->getNodeClass(); 332 } 333 334 return $test instanceof Twig_Test_Node ? $test->getClass() : 'Twig_Node_Expression_Test'; 335 } 336 337 public function getName() 338 { 339 return 'core'; 340 } 341 } 342 343 /** 344 * Cycles over a value. 345 * 346 * @param ArrayAccess|array $values An array or an ArrayAccess instance 347 * @param int $position The cycle position 348 * 349 * @return string The next value in the cycle 350 */ 351 function twig_cycle($values, $position) 352 { 353 if (!is_array($values) && !$values instanceof ArrayAccess) { 354 return $values; 355 } 356 357 return $values[$position % count($values)]; 358 } 359 360 /** 361 * Returns a random value depending on the supplied parameter type: 362 * - a random item from a Traversable or array 363 * - a random character from a string 364 * - a random integer between 0 and the integer parameter. 365 * 366 * @param Twig_Environment $env A Twig_Environment instance 367 * @param Traversable|array|int|string $values The values to pick a random item from 368 * 369 * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is). 370 * 371 * @return mixed A random value from the given sequence 372 */ 373 function twig_random(Twig_Environment $env, $values = null) 374 { 375 if (null === $values) { 376 return mt_rand(); 377 } 378 379 if (is_int($values) || is_float($values)) { 380 return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values); 381 } 382 383 if ($values instanceof Traversable) { 384 $values = iterator_to_array($values); 385 } elseif (is_string($values)) { 386 if ('' === $values) { 387 return ''; 388 } 389 if (null !== $charset = $env->getCharset()) { 390 if ('UTF-8' !== $charset) { 391 $values = twig_convert_encoding($values, 'UTF-8', $charset); 392 } 393 394 // unicode version of str_split() 395 // split at all positions, but not after the start and not before the end 396 $values = preg_split('/(?<!^)(?!$)/u', $values); 397 398 if ('UTF-8' !== $charset) { 399 foreach ($values as $i => $value) { 400 $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8'); 401 } 402 } 403 } else { 404 return $values[mt_rand(0, strlen($values) - 1)]; 405 } 406 } 407 408 if (!is_array($values)) { 409 return $values; 410 } 411 412 if (0 === count($values)) { 413 throw new Twig_Error_Runtime('The random function cannot pick from an empty array.'); 414 } 415 416 return $values[array_rand($values, 1)]; 417 } 418 419 /** 420 * Converts a date to the given format. 421 * 422 * <pre> 423 * {{ post.published_at|date("m/d/Y") }} 424 * </pre> 425 * 426 * @param Twig_Environment $env A Twig_Environment instance 427 * @param DateTime|DateTimeInterface|DateInterval|string $date A date 428 * @param string|null $format The target format, null to use the default 429 * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged 430 * 431 * @return string The formatted date 432 */ 433 function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null) 434 { 435 if (null === $format) { 436 $formats = $env->getExtension('core')->getDateFormat(); 437 $format = $date instanceof DateInterval ? $formats[1] : $formats[0]; 438 } 439 440 if ($date instanceof DateInterval) { 441 return $date->format($format); 442 } 443 444 return twig_date_converter($env, $date, $timezone)->format($format); 445 } 446 447 /** 448 * Returns a new date object modified. 449 * 450 * <pre> 451 * {{ post.published_at|date_modify("-1day")|date("m/d/Y") }} 452 * </pre> 453 * 454 * @param Twig_Environment $env A Twig_Environment instance 455 * @param DateTime|string $date A date 456 * @param string $modifier A modifier string 457 * 458 * @return DateTime A new date object 459 */ 460 function twig_date_modify_filter(Twig_Environment $env, $date, $modifier) 461 { 462 $date = twig_date_converter($env, $date, false); 463 $resultDate = $date->modify($modifier); 464 465 // This is a hack to ensure PHP 5.2 support and support for DateTimeImmutable 466 // DateTime::modify does not return the modified DateTime object < 5.3.0 467 // and DateTimeImmutable does not modify $date. 468 return null === $resultDate ? $date : $resultDate; 469 } 470 471 /** 472 * Converts an input to a DateTime instance. 473 * 474 * <pre> 475 * {% if date(user.created_at) < date('+2days') %} 476 * {# do something #} 477 * {% endif %} 478 * </pre> 479 * 480 * @param Twig_Environment $env A Twig_Environment instance 481 * @param DateTime|DateTimeInterface|string|null $date A date 482 * @param DateTimeZone|string|null|false $timezone The target timezone, null to use the default, false to leave unchanged 483 * 484 * @return DateTime A DateTime instance 485 */ 486 function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null) 487 { 488 // determine the timezone 489 if (false !== $timezone) { 490 if (null === $timezone) { 491 $timezone = $env->getExtension('core')->getTimezone(); 492 } elseif (!$timezone instanceof DateTimeZone) { 493 $timezone = new DateTimeZone($timezone); 494 } 495 } 496 497 // immutable dates 498 if ($date instanceof DateTimeImmutable) { 499 return false !== $timezone ? $date->setTimezone($timezone) : $date; 500 } 501 502 if ($date instanceof DateTime || $date instanceof DateTimeInterface) { 503 $date = clone $date; 504 if (false !== $timezone) { 505 $date->setTimezone($timezone); 506 } 507 508 return $date; 509 } 510 511 if (null === $date || 'now' === $date) { 512 return new DateTime($date, false !== $timezone ? $timezone : $env->getExtension('core')->getTimezone()); 513 } 514 515 $asString = (string) $date; 516 if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) { 517 $date = new DateTime('@'.$date); 518 } else { 519 $date = new DateTime($date, $env->getExtension('core')->getTimezone()); 520 } 521 522 if (false !== $timezone) { 523 $date->setTimezone($timezone); 524 } 525 526 return $date; 527 } 528 529 /** 530 * Replaces strings within a string. 531 * 532 * @param string $str String to replace in 533 * @param array|Traversable $from Replace values 534 * @param string|null $to Replace to, deprecated (@see http://php.net/manual/en/function.strtr.php) 535 * 536 * @return string 537 */ 538 function twig_replace_filter($str, $from, $to = null) 539 { 540 if ($from instanceof Traversable) { 541 $from = iterator_to_array($from); 542 } elseif (is_string($from) && is_string($to)) { 543 @trigger_error('Using "replace" with character by character replacement is deprecated since version 1.22 and will be removed in Twig 2.0', E_USER_DEPRECATED); 544 545 return strtr($str, $from, $to); 546 } elseif (!is_array($from)) { 547 throw new Twig_Error_Runtime(sprintf('The "replace" filter expects an array or "Traversable" as replace values, got "%s".',is_object($from) ? get_class($from) : gettype($from))); 548 } 549 550 return strtr($str, $from); 551 } 552 553 /** 554 * Rounds a number. 555 * 556 * @param int|float $value The value to round 557 * @param int|float $precision The rounding precision 558 * @param string $method The method to use for rounding 559 * 560 * @return int|float The rounded number 561 */ 562 function twig_round($value, $precision = 0, $method = 'common') 563 { 564 if ('common' == $method) { 565 return round($value, $precision); 566 } 567 568 if ('ceil' != $method && 'floor' != $method) { 569 throw new Twig_Error_Runtime('The round filter only supports the "common", "ceil", and "floor" methods.'); 570 } 571 572 return $method($value * pow(10, $precision)) / pow(10, $precision); 573 } 574 575 /** 576 * Number format filter. 577 * 578 * All of the formatting options can be left null, in that case the defaults will 579 * be used. Supplying any of the parameters will override the defaults set in the 580 * environment object. 581 * 582 * @param Twig_Environment $env A Twig_Environment instance 583 * @param mixed $number A float/int/string of the number to format 584 * @param int $decimal The number of decimal points to display. 585 * @param string $decimalPoint The character(s) to use for the decimal point. 586 * @param string $thousandSep The character(s) to use for the thousands separator. 587 * 588 * @return string The formatted number 589 */ 590 function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null) 591 { 592 $defaults = $env->getExtension('core')->getNumberFormat(); 593 if (null === $decimal) { 594 $decimal = $defaults[0]; 595 } 596 597 if (null === $decimalPoint) { 598 $decimalPoint = $defaults[1]; 599 } 600 601 if (null === $thousandSep) { 602 $thousandSep = $defaults[2]; 603 } 604 605 return number_format((float) $number, $decimal, $decimalPoint, $thousandSep); 606 } 607 608 /** 609 * URL encodes (RFC 3986) a string as a path segment or an array as a query string. 610 * 611 * @param string|array $url A URL or an array of query parameters 612 * 613 * @return string The URL encoded value 614 */ 615 function twig_urlencode_filter($url) 616 { 617 if (is_array($url)) { 618 if (defined('PHP_QUERY_RFC3986')) { 619 return http_build_query($url, '', '&', PHP_QUERY_RFC3986); 620 } 621 622 return http_build_query($url, '', '&'); 623 } 624 625 return rawurlencode($url); 626 } 627 628 if (PHP_VERSION_ID < 50300) { 629 /** 630 * JSON encodes a variable. 631 * 632 * @param mixed $value The value to encode. 633 * @param int $options Not used on PHP 5.2.x 634 * 635 * @return mixed The JSON encoded value 636 */ 637 function twig_jsonencode_filter($value, $options = 0) 638 { 639 if ($value instanceof Twig_Markup) { 640 $value = (string) $value; 641 } elseif (is_array($value)) { 642 array_walk_recursive($value, '_twig_markup2string'); 643 } 644 645 return json_encode($value); 646 } 647 } else { 648 /** 649 * JSON encodes a variable. 650 * 651 * @param mixed $value The value to encode. 652 * @param int $options Bitmask consisting of JSON_HEX_QUOT, JSON_HEX_TAG, JSON_HEX_AMP, JSON_HEX_APOS, JSON_NUMERIC_CHECK, JSON_PRETTY_PRINT, JSON_UNESCAPED_SLASHES, JSON_FORCE_OBJECT 653 * 654 * @return mixed The JSON encoded value 655 */ 656 function twig_jsonencode_filter($value, $options = 0) 657 { 658 if ($value instanceof Twig_Markup) { 659 $value = (string) $value; 660 } elseif (is_array($value)) { 661 array_walk_recursive($value, '_twig_markup2string'); 662 } 663 664 return json_encode($value, $options); 665 } 666 } 667 668 function _twig_markup2string(&$value) 669 { 670 if ($value instanceof Twig_Markup) { 671 $value = (string) $value; 672 } 673 } 674 675 /** 676 * Merges an array with another one. 677 * 678 * <pre> 679 * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %} 680 * 681 * {% set items = items|merge({ 'peugeot': 'car' }) %} 682 * 683 * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #} 684 * </pre> 685 * 686 * @param array|Traversable $arr1 An array 687 * @param array|Traversable $arr2 An array 688 * 689 * @return array The merged array 690 */ 691 function twig_array_merge($arr1, $arr2) 692 { 693 if ($arr1 instanceof Traversable) { 694 $arr1 = iterator_to_array($arr1); 695 } elseif (!is_array($arr1)) { 696 throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as first argument.', gettype($arr1))); 697 } 698 699 if ($arr2 instanceof Traversable) { 700 $arr2 = iterator_to_array($arr2); 701 } elseif (!is_array($arr2)) { 702 throw new Twig_Error_Runtime(sprintf('The merge filter only works with arrays or "Traversable", got "%s" as second argument.', gettype($arr2))); 703 } 704 705 return array_merge($arr1, $arr2); 706 } 707 708 /** 709 * Slices a variable. 710 * 711 * @param Twig_Environment $env A Twig_Environment instance 712 * @param mixed $item A variable 713 * @param int $start Start of the slice 714 * @param int $length Size of the slice 715 * @param bool $preserveKeys Whether to preserve key or not (when the input is an array) 716 * 717 * @return mixed The sliced variable 718 */ 719 function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false) 720 { 721 if ($item instanceof Traversable) { 722 if ($item instanceof IteratorAggregate) { 723 $item = $item->getIterator(); 724 } 725 726 if ($start >= 0 && $length >= 0 && $item instanceof Iterator) { 727 try { 728 return iterator_to_array(new LimitIterator($item, $start, $length === null ? -1 : $length), $preserveKeys); 729 } catch (OutOfBoundsException $exception) { 730 return array(); 731 } 732 } 733 734 $item = iterator_to_array($item, $preserveKeys); 735 } 736 737 if (is_array($item)) { 738 return array_slice($item, $start, $length, $preserveKeys); 739 } 740 741 $item = (string) $item; 742 743 if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) { 744 return (string) mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset); 745 } 746 747 return (string) (null === $length ? substr($item, $start) : substr($item, $start, $length)); 748 } 749 750 /** 751 * Returns the first element of the item. 752 * 753 * @param Twig_Environment $env A Twig_Environment instance 754 * @param mixed $item A variable 755 * 756 * @return mixed The first element of the item 757 */ 758 function twig_first(Twig_Environment $env, $item) 759 { 760 $elements = twig_slice($env, $item, 0, 1, false); 761 762 return is_string($elements) ? $elements : current($elements); 763 } 764 765 /** 766 * Returns the last element of the item. 767 * 768 * @param Twig_Environment $env A Twig_Environment instance 769 * @param mixed $item A variable 770 * 771 * @return mixed The last element of the item 772 */ 773 function twig_last(Twig_Environment $env, $item) 774 { 775 $elements = twig_slice($env, $item, -1, 1, false); 776 777 return is_string($elements) ? $elements : current($elements); 778 } 779 780 /** 781 * Joins the values to a string. 782 * 783 * The separator between elements is an empty string per default, you can define it with the optional parameter. 784 * 785 * <pre> 786 * {{ [1, 2, 3]|join('|') }} 787 * {# returns 1|2|3 #} 788 * 789 * {{ [1, 2, 3]|join }} 790 * {# returns 123 #} 791 * </pre> 792 * 793 * @param array $value An array 794 * @param string $glue The separator 795 * 796 * @return string The concatenated string 797 */ 798 function twig_join_filter($value, $glue = '') 799 { 800 if ($value instanceof Traversable) { 801 $value = iterator_to_array($value, false); 802 } 803 804 return implode($glue, (array) $value); 805 } 806 807 /** 808 * Splits the string into an array. 809 * 810 * <pre> 811 * {{ "one,two,three"|split(',') }} 812 * {# returns [one, two, three] #} 813 * 814 * {{ "one,two,three,four,five"|split(',', 3) }} 815 * {# returns [one, two, "three,four,five"] #} 816 * 817 * {{ "123"|split('') }} 818 * {# returns [1, 2, 3] #} 819 * 820 * {{ "aabbcc"|split('', 2) }} 821 * {# returns [aa, bb, cc] #} 822 * </pre> 823 * 824 * @param Twig_Environment $env A Twig_Environment instance 825 * @param string $value A string 826 * @param string $delimiter The delimiter 827 * @param int $limit The limit 828 * 829 * @return array The split string as an array 830 */ 831 function twig_split_filter(Twig_Environment $env, $value, $delimiter, $limit = null) 832 { 833 if (!empty($delimiter)) { 834 return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit); 835 } 836 837 if (!function_exists('mb_get_info') || null === $charset = $env->getCharset()) { 838 return str_split($value, null === $limit ? 1 : $limit); 839 } 840 841 if ($limit <= 1) { 842 return preg_split('/(?<!^)(?!$)/u', $value); 843 } 844 845 $length = mb_strlen($value, $charset); 846 if ($length < $limit) { 847 return array($value); 848 } 849 850 $r = array(); 851 for ($i = 0; $i < $length; $i += $limit) { 852 $r[] = mb_substr($value, $i, $limit, $charset); 853 } 854 855 return $r; 856 } 857 858 // The '_default' filter is used internally to avoid using the ternary operator 859 // which costs a lot for big contexts (before PHP 5.4). So, on average, 860 // a function call is cheaper. 861 /** 862 * @internal 863 */ 864 function _twig_default_filter($value, $default = '') 865 { 866 if (twig_test_empty($value)) { 867 return $default; 868 } 869 870 return $value; 871 } 872 873 /** 874 * Returns the keys for the given array. 875 * 876 * It is useful when you want to iterate over the keys of an array: 877 * 878 * <pre> 879 * {% for key in array|keys %} 880 * {# ... #} 881 * {% endfor %} 882 * </pre> 883 * 884 * @param array $array An array 885 * 886 * @return array The keys 887 */ 888 function twig_get_array_keys_filter($array) 889 { 890 if ($array instanceof Traversable) { 891 return array_keys(iterator_to_array($array)); 892 } 893 894 if (!is_array($array)) { 895 return array(); 896 } 897 898 return array_keys($array); 899 } 900 901 /** 902 * Reverses a variable. 903 * 904 * @param Twig_Environment $env A Twig_Environment instance 905 * @param array|Traversable|string $item An array, a Traversable instance, or a string 906 * @param bool $preserveKeys Whether to preserve key or not 907 * 908 * @return mixed The reversed input 909 */ 910 function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false) 911 { 912 if ($item instanceof Traversable) { 913 return array_reverse(iterator_to_array($item), $preserveKeys); 914 } 915 916 if (is_array($item)) { 917 return array_reverse($item, $preserveKeys); 918 } 919 920 if (null !== $charset = $env->getCharset()) { 921 $string = (string) $item; 922 923 if ('UTF-8' !== $charset) { 924 $item = twig_convert_encoding($string, 'UTF-8', $charset); 925 } 926 927 preg_match_all('/./us', $item, $matches); 928 929 $string = implode('', array_reverse($matches[0])); 930 931 if ('UTF-8' !== $charset) { 932 $string = twig_convert_encoding($string, $charset, 'UTF-8'); 933 } 934 935 return $string; 936 } 937 938 return strrev((string) $item); 939 } 940 941 /** 942 * Sorts an array. 943 * 944 * @param array|Traversable $array 945 * 946 * @return array 947 */ 948 function twig_sort_filter($array) 949 { 950 if ($array instanceof Traversable) { 951 $array = iterator_to_array($array); 952 } elseif (!is_array($array)) { 953 throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array))); 954 } 955 956 asort($array); 957 958 return $array; 959 } 960 961 /** 962 * @internal 963 */ 964 function twig_in_filter($value, $compare) 965 { 966 if (is_array($compare)) { 967 return in_array($value, $compare, is_object($value) || is_resource($value)); 968 } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) { 969 return '' === $value || false !== strpos($compare, (string) $value); 970 } elseif ($compare instanceof Traversable) { 971 return in_array($value, iterator_to_array($compare, false), is_object($value) || is_resource($value)); 972 } 973 974 return false; 975 } 976 977 /** 978 * Escapes a string. 979 * 980 * @param Twig_Environment $env A Twig_Environment instance 981 * @param string $string The value to be escaped 982 * @param string $strategy The escaping strategy 983 * @param string $charset The charset 984 * @param bool $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false) 985 * 986 * @return string 987 */ 988 function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false) 989 { 990 if ($autoescape && $string instanceof Twig_Markup) { 991 return $string; 992 } 993 994 if (!is_string($string)) { 995 if (is_object($string) && method_exists($string, '__toString')) { 996 $string = (string) $string; 997 } elseif (in_array($strategy, array('html', 'js', 'css', 'html_attr', 'url'))) { 998 return $string; 999 } 1000 } 1001 1002 if (null === $charset) { 1003 $charset = $env->getCharset(); 1004 } 1005 1006 switch ($strategy) { 1007 case 'html': 1008 // see http://php.net/htmlspecialchars 1009 1010 // Using a static variable to avoid initializing the array 1011 // each time the function is called. Moving the declaration on the 1012 // top of the function slow downs other escaping strategies. 1013 static $htmlspecialcharsCharsets; 1014 1015 if (null === $htmlspecialcharsCharsets) { 1016 if (defined('HHVM_VERSION')) { 1017 $htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true); 1018 } else { 1019 $htmlspecialcharsCharsets = array( 1020 'ISO-8859-1' => true, 'ISO8859-1' => true, 1021 'ISO-8859-15' => true, 'ISO8859-15' => true, 1022 'utf-8' => true, 'UTF-8' => true, 1023 'CP866' => true, 'IBM866' => true, '866' => true, 1024 'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true, 1025 '1251' => true, 1026 'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true, 1027 'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true, 1028 'BIG5' => true, '950' => true, 1029 'GB2312' => true, '936' => true, 1030 'BIG5-HKSCS' => true, 1031 'SHIFT_JIS' => true, 'SJIS' => true, '932' => true, 1032 'EUC-JP' => true, 'EUCJP' => true, 1033 'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true, 1034 ); 1035 } 1036 } 1037 1038 if (isset($htmlspecialcharsCharsets[$charset])) { 1039 return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); 1040 } 1041 1042 if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) { 1043 // cache the lowercase variant for future iterations 1044 $htmlspecialcharsCharsets[$charset] = true; 1045 1046 return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset); 1047 } 1048 1049 $string = twig_convert_encoding($string, 'UTF-8', $charset); 1050 $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); 1051 1052 return twig_convert_encoding($string, $charset, 'UTF-8'); 1053 1054 case 'js': 1055 // escape all non-alphanumeric characters 1056 // into their \xHH or \uHHHH representations 1057 if ('UTF-8' !== $charset) { 1058 $string = twig_convert_encoding($string, 'UTF-8', $charset); 1059 } 1060 1061 if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { 1062 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); 1063 } 1064 1065 $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string); 1066 1067 if ('UTF-8' !== $charset) { 1068 $string = twig_convert_encoding($string, $charset, 'UTF-8'); 1069 } 1070 1071 return $string; 1072 1073 case 'css': 1074 if ('UTF-8' !== $charset) { 1075 $string = twig_convert_encoding($string, 'UTF-8', $charset); 1076 } 1077 1078 if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { 1079 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); 1080 } 1081 1082 $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string); 1083 1084 if ('UTF-8' !== $charset) { 1085 $string = twig_convert_encoding($string, $charset, 'UTF-8'); 1086 } 1087 1088 return $string; 1089 1090 case 'html_attr': 1091 if ('UTF-8' !== $charset) { 1092 $string = twig_convert_encoding($string, 'UTF-8', $charset); 1093 } 1094 1095 if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) { 1096 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.'); 1097 } 1098 1099 $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string); 1100 1101 if ('UTF-8' !== $charset) { 1102 $string = twig_convert_encoding($string, $charset, 'UTF-8'); 1103 } 1104 1105 return $string; 1106 1107 case 'url': 1108 if (PHP_VERSION_ID < 50300) { 1109 return str_replace('%7E', '~', rawurlencode($string)); 1110 } 1111 1112 return rawurlencode($string); 1113 1114 default: 1115 static $escapers; 1116 1117 if (null === $escapers) { 1118 $escapers = $env->getExtension('core')->getEscapers(); 1119 } 1120 1121 if (isset($escapers[$strategy])) { 1122 return call_user_func($escapers[$strategy], $env, $string, $charset); 1123 } 1124 1125 $validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers))); 1126 1127 throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies)); 1128 } 1129 } 1130 1131 /** 1132 * @internal 1133 */ 1134 function twig_escape_filter_is_safe(Twig_Node $filterArgs) 1135 { 1136 foreach ($filterArgs as $arg) { 1137 if ($arg instanceof Twig_Node_Expression_Constant) { 1138 return array($arg->getAttribute('value')); 1139 } 1140 1141 return array(); 1142 } 1143 1144 return array('html'); 1145 } 1146 1147 if (function_exists('mb_convert_encoding')) { 1148 function twig_convert_encoding($string, $to, $from) 1149 { 1150 return mb_convert_encoding($string, $to, $from); 1151 } 1152 } elseif (function_exists('iconv')) { 1153 function twig_convert_encoding($string, $to, $from) 1154 { 1155 return iconv($from, $to, $string); 1156 } 1157 } else { 1158 function twig_convert_encoding($string, $to, $from) 1159 { 1160 throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).'); 1161 } 1162 } 1163 1164 function _twig_escape_js_callback($matches) 1165 { 1166 $char = $matches[0]; 1167 1168 // \xHH 1169 if (!isset($char[1])) { 1170 return '\\x'.strtoupper(substr('00'.bin2hex($char), -2)); 1171 } 1172 1173 // \uHHHH 1174 $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); 1175 1176 return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4)); 1177 } 1178 1179 function _twig_escape_css_callback($matches) 1180 { 1181 $char = $matches[0]; 1182 1183 // \xHH 1184 if (!isset($char[1])) { 1185 $hex = ltrim(strtoupper(bin2hex($char)), '0'); 1186 if (0 === strlen($hex)) { 1187 $hex = '0'; 1188 } 1189 1190 return '\\'.$hex.' '; 1191 } 1192 1193 // \uHHHH 1194 $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8'); 1195 1196 return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' '; 1197 } 1198 1199 /** 1200 * This function is adapted from code coming from Zend Framework. 1201 * 1202 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com) 1203 * @license http://framework.zend.com/license/new-bsd New BSD License 1204 */ 1205 function _twig_escape_html_attr_callback($matches) 1206 { 1207 /* 1208 * While HTML supports far more named entities, the lowest common denominator 1209 * has become HTML5's XML Serialisation which is restricted to the those named 1210 * entities that XML supports. Using HTML entities would result in this error: 1211 * XML Parsing Error: undefined entity 1212 */ 1213 static $entityMap = array( 1214 34 => 'quot', /* quotation mark */ 1215 38 => 'amp', /* ampersand */ 1216 60 => 'lt', /* less-than sign */ 1217 62 => 'gt', /* greater-than sign */ 1218 ); 1219 1220 $chr = $matches[0]; 1221 $ord = ord($chr); 1222 1223 /* 1224 * The following replaces characters undefined in HTML with the 1225 * hex entity for the Unicode replacement character. 1226 */ 1227 if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) { 1228 return '�'; 1229 } 1230 1231 /* 1232 * Check if the current character to escape has a name entity we should 1233 * replace it with while grabbing the hex value of the character. 1234 */ 1235 if (strlen($chr) == 1) { 1236 $hex = strtoupper(substr('00'.bin2hex($chr), -2)); 1237 } else { 1238 $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8'); 1239 $hex = strtoupper(substr('0000'.bin2hex($chr), -4)); 1240 } 1241 1242 $int = hexdec($hex); 1243 if (array_key_exists($int, $entityMap)) { 1244 return sprintf('&%s;', $entityMap[$int]); 1245 } 1246 1247 /* 1248 * Per OWASP recommendations, we'll use hex entities for any other 1249 * characters where a named entity does not exist. 1250 */ 1251 return sprintf('&#x%s;', $hex); 1252 } 1253 1254 // add multibyte extensions if possible 1255 if (function_exists('mb_get_info')) { 1256 /** 1257 * Returns the length of a variable. 1258 * 1259 * @param Twig_Environment $env A Twig_Environment instance 1260 * @param mixed $thing A variable 1261 * 1262 * @return int The length of the value 1263 */ 1264 function twig_length_filter(Twig_Environment $env, $thing) 1265 { 1266 return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing); 1267 } 1268 1269 /** 1270 * Converts a string to uppercase. 1271 * 1272 * @param Twig_Environment $env A Twig_Environment instance 1273 * @param string $string A string 1274 * 1275 * @return string The uppercased string 1276 */ 1277 function twig_upper_filter(Twig_Environment $env, $string) 1278 { 1279 if (null !== $charset = $env->getCharset()) { 1280 return mb_strtoupper($string, $charset); 1281 } 1282 1283 return strtoupper($string); 1284 } 1285 1286 /** 1287 * Converts a string to lowercase. 1288 * 1289 * @param Twig_Environment $env A Twig_Environment instance 1290 * @param string $string A string 1291 * 1292 * @return string The lowercased string 1293 */ 1294 function twig_lower_filter(Twig_Environment $env, $string) 1295 { 1296 if (null !== $charset = $env->getCharset()) { 1297 return mb_strtolower($string, $charset); 1298 } 1299 1300 return strtolower($string); 1301 } 1302 1303 /** 1304 * Returns a titlecased string. 1305 * 1306 * @param Twig_Environment $env A Twig_Environment instance 1307 * @param string $string A string 1308 * 1309 * @return string The titlecased string 1310 */ 1311 function twig_title_string_filter(Twig_Environment $env, $string) 1312 { 1313 if (null !== $charset = $env->getCharset()) { 1314 return mb_convert_case($string, MB_CASE_TITLE, $charset); 1315 } 1316 1317 return ucwords(strtolower($string)); 1318 } 1319 1320 /** 1321 * Returns a capitalized string. 1322 * 1323 * @param Twig_Environment $env A Twig_Environment instance 1324 * @param string $string A string 1325 * 1326 * @return string The capitalized string 1327 */ 1328 function twig_capitalize_string_filter(Twig_Environment $env, $string) 1329 { 1330 if (null !== $charset = $env->getCharset()) { 1331 return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset); 1332 } 1333 1334 return ucfirst(strtolower($string)); 1335 } 1336 } 1337 // and byte fallback 1338 else { 1339 /** 1340 * Returns the length of a variable. 1341 * 1342 * @param Twig_Environment $env A Twig_Environment instance 1343 * @param mixed $thing A variable 1344 * 1345 * @return int The length of the value 1346 */ 1347 function twig_length_filter(Twig_Environment $env, $thing) 1348 { 1349 return is_scalar($thing) ? strlen($thing) : count($thing); 1350 } 1351 1352 /** 1353 * Returns a titlecased string. 1354 * 1355 * @param Twig_Environment $env A Twig_Environment instance 1356 * @param string $string A string 1357 * 1358 * @return string The titlecased string 1359 */ 1360 function twig_title_string_filter(Twig_Environment $env, $string) 1361 { 1362 return ucwords(strtolower($string)); 1363 } 1364 1365 /** 1366 * Returns a capitalized string. 1367 * 1368 * @param Twig_Environment $env A Twig_Environment instance 1369 * @param string $string A string 1370 * 1371 * @return string The capitalized string 1372 */ 1373 function twig_capitalize_string_filter(Twig_Environment $env, $string) 1374 { 1375 return ucfirst(strtolower($string)); 1376 } 1377 } 1378 1379 /** 1380 * @internal 1381 */ 1382 function twig_ensure_traversable($seq) 1383 { 1384 if ($seq instanceof Traversable || is_array($seq)) { 1385 return $seq; 1386 } 1387 1388 return array(); 1389 } 1390 1391 /** 1392 * Checks if a variable is empty. 1393 * 1394 * <pre> 1395 * {# evaluates to true if the foo variable is null, false, or the empty string #} 1396 * {% if foo is empty %} 1397 * {# ... #} 1398 * {% endif %} 1399 * </pre> 1400 * 1401 * @param mixed $value A variable 1402 * 1403 * @return bool true if the value is empty, false otherwise 1404 */ 1405 function twig_test_empty($value) 1406 { 1407 if ($value instanceof Countable) { 1408 return 0 == count($value); 1409 } 1410 1411 return '' === $value || false === $value || null === $value || array() === $value; 1412 } 1413 1414 /** 1415 * Checks if a variable is traversable. 1416 * 1417 * <pre> 1418 * {# evaluates to true if the foo variable is an array or a traversable object #} 1419 * {% if foo is traversable %} 1420 * {# ... #} 1421 * {% endif %} 1422 * </pre> 1423 * 1424 * @param mixed $value A variable 1425 * 1426 * @return bool true if the value is traversable 1427 */ 1428 function twig_test_iterable($value) 1429 { 1430 return $value instanceof Traversable || is_array($value); 1431 } 1432 1433 /** 1434 * Renders a template. 1435 * 1436 * @param Twig_Environment $env 1437 * @param array $context 1438 * @param string|array $template The template to render or an array of templates to try consecutively 1439 * @param array $variables The variables to pass to the template 1440 * @param bool $withContext 1441 * @param bool $ignoreMissing Whether to ignore missing templates or not 1442 * @param bool $sandboxed Whether to sandbox the template or not 1443 * 1444 * @return string The rendered template 1445 */ 1446 function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false) 1447 { 1448 $alreadySandboxed = false; 1449 $sandbox = null; 1450 if ($withContext) { 1451 $variables = array_merge($context, $variables); 1452 } 1453 1454 if ($isSandboxed = $sandboxed && $env->hasExtension('sandbox')) { 1455 $sandbox = $env->getExtension('sandbox'); 1456 if (!$alreadySandboxed = $sandbox->isSandboxed()) { 1457 $sandbox->enableSandbox(); 1458 } 1459 } 1460 1461 $result = null; 1462 try { 1463 $result = $env->resolveTemplate($template)->render($variables); 1464 } catch (Twig_Error_Loader $e) { 1465 if (!$ignoreMissing) { 1466 if ($isSandboxed && !$alreadySandboxed) { 1467 $sandbox->disableSandbox(); 1468 } 1469 1470 throw $e; 1471 } 1472 } 1473 1474 if ($isSandboxed && !$alreadySandboxed) { 1475 $sandbox->disableSandbox(); 1476 } 1477 1478 return $result; 1479 } 1480 1481 /** 1482 * Returns a template content without rendering it. 1483 * 1484 * @param Twig_Environment $env 1485 * @param string $name The template name 1486 * @param bool $ignoreMissing Whether to ignore missing templates or not 1487 * 1488 * @return string The template source 1489 */ 1490 function twig_source(Twig_Environment $env, $name, $ignoreMissing = false) 1491 { 1492 try { 1493 return $env->getLoader()->getSource($name); 1494 } catch (Twig_Error_Loader $e) { 1495 if (!$ignoreMissing) { 1496 throw $e; 1497 } 1498 } 1499 } 1500 1501 /** 1502 * Provides the ability to get constants from instances as well as class/global constants. 1503 * 1504 * @param string $constant The name of the constant 1505 * @param null|object $object The object to get the constant from 1506 * 1507 * @return string 1508 */ 1509 function twig_constant($constant, $object = null) 1510 { 1511 if (null !== $object) { 1512 $constant = get_class($object).'::'.$constant; 1513 } 1514 1515 return constant($constant); 1516 } 1517 1518 /** 1519 * Batches item. 1520 * 1521 * @param array $items An array of items 1522 * @param int $size The size of the batch 1523 * @param mixed $fill A value used to fill missing items 1524 * 1525 * @return array 1526 */ 1527 function twig_array_batch($items, $size, $fill = null) 1528 { 1529 if ($items instanceof Traversable) { 1530 $items = iterator_to_array($items, false); 1531 } 1532 1533 $size = ceil($size); 1534 1535 $result = array_chunk($items, $size, true); 1536 1537 if (null !== $fill && !empty($result)) { 1538 $last = count($result) - 1; 1539 if ($fillCount = $size - count($result[$last])) { 1540 $result[$last] = array_merge( 1541 $result[$last], 1542 array_fill(0, $fillCount, $fill) 1543 ); 1544 } 1545 } 1546 1547 return $result; 1548 }