Dotclear

source: inc/libs/Twig/Extension/Core.php @ 1149:1657e862089c

Revision 1149:1657e862089c, 42.9 KB checked in by dsls, 13 years ago (diff)

Fixed unix case-sensitive twig directory

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

Sites map