PageRenderTime 61ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/vendor/twig/twig/lib/Twig/Extension/Core.php

https://bitbucket.org/ingsol/music_sonata
PHP | 1037 lines | 550 code | 126 blank | 361 comment | 96 complexity | 6a593e152cc17f267a4f5a0d893d27f1 MD5 | raw file
Possible License(s): BSD-2-Clause, LGPL-2.1, Apache-2.0, JSON, LGPL-3.0, BSD-3-Clause
  1. <?php
  2. if (!defined('ENT_SUBSTITUTE')) {
  3. define('ENT_SUBSTITUTE', 8);
  4. }
  5. /*
  6. * This file is part of Twig.
  7. *
  8. * (c) 2009 Fabien Potencier
  9. *
  10. * For the full copyright and license information, please view the LICENSE
  11. * file that was distributed with this source code.
  12. */
  13. class Twig_Extension_Core extends Twig_Extension
  14. {
  15. protected $dateFormats = array('F j, Y H:i', '%d days');
  16. protected $numberFormat = array(0, '.', ',');
  17. protected $timezone = null;
  18. /**
  19. * Sets the default format to be used by the date filter.
  20. *
  21. * @param string $format The default date format string
  22. * @param string $dateIntervalFormat The default date interval format string
  23. */
  24. public function setDateFormat($format = null, $dateIntervalFormat = null)
  25. {
  26. if (null !== $format) {
  27. $this->dateFormats[0] = $format;
  28. }
  29. if (null !== $dateIntervalFormat) {
  30. $this->dateFormats[1] = $dateIntervalFormat;
  31. }
  32. }
  33. /**
  34. * Gets the default format to be used by the date filter.
  35. *
  36. * @return array The default date format string and the default date interval format string
  37. */
  38. public function getDateFormat()
  39. {
  40. return $this->dateFormats;
  41. }
  42. /**
  43. * Sets the default timezone to be used by the date filter.
  44. *
  45. * @param DateTimeZone|string $timezone The default timezone string or a DateTimeZone object
  46. */
  47. public function setTimezone($timezone)
  48. {
  49. $this->timezone = $timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone);
  50. }
  51. /**
  52. * Gets the default timezone to be used by the date filter.
  53. *
  54. * @return DateTimeZone The default timezone currently in use
  55. */
  56. public function getTimezone()
  57. {
  58. return $this->timezone;
  59. }
  60. /**
  61. * Sets the default format to be used by the number_format filter.
  62. *
  63. * @param integer $decimal The number of decimal places to use.
  64. * @param string $decimalPoint The character(s) to use for the decimal point.
  65. * @param string $thousandSep The character(s) to use for the thousands separator.
  66. */
  67. public function setNumberFormat($decimal, $decimalPoint, $thousandSep)
  68. {
  69. $this->numberFormat = array($decimal, $decimalPoint, $thousandSep);
  70. }
  71. /**
  72. * Get the default format used by the number_format filter.
  73. *
  74. * @return array The arguments for number_format()
  75. */
  76. public function getNumberFormat()
  77. {
  78. return $this->numberFormat;
  79. }
  80. /**
  81. * Returns the token parser instance to add to the existing list.
  82. *
  83. * @return array An array of Twig_TokenParser instances
  84. */
  85. public function getTokenParsers()
  86. {
  87. return array(
  88. new Twig_TokenParser_For(),
  89. new Twig_TokenParser_If(),
  90. new Twig_TokenParser_Extends(),
  91. new Twig_TokenParser_Include(),
  92. new Twig_TokenParser_Block(),
  93. new Twig_TokenParser_Use(),
  94. new Twig_TokenParser_Filter(),
  95. new Twig_TokenParser_Macro(),
  96. new Twig_TokenParser_Import(),
  97. new Twig_TokenParser_From(),
  98. new Twig_TokenParser_Set(),
  99. new Twig_TokenParser_Spaceless(),
  100. new Twig_TokenParser_Flush(),
  101. new Twig_TokenParser_Do(),
  102. new Twig_TokenParser_Embed(),
  103. );
  104. }
  105. /**
  106. * Returns a list of filters to add to the existing list.
  107. *
  108. * @return array An array of filters
  109. */
  110. public function getFilters()
  111. {
  112. $filters = array(
  113. // formatting filters
  114. 'date' => new Twig_Filter_Function('twig_date_format_filter', array('needs_environment' => true)),
  115. 'format' => new Twig_Filter_Function('sprintf'),
  116. 'replace' => new Twig_Filter_Function('strtr'),
  117. 'number_format' => new Twig_Filter_Function('twig_number_format_filter', array('needs_environment' => true)),
  118. // encoding
  119. 'url_encode' => new Twig_Filter_Function('twig_urlencode_filter'),
  120. 'json_encode' => new Twig_Filter_Function('twig_jsonencode_filter'),
  121. 'convert_encoding' => new Twig_Filter_Function('twig_convert_encoding'),
  122. // string filters
  123. 'title' => new Twig_Filter_Function('twig_title_string_filter', array('needs_environment' => true)),
  124. 'capitalize' => new Twig_Filter_Function('twig_capitalize_string_filter', array('needs_environment' => true)),
  125. 'upper' => new Twig_Filter_Function('strtoupper'),
  126. 'lower' => new Twig_Filter_Function('strtolower'),
  127. 'striptags' => new Twig_Filter_Function('strip_tags'),
  128. 'trim' => new Twig_Filter_Function('trim'),
  129. 'nl2br' => new Twig_Filter_Function('nl2br', array('pre_escape' => 'html', 'is_safe' => array('html'))),
  130. // array helpers
  131. 'join' => new Twig_Filter_Function('twig_join_filter'),
  132. 'sort' => new Twig_Filter_Function('twig_sort_filter'),
  133. 'merge' => new Twig_Filter_Function('twig_array_merge'),
  134. // string/array filters
  135. 'reverse' => new Twig_Filter_Function('twig_reverse_filter', array('needs_environment' => true)),
  136. 'length' => new Twig_Filter_Function('twig_length_filter', array('needs_environment' => true)),
  137. 'slice' => new Twig_Filter_Function('twig_slice', array('needs_environment' => true)),
  138. // iteration and runtime
  139. 'default' => new Twig_Filter_Node('Twig_Node_Expression_Filter_Default'),
  140. '_default' => new Twig_Filter_Function('_twig_default_filter'),
  141. 'keys' => new Twig_Filter_Function('twig_get_array_keys_filter'),
  142. // escaping
  143. 'escape' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
  144. 'e' => new Twig_Filter_Function('twig_escape_filter', array('needs_environment' => true, 'is_safe_callback' => 'twig_escape_filter_is_safe')),
  145. );
  146. if (function_exists('mb_get_info')) {
  147. $filters['upper'] = new Twig_Filter_Function('twig_upper_filter', array('needs_environment' => true));
  148. $filters['lower'] = new Twig_Filter_Function('twig_lower_filter', array('needs_environment' => true));
  149. }
  150. return $filters;
  151. }
  152. /**
  153. * Returns a list of global functions to add to the existing list.
  154. *
  155. * @return array An array of global functions
  156. */
  157. public function getFunctions()
  158. {
  159. return array(
  160. 'range' => new Twig_Function_Function('range'),
  161. 'constant' => new Twig_Function_Function('constant'),
  162. 'cycle' => new Twig_Function_Function('twig_cycle'),
  163. 'random' => new Twig_Function_Function('twig_random', array('needs_environment' => true)),
  164. 'date' => new Twig_Function_Function('twig_date_converter', array('needs_environment' => true)),
  165. );
  166. }
  167. /**
  168. * Returns a list of tests to add to the existing list.
  169. *
  170. * @return array An array of tests
  171. */
  172. public function getTests()
  173. {
  174. return array(
  175. 'even' => new Twig_Test_Node('Twig_Node_Expression_Test_Even'),
  176. 'odd' => new Twig_Test_Node('Twig_Node_Expression_Test_Odd'),
  177. 'defined' => new Twig_Test_Node('Twig_Node_Expression_Test_Defined'),
  178. 'sameas' => new Twig_Test_Node('Twig_Node_Expression_Test_Sameas'),
  179. 'none' => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
  180. 'null' => new Twig_Test_Node('Twig_Node_Expression_Test_Null'),
  181. 'divisibleby' => new Twig_Test_Node('Twig_Node_Expression_Test_Divisibleby'),
  182. 'constant' => new Twig_Test_Node('Twig_Node_Expression_Test_Constant'),
  183. 'empty' => new Twig_Test_Function('twig_test_empty'),
  184. 'iterable' => new Twig_Test_Function('twig_test_iterable'),
  185. );
  186. }
  187. /**
  188. * Returns a list of operators to add to the existing list.
  189. *
  190. * @return array An array of operators
  191. */
  192. public function getOperators()
  193. {
  194. return array(
  195. array(
  196. 'not' => array('precedence' => 50, 'class' => 'Twig_Node_Expression_Unary_Not'),
  197. '-' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Neg'),
  198. '+' => array('precedence' => 500, 'class' => 'Twig_Node_Expression_Unary_Pos'),
  199. ),
  200. array(
  201. 'b-and' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseAnd', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  202. 'b-xor' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseXor', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  203. 'b-or' => array('precedence' => 5, 'class' => 'Twig_Node_Expression_Binary_BitwiseOr', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  204. 'or' => array('precedence' => 10, 'class' => 'Twig_Node_Expression_Binary_Or', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  205. 'and' => array('precedence' => 15, 'class' => 'Twig_Node_Expression_Binary_And', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  206. '==' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Equal', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  207. '!=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  208. '<' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Less', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  209. '>' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_Greater', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  210. '>=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_GreaterEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  211. '<=' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_LessEqual', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  212. 'not in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_NotIn', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  213. 'in' => array('precedence' => 20, 'class' => 'Twig_Node_Expression_Binary_In', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  214. '..' => array('precedence' => 25, 'class' => 'Twig_Node_Expression_Binary_Range', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  215. '+' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Add', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  216. '-' => array('precedence' => 30, 'class' => 'Twig_Node_Expression_Binary_Sub', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  217. '~' => array('precedence' => 40, 'class' => 'Twig_Node_Expression_Binary_Concat', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  218. '*' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mul', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  219. '/' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Div', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  220. '//' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_FloorDiv', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  221. '%' => array('precedence' => 60, 'class' => 'Twig_Node_Expression_Binary_Mod', 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  222. 'is' => array('precedence' => 100, 'callable' => array($this, 'parseTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  223. 'is not' => array('precedence' => 100, 'callable' => array($this, 'parseNotTestExpression'), 'associativity' => Twig_ExpressionParser::OPERATOR_LEFT),
  224. '**' => array('precedence' => 200, 'class' => 'Twig_Node_Expression_Binary_Power', 'associativity' => Twig_ExpressionParser::OPERATOR_RIGHT),
  225. ),
  226. );
  227. }
  228. public function parseNotTestExpression(Twig_Parser $parser, $node)
  229. {
  230. return new Twig_Node_Expression_Unary_Not($this->parseTestExpression($parser, $node), $parser->getCurrentToken()->getLine());
  231. }
  232. public function parseTestExpression(Twig_Parser $parser, $node)
  233. {
  234. $stream = $parser->getStream();
  235. $name = $stream->expect(Twig_Token::NAME_TYPE)->getValue();
  236. $arguments = null;
  237. if ($stream->test(Twig_Token::PUNCTUATION_TYPE, '(')) {
  238. $arguments = $parser->getExpressionParser()->parseArguments();
  239. }
  240. $class = $this->getTestNodeClass($parser->getEnvironment(), $name);
  241. return new $class($node, $name, $arguments, $parser->getCurrentToken()->getLine());
  242. }
  243. protected function getTestNodeClass(Twig_Environment $env, $name)
  244. {
  245. $testMap = $env->getTests();
  246. if (isset($testMap[$name]) && $testMap[$name] instanceof Twig_Test_Node) {
  247. return $testMap[$name]->getClass();
  248. }
  249. return 'Twig_Node_Expression_Test';
  250. }
  251. /**
  252. * Returns the name of the extension.
  253. *
  254. * @return string The extension name
  255. */
  256. public function getName()
  257. {
  258. return 'core';
  259. }
  260. }
  261. /**
  262. * Cycles over a value.
  263. *
  264. * @param ArrayAccess|array $values An array or an ArrayAccess instance
  265. * @param integer $i The cycle value
  266. *
  267. * @return string The next value in the cycle
  268. */
  269. function twig_cycle($values, $i)
  270. {
  271. if (!is_array($values) && !$values instanceof ArrayAccess) {
  272. return $values;
  273. }
  274. return $values[$i % count($values)];
  275. }
  276. /**
  277. * Returns a random value depending on the supplied parameter type:
  278. * - a random item from a Traversable or array
  279. * - a random character from a string
  280. * - a random integer between 0 and the integer parameter
  281. *
  282. * @param Twig_Environment $env A Twig_Environment instance
  283. * @param Traversable|array|int|string $values The values to pick a random item from
  284. *
  285. * @throws Twig_Error_Runtime When $values is an empty array (does not apply to an empty string which is returned as is).
  286. *
  287. * @return mixed A random value from the given sequence
  288. */
  289. function twig_random(Twig_Environment $env, $values = null)
  290. {
  291. if (null === $values) {
  292. return mt_rand();
  293. }
  294. if (is_int($values) || is_float($values)) {
  295. return $values < 0 ? mt_rand($values, 0) : mt_rand(0, $values);
  296. }
  297. if ($values instanceof Traversable) {
  298. $values = iterator_to_array($values);
  299. } elseif (is_string($values)) {
  300. if ('' === $values) {
  301. return '';
  302. }
  303. if (null !== $charset = $env->getCharset()) {
  304. if ('UTF-8' != $charset) {
  305. $values = twig_convert_encoding($values, 'UTF-8', $charset);
  306. }
  307. // unicode version of str_split()
  308. // split at all positions, but not after the start and not before the end
  309. $values = preg_split('/(?<!^)(?!$)/u', $values);
  310. if ('UTF-8' != $charset) {
  311. foreach ($values as $i => $value) {
  312. $values[$i] = twig_convert_encoding($value, $charset, 'UTF-8');
  313. }
  314. }
  315. } else {
  316. return $values[mt_rand(0, strlen($values) - 1)];
  317. }
  318. }
  319. if (!is_array($values)) {
  320. return $values;
  321. }
  322. if (0 === count($values)) {
  323. throw new Twig_Error_Runtime('The random function cannot pick from an empty array.');
  324. }
  325. return $values[array_rand($values, 1)];
  326. }
  327. /**
  328. * Converts a date to the given format.
  329. *
  330. * <pre>
  331. * {{ post.published_at|date("m/d/Y") }}
  332. * </pre>
  333. *
  334. * @param Twig_Environment $env A Twig_Environment instance
  335. * @param DateTime|DateInterval|string $date A date
  336. * @param string $format A format
  337. * @param DateTimeZone|string $timezone A timezone
  338. *
  339. * @return string The formatter date
  340. */
  341. function twig_date_format_filter(Twig_Environment $env, $date, $format = null, $timezone = null)
  342. {
  343. if (null === $format) {
  344. $formats = $env->getExtension('core')->getDateFormat();
  345. $format = $date instanceof DateInterval ? $formats[1] : $formats[0];
  346. }
  347. if ($date instanceof DateInterval || $date instanceof DateTime) {
  348. if (null !== $timezone) {
  349. $date = clone $date;
  350. $date->setTimezone($timezone instanceof DateTimeZone ? $timezone : new DateTimeZone($timezone));
  351. }
  352. return $date->format($format);
  353. }
  354. return twig_date_converter($env, $date, $timezone)->format($format);
  355. }
  356. /**
  357. * Converts an input to a DateTime instance.
  358. *
  359. * <pre>
  360. * {% if date(user.created_at) < date('+2days') %}
  361. * {# do something #}
  362. * {% endif %}
  363. * </pre>
  364. *
  365. * @param Twig_Environment $env A Twig_Environment instance
  366. * @param DateTime|string $date A date
  367. * @param DateTimeZone|string $timezone A timezone
  368. *
  369. * @return DateTime A DateTime instance
  370. */
  371. function twig_date_converter(Twig_Environment $env, $date = null, $timezone = null)
  372. {
  373. if ($date instanceof DateTime) {
  374. return $date;
  375. }
  376. $asString = (string) $date;
  377. if (ctype_digit($asString) || (!empty($asString) && '-' === $asString[0] && ctype_digit(substr($asString, 1)))) {
  378. $date = new DateTime('@'.$date);
  379. $date->setTimezone(new DateTimeZone(date_default_timezone_get()));
  380. } else {
  381. $date = new DateTime($date);
  382. }
  383. // set Timezone
  384. if (null !== $timezone) {
  385. if (!$timezone instanceof DateTimeZone) {
  386. $timezone = new DateTimeZone($timezone);
  387. }
  388. $date->setTimezone($timezone);
  389. } elseif (($timezone = $env->getExtension('core')->getTimezone()) instanceof DateTimeZone) {
  390. $date->setTimezone($timezone);
  391. }
  392. return $date;
  393. }
  394. /**
  395. * Number format filter.
  396. *
  397. * All of the formatting options can be left null, in that case the defaults will
  398. * be used. Supplying any of the parameters will override the defaults set in the
  399. * environment object.
  400. *
  401. * @param Twig_Environment $env A Twig_Environment instance
  402. * @param mixed $number A float/int/string of the number to format
  403. * @param int $decimal The number of decimal points to display.
  404. * @param string $decimalPoint The character(s) to use for the decimal point.
  405. * @param string $thousandSep The character(s) to use for the thousands separator.
  406. *
  407. * @return string The formatted number
  408. */
  409. function twig_number_format_filter(Twig_Environment $env, $number, $decimal = null, $decimalPoint = null, $thousandSep = null)
  410. {
  411. $defaults = $env->getExtension('core')->getNumberFormat();
  412. if (null === $decimal) {
  413. $decimal = $defaults[0];
  414. }
  415. if (null === $decimalPoint) {
  416. $decimalPoint = $defaults[1];
  417. }
  418. if (null === $thousandSep) {
  419. $thousandSep = $defaults[2];
  420. }
  421. return number_format((float) $number, $decimal, $decimalPoint, $thousandSep);
  422. }
  423. /**
  424. * URL encodes a string.
  425. *
  426. * @param string $url A URL
  427. * @param bool $raw true to use rawurlencode() instead of urlencode
  428. *
  429. * @return string The URL encoded value
  430. */
  431. function twig_urlencode_filter($url, $raw = false)
  432. {
  433. if ($raw) {
  434. return rawurlencode($url);
  435. }
  436. return urlencode($url);
  437. }
  438. if (version_compare(PHP_VERSION, '5.3.0', '<')) {
  439. /**
  440. * JSON encodes a variable.
  441. *
  442. * @param mixed $value The value to encode.
  443. * @param integer $options Not used on PHP 5.2.x
  444. *
  445. * @return mixed The JSON encoded value
  446. */
  447. function twig_jsonencode_filter($value, $options = 0)
  448. {
  449. if ($value instanceof Twig_Markup) {
  450. $value = (string) $value;
  451. } elseif (is_array($value)) {
  452. array_walk_recursive($value, '_twig_markup2string');
  453. }
  454. return json_encode($value);
  455. }
  456. } else {
  457. /**
  458. * JSON encodes a variable.
  459. *
  460. * @param mixed $value The value to encode.
  461. * @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
  462. *
  463. * @return mixed The JSON encoded value
  464. */
  465. function twig_jsonencode_filter($value, $options = 0)
  466. {
  467. if ($value instanceof Twig_Markup) {
  468. $value = (string) $value;
  469. } elseif (is_array($value)) {
  470. array_walk_recursive($value, '_twig_markup2string');
  471. }
  472. return json_encode($value, $options);
  473. }
  474. }
  475. function _twig_markup2string(&$value)
  476. {
  477. if ($value instanceof Twig_Markup) {
  478. $value = (string) $value;
  479. }
  480. }
  481. /**
  482. * Merges an array with another one.
  483. *
  484. * <pre>
  485. * {% set items = { 'apple': 'fruit', 'orange': 'fruit' } %}
  486. *
  487. * {% set items = items|merge({ 'peugeot': 'car' }) %}
  488. *
  489. * {# items now contains { 'apple': 'fruit', 'orange': 'fruit', 'peugeot': 'car' } #}
  490. * </pre>
  491. *
  492. * @param array $arr1 An array
  493. * @param array $arr2 An array
  494. *
  495. * @return array The merged array
  496. */
  497. function twig_array_merge($arr1, $arr2)
  498. {
  499. if (!is_array($arr1) || !is_array($arr2)) {
  500. throw new Twig_Error_Runtime('The merge filter only works with arrays or hashes.');
  501. }
  502. return array_merge($arr1, $arr2);
  503. }
  504. /**
  505. * Slices a variable.
  506. *
  507. * @param Twig_Environment $env A Twig_Environment instance
  508. * @param mixed $item A variable
  509. * @param integer $start Start of the slice
  510. * @param integer $length Size of the slice
  511. * @param Boolean $preserveKeys Whether to preserve key or not (when the input is an array)
  512. *
  513. * @return mixed The sliced variable
  514. */
  515. function twig_slice(Twig_Environment $env, $item, $start, $length = null, $preserveKeys = false)
  516. {
  517. if ($item instanceof Traversable) {
  518. $item = iterator_to_array($item, false);
  519. }
  520. if (is_array($item)) {
  521. return array_slice($item, $start, $length, $preserveKeys);
  522. }
  523. $item = (string) $item;
  524. if (function_exists('mb_get_info') && null !== $charset = $env->getCharset()) {
  525. return mb_substr($item, $start, null === $length ? mb_strlen($item, $charset) - $start : $length, $charset);
  526. }
  527. return null === $length ? substr($item, $start) : substr($item, $start, $length);
  528. }
  529. /**
  530. * Joins the values to a string.
  531. *
  532. * The separator between elements is an empty string per default, you can define it with the optional parameter.
  533. *
  534. * <pre>
  535. * {{ [1, 2, 3]|join('|') }}
  536. * {# returns 1|2|3 #}
  537. *
  538. * {{ [1, 2, 3]|join }}
  539. * {# returns 123 #}
  540. * </pre>
  541. *
  542. * @param array $value An array
  543. * @param string $glue The separator
  544. *
  545. * @return string The concatenated string
  546. */
  547. function twig_join_filter($value, $glue = '')
  548. {
  549. if ($value instanceof Traversable) {
  550. $value = iterator_to_array($value, false);
  551. }
  552. return implode($glue, (array) $value);
  553. }
  554. // The '_default' filter is used internally to avoid using the ternary operator
  555. // which costs a lot for big contexts (before PHP 5.4). So, on average,
  556. // a function call is cheaper.
  557. function _twig_default_filter($value, $default = '')
  558. {
  559. if (twig_test_empty($value)) {
  560. return $default;
  561. }
  562. return $value;
  563. }
  564. /**
  565. * Returns the keys for the given array.
  566. *
  567. * It is useful when you want to iterate over the keys of an array:
  568. *
  569. * <pre>
  570. * {% for key in array|keys %}
  571. * {# ... #}
  572. * {% endfor %}
  573. * </pre>
  574. *
  575. * @param array $array An array
  576. *
  577. * @return array The keys
  578. */
  579. function twig_get_array_keys_filter($array)
  580. {
  581. if (is_object($array) && $array instanceof Traversable) {
  582. return array_keys(iterator_to_array($array));
  583. }
  584. if (!is_array($array)) {
  585. return array();
  586. }
  587. return array_keys($array);
  588. }
  589. /**
  590. * Reverses a variable.
  591. *
  592. * @param Twig_Environment $env A Twig_Environment instance
  593. * @param array|Traversable|string $item An array, a Traversable instance, or a string
  594. * @param Boolean $preserveKeys Whether to preserve key or not
  595. *
  596. * @return mixed The reversed input
  597. */
  598. function twig_reverse_filter(Twig_Environment $env, $item, $preserveKeys = false)
  599. {
  600. if (is_object($item) && $item instanceof Traversable) {
  601. return array_reverse(iterator_to_array($item), $preserveKeys);
  602. }
  603. if (is_array($item)) {
  604. return array_reverse($item, $preserveKeys);
  605. }
  606. if (null !== $charset = $env->getCharset()) {
  607. $string = (string) $item;
  608. if ('UTF-8' != $charset) {
  609. $item = twig_convert_encoding($string, 'UTF-8', $charset);
  610. }
  611. preg_match_all('/./us', $item, $matches);
  612. $string = implode('', array_reverse($matches[0]));
  613. if ('UTF-8' != $charset) {
  614. $string = twig_convert_encoding($string, $charset, 'UTF-8');
  615. }
  616. return $string;
  617. }
  618. return strrev((string) $item);
  619. }
  620. /**
  621. * Sorts an array.
  622. *
  623. * @param array $array An array
  624. */
  625. function twig_sort_filter($array)
  626. {
  627. asort($array);
  628. return $array;
  629. }
  630. /* used internally */
  631. function twig_in_filter($value, $compare)
  632. {
  633. if (is_array($compare)) {
  634. return in_array($value, $compare);
  635. } elseif (is_string($compare)) {
  636. if (!strlen((string) $value)) {
  637. return empty($compare);
  638. }
  639. return false !== strpos($compare, (string) $value);
  640. } elseif (is_object($compare) && $compare instanceof Traversable) {
  641. return in_array($value, iterator_to_array($compare, false));
  642. }
  643. return false;
  644. }
  645. /**
  646. * Escapes a string.
  647. *
  648. * @param Twig_Environment $env A Twig_Environment instance
  649. * @param string $string The value to be escaped
  650. * @param string $strategy The escaping strategy
  651. * @param string $charset The charset
  652. * @param Boolean $autoescape Whether the function is called by the auto-escaping feature (true) or by the developer (false)
  653. */
  654. function twig_escape_filter(Twig_Environment $env, $string, $strategy = 'html', $charset = null, $autoescape = false)
  655. {
  656. if ($autoescape && is_object($string) && $string instanceof Twig_Markup) {
  657. return $string;
  658. }
  659. if (!is_string($string) && !(is_object($string) && method_exists($string, '__toString'))) {
  660. return $string;
  661. }
  662. if (null === $charset) {
  663. $charset = $env->getCharset();
  664. }
  665. $string = (string) $string;
  666. switch ($strategy) {
  667. case 'js':
  668. // escape all non-alphanumeric characters
  669. // into their \xHH or \uHHHH representations
  670. if ('UTF-8' != $charset) {
  671. $string = twig_convert_encoding($string, 'UTF-8', $charset);
  672. }
  673. if (null === $string = preg_replace_callback('#[^\p{L}\p{N} ]#u', '_twig_escape_js_callback', $string)) {
  674. throw new Twig_Error_Runtime('The string to escape is not a valid UTF-8 string.');
  675. }
  676. if ('UTF-8' != $charset) {
  677. $string = twig_convert_encoding($string, $charset, 'UTF-8');
  678. }
  679. return $string;
  680. case 'html':
  681. // see http://php.net/htmlspecialchars
  682. // Using a static variable to avoid initializing the array
  683. // each time the function is called. Moving the declaration on the
  684. // top of the function slow downs other escaping strategies.
  685. static $htmlspecialcharsCharsets = array(
  686. 'iso-8859-1' => true, 'iso8859-1' => true,
  687. 'iso-8859-15' => true, 'iso8859-15' => true,
  688. 'utf-8' => true,
  689. 'cp866' => true, 'ibm866' => true, '866' => true,
  690. 'cp1251' => true, 'windows-1251' => true, 'win-1251' => true,
  691. '1251' => true,
  692. 'cp1252' => true, 'windows-1252' => true, '1252' => true,
  693. 'koi8-r' => true, 'koi8-ru' => true, 'koi8r' => true,
  694. 'big5' => true, '950' => true,
  695. 'gb2312' => true, '936' => true,
  696. 'big5-hkscs' => true,
  697. 'shift_jis' => true, 'sjis' => true, '932' => true,
  698. 'euc-jp' => true, 'eucjp' => true,
  699. 'iso8859-5' => true, 'iso-8859-5' => true, 'macroman' => true,
  700. );
  701. if (isset($htmlspecialcharsCharsets[strtolower($charset)])) {
  702. return htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, $charset);
  703. }
  704. $string = twig_convert_encoding($string, 'UTF-8', $charset);
  705. $string = htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
  706. return twig_convert_encoding($string, $charset, 'UTF-8');
  707. default:
  708. throw new Twig_Error_Runtime(sprintf('Invalid escaping strategy "%s" (valid ones: html, js).', $strategy));
  709. }
  710. }
  711. /* used internally */
  712. function twig_escape_filter_is_safe(Twig_Node $filterArgs)
  713. {
  714. foreach ($filterArgs as $arg) {
  715. if ($arg instanceof Twig_Node_Expression_Constant) {
  716. return array($arg->getAttribute('value'));
  717. }
  718. return array();
  719. }
  720. return array('html');
  721. }
  722. if (function_exists('mb_convert_encoding')) {
  723. function twig_convert_encoding($string, $to, $from)
  724. {
  725. return mb_convert_encoding($string, $to, $from);
  726. }
  727. } elseif (function_exists('iconv')) {
  728. function twig_convert_encoding($string, $to, $from)
  729. {
  730. return iconv($from, $to, $string);
  731. }
  732. } else {
  733. function twig_convert_encoding($string, $to, $from)
  734. {
  735. throw new Twig_Error_Runtime('No suitable convert encoding function (use UTF-8 as your encoding or install the iconv or mbstring extension).');
  736. }
  737. }
  738. function _twig_escape_js_callback($matches)
  739. {
  740. $char = $matches[0];
  741. // \xHH
  742. if (!isset($char[1])) {
  743. return '\\x'.substr('00'.bin2hex($char), -2);
  744. }
  745. // \uHHHH
  746. $char = twig_convert_encoding($char, 'UTF-16BE', 'UTF-8');
  747. return '\\u'.substr('0000'.bin2hex($char), -4);
  748. }
  749. // add multibyte extensions if possible
  750. if (function_exists('mb_get_info')) {
  751. /**
  752. * Returns the length of a variable.
  753. *
  754. * @param Twig_Environment $env A Twig_Environment instance
  755. * @param mixed $thing A variable
  756. *
  757. * @return integer The length of the value
  758. */
  759. function twig_length_filter(Twig_Environment $env, $thing)
  760. {
  761. return is_scalar($thing) ? mb_strlen($thing, $env->getCharset()) : count($thing);
  762. }
  763. /**
  764. * Converts a string to uppercase.
  765. *
  766. * @param Twig_Environment $env A Twig_Environment instance
  767. * @param string $string A string
  768. *
  769. * @return string The uppercased string
  770. */
  771. function twig_upper_filter(Twig_Environment $env, $string)
  772. {
  773. if (null !== ($charset = $env->getCharset())) {
  774. return mb_strtoupper($string, $charset);
  775. }
  776. return strtoupper($string);
  777. }
  778. /**
  779. * Converts a string to lowercase.
  780. *
  781. * @param Twig_Environment $env A Twig_Environment instance
  782. * @param string $string A string
  783. *
  784. * @return string The lowercased string
  785. */
  786. function twig_lower_filter(Twig_Environment $env, $string)
  787. {
  788. if (null !== ($charset = $env->getCharset())) {
  789. return mb_strtolower($string, $charset);
  790. }
  791. return strtolower($string);
  792. }
  793. /**
  794. * Returns a titlecased string.
  795. *
  796. * @param Twig_Environment $env A Twig_Environment instance
  797. * @param string $string A string
  798. *
  799. * @return string The titlecased string
  800. */
  801. function twig_title_string_filter(Twig_Environment $env, $string)
  802. {
  803. if (null !== ($charset = $env->getCharset())) {
  804. return mb_convert_case($string, MB_CASE_TITLE, $charset);
  805. }
  806. return ucwords(strtolower($string));
  807. }
  808. /**
  809. * Returns a capitalized string.
  810. *
  811. * @param Twig_Environment $env A Twig_Environment instance
  812. * @param string $string A string
  813. *
  814. * @return string The capitalized string
  815. */
  816. function twig_capitalize_string_filter(Twig_Environment $env, $string)
  817. {
  818. if (null !== ($charset = $env->getCharset())) {
  819. return mb_strtoupper(mb_substr($string, 0, 1, $charset), $charset).
  820. mb_strtolower(mb_substr($string, 1, mb_strlen($string, $charset), $charset), $charset);
  821. }
  822. return ucfirst(strtolower($string));
  823. }
  824. }
  825. // and byte fallback
  826. else
  827. {
  828. /**
  829. * Returns the length of a variable.
  830. *
  831. * @param Twig_Environment $env A Twig_Environment instance
  832. * @param mixed $thing A variable
  833. *
  834. * @return integer The length of the value
  835. */
  836. function twig_length_filter(Twig_Environment $env, $thing)
  837. {
  838. return is_scalar($thing) ? strlen($thing) : count($thing);
  839. }
  840. /**
  841. * Returns a titlecased string.
  842. *
  843. * @param Twig_Environment $env A Twig_Environment instance
  844. * @param string $string A string
  845. *
  846. * @return string The titlecased string
  847. */
  848. function twig_title_string_filter(Twig_Environment $env, $string)
  849. {
  850. return ucwords(strtolower($string));
  851. }
  852. /**
  853. * Returns a capitalized string.
  854. *
  855. * @param Twig_Environment $env A Twig_Environment instance
  856. * @param string $string A string
  857. *
  858. * @return string The capitalized string
  859. */
  860. function twig_capitalize_string_filter(Twig_Environment $env, $string)
  861. {
  862. return ucfirst(strtolower($string));
  863. }
  864. }
  865. /* used internally */
  866. function twig_ensure_traversable($seq)
  867. {
  868. if ($seq instanceof Traversable || is_array($seq)) {
  869. return $seq;
  870. }
  871. return array();
  872. }
  873. /**
  874. * Checks if a variable is empty.
  875. *
  876. * <pre>
  877. * {# evaluates to true if the foo variable is null, false, or the empty string #}
  878. * {% if foo is empty %}
  879. * {# ... #}
  880. * {% endif %}
  881. * </pre>
  882. *
  883. * @param mixed $value A variable
  884. *
  885. * @return Boolean true if the value is empty, false otherwise
  886. */
  887. function twig_test_empty($value)
  888. {
  889. if ($value instanceof Countable) {
  890. return 0 == count($value);
  891. }
  892. return false === $value || (empty($value) && '0' != $value);
  893. }
  894. /**
  895. * Checks if a variable is traversable.
  896. *
  897. * <pre>
  898. * {# evaluates to true if the foo variable is an array or a traversable object #}
  899. * {% if foo is traversable %}
  900. * {# ... #}
  901. * {% endif %}
  902. * </pre>
  903. *
  904. * @param mixed $value A variable
  905. *
  906. * @return Boolean true if the value is traversable
  907. */
  908. function twig_test_iterable($value)
  909. {
  910. return $value instanceof Traversable || is_array($value);
  911. }