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