Dotclear

source: inc/libs/twig/Extension/Core.php @ 991:e42f791e0975

Revision 991:e42f791e0975, 39.9 KB checked in by Dsls <dsls@…>, 13 years ago (diff)

New twig branch. 1st step : add twig 1.11.1

Line 
1<?php
2
3if (!defined('ENT_SUBSTITUTE')) {
4    define('ENT_SUBSTITUTE', 8);
5}
6
7/*
8 * This file is part of Twig.
9 *
10 * (c) 2009 Fabien Potencier
11 *
12 * For the full copyright and license information, please view the LICENSE
13 * file that was distributed with this source code.
14 */
15class Twig_Extension_Core extends Twig_Extension
16{
17    protected $dateFormats = array('F j, Y H:i', '%d days');
18    protected $numberFormat = array(0, '.', ',');
19    protected $timezone = null;
20
21    /**
22     * Sets the default format to be used by the date filter.
23     *
24     * @param string $format             The default date format string
25     * @param string $dateIntervalFormat The default date interval format string
26     */
27    public function setDateFormat($format = null, $dateIntervalFormat = null)
28    {
29        if (null !== $format) {
30            $this->dateFormats[0] = $format;
31        }
32
33        if (null !== $dateIntervalFormat) {
34            $this->dateFormats[1] = $dateIntervalFormat;
35        }
36    }
37
38    /**
39     * Gets the default format to be used by the date filter.
40     *
41     * @return array The default date format string and the default date interval format string
42     */
43    public function getDateFormat()
44    {
45        return $this->dateFormats;
46    }
47
48    /**
49     * Sets the default timezone to be used by the date filter.
50     *
51     * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
52     */
53    public function setTimezone($timezone)
54    {
55        $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
56    }
57
58    /**
59     * Gets the default timezone to be used by the date filter.
60     *
61     * @return DateTimeZone The default timezone currently in use
62     */
63    public function getTimezone()
64    {
65        if (null === $this->timezone) {
66            $this->timezone = new DateTimeZone(date_default_timezone_get());
67        }
68
69        return $this->timezone;
70    }
71
72    /**
73     * Sets the default format to be used by the number_format filter.
74     *
75     * @param integer $decimal      The number of decimal places to use.
76     * @param string  $decimalPoint The character(s) to use for the decimal point.
77     * @param string  $thousandSep  The character(s) to use for the thousands separator.
78     */
79    public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
80    {
81        $this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
82    }
83
84    /**
85     * Get the default format used by the number_format filter.
86     *
87     * @return array The arguments for number_format()
88     */
89    public function getNumberFormat()
90    {
91        return $this->numberFormat;
92    }
93
94    /**
95     * Returns the token parser instance to add to the existing list.
96     *
97     * @return array An array of Twig_TokenParser instances
98     */
99    public function getTokenParsers()
100    {
101        return array(
102            new Twig_TokenParser_For(),
103            new Twig_TokenParser_If(),
104            new Twig_TokenParser_Extends(),
105            new Twig_TokenParser_Include(),
106            new Twig_TokenParser_Block(),
107            new Twig_TokenParser_Use(),
108            new Twig_TokenParser_Filter(),
109            new Twig_TokenParser_Macro(),
110            new Twig_TokenParser_Import(),
111            new Twig_TokenParser_From(),
112            new Twig_TokenParser_Set(),
113            new Twig_TokenParser_Spaceless(),
114            new Twig_TokenParser_Flush(),
115            new Twig_TokenParser_Do(),
116            new Twig_TokenParser_Embed(),
117        );
118    }
119
120    /**
121     * Returns a list of filters to add to the existing list.
122     *
123     * @return array An array of filters
124     */
125    public function getFilters()
126    {
127        $filters = array(
128            // formatting filters
129            'date'          => new Twig_Filter_Function('twig_date_format_filter', array('needs_environment' => true)),
130            'date_modify'   => new Twig_Filter_Function('twig_date_modify_filter', array('needs_environment' => true)),
131            'format'        => new Twig_Filter_Function('sprintf'),
132            'replace'       => new Twig_Filter_Function('strtr'),
133            'number_format' => new Twig_Filter_Function('twig_number_format_filter', array('needs_environment' => true)),
134            'abs'           => new Twig_Filter_Function('abs'),
135
136            // encoding
137            'url_encode'       => new Twig_Filter_Function('twig_urlencode_filter'),
138            'json_encode'      => new Twig_Filter_Function('twig_jsonencode_filter'),
139            'convert_encoding' => new Twig_Filter_Function('twig_convert_encoding'),
140
141            // string filters
142            'title'      => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)),
143            'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)),
144            'upper'      => new Twig_Filter_Function('strtoupper'),
145            'lower'      => new Twig_Filter_Function('strtolower'),
146            'striptags'  => new Twig_Filter_Function('strip_tags'),
147            'trim'       => new Twig_Filter_Function('trim'),
148            'nl2br'      => new Twig_Filter_Function('nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
149
150            // array helpers
151            'join'    => new Twig_Filter_Function('twig_join_filter'),
152            'split'   => new Twig_Filter_Function('twig_split_filter'),
153            'sort'    => new Twig_Filter_Function('twig_sort_filter'),
154            'merge'   => new Twig_Filter_Function('twig_array_merge'),
155
156            // string/array filters
157            'reverse' => new Twig_Filter_Function('twig_reverse_filter', array('needs_environment' => true)),
158            'length'  => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)),
159            'slice'   => new Twig_Filter_Function('twig_slice', array('needs_environment' => true)),
160
161            // iteration and runtime
162            'default' => new Twig_Filter_Node('Twig_Node_Expression_Filter_Default'),
163            '_default' => new Twig_Filter_Function('_twig_default_filter'),
164
165            'keys'    => new Twig_Filter_Function('twig_get_array_keys_filter'),
166
167            // escaping
168            'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
169            'e'      => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
170        );
171
172        if (function_exists('mb_get_info')) {
173            $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true));
174            $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true));
175        }
176
177        return $filters;
178    }
179
180    /**
181     * Returns a list of global functions to add to the existing list.
182     *
183     * @return array An array of global functions
184     */
185    public function getFunctions()
186    {
187        return array(
188            'range'    => new Twig_Function_Function('range'),
189            'constant' => new Twig_Function_Function('constant'),
190            'cycle'    => new Twig_Function_Function('twig_cycle'),
191            'random'   => new Twig_Function_Function('twig_random', array('needs_environment' => true)),
192            'date'     => new Twig_Function_Function('twig_date_converter', array('needs_environment' => true)),
193        );
194    }
195
196    /**
197     * Returns a list of tests to add to the existing list.
198     *
199     * @return array An array of tests
200     */
201    public function getTests()
202    {
203        return array(
204            'even'        => new Twig_Test_Node('Twig_Node_Expression_Test_Even'),
205            'odd'         => new Twig_Test_Node('Twig_Node_Expression_Test_Odd'),
206            'defined'     => new Twig_Test_Node('Twig_Node_Expression_Test_Defined'),
207            'sameas'      => new Twig_Test_Node('Twig_Node_Expression_Test_Sameas'),
208            'none'        => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
209            'null'        => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
210            'divisibleby' => new Twig_Test_Node('Twig_Node_Expression_Test_Divisibleby'),
211            'constant'    => new Twig_Test_Node('Twig_Node_Expression_Test_Constant'),
212            'empty'       => new Twig_Test_Function('twig_test_empty'),
213            'iterable'    => new Twig_Test_Function('twig_test_iterable'),
214        );
215    }
216
217    /**
218     * Returns a list of operators to add to the existing list.
219     *
220     * @return array An array of operators
221     */
222    public function getOperators()
223    {
224        return array(
225            array(
226                'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
227                '-'   => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
228                '+'   => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
229            ),
230            array(
231                'or'     => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
232                'and'    => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
233                'b-or'   => array('precedence' => 16, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
234                'b-xor'  => array('precedence' => 17, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
235                'b-and'  => array('precedence' => 18, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
236                '=='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
237                '!='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
238                '<'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
239                '>'      => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
240                '>='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
241                '<='     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
242                'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
243                'in'     => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
244                '..'     => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
245                '+'      => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
246                '-'      => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
247                '~'      => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
248                '*'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
249                '/'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
250                '//'     => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
251                '%'      => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
252                'is'     => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
253                'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
254                '**'     => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
255            ),
256        );
257    }
258
259    public function parseNotTestExpression(Twig_Parser $parser, $node)
260    {
261        return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
262    }
263
264    public function parseTestExpression(Twig_Parser $parser, $node)
265    {
266        $stream = $parser->getStream();
267        $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
268        $arguments = null;
269        if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
270            $arguments = $parser->getExpressionParser()->parseArguments();
271        }
272
273        $class = $this->getTestNodeClass($parser->getEnvironment(), $name);
274
275        return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
276    }
277
278    protected function getTestNodeClass(Twig_Environment $env, $name)
279    {
280        $testMap = $env->getTests();
281        if (isset($testMap[$name]) && $testMap[$name] instanceof Twig_Test_Node) {
282            return $testMap[$name]->getClass();
283        }
284
285        return 'Twig_Node_Expression_Test';
286    }
287
288    /**
289     * Returns the name of the extension.
290     *
291     * @return string The extension name
292     */
293    public function getName()
294    {
295        return 'core';
296    }
297}
298
299/**
300 * Cycles over a value.
301 *
302 * @param ArrayAccess|array $values An array or an ArrayAccess instance
303 * @param integer           $i      The cycle value
304 *
305 * @return string The next value in the cycle
306 */
307function twig_cycle($values, $i)
308{
309    if (!is_array($values) && !$values instanceof ArrayAccess) {
310        return $values;
311    }
312
313    return $values[$i % count($values)];
314}
315
316/**
317 * Returns a random value depending on the supplied parameter type:
318 * - a random item from a Traversable or array
319 * - a random character from a string
320 * - a random integer between 0 and the integer parameter
321 *
322 * @param Twig_Environment                 $env    A Twig_Environment instance
323 * @param Traversable|array|integer|string $values The values to pick a random item from
324 *
325 * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
326 *
327 * @return mixed A random value from the given sequence
328 */
329function twig_random(Twig_Environment $env, $values = null)
330{
331    if (null === $values) {
332        return mt_rand();
333    }
334
335    if (is_int($values) || is_float($values)) {
336        return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
337    }
338
339    if ($values instanceof Traversable) {
340        $values = iterator_to_array($values);
341    } elseif (is_string($values)) {
342        if ('' === $values) {
343            return '';
344        }
345        if (null !== $charset = $env->getCharset()) {
346            if ('UTF-8' != $charset) {
347                $values = twig_convert_encoding($values, 'UTF-8', $charset);
348            }
349
350            // unicode version of str_split()
351            // split at all positions, but not after the start and not before the end
352            $values = preg_split('/(?<!^)(?!$)/u', $values);
353
354            if ('UTF-8' != $charset) {
355                foreach ($values as $i => $value) {
356                    $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
357                }
358            }
359        } else {
360            return $values[mt_rand(0, strlen($values) - 1)];
361        }
362    }
363
364    if (!is_array($values)) {
365        return $values;
366    }
367
368    if (0 === count($values)) {
369        throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
370    }
371
372    return $values[array_rand($values, 1)];
373}
374
375/**
376 * Converts a date to the given format.
377 *
378 * <pre>
379 *   {{ post.published_at|date("m/d/Y") }}
380 * </pre>
381 *
382 * @param Twig_Environment             $env      A Twig_Environment instance
383 * @param DateTime|DateInterval|string $date     A date
384 * @param string                       $format   A format
385 * @param DateTimeZone|string          $timezone A timezone
386 *
387 * @return string The formatted date
388 */
389function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
390{
391    if (null === $format) {
392        $formats = $env->getExtension('core')->getDateFormat();
393        $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
394    }
395
396    if ($date instanceof DateInterval) {
397        return $date->format($format);
398    }
399
400    return twig_date_converter($env, $date, $timezone)->format($format);
401}
402
403/**
404 * Returns a new date object modified
405 *
406 * <pre>
407 *   {{ post.published_at|modify("-1day")|date("m/d/Y") }}
408 * </pre>
409 *
410 * @param Twig_Environment  $env      A Twig_Environment instance
411 * @param DateTime|string   $date     A date
412 * @param string            $modifier A modifier string
413 *
414 * @return DateTime A new date object
415 */
416function twig_date_modify_filter(Twig_Environment $env, $date, $modifier)
417{
418    $date = twig_date_converter($env, $date, false);
419    $date->modify($modifier);
420
421    return $date;
422}
423
424/**
425 * Converts an input to a DateTime instance.
426 *
427 * <pre>
428 *    {% if date(user.created_at) < date('+2days') %}
429 *      {# do something #}
430 *    {% endif %}
431 * </pre>
432 *
433 * @param Twig_Environment    $env      A Twig_Environment instance
434 * @param DateTime|string     $date     A date
435 * @param DateTimeZone|string $timezone A timezone
436 *
437 * @return DateTime A DateTime instance
438 */
439function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
440{
441    // determine the timezone
442    if (!$timezone) {
443        $defaultTimezone = $env->getExtension('core')->getTimezone();
444    } elseif (!$timezone instanceof DateTimeZone) {
445        $defaultTimezone = new DateTimeZone($timezone);
446    } else {
447        $defaultTimezone = $timezone;
448    }
449
450    if ($date instanceof DateTime) {
451        $date = clone $date;
452        if (false !== $timezone) {
453            $date->setTimezone($defaultTimezone);
454        }
455
456        return $date;
457    }
458
459    $asString = (string) $date;
460    if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
461        $date = new DateTime('@'.$date);
462        $date->setTimezone($defaultTimezone);
463
464        return $date;
465    }
466
467    return new DateTime($date, $defaultTimezone);
468}
469
470/**
471 * Number format filter.
472 *
473 * All of the formatting options can be left null, in that case the defaults will
474 * be used.  Supplying any of the parameters will override the defaults set in the
475 * environment object.
476 *
477 * @param Twig_Environment    $env          A Twig_Environment instance
478 * @param mixed               $number       A float/int/string of the number to format
479 * @param integer             $decimal      The number of decimal points to display.
480 * @param string              $decimalPoint The character(s) to use for the decimal point.
481 * @param string              $thousandSep  The character(s) to use for the thousands separator.
482 *
483 * @return string The formatted number
484 */
485function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
486{
487    $defaults = $env->getExtension('core')->getNumberFormat();
488    if (null === $decimal) {
489        $decimal = $defaults[0];
490    }
491
492    if (null === $decimalPoint) {
493        $decimalPoint = $defaults[1];
494    }
495
496    if (null === $thousandSep) {
497        $thousandSep = $defaults[2];
498    }
499
500    return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
501}
502
503/**
504 * URL encodes a string.
505 *
506 * @param string $url A URL
507 * @param bool   $raw true to use rawurlencode() instead of urlencode
508 *
509 * @return string The URL encoded value
510 */
511function twig_urlencode_filter($url, $raw = false)
512{
513    if ($raw) {
514        return rawurlencode($url);
515    }
516
517    return urlencode($url);
518}
519
520if (version_compare(PHP_VERSION, '5.3.0', '<')) {
521    /**
522     * JSON encodes a variable.
523     *
524     * @param mixed   $value   The value to encode.
525     * @param integer $options Not used on PHP 5.2.x
526     *
527     * @return mixed The JSON encoded value
528     */
529    function twig_jsonencode_filter($value, $options = 0)
530    {
531        if ($value instanceof Twig_Markup) {
532            $value = (string) $value;
533        } elseif (is_array($value)) {
534            array_walk_recursive($value, '_twig_markup2string');
535        }
536
537        return json_encode($value);
538    }
539} else {
540    /**
541     * JSON encodes a variable.
542     *
543     * @param mixed   $value   The value to encode.
544     * @param integer $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
545     *
546     * @return mixed The JSON encoded value
547     */
548    function twig_jsonencode_filter($value, $options = 0)
549    {
550        if ($value instanceof Twig_Markup) {
551            $value = (string) $value;
552        } elseif (is_array($value)) {
553            array_walk_recursive($value, '_twig_markup2string');
554        }
555
556        return json_encode($value, $options);
557    }
558}
559
560function _twig_markup2string(&$value)
561{
562    if ($value instanceof Twig_Markup) {
563        $value = (string) $value;
564    }
565}
566
567/**
568 * Merges an array with another one.
569 *
570 * <pre>
571 *  {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
572 *
573 *  {% set items = items|merge({ 'peugeot': 'car' }) %}
574 *
575 *  {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
576 * </pre>
577 *
578 * @param array $arr1 An array
579 * @param array $arr2 An array
580 *
581 * @return array The merged array
582 */
583function twig_array_merge($arr1, $arr2)
584{
585    if (!is_array($arr1) || !is_array($arr2)) {
586        throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.');
587    }
588
589    return array_merge($arr1, $arr2);
590}
591
592/**
593 * Slices a variable.
594 *
595 * @param Twig_Environment $env          A Twig_Environment instance
596 * @param mixed            $item         A variable
597 * @param integer          $start        Start of the slice
598 * @param integer          $length       Size of the slice
599 * @param Boolean          $preserveKeys Whether to preserve key or not (when the input is an array)
600 *
601 * @return mixed The sliced variable
602 */
603function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
604{
605    if ($item instanceof Traversable) {
606        $item = iterator_to_array($item, false);
607    }
608
609    if (is_array($item)) {
610        return array_slice($item, $start, $length, $preserveKeys);
611    }
612
613    $item = (string) $item;
614
615    if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
616        return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
617    }
618
619    return null === $length ? substr($item, $start) : substr($item, $start, $length);
620}
621
622/**
623 * Joins the values to a string.
624 *
625 * The separator between elements is an empty string per default, you can define it with the optional parameter.
626 *
627 * <pre>
628 *  {{ [1, 2, 3]|join('|') }}
629 *  {# returns 1|2|3 #}
630 *
631 *  {{ [1, 2, 3]|join }}
632 *  {# returns 123 #}
633 * </pre>
634 *
635 * @param array  $value An array
636 * @param string $glue  The separator
637 *
638 * @return string The concatenated string
639 */
640function twig_join_filter($value, $glue = '')
641{
642    if ($value instanceof Traversable) {
643        $value = iterator_to_array($value, false);
644    }
645
646    return implode($glue, (array) $value);
647}
648
649/**
650 * Splits the string into an array.
651 *
652 * <pre>
653 *  {{ "one,two,three"|split(',') }}
654 *  {# returns [one, two, three] #}
655 *
656 *  {{ "one,two,three,four,five"|split(',', 3) }}
657 *  {# returns [one, two, "three,four,five"] #}
658 *
659 *  {{ "123"|split('') }}
660 *  {# returns [1, 2, 3] #}
661 *
662 *  {{ "aabbcc"|split('', 2) }}
663 *  {# returns [aa, bb, cc] #}
664 * </pre>
665 *
666 * @param string  $value     A string
667 * @param string  $delimiter The delimiter
668 * @param integer $limit     The limit
669 *
670 * @return array The split string as an array
671 */
672function twig_split_filter($value, $delimiter, $limit = null)
673{
674    if (empty($delimiter)) {
675        return str_split($value, null === $limit ? 1 : $limit);
676    }
677
678    return null === $limit ? explode($delimiter, $value) : explode($delimiter, $value, $limit);
679}
680
681// The '_default' filter is used internally to avoid using the ternary operator
682// which costs a lot for big contexts (before PHP 5.4). So, on average,
683// a function call is cheaper.
684function _twig_default_filter($value, $default = '')
685{
686    if (twig_test_empty($value)) {
687        return $default;
688    }
689
690    return $value;
691}
692
693/**
694 * Returns the keys for the given array.
695 *
696 * It is useful when you want to iterate over the keys of an array:
697 *
698 * <pre>
699 *  {% for key in array|keys %}
700 *      {# ... #}
701 *  {% endfor %}
702 * </pre>
703 *
704 * @param array $array An array
705 *
706 * @return array The keys
707 */
708function twig_get_array_keys_filter($array)
709{
710    if (is_object($array) && $array instanceof Traversable) {
711        return array_keys(iterator_to_array($array));
712    }
713
714    if (!is_array($array)) {
715        return array();
716    }
717
718    return array_keys($array);
719}
720
721/**
722 * Reverses a variable.
723 *
724 * @param Twig_Environment         $env          A Twig_Environment instance
725 * @param array|Traversable|string $item         An array, a Traversable instance, or a string
726 * @param Boolean                  $preserveKeys Whether to preserve key or not
727 *
728 * @return mixed The reversed input
729 */
730function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
731{
732    if (is_object($item) && $item instanceof Traversable) {
733        return array_reverse(iterator_to_array($item), $preserveKeys);
734    }
735
736    if (is_array($item)) {
737        return array_reverse($item, $preserveKeys);
738    }
739
740    if (null !== $charset = $env->getCharset()) {
741        $string = (string) $item;
742
743        if ('UTF-8' != $charset) {
744            $item = twig_convert_encoding($string, 'UTF-8', $charset);
745        }
746
747        preg_match_all('/./us', $item, $matches);
748
749        $string = implode('', array_reverse($matches[0]));
750
751        if ('UTF-8' != $charset) {
752            $string = twig_convert_encoding($string, $charset, 'UTF-8');
753        }
754
755        return $string;
756    }
757
758    return strrev((string) $item);
759}
760
761/**
762 * Sorts an array.
763 *
764 * @param array $array An array
765 */
766function twig_sort_filter($array)
767{
768    asort($array);
769
770    return $array;
771}
772
773/* used internally */
774function twig_in_filter($value, $compare)
775{
776    $strict = is_object($value);
777
778    if (is_array($compare)) {
779        return in_array($value, $compare, $strict);
780    } elseif (is_string($compare)) {
781        if (!strlen((string) $value)) {
782            return empty($compare);
783        }
784
785        return false !== strpos($compare, (string) $value);
786    } elseif (is_object($compare) && $compare instanceof Traversable) {
787        return in_array($value, iterator_to_array($compare, false), $strict);
788    }
789
790    return false;
791}
792
793/**
794 * Escapes a string.
795 *
796 * @param Twig_Environment $env        A Twig_Environment instance
797 * @param string           $string     The value to be escaped
798 * @param string           $strategy   The escaping strategy
799 * @param string           $charset    The charset
800 * @param Boolean          $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
801 */
802function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
803{
804    if ($autoescape && is_object($string) && $string instanceof Twig_Markup) {
805        return $string;
806    }
807
808    if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
809        return $string;
810    }
811
812    if (null === $charset) {
813        $charset = $env->getCharset();
814    }
815
816    $string = (string) $string;
817
818    switch ($strategy) {
819        case 'js':
820            // escape all non-alphanumeric characters
821            // into their \xHH or \uHHHH representations
822            if ('UTF-8' != $charset) {
823                $string = twig_convert_encoding($string, 'UTF-8', $charset);
824            }
825
826            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
827                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
828            }
829
830            $string = preg_replace_callback('#[^a-zA-Z0-9,\._]#Su', '_twig_escape_js_callback', $string);
831
832            if ('UTF-8' != $charset) {
833                $string = twig_convert_encoding($string, $charset, 'UTF-8');
834            }
835
836            return $string;
837
838        case 'css':
839            if ('UTF-8' != $charset) {
840                $string = twig_convert_encoding($string, 'UTF-8', $charset);
841            }
842
843            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
844                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
845            }
846
847            $string = preg_replace_callback('#[^a-zA-Z0-9]#Su', '_twig_escape_css_callback', $string);
848
849            if ('UTF-8' != $charset) {
850                $string = twig_convert_encoding($string, $charset, 'UTF-8');
851            }
852
853            return $string;
854
855        case 'html_attr':
856            if ('UTF-8' != $charset) {
857                $string = twig_convert_encoding($string, 'UTF-8', $charset);
858            }
859
860            if (0 == strlen($string) ? false : (1 == preg_match('/^./su', $string) ? false : true)) {
861                throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
862            }
863
864            $string = preg_replace_callback('#[^a-zA-Z0-9,\.\-_]#Su', '_twig_escape_html_attr_callback', $string);
865
866            if ('UTF-8' != $charset) {
867                $string = twig_convert_encoding($string, $charset, 'UTF-8');
868            }
869
870            return $string;
871
872        case 'html':
873            // see http://php.net/htmlspecialchars
874
875            // Using a static variable to avoid initializing the array
876            // each time the function is called. Moving the declaration on the
877            // top of the function slow downs other escaping strategies.
878            static $htmlspecialcharsCharsets = array(
879                'iso-8859-1' => true, 'iso8859-1' => true,
880                'iso-8859-15' => true, 'iso8859-15' => true,
881                'utf-8' => true,
882                'cp866' => true, 'ibm866' => true, '866' => true,
883                'cp1251' => true, 'windows-1251' => true, 'win-1251' => true,
884                '1251' => true,
885                'cp1252' => true, 'windows-1252' => true, '1252' => true,
886                'koi8-r' => true, 'koi8-ru' => true, 'koi8r' => true,
887                'big5' => true, '950' => true,
888                'gb2312' => true, '936' => true,
889                'big5-hkscs' => true,
890                'shift_jis' => true, 'sjis' => true, '932' => true,
891                'euc-jp' => true, 'eucjp' => true,
892                'iso8859-5' => true, 'iso-8859-5' => true, 'macroman' => true,
893            );
894
895            if (isset($htmlspecialcharsCharsets[strtolower($charset)])) {
896                return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
897            }
898
899            $string = twig_convert_encoding($string, 'UTF-8', $charset);
900            $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
901
902            return twig_convert_encoding($string, $charset, 'UTF-8');
903
904        case 'url':
905            if (version_compare(PHP_VERSION, '5.3.0', '<')) {
906                return str_replace('%7E', '~', rawurlencode($string));
907            }
908
909            return rawurlencode($string);
910
911        default:
912            throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js, url, css, and html_attr).', $strategy));
913    }
914}
915
916/* used internally */
917function twig_escape_filter_is_safe(Twig_Node $filterArgs)
918{
919    foreach ($filterArgs as $arg) {
920        if ($arg instanceof Twig_Node_Expression_Constant) {
921            return array($arg->getAttribute('value'));
922        }
923
924        return array();
925    }
926
927    return array('html');
928}
929
930if (function_exists('mb_convert_encoding')) {
931    function twig_convert_encoding($string, $to, $from)
932    {
933        return mb_convert_encoding($string, $to, $from);
934    }
935} elseif (function_exists('iconv')) {
936    function twig_convert_encoding($string, $to, $from)
937    {
938        return iconv($from, $to, $string);
939    }
940} else {
941    function twig_convert_encoding($string, $to, $from)
942    {
943        throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
944    }
945}
946
947function _twig_escape_js_callback($matches)
948{
949    $char = $matches[0];
950
951    // \xHH
952    if (!isset($char[1])) {
953        return '\\x'.strtoupper(substr('00'.bin2hex($char), -2));
954    }
955
956    // \uHHHH
957    $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
958
959    return '\\u'.strtoupper(substr('0000'.bin2hex($char), -4));
960}
961
962function _twig_escape_css_callback($matches)
963{
964    $char = $matches[0];
965
966    // \xHH
967    if (!isset($char[1])) {
968        $hex = ltrim(strtoupper(bin2hex($char)), '0');
969        if (0 === strlen($hex)) {
970            $hex = '0';
971        }
972
973        return '\\'.$hex.' ';
974    }
975
976    // \uHHHH
977    $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
978
979    return '\\'.ltrim(strtoupper(bin2hex($char)), '0').' ';
980}
981
982/**
983 * This function is adapted from code coming from Zend Framework.
984 *
985 * @copyright Copyright (c) 2005-2012 Zend Technologies USA Inc. (http://www.zend.com)
986 * @license   http://framework.zend.com/license/new-bsd New BSD License
987 */
988function _twig_escape_html_attr_callback($matches)
989{
990    /*
991     * While HTML supports far more named entities, the lowest common denominator
992     * has become HTML5's XML Serialisation which is restricted to the those named
993     * entities that XML supports. Using HTML entities would result in this error:
994     *     XML Parsing Error: undefined entity
995     */
996    static $entityMap = array(
997        34 => 'quot', /* quotation mark */
998        38 => 'amp',  /* ampersand */
999        60 => 'lt',   /* less-than sign */
1000        62 => 'gt',   /* greater-than sign */
1001    );
1002
1003    $chr = $matches[0];
1004    $ord = ord($chr);
1005
1006    /**
1007     * The following replaces characters undefined in HTML with the
1008     * hex entity for the Unicode replacement character.
1009     */
1010    if (($ord <= 0x1f && $chr != "\t" && $chr != "\n" && $chr != "\r") || ($ord >= 0x7f && $ord <= 0x9f)) {
1011        return '&#xFFFD;';
1012    }
1013
1014    /**
1015     * Check if the current character to escape has a name entity we should
1016     * replace it with while grabbing the hex value of the character.
1017     */
1018    if (strlen($chr) == 1) {
1019        $hex = strtoupper(substr('00'.bin2hex($chr), -2));
1020    } else {
1021        $chr = twig_convert_encoding($chr, 'UTF-16BE', 'UTF-8');
1022        $hex = strtoupper(substr('0000'.bin2hex($chr), -4));
1023    }
1024
1025    $int = hexdec($hex);
1026    if (array_key_exists($int, $entityMap)) {
1027        return sprintf('&%s;', $entityMap[$int]);
1028    }
1029
1030    /**
1031     * Per OWASP recommendations, we'll use hex entities for any other
1032     * characters where a named entity does not exist.
1033     */
1034
1035    return sprintf('&#x%s;', $hex);
1036}
1037
1038// add multibyte extensions if possible
1039if (function_exists('mb_get_info')) {
1040    /**
1041     * Returns the length of a variable.
1042     *
1043     * @param Twig_Environment $env   A Twig_Environment instance
1044     * @param mixed            $thing A variable
1045     *
1046     * @return integer The length of the value
1047     */
1048    function twig_length_filter(Twig_Environment $env, $thing)
1049    {
1050        return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
1051    }
1052
1053    /**
1054     * Converts a string to uppercase.
1055     *
1056     * @param Twig_Environment $env    A Twig_Environment instance
1057     * @param string           $string A string
1058     *
1059     * @return string The uppercased string
1060     */
1061    function twig_upper_filter(Twig_Environment $env, $string)
1062    {
1063        if (null !== ($charset = $env->getCharset())) {
1064            return mb_strtoupper($string, $charset);
1065        }
1066
1067        return strtoupper($string);
1068    }
1069
1070    /**
1071     * Converts a string to lowercase.
1072     *
1073     * @param Twig_Environment $env    A Twig_Environment instance
1074     * @param string           $string A string
1075     *
1076     * @return string The lowercased string
1077     */
1078    function twig_lower_filter(Twig_Environment $env, $string)
1079    {
1080        if (null !== ($charset = $env->getCharset())) {
1081            return mb_strtolower($string, $charset);
1082        }
1083
1084        return strtolower($string);
1085    }
1086
1087    /**
1088     * Returns a titlecased string.
1089     *
1090     * @param Twig_Environment $env    A Twig_Environment instance
1091     * @param string           $string A string
1092     *
1093     * @return string The titlecased string
1094     */
1095    function twig_title_string_filter(Twig_Environment $env, $string)
1096    {
1097        if (null !== ($charset = $env->getCharset())) {
1098            return mb_convert_case($string, MB_CASE_TITLE, $charset);
1099        }
1100
1101        return ucwords(strtolower($string));
1102    }
1103
1104    /**
1105     * Returns a capitalized string.
1106     *
1107     * @param Twig_Environment $env    A Twig_Environment instance
1108     * @param string           $string A string
1109     *
1110     * @return string The capitalized string
1111     */
1112    function twig_capitalize_string_filter(Twig_Environment $env, $string)
1113    {
1114        if (null !== ($charset = $env->getCharset())) {
1115            return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).
1116                         mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
1117        }
1118
1119        return ucfirst(strtolower($string));
1120    }
1121}
1122// and byte fallback
1123else {
1124    /**
1125     * Returns the length of a variable.
1126     *
1127     * @param Twig_Environment $env   A Twig_Environment instance
1128     * @param mixed            $thing A variable
1129     *
1130     * @return integer The length of the value
1131     */
1132    function twig_length_filter(Twig_Environment $env, $thing)
1133    {
1134        return is_scalar($thing) ? strlen($thing) : count($thing);
1135    }
1136
1137    /**
1138     * Returns a titlecased string.
1139     *
1140     * @param Twig_Environment $env    A Twig_Environment instance
1141     * @param string           $string A string
1142     *
1143     * @return string The titlecased string
1144     */
1145    function twig_title_string_filter(Twig_Environment $env, $string)
1146    {
1147        return ucwords(strtolower($string));
1148    }
1149
1150    /**
1151     * Returns a capitalized string.
1152     *
1153     * @param Twig_Environment $env    A Twig_Environment instance
1154     * @param string           $string A string
1155     *
1156     * @return string The capitalized string
1157     */
1158    function twig_capitalize_string_filter(Twig_Environment $env, $string)
1159    {
1160        return ucfirst(strtolower($string));
1161    }
1162}
1163
1164/* used internally */
1165function twig_ensure_traversable($seq)
1166{
1167    if ($seq instanceof Traversable || is_array($seq)) {
1168        return $seq;
1169    }
1170
1171    return array();
1172}
1173
1174/**
1175 * Checks if a variable is empty.
1176 *
1177 * <pre>
1178 * {# evaluates to true if the foo variable is null, false, or the empty string #}
1179 * {% if foo is empty %}
1180 *     {# ... #}
1181 * {% endif %}
1182 * </pre>
1183 *
1184 * @param mixed $value A variable
1185 *
1186 * @return Boolean true if the value is empty, false otherwise
1187 */
1188function twig_test_empty($value)
1189{
1190    if ($value instanceof Countable) {
1191        return 0 == count($value);
1192    }
1193
1194    return false === $value || (empty($value) && '0' != $value);
1195}
1196
1197/**
1198 * Checks if a variable is traversable.
1199 *
1200 * <pre>
1201 * {# evaluates to true if the foo variable is an array or a traversable object #}
1202 * {% if foo is traversable %}
1203 *     {# ... #}
1204 * {% endif %}
1205 * </pre>
1206 *
1207 * @param mixed $value A variable
1208 *
1209 * @return Boolean true if the value is traversable
1210 */
1211function twig_test_iterable($value)
1212{
1213    return $value instanceof Traversable || is_array($value);
1214}
Note: See TracBrowser for help on using the repository browser.

Sites map