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