4963a1679f30abd57f5db7325e52b94482871beb
[yaffs-website] / vendor / twig / twig / lib / Twig / Extension / Core.php
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) 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  */
20 class 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', 'twig_trim_filter'),
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  */
288 function 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  */
310 function 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  */
370 function 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  */
397 function 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  */
423 function 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  */
475 function 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  */
499 function 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  */
527 function 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  */
552 function 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
565 if (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
605 function _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  */
628 function 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  */
656 function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
657 {
658     if ($item instanceof Traversable) {
659         while ($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  */
695 function 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  */
710 function 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  */
735 function 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  */
768 function 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  */
801 function _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  */
825 function twig_get_array_keys_filter($array)
826 {
827     if ($array instanceof Traversable) {
828         while ($array instanceof IteratorAggregate) {
829             $array = $array->getIterator();
830         }
831
832         if ($array instanceof Iterator) {
833             $keys = array();
834             $array->rewind();
835             while ($array->valid()) {
836                 $keys[] = $array->key();
837                 $array->next();
838             }
839
840             return $keys;
841         }
842
843         $keys = array();
844         foreach ($array as $key => $item) {
845             $keys[] = $key;
846         }
847
848         return $keys;
849     }
850
851     if (!is_array($array)) {
852         return array();
853     }
854
855     return array_keys($array);
856 }
857
858 /**
859  * Reverses a variable.
860  *
861  * @param Twig_Environment         $env
862  * @param array|Traversable|string $item         An array, a Traversable instance, or a string
863  * @param bool                     $preserveKeys Whether to preserve key or not
864  *
865  * @return mixed The reversed input
866  */
867 function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
868 {
869     if ($item instanceof Traversable) {
870         return array_reverse(iterator_to_array($item), $preserveKeys);
871     }
872
873     if (is_array($item)) {
874         return array_reverse($item, $preserveKeys);
875     }
876
877     if (null !== $charset = $env->getCharset()) {
878         $string = (string) $item;
879
880         if ('UTF-8' !== $charset) {
881             $item = twig_convert_encoding($string, 'UTF-8', $charset);
882         }
883
884         preg_match_all('/./us', $item, $matches);
885
886         $string = implode('', array_reverse($matches[0]));
887
888         if ('UTF-8' !== $charset) {
889             $string = twig_convert_encoding($string, $charset, 'UTF-8');
890         }
891
892         return $string;
893     }
894
895     return strrev((string) $item);
896 }
897
898 /**
899  * Sorts an array.
900  *
901  * @param array|Traversable $array
902  *
903  * @return array
904  */
905 function twig_sort_filter($array)
906 {
907     if ($array instanceof Traversable) {
908         $array = iterator_to_array($array);
909     } elseif (!is_array($array)) {
910         throw new Twig_Error_Runtime(sprintf('The sort filter only works with arrays or "Traversable", got "%s".', gettype($array)));
911     }
912
913     asort($array);
914
915     return $array;
916 }
917
918 /**
919  * @internal
920  */
921 function twig_in_filter($value, $compare)
922 {
923     if (is_array($compare)) {
924         return in_array($value, $compare, is_object($value) || is_resource($value));
925     } elseif (is_string($compare) && (is_string($value) || is_int($value) || is_float($value))) {
926         return '' === $value || false !== strpos($compare, (string) $value);
927     } elseif ($compare instanceof Traversable) {
928         if (is_object($value) || is_resource($value)) {
929             foreach ($compare as $item) {
930                 if ($item === $value) {
931                     return true;
932                 }
933             }
934         } else {
935             foreach ($compare as $item) {
936                 if ($item == $value) {
937                     return true;
938                 }
939             }
940         }
941
942         return false;
943     }
944
945     return false;
946 }
947
948 /**
949  * Returns a trimmed string.
950  *
951  * @return string
952  *
953  * @throws Twig_Error_Runtime When an invalid trimming side is used (not a string or not 'left', 'right', or 'both')
954  */
955 function twig_trim_filter($string, $characterMask = null, $side = 'both')
956 {
957     if (null === $characterMask) {
958         $characterMask = " \t\n\r\0\x0B";
959     }
960
961     switch ($side) {
962         case 'both':
963             return trim($string, $characterMask);
964         case 'left':
965             return ltrim($string, $characterMask);
966         case 'right':
967             return rtrim($string, $characterMask);
968         default:
969             throw new Twig_Error_Runtime('Trimming side must be "left", "right" or "both".');
970     }
971 }
972
973 /**
974  * Escapes a string.
975  *
976  * @param Twig_Environment $env
977  * @param mixed            $string     The value to be escaped
978  * @param string           $strategy   The escaping strategy
979  * @param string           $charset    The charset
980  * @param bool             $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
981  *
982  * @return string
983  */
984 function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
985 {
986     if ($autoescape && $string instanceof Twig_Markup) {
987         return $string;
988     }
989
990     if (!is_string($string)) {
991         if (is_object($string) && method_exists($string, '__toString')) {
992             $string = (string) $string;
993         } elseif (in_array($strategy, array('html', 'js', 'css', 'html_attr', 'url'))) {
994             return $string;
995         }
996     }
997
998     if (null === $charset) {
999         $charset = $env->getCharset();
1000     }
1001
1002     switch ($strategy) {
1003         case 'html':
1004             // see http://php.net/htmlspecialchars
1005
1006             // Using a static variable to avoid initializing the array
1007             // each time the function is called. Moving the declaration on the
1008             // top of the function slow downs other escaping strategies.
1009             static $htmlspecialcharsCharsets;
1010
1011             if (null === $htmlspecialcharsCharsets) {
1012                 if (defined('HHVM_VERSION')) {
1013                     $htmlspecialcharsCharsets = array('utf-8' => true, 'UTF-8' => true);
1014                 } else {
1015                     $htmlspecialcharsCharsets = array(
1016                         'ISO-8859-1' => true, 'ISO8859-1' => true,
1017                         'ISO-8859-15' => true, 'ISO8859-15' => true,
1018                         'utf-8' => true, 'UTF-8' => true,
1019                         'CP866' => true, 'IBM866' => true, '866' => true,
1020                         'CP1251' => true, 'WINDOWS-1251' => true, 'WIN-1251' => true,
1021                         '1251' => true,
1022                         'CP1252' => true, 'WINDOWS-1252' => true, '1252' => true,
1023                         'KOI8-R' => true, 'KOI8-RU' => true, 'KOI8R' => true,
1024                         'BIG5' => true, '950' => true,
1025                         'GB2312' => true, '936' => true,
1026                         'BIG5-HKSCS' => true,
1027                         'SHIFT_JIS' => true, 'SJIS' => true, '932' => true,
1028                         'EUC-JP' => true, 'EUCJP' => true,
1029                         'ISO8859-5' => true, 'ISO-8859-5' => true, 'MACROMAN' => true,
1030                     );
1031                 }
1032             }
1033
1034             if (isset($htmlspecialcharsCharsets[$charset])) {
1035                 return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
1036             }
1037
1038             if (isset($htmlspecialcharsCharsets[strtoupper($charset)])) {
1039                 // cache the lowercase variant for future iterations
1040                 $htmlspecialcharsCharsets[$charset] = true;
1041
1042                 return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
1043             }
1044
1045             $string = twig_convert_encoding($string, 'UTF-8', $charset);
1046             $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
1047
1048             return twig_convert_encoding($string, $charset, 'UTF-8');
1049
1050         case 'js':
1051             // escape all non-alphanumeric characters
1052             // into their \xHH or \uHHHH representations
1053             if ('UTF-8' !== $charset) {
1054                 $string = twig_convert_encoding($string, 'UTF-8', $charset);
1055             }
1056
1057             if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) {
1058                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1059             }
1060
1061             $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string);
1062
1063             if ('UTF-8' !== $charset) {
1064                 $string = twig_convert_encoding($string, $charset, 'UTF-8');
1065             }
1066
1067             return $string;
1068
1069         case 'css':
1070             if ('UTF-8' !== $charset) {
1071                 $string = twig_convert_encoding($string, 'UTF-8', $charset);
1072             }
1073
1074             if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) {
1075                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1076             }
1077
1078             $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string);
1079
1080             if ('UTF-8' !== $charset) {
1081                 $string = twig_convert_encoding($string, $charset, 'UTF-8');
1082             }
1083
1084             return $string;
1085
1086         case 'html_attr':
1087             if ('UTF-8' !== $charset) {
1088                 $string = twig_convert_encoding($string, 'UTF-8', $charset);
1089             }
1090
1091             if (0 == strlen($string) ? false : 1 !== preg_match('/^./su', $string)) {
1092                 throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
1093             }
1094
1095             $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string);
1096
1097             if ('UTF-8' !== $charset) {
1098                 $string = twig_convert_encoding($string, $charset, 'UTF-8');
1099             }
1100
1101             return $string;
1102
1103         case 'url':
1104             if (PHP_VERSION_ID < 50300) {
1105                 return str_replace('%7E', '~', rawurlencode($string));
1106             }
1107
1108             return rawurlencode($string);
1109
1110         default:
1111             static $escapers;
1112
1113             if (null === $escapers) {
1114                 $escapers = $env->getExtension('Twig_Extension_Core')->getEscapers();
1115             }
1116
1117             if (isset($escapers[$strategy])) {
1118                 return call_user_func($escapers[$strategy], $env, $string, $charset);
1119             }
1120
1121             $validStrategies = implode(', ', array_merge(array('html', 'js', 'url', 'css', 'html_attr'), array_keys($escapers)));
1122
1123             throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: %s).', $strategy, $validStrategies));
1124     }
1125 }
1126
1127 /**
1128  * @internal
1129  */
1130 function twig_escape_filter_is_safe(Twig_Node $filterArgs)
1131 {
1132     foreach ($filterArgs as $arg) {
1133         if ($arg instanceof Twig_Node_Expression_Constant) {
1134             return array($arg->getAttribute('value'));
1135         }
1136
1137         return array();
1138     }
1139
1140     return array('html');
1141 }
1142
1143 if (function_exists('mb_convert_encoding')) {
1144     function twig_convert_encoding($string, $to, $from)
1145     {
1146         return mb_convert_encoding($string, $to, $from);
1147     }
1148 } elseif (function_exists('iconv')) {
1149     function twig_convert_encoding($string, $to, $from)
1150     {
1151         return iconv($from, $to, $string);
1152     }
1153 } else {
1154     function twig_convert_encoding($string, $to, $from)
1155     {
1156         throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
1157     }
1158 }
1159
1160 function _twig_escape_js_callback($matches)
1161 {
1162     $char = $matches[0];
1163
1164     // \xHH
1165     if (!isset($char[1])) {
1166         return '\\x'.strtoupper(substr('00'.bin2hex($char), -2));
1167     }
1168
1169     // \uHHHH
1170     $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
1171     $char = strtoupper(bin2hex($char));
1172
1173     if (4 >= strlen($char)) {
1174         return sprintf('\u%04s', $char);
1175     }
1176
1177     return sprintf('\u%04s\u%04s', substr($char, 0, -4), substr($char, -4));
1178 }
1179
1180 function _twig_escape_css_callback($matches)
1181 {
1182     $char = $matches[0];
1183
1184     // \xHH
1185     if (!isset($char[1])) {
1186         $hex = ltrim(strtoupper(bin2hex($char)), '0');
1187         if (0 === strlen($hex)) {
1188             $hex = '0';
1189         }
1190
1191         return '\\'.$hex.' ';
1192     }
1193
1194     // \uHHHH
1195     $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
1196
1197     return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' ';
1198 }
1199
1200 /**
1201  * This function is adapted from code coming from Zend Framework.
1202  *
1203  * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
1204  * @license   http://framework.zend.com/license/new-bsd New BSD License
1205  */
1206 function _twig_escape_html_attr_callback($matches)
1207 {
1208     /*
1209      * While HTML supports far more named entities, the lowest common denominator
1210      * has become HTML5's XML Serialisation which is restricted to the those named
1211      * entities that XML supports. Using HTML entities would result in this error:
1212      *     XML Parsing Error: undefined entity
1213      */
1214     static $entityMap = array(
1215         34 => 'quot', /* quotation mark */
1216         38 => 'amp',  /* ampersand */
1217         60 => 'lt',   /* less-than sign */
1218         62 => 'gt',   /* greater-than sign */
1219     );
1220
1221     $chr = $matches[0];
1222     $ord = ord($chr);
1223
1224     /*
1225      * The following replaces characters undefined in HTML with the
1226      * hex entity for the Unicode replacement character.
1227      */
1228     if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
1229         return '&#xFFFD;';
1230     }
1231
1232     /*
1233      * Check if the current character to escape has a name entity we should
1234      * replace it with while grabbing the hex value of the character.
1235      */
1236     if (strlen($chr) == 1) {
1237         $hex = strtoupper(substr('00'.bin2hex($chr), -2));
1238     } else {
1239         $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
1240         $hex = strtoupper(substr('0000'.bin2hex($chr), -4));
1241     }
1242
1243     $int = hexdec($hex);
1244     if (array_key_exists($int, $entityMap)) {
1245         return sprintf('&%s;', $entityMap[$int]);
1246     }
1247
1248     /*
1249      * Per OWASP recommendations, we'll use hex entities for any other
1250      * characters where a named entity does not exist.
1251      */
1252     return sprintf('&#x%s;', $hex);
1253 }
1254
1255 // add multibyte extensions if possible
1256 if (function_exists('mb_get_info')) {
1257     /**
1258      * Returns the length of a variable.
1259      *
1260      * @param Twig_Environment $env
1261      * @param mixed            $thing A variable
1262      *
1263      * @return int The length of the value
1264      */
1265     function twig_length_filter(Twig_Environment $env, $thing)
1266     {
1267         if (is_scalar($thing)) {
1268             return mb_strlen($thing, $env->getCharset());
1269         }
1270
1271         if (method_exists($thing, '__toString') && !$thing instanceof \Countable) {
1272             return mb_strlen((string) $thing, $env->getCharset());
1273         }
1274
1275         return count($thing);
1276     }
1277
1278     /**
1279      * Converts a string to uppercase.
1280      *
1281      * @param Twig_Environment $env
1282      * @param string           $string A string
1283      *
1284      * @return string The uppercased string
1285      */
1286     function twig_upper_filter(Twig_Environment $env, $string)
1287     {
1288         if (null !== $charset = $env->getCharset()) {
1289             return mb_strtoupper($string, $charset);
1290         }
1291
1292         return strtoupper($string);
1293     }
1294
1295     /**
1296      * Converts a string to lowercase.
1297      *
1298      * @param Twig_Environment $env
1299      * @param string           $string A string
1300      *
1301      * @return string The lowercased string
1302      */
1303     function twig_lower_filter(Twig_Environment $env, $string)
1304     {
1305         if (null !== $charset = $env->getCharset()) {
1306             return mb_strtolower($string, $charset);
1307         }
1308
1309         return strtolower($string);
1310     }
1311
1312     /**
1313      * Returns a titlecased string.
1314      *
1315      * @param Twig_Environment $env
1316      * @param string           $string A string
1317      *
1318      * @return string The titlecased string
1319      */
1320     function twig_title_string_filter(Twig_Environment $env, $string)
1321     {
1322         if (null !== $charset = $env->getCharset()) {
1323             return mb_convert_case($string, MB_CASE_TITLE, $charset);
1324         }
1325
1326         return ucwords(strtolower($string));
1327     }
1328
1329     /**
1330      * Returns a capitalized string.
1331      *
1332      * @param Twig_Environment $env
1333      * @param string           $string A string
1334      *
1335      * @return string The capitalized string
1336      */
1337     function twig_capitalize_string_filter(Twig_Environment $env, $string)
1338     {
1339         if (null !== $charset = $env->getCharset()) {
1340             return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
1341         }
1342
1343         return ucfirst(strtolower($string));
1344     }
1345 }
1346 // and byte fallback
1347 else {
1348     /**
1349      * Returns the length of a variable.
1350      *
1351      * @param Twig_Environment $env
1352      * @param mixed            $thing A variable
1353      *
1354      * @return int The length of the value
1355      */
1356     function twig_length_filter(Twig_Environment $env, $thing)
1357     {
1358         if (is_scalar($thing)) {
1359             return strlen($thing);
1360         }
1361
1362         if (method_exists($thing, '__toString') && !$thing instanceof \Countable) {
1363             return strlen((string) $thing);
1364         }
1365
1366         return count($thing);
1367     }
1368
1369     /**
1370      * Returns a titlecased string.
1371      *
1372      * @param Twig_Environment $env
1373      * @param string           $string A string
1374      *
1375      * @return string The titlecased string
1376      */
1377     function twig_title_string_filter(Twig_Environment $env, $string)
1378     {
1379         return ucwords(strtolower($string));
1380     }
1381
1382     /**
1383      * Returns a capitalized string.
1384      *
1385      * @param Twig_Environment $env
1386      * @param string           $string A string
1387      *
1388      * @return string The capitalized string
1389      */
1390     function twig_capitalize_string_filter(Twig_Environment $env, $string)
1391     {
1392         return ucfirst(strtolower($string));
1393     }
1394 }
1395
1396 /**
1397  * @internal
1398  */
1399 function twig_ensure_traversable($seq)
1400 {
1401     if ($seq instanceof Traversable || is_array($seq)) {
1402         return $seq;
1403     }
1404
1405     return array();
1406 }
1407
1408 /**
1409  * Checks if a variable is empty.
1410  *
1411  * <pre>
1412  * {# evaluates to true if the foo variable is null, false, or the empty string #}
1413  * {% if foo is empty %}
1414  *     {# ... #}
1415  * {% endif %}
1416  * </pre>
1417  *
1418  * @param mixed $value A variable
1419  *
1420  * @return bool true if the value is empty, false otherwise
1421  */
1422 function twig_test_empty($value)
1423 {
1424     if ($value instanceof Countable) {
1425         return 0 == count($value);
1426     }
1427
1428     if (method_exists($value, '__toString')) {
1429         return '' === (string) $value;
1430     }
1431
1432     return '' === $value || false === $value || null === $value || array() === $value;
1433 }
1434
1435 /**
1436  * Checks if a variable is traversable.
1437  *
1438  * <pre>
1439  * {# evaluates to true if the foo variable is an array or a traversable object #}
1440  * {% if foo is traversable %}
1441  *     {# ... #}
1442  * {% endif %}
1443  * </pre>
1444  *
1445  * @param mixed $value A variable
1446  *
1447  * @return bool true if the value is traversable
1448  */
1449 function twig_test_iterable($value)
1450 {
1451     return $value instanceof Traversable || is_array($value);
1452 }
1453
1454 /**
1455  * Renders a template.
1456  *
1457  * @param Twig_Environment $env
1458  * @param array            $context
1459  * @param string|array     $template      The template to render or an array of templates to try consecutively
1460  * @param array            $variables     The variables to pass to the template
1461  * @param bool             $withContext
1462  * @param bool             $ignoreMissing Whether to ignore missing templates or not
1463  * @param bool             $sandboxed     Whether to sandbox the template or not
1464  *
1465  * @return string The rendered template
1466  */
1467 function twig_include(Twig_Environment $env, $context, $template, $variables = array(), $withContext = true, $ignoreMissing = false, $sandboxed = false)
1468 {
1469     $alreadySandboxed = false;
1470     $sandbox = null;
1471     if ($withContext) {
1472         $variables = array_merge($context, $variables);
1473     }
1474
1475     if ($isSandboxed = $sandboxed && $env->hasExtension('Twig_Extension_Sandbox')) {
1476         $sandbox = $env->getExtension('Twig_Extension_Sandbox');
1477         if (!$alreadySandboxed = $sandbox->isSandboxed()) {
1478             $sandbox->enableSandbox();
1479         }
1480     }
1481
1482     $result = null;
1483     try {
1484         $result = $env->resolveTemplate($template)->render($variables);
1485     } catch (Twig_Error_Loader $e) {
1486         if (!$ignoreMissing) {
1487             if ($isSandboxed && !$alreadySandboxed) {
1488                 $sandbox->disableSandbox();
1489             }
1490
1491             throw $e;
1492         }
1493     } catch (Throwable $e) {
1494         if ($isSandboxed && !$alreadySandboxed) {
1495             $sandbox->disableSandbox();
1496         }
1497
1498         throw $e;
1499     } catch (Exception $e) {
1500         if ($isSandboxed && !$alreadySandboxed) {
1501             $sandbox->disableSandbox();
1502         }
1503
1504         throw $e;
1505     }
1506
1507     if ($isSandboxed && !$alreadySandboxed) {
1508         $sandbox->disableSandbox();
1509     }
1510
1511     return $result;
1512 }
1513
1514 /**
1515  * Returns a template content without rendering it.
1516  *
1517  * @param Twig_Environment $env
1518  * @param string           $name          The template name
1519  * @param bool             $ignoreMissing Whether to ignore missing templates or not
1520  *
1521  * @return string The template source
1522  */
1523 function twig_source(Twig_Environment $env, $name, $ignoreMissing = false)
1524 {
1525     $loader = $env->getLoader();
1526     try {
1527         if (!$loader instanceof Twig_SourceContextLoaderInterface) {
1528             return $loader->getSource($name);
1529         } else {
1530             return $loader->getSourceContext($name)->getCode();
1531         }
1532     } catch (Twig_Error_Loader $e) {
1533         if (!$ignoreMissing) {
1534             throw $e;
1535         }
1536     }
1537 }
1538
1539 /**
1540  * Provides the ability to get constants from instances as well as class/global constants.
1541  *
1542  * @param string      $constant The name of the constant
1543  * @param null|object $object   The object to get the constant from
1544  *
1545  * @return string
1546  */
1547 function twig_constant($constant, $object = null)
1548 {
1549     if (null !== $object) {
1550         $constant = get_class($object).'::'.$constant;
1551     }
1552
1553     return constant($constant);
1554 }
1555
1556 /**
1557  * Checks if a constant exists.
1558  *
1559  * @param string      $constant The name of the constant
1560  * @param null|object $object   The object to get the constant from
1561  *
1562  * @return bool
1563  */
1564 function twig_constant_is_defined($constant, $object = null)
1565 {
1566     if (null !== $object) {
1567         $constant = get_class($object).'::'.$constant;
1568     }
1569
1570     return defined($constant);
1571 }
1572
1573 /**
1574  * Batches item.
1575  *
1576  * @param array $items An array of items
1577  * @param int   $size  The size of the batch
1578  * @param mixed $fill  A value used to fill missing items
1579  *
1580  * @return array
1581  */
1582 function twig_array_batch($items, $size, $fill = null)
1583 {
1584     if ($items instanceof Traversable) {
1585         $items = iterator_to_array($items, false);
1586     }
1587
1588     $size = ceil($size);
1589
1590     $result = array_chunk($items, $size, true);
1591
1592     if (null !== $fill && !empty($result)) {
1593         $last = count($result) - 1;
1594         if ($fillCount = $size - count($result[$last])) {
1595             $result[$last] = array_merge(
1596                 $result[$last],
1597                 array_fill(0, $fillCount, $fill)
1598             );
1599         }
1600     }
1601
1602     return $result;
1603 }