KXStudio Website https://kx.studio/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

486 lines
13KB

  1. <?php
  2. /**
  3. * Twig::Runtime
  4. * ~~~~~~~~~~~~~
  5. *
  6. * The twig runtime environment.
  7. *
  8. * :copyright: 2008 by Armin Ronacher.
  9. * :license: BSD.
  10. */
  11. $twig_filters = array(
  12. // formatting filters
  13. 'date' => 'twig_date_format_filter',
  14. 'strftime' => 'twig_strftime_format_filter',
  15. 'strtotime' => 'strtotime',
  16. 'numberformat' => 'number_format',
  17. 'moneyformat' => 'money_format',
  18. 'filesizeformat' => 'twig_filesize_format_filter',
  19. 'format' => 'sprintf',
  20. 'relative' => 'relative_time',
  21. // numbers
  22. 'even' => 'twig_is_even_filter',
  23. 'odd' => 'twig_is_odd_filter',
  24. // escaping and encoding
  25. 'escape' => 'twig_escape_filter',
  26. 'e' => 'twig_escape_filter',
  27. 'urlencode' => 'twig_urlencode_filter',
  28. 'quotes' => 'twig_quotes_filter',
  29. 'slashes' => 'addslashes',
  30. // string filters
  31. 'title' => 'twig_title_string_filter',
  32. 'capitalize' => 'twig_capitalize_string_filter',
  33. 'upper' => 'strtoupper',
  34. 'lower' => 'strtolower',
  35. 'strip' => 'trim',
  36. 'rstrip' => 'rtrim',
  37. 'lstrip' => 'ltrim',
  38. 'translate' => 'twig_translate_string_filter',
  39. 'translate_plural' => 'twig_translate_plural_string_filter',
  40. 'normalize' => 'normalize',
  41. 'truncate' => 'twig_truncate_filter',
  42. 'excerpt' => 'twig_excerpt_filter',
  43. 'replace' => 'twig_replace_filter',
  44. 'match' => 'twig_match_filter',
  45. 'contains' => 'substr_count',
  46. 'linebreaks' => 'nl2br',
  47. 'camelize' => 'camelize',
  48. 'strip_tags' => 'strip_tags',
  49. 'pluralize' => 'twig_pluralize_string_filter',
  50. 'depluralize' => 'twig_depluralize_string_filter',
  51. 'sanitize' => 'sanitize',
  52. 'repeat' => 'str_repeat',
  53. // array helpers
  54. 'join' => 'twig_join_filter',
  55. 'split' => 'twig_split_filter',
  56. 'first' => 'twig_first_filter',
  57. 'offset' => 'twig_offset_filter',
  58. 'last' => 'twig_last_filter',
  59. 'reverse' => 'array_reverse',
  60. 'length' => 'twig_length_filter',
  61. 'count' => 'count',
  62. 'sort' => 'twig_sort_filter',
  63. // iteration and runtime
  64. 'default' => 'twig_default_filter',
  65. 'keys' => 'array_keys',
  66. 'items' => 'twig_get_array_items_filter',
  67. // debugging
  68. 'inspect' => 'twig_inspect_filter',
  69. 'uploaded' => 'uploaded',
  70. 'fallback' => 'oneof',
  71. 'selected' => 'twig_selected_filter',
  72. 'checked' => 'twig_checked_filter',
  73. 'option_selected' => 'twig_option_selected_filter'
  74. );
  75. class Twig_LoopContextIterator implements Iterator
  76. {
  77. public $context;
  78. public $seq;
  79. public $idx;
  80. public $length;
  81. public $parent;
  82. public function __construct(&$context, $seq, $parent)
  83. {
  84. $this->context = $context;
  85. $this->seq = $seq;
  86. $this->idx = 0;
  87. $this->length = count($seq);
  88. $this->parent = $parent;
  89. }
  90. public function rewind() {}
  91. public function key() {}
  92. public function valid()
  93. {
  94. return $this->idx < $this->length;
  95. }
  96. public function next()
  97. {
  98. $this->idx++;
  99. }
  100. public function current()
  101. {
  102. return $this;
  103. }
  104. }
  105. function unretarded_array_unshift(&$arr, &$val) {
  106. $arr = array_merge(array(&$val), $arr);
  107. }
  108. /**
  109. * This is called like an ordinary filter just with the name of the filter
  110. * as first argument. Currently we just raise an exception here but it
  111. * would make sense in the future to allow dynamic filter lookup for plugins
  112. * or something like that.
  113. */
  114. function twig_missing_filter($name)
  115. {
  116. $args = func_get_args();
  117. array_shift($args);
  118. $text = $args[0];
  119. array_shift($args);
  120. array_unshift($args, $name);
  121. unretarded_array_unshift($args, $text);
  122. $trigger = Trigger::current();
  123. if ($trigger->exists($name))
  124. return call_user_func_array(array($trigger, "filter"), $args);
  125. return $text;
  126. }
  127. function twig_get_attribute($obj, $item, $function = true)
  128. {
  129. if (is_array($obj) && isset($obj[$item]))
  130. return $obj[$item];
  131. if (!is_object($obj))
  132. return NULL;
  133. if ($function and method_exists($obj, $item))
  134. return call_user_func(array($obj, $item));
  135. if (property_exists($obj, $item)) {
  136. $tmp = get_object_vars($obj);
  137. return $tmp[$item];
  138. }
  139. $method = 'get' . ucfirst($item);
  140. if ($function and method_exists($obj, $method))
  141. return call_user_func(array($obj, $method));
  142. if (is_object($obj)) {
  143. @$obj->$item; # Funky way of allowing __get to activate before returning the value.
  144. return @$obj->$item;
  145. }
  146. return NULL;
  147. }
  148. function twig_paginate(&$context, $as, $over, $per_page)
  149. {
  150. $name = (in_array("page", Paginator::$names)) ? $as."_page" : "page" ;
  151. if (count($over) == 2 and $over[0] instanceof Model and is_string($over[1]))
  152. $context[$as] = $context["::parent"][$as] = new Paginator($over[0]->__getPlaceholders($over[1]), $per_page, $name);
  153. else
  154. $context[$as] = $context["::parent"][$as] = new Paginator($over, $per_page, $name);
  155. }
  156. function twig_iterate(&$context, $seq)
  157. {
  158. $parent = isset($context['loop']) ? $context['loop'] : null;
  159. $seq = twig_make_array($seq);
  160. $context['loop'] = array('parent' => $parent, 'iterated' => false);
  161. return new Twig_LoopContextIterator($context, $seq, $parent);
  162. }
  163. function twig_set_loop_context(&$context, $iterator, $target)
  164. {
  165. $context[$target] = $iterator->seq[$iterator->idx];
  166. $context['loop'] = twig_make_loop_context($iterator);
  167. }
  168. function twig_set_loop_context_multitarget(&$context, $iterator, $targets)
  169. {
  170. $values = $iterator->seq[$iterator->idx];
  171. if (!is_array($values))
  172. $values = array($values);
  173. $idx = 0;
  174. foreach ($values as $value) {
  175. if (!isset($targets[$idx]))
  176. break;
  177. $context[$targets[$idx++]] = $value;
  178. }
  179. $context['loop'] = twig_make_loop_context($iterator);
  180. }
  181. function twig_make_loop_context($iterator)
  182. {
  183. return array(
  184. 'parent' => $iterator->parent,
  185. 'length' => $iterator->length,
  186. 'index0' => $iterator->idx,
  187. 'index' => $iterator->idx + 1,
  188. 'revindex0' => $iterator->length - $iterator->idx - 1,
  189. 'revindex '=> $iterator->length - $iterator->idx,
  190. 'first' => $iterator->idx == 0,
  191. 'last' => $iterator->idx + 1 == $iterator->length,
  192. 'iterated' => true
  193. );
  194. }
  195. function twig_make_array($object)
  196. {
  197. if (is_array($object))
  198. return array_values($object);
  199. elseif (is_object($object)) {
  200. $result = array();
  201. foreach ($object as $value)
  202. $result[] = $value;
  203. return $result;
  204. }
  205. return array();
  206. }
  207. function twig_date_format_filter($timestamp, $format='F j, Y, G:i')
  208. {
  209. return when($format, $timestamp);
  210. }
  211. function twig_strftime_format_filter($timestamp, $format='%x %X')
  212. {
  213. return when($format, $timestamp, true);
  214. }
  215. function twig_urlencode_filter($url, $raw=false)
  216. {
  217. if ($raw)
  218. return rawurlencode($url);
  219. return urlencode($url);
  220. }
  221. function twig_join_filter($value, $glue='')
  222. {
  223. return implode($glue, (array) $value);
  224. }
  225. function twig_default_filter($value, $default='')
  226. {
  227. return is_null($value) ? $default : $value;
  228. }
  229. function twig_get_array_items_filter($array)
  230. {
  231. $result = array();
  232. foreach ($array as $key => $value)
  233. $result[] = array($key, $value);
  234. return $result;
  235. }
  236. function twig_filesize_format_filter($value)
  237. {
  238. $value = max(0, (int)$value);
  239. $places = strlen($value);
  240. if ($places <= 9 && $places >= 7) {
  241. $value = number_format($value / 1048576, 1);
  242. return "$value MB";
  243. }
  244. if ($places >= 10) {
  245. $value = number_format($value / 1073741824, 1);
  246. return "$value GB";
  247. }
  248. $value = number_format($value / 1024, 1);
  249. return "$value KB";
  250. }
  251. function twig_is_even_filter($value)
  252. {
  253. return $value % 2 == 0;
  254. }
  255. function twig_is_odd_filter($value)
  256. {
  257. return $value % 2 == 1;
  258. }
  259. function twig_replace_filter($str, $search, $replace, $regex = false)
  260. {
  261. if ($regex)
  262. return preg_replace($search, $replace, $str);
  263. else
  264. return str_replace($search, $replace, $str);
  265. }
  266. function twig_match_filter($str, $match)
  267. {
  268. return preg_match($match, $str);
  269. }
  270. // add multibyte extensions if possible
  271. if (function_exists('mb_get_info')) {
  272. function twig_upper_filter($string)
  273. {
  274. $template = twig_get_current_template();
  275. if (!is_null($template->charset))
  276. return mb_strtoupper($string, $template->charset);
  277. return strtoupper($string);
  278. }
  279. function twig_lower_filter($string)
  280. {
  281. $template = twig_get_current_template();
  282. if (!is_null($template->charset))
  283. return mb_strtolower($string, $template->charset);
  284. return strtolower($string);
  285. }
  286. function twig_title_string_filter($string)
  287. {
  288. $template = twig_get_current_template();
  289. if (is_null($template->charset))
  290. return ucwords(strtolower($string));
  291. return mb_convert_case($string, MB_CASE_TITLE, $template->charset);
  292. }
  293. function twig_capitalize_string_filter($string)
  294. {
  295. $template = twig_get_current_template();
  296. if (is_null($template->charset))
  297. return ucfirst(strtolower($string));
  298. return mb_strtoupper(mb_substr($string, 0, 1, $template->charset)) .
  299. mb_strtolower(mb_substr($string, 1, null, $template->charset));
  300. }
  301. // override the builtins
  302. $twig_filters['upper'] = 'twig_upper_filter';
  303. $twig_filters['lower'] = 'twig_lower_filter';
  304. }
  305. // and byte fallback
  306. else {
  307. function twig_title_string_filter($string)
  308. {
  309. return ucwords(strtolower($string));
  310. }
  311. function twig_capitalize_string_filter($string)
  312. {
  313. return ucfirst(strtolower($string));
  314. }
  315. }
  316. function twig_translate_string_filter($string, $domain = "theme") {
  317. $domain = ($domain == "theme" and ADMIN) ? "chyrp" : $domain ;
  318. return __($string, $domain);
  319. }
  320. function twig_translate_plural_string_filter($single, $plural, $number, $domain = "theme") {
  321. $domain = ($domain == "theme" and ADMIN) ? "chyrp" : $domain ;
  322. return _p($single, $plural, $number, $domain);
  323. }
  324. function twig_inspect_filter($thing) {
  325. if (ini_get("xdebug.var_display_max_depth") == -1)
  326. return var_dump($thing);
  327. else
  328. return '<pre class="chyrp_inspect"><code>' .
  329. fix(var_export($thing, true)) .
  330. '</code></pre>';
  331. }
  332. function twig_split_filter($string, $cut = " ") {
  333. return explode($cut, $string);
  334. }
  335. function twig_first_filter($array) {
  336. foreach ($array as $key => &$val)
  337. return $val; # Return the first one.
  338. return false;
  339. }
  340. function twig_last_filter($array) {
  341. return $array[count($array) - 1];
  342. }
  343. function twig_offset_filter($array, $offset = 0) {
  344. return $array[$offset];
  345. }
  346. function twig_selected_filter($foo) {
  347. $try = func_get_args();
  348. array_shift($try);
  349. $just_class = (end($try) === true);
  350. if ($just_class)
  351. array_pop($try);
  352. if (is_array($try[0])) {
  353. foreach ($try as $index => $it)
  354. if ($index)
  355. $try[0][] = $it;
  356. $try = $try[0];
  357. }
  358. if (in_array($foo, $try))
  359. return ($just_class) ? " selected" : ' class="selected"' ;
  360. }
  361. function twig_checked_filter($foo) {
  362. if ($foo)
  363. return ' checked="checked"';
  364. }
  365. function twig_option_selected_filter($foo) {
  366. $try = func_get_args();
  367. array_shift($try);
  368. if (in_array($foo, $try))
  369. return ' selected="selected"';
  370. }
  371. function twig_pluralize_string_filter($string, $number = null) {
  372. if ($number and $number == 1)
  373. return $string;
  374. else
  375. return pluralize($string);
  376. }
  377. function twig_depluralize_string_filter($string) {
  378. return depluralize($string);
  379. }
  380. function twig_quotes_filter($string) {
  381. return str_replace(array('"', "'"), array('\"', "\\'"), $string);
  382. }
  383. function twig_length_filter($thing) {
  384. if (is_string($thing))
  385. return strlen($thing);
  386. else
  387. return count($thing);
  388. }
  389. function twig_escape_filter($string, $quotes = true, $decode = true) {
  390. if (!is_string($string)) # Certain post attributes might be parsed from YAML to an array,
  391. return $string; # in which case the module provides a value. However, the attr
  392. # is still passed to the "fallback" and "fix" filters when editing.
  393. $safe = fix($string, $quotes);
  394. return $decode ? preg_replace("/&amp;(#?[A-Za-z0-9]+);/", "&\\1;", $safe) : $safe ;
  395. }
  396. function twig_truncate_filter($text, $length = 100, $ending = "...", $exact = false, $html = true) {
  397. return truncate($text, $length, $ending, $exact, $html);
  398. }
  399. function twig_excerpt_filter($text, $length = 200, $ending = "...", $exact = false, $html = true) {
  400. $paragraphs = preg_split("/(\r?\n\r?\n|\r\r)/", $text);
  401. if (count($paragraphs) > 1)
  402. return $paragraphs[0];
  403. else
  404. return truncate($text, $length, $ending, $exact, $html);
  405. }
  406. function twig_sort_filter($array) {
  407. asort($array);
  408. return $array;
  409. }