TwigExtension.php 36 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  1. <?php
  2. /**
  3. * @package Grav.Common.Twig
  4. *
  5. * @copyright Copyright (C) 2015 - 2018 Trilby Media, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Common\Twig;
  9. use Grav\Common\Grav;
  10. use Grav\Common\Page\Collection;
  11. use Grav\Common\Page\Media;
  12. use Grav\Common\Twig\TokenParser\TwigTokenParserScript;
  13. use Grav\Common\Twig\TokenParser\TwigTokenParserStyle;
  14. use Grav\Common\Twig\TokenParser\TwigTokenParserSwitch;
  15. use Grav\Common\Twig\TokenParser\TwigTokenParserTryCatch;
  16. use Grav\Common\Twig\TokenParser\TwigTokenParserMarkdown;
  17. use Grav\Common\User\User;
  18. use Grav\Common\Utils;
  19. use Grav\Common\Yaml;
  20. use Grav\Common\Markdown\Parsedown;
  21. use Grav\Common\Markdown\ParsedownExtra;
  22. use Grav\Common\Helpers\Base32;
  23. use RocketTheme\Toolbox\ResourceLocator\UniformResourceLocator;
  24. class TwigExtension extends \Twig_Extension implements \Twig_Extension_GlobalsInterface
  25. {
  26. protected $grav;
  27. protected $debugger;
  28. protected $config;
  29. /**
  30. * TwigExtension constructor.
  31. */
  32. public function __construct()
  33. {
  34. $this->grav = Grav::instance();
  35. $this->debugger = isset($this->grav['debugger']) ? $this->grav['debugger'] : null;
  36. $this->config = $this->grav['config'];
  37. }
  38. /**
  39. * Register some standard globals
  40. *
  41. * @return array
  42. */
  43. public function getGlobals()
  44. {
  45. return [
  46. 'grav' => $this->grav,
  47. ];
  48. }
  49. /**
  50. * Return a list of all filters.
  51. *
  52. * @return array
  53. */
  54. public function getFilters()
  55. {
  56. return [
  57. new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']),
  58. new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']),
  59. new \Twig_SimpleFilter('contains', [$this, 'containsFilter']),
  60. new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']),
  61. new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']),
  62. new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']),
  63. new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']),
  64. new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']),
  65. new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']),
  66. new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']),
  67. new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']),
  68. new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']),
  69. new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['is_safe' => ['html']]),
  70. new \Twig_SimpleFilter('md5', [$this, 'md5Filter']),
  71. new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']),
  72. new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']),
  73. new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']),
  74. new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']),
  75. new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']),
  76. new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']),
  77. new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']),
  78. new \Twig_SimpleFilter('pad', [$this, 'padFilter']),
  79. new \Twig_SimpleFilter('regex_replace', [$this, 'regexReplace']),
  80. new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']),
  81. new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']),
  82. new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']),
  83. new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']),
  84. new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']),
  85. new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']),
  86. new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']),
  87. new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']),
  88. new \Twig_SimpleFilter('array_unique', 'array_unique'),
  89. new \Twig_SimpleFilter('basename', 'basename'),
  90. new \Twig_SimpleFilter('dirname', 'dirname'),
  91. new \Twig_SimpleFilter('print_r', 'print_r'),
  92. new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']),
  93. new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']),
  94. // Translations
  95. new \Twig_SimpleFilter('t', [$this, 'translate']),
  96. new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']),
  97. new \Twig_SimpleFilter('ta', [$this, 'translateArray']),
  98. // Casting values
  99. new \Twig_SimpleFilter('string', [$this, 'stringFilter']),
  100. new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => true]),
  101. new \Twig_SimpleFilter('bool', [$this, 'boolFilter']),
  102. new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => true]),
  103. new \Twig_SimpleFilter('array', [$this, 'arrayFilter']),
  104. ];
  105. }
  106. /**
  107. * Return a list of all functions.
  108. *
  109. * @return array
  110. */
  111. public function getFunctions()
  112. {
  113. return [
  114. new \Twig_SimpleFunction('array', [$this, 'arrayFilter']),
  115. new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']),
  116. new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'),
  117. new \Twig_SimpleFunction('array_unique', 'array_unique'),
  118. new \Twig_SimpleFunction('array_intersect', [$this, 'arrayIntersectFunc']),
  119. new \Twig_simpleFunction('authorize', [$this, 'authorize']),
  120. new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
  121. new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]),
  122. new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']),
  123. new \Twig_SimpleFunction('print_r', 'print_r'),
  124. new \Twig_SimpleFunction('http_response_code', 'http_response_code'),
  125. new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]),
  126. new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]),
  127. new \Twig_SimpleFunction('gist', [$this, 'gistFunc']),
  128. new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']),
  129. new \Twig_SimpleFunction('pathinfo', 'pathinfo'),
  130. new \Twig_simpleFunction('random_string', [$this, 'randomStringFunc']),
  131. new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']),
  132. new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']),
  133. new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']),
  134. new \Twig_SimpleFunction('string', [$this, 'stringFunc']),
  135. new \Twig_SimpleFunction('url', [$this, 'urlFunc']),
  136. new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']),
  137. new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']),
  138. new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']),
  139. new \Twig_SimpleFunction('range', [$this, 'rangeFunc']),
  140. new \Twig_SimpleFunction('isajaxrequest', [$this, 'isAjaxFunc']),
  141. new \Twig_SimpleFunction('exif', [$this, 'exifFunc']),
  142. new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']),
  143. new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']),
  144. new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']),
  145. new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']),
  146. new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']),
  147. new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']),
  148. new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']),
  149. new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFilter']),
  150. // Translations
  151. new \Twig_simpleFunction('t', [$this, 'translate']),
  152. new \Twig_simpleFunction('tl', [$this, 'translateLanguage']),
  153. new \Twig_simpleFunction('ta', [$this, 'translateArray']),
  154. ];
  155. }
  156. /**
  157. * @return array
  158. */
  159. public function getTokenParsers()
  160. {
  161. return [
  162. new TwigTokenParserTryCatch(),
  163. new TwigTokenParserScript(),
  164. new TwigTokenParserStyle(),
  165. new TwigTokenParserMarkdown(),
  166. new TwigTokenParserSwitch(),
  167. ];
  168. }
  169. /**
  170. * Filters field name by changing dot notation into array notation.
  171. *
  172. * @param string $str
  173. *
  174. * @return string
  175. */
  176. public function fieldNameFilter($str)
  177. {
  178. $path = explode('.', rtrim($str, '.'));
  179. return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : '');
  180. }
  181. /**
  182. * Protects email address.
  183. *
  184. * @param string $str
  185. *
  186. * @return string
  187. */
  188. public function safeEmailFilter($str)
  189. {
  190. $email = '';
  191. for ( $i = 0, $len = strlen( $str ); $i < $len; $i++ ) {
  192. $j = mt_rand( 0, 1);
  193. if ( $j === 0 ) {
  194. $email .= '&#' . ord( $str[$i] ) . ';';
  195. } elseif ( $j === 1 ) {
  196. $email .= $str[$i];
  197. }
  198. }
  199. return str_replace( '@', '&#64;', $email );
  200. }
  201. /**
  202. * Returns array in a random order.
  203. *
  204. * @param array $original
  205. * @param int $offset Can be used to return only slice of the array.
  206. *
  207. * @return array
  208. */
  209. public function randomizeFilter($original, $offset = 0)
  210. {
  211. if (!is_array($original)) {
  212. return $original;
  213. }
  214. if ($original instanceof \Traversable) {
  215. $original = iterator_to_array($original, false);
  216. }
  217. $sorted = [];
  218. $random = array_slice($original, $offset);
  219. shuffle($random);
  220. $sizeOf = count($original);
  221. for ($x = 0; $x < $sizeOf; $x++) {
  222. if ($x < $offset) {
  223. $sorted[] = $original[$x];
  224. } else {
  225. $sorted[] = array_shift($random);
  226. }
  227. }
  228. return $sorted;
  229. }
  230. /**
  231. * Returns the modulus of an integer
  232. *
  233. * @param string|int $number
  234. * @param int $divider
  235. * @param array $items array of items to select from to return
  236. *
  237. * @return int
  238. */
  239. public function modulusFilter($number, $divider, $items = null)
  240. {
  241. if (is_string($number)) {
  242. $number = strlen($number);
  243. }
  244. $remainder = $number % $divider;
  245. if (is_array($items)) {
  246. if (isset($items[$remainder])) {
  247. return $items[$remainder];
  248. }
  249. return $items[0];
  250. }
  251. return $remainder;
  252. }
  253. /**
  254. * Inflector supports following notations:
  255. *
  256. * `{{ 'person'|pluralize }} => people`
  257. * `{{ 'shoes'|singularize }} => shoe`
  258. * `{{ 'welcome page'|titleize }} => "Welcome Page"`
  259. * `{{ 'send_email'|camelize }} => SendEmail`
  260. * `{{ 'CamelCased'|underscorize }} => camel_cased`
  261. * `{{ 'Something Text'|hyphenize }} => something-text`
  262. * `{{ 'something_text_to_read'|humanize }} => "Something text to read"`
  263. * `{{ '181'|monthize }} => 5`
  264. * `{{ '10'|ordinalize }} => 10th`
  265. *
  266. * @param string $action
  267. * @param string $data
  268. * @param int $count
  269. *
  270. * @return mixed
  271. */
  272. public function inflectorFilter($action, $data, $count = null)
  273. {
  274. $action = $action . 'ize';
  275. $inflector = $this->grav['inflector'];
  276. if (\in_array(
  277. $action,
  278. ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'],
  279. true
  280. )) {
  281. return $inflector->$action($data);
  282. }
  283. if (\in_array($action, ['pluralize', 'singularize'], true)) {
  284. if ($count) {
  285. return $inflector->$action($data, $count);
  286. }
  287. return $inflector->$action($data);
  288. }
  289. return $data;
  290. }
  291. /**
  292. * Return MD5 hash from the input.
  293. *
  294. * @param string $str
  295. *
  296. * @return string
  297. */
  298. public function md5Filter($str)
  299. {
  300. return md5($str);
  301. }
  302. /**
  303. * Return Base32 encoded string
  304. *
  305. * @param $str
  306. * @return string
  307. */
  308. public function base32EncodeFilter($str)
  309. {
  310. return Base32::encode($str);
  311. }
  312. /**
  313. * Return Base32 decoded string
  314. *
  315. * @param $str
  316. * @return bool|string
  317. */
  318. public function base32DecodeFilter($str)
  319. {
  320. return Base32::decode($str);
  321. }
  322. /**
  323. * Return Base64 encoded string
  324. *
  325. * @param $str
  326. * @return string
  327. */
  328. public function base64EncodeFilter($str)
  329. {
  330. return base64_encode($str);
  331. }
  332. /**
  333. * Return Base64 decoded string
  334. *
  335. * @param $str
  336. * @return bool|string
  337. */
  338. public function base64DecodeFilter($str)
  339. {
  340. return base64_decode($str);
  341. }
  342. /**
  343. * Sorts a collection by key
  344. *
  345. * @param array $input
  346. * @param string $filter
  347. * @param int $direction
  348. * @param int $sort_flags
  349. *
  350. * @return array
  351. */
  352. public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR)
  353. {
  354. return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags);
  355. }
  356. /**
  357. * Return ksorted collection.
  358. *
  359. * @param array $array
  360. *
  361. * @return array
  362. */
  363. public function ksortFilter($array)
  364. {
  365. if (null === $array) {
  366. $array = [];
  367. }
  368. ksort($array);
  369. return $array;
  370. }
  371. /**
  372. * Wrapper for chunk_split() function
  373. *
  374. * @param $value
  375. * @param $chars
  376. * @param string $split
  377. * @return string
  378. */
  379. public function chunkSplitFilter($value, $chars, $split = '-')
  380. {
  381. return chunk_split($value, $chars, $split);
  382. }
  383. /**
  384. * determine if a string contains another
  385. *
  386. * @param String $haystack
  387. * @param String $needle
  388. *
  389. * @return boolean
  390. */
  391. public function containsFilter($haystack, $needle)
  392. {
  393. return (strpos($haystack, $needle) !== false);
  394. }
  395. /**
  396. * displays a facebook style 'time ago' formatted date/time
  397. *
  398. * @param $date
  399. * @param $long_strings
  400. *
  401. * @return boolean
  402. */
  403. public function nicetimeFunc($date, $long_strings = true)
  404. {
  405. if (empty($date)) {
  406. return $this->grav['language']->translate('NICETIME.NO_DATE_PROVIDED', null, true);
  407. }
  408. if ($long_strings) {
  409. $periods = [
  410. "NICETIME.SECOND",
  411. "NICETIME.MINUTE",
  412. "NICETIME.HOUR",
  413. "NICETIME.DAY",
  414. "NICETIME.WEEK",
  415. "NICETIME.MONTH",
  416. "NICETIME.YEAR",
  417. "NICETIME.DECADE"
  418. ];
  419. } else {
  420. $periods = [
  421. "NICETIME.SEC",
  422. "NICETIME.MIN",
  423. "NICETIME.HR",
  424. "NICETIME.DAY",
  425. "NICETIME.WK",
  426. "NICETIME.MO",
  427. "NICETIME.YR",
  428. "NICETIME.DEC"
  429. ];
  430. }
  431. $lengths = ["60", "60", "24", "7", "4.35", "12", "10"];
  432. $now = time();
  433. // check if unix timestamp
  434. if ((string)(int)$date == $date) {
  435. $unix_date = $date;
  436. } else {
  437. $unix_date = strtotime($date);
  438. }
  439. // check validity of date
  440. if (empty($unix_date)) {
  441. return $this->grav['language']->translate('NICETIME.BAD_DATE', null, true);
  442. }
  443. // is it future date or past date
  444. if ($now > $unix_date) {
  445. $difference = $now - $unix_date;
  446. $tense = $this->grav['language']->translate('NICETIME.AGO', null, true);
  447. } else if ($now == $unix_date) {
  448. $difference = $now - $unix_date;
  449. $tense = $this->grav['language']->translate('NICETIME.JUST_NOW', null, false);
  450. } else {
  451. $difference = $unix_date - $now;
  452. $tense = $this->grav['language']->translate('NICETIME.FROM_NOW', null, true);
  453. }
  454. for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) {
  455. $difference /= $lengths[$j];
  456. }
  457. $difference = round($difference);
  458. if ($difference != 1) {
  459. $periods[$j] .= '_PLURAL';
  460. }
  461. if ($this->grav['language']->getTranslation($this->grav['language']->getLanguage(),
  462. $periods[$j] . '_MORE_THAN_TWO')
  463. ) {
  464. if ($difference > 2) {
  465. $periods[$j] .= '_MORE_THAN_TWO';
  466. }
  467. }
  468. $periods[$j] = $this->grav['language']->translate($periods[$j], null, true);
  469. if ($now == $unix_date) {
  470. return "{$tense}";
  471. }
  472. return "$difference $periods[$j] {$tense}";
  473. }
  474. /**
  475. * @param $string
  476. *
  477. * @return mixed
  478. */
  479. public function absoluteUrlFilter($string)
  480. {
  481. $url = $this->grav['uri']->base();
  482. $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string);
  483. return $string;
  484. }
  485. /**
  486. * @param $string
  487. *
  488. * @param bool $block Block or Line processing
  489. * @return mixed|string
  490. */
  491. public function markdownFunction($string, $block = true)
  492. {
  493. $page = $this->grav['page'];
  494. $defaults = $this->config->get('system.pages.markdown');
  495. // Initialize the preferred variant of Parsedown
  496. if ($defaults['extra']) {
  497. $parsedown = new ParsedownExtra($page, $defaults);
  498. } else {
  499. $parsedown = new Parsedown($page, $defaults);
  500. }
  501. if ($block) {
  502. $string = $parsedown->text($string);
  503. } else {
  504. $string = $parsedown->line($string);
  505. }
  506. return $string;
  507. }
  508. /**
  509. * @param $haystack
  510. * @param $needle
  511. *
  512. * @return bool
  513. */
  514. public function startsWithFilter($haystack, $needle)
  515. {
  516. return Utils::startsWith($haystack, $needle);
  517. }
  518. /**
  519. * @param $haystack
  520. * @param $needle
  521. *
  522. * @return bool
  523. */
  524. public function endsWithFilter($haystack, $needle)
  525. {
  526. return Utils::endsWith($haystack, $needle);
  527. }
  528. /**
  529. * @param $value
  530. * @param null $default
  531. *
  532. * @return null
  533. */
  534. public function definedDefaultFilter($value, $default = null)
  535. {
  536. return null !== $value ? $value : $default;
  537. }
  538. /**
  539. * @param $value
  540. * @param null $chars
  541. *
  542. * @return string
  543. */
  544. public function rtrimFilter($value, $chars = null)
  545. {
  546. return rtrim($value, $chars);
  547. }
  548. /**
  549. * @param $value
  550. * @param null $chars
  551. *
  552. * @return string
  553. */
  554. public function ltrimFilter($value, $chars = null)
  555. {
  556. return ltrim($value, $chars);
  557. }
  558. /**
  559. * Casts input to string.
  560. *
  561. * @param mixed $input
  562. * @return string
  563. */
  564. public function stringFilter($input)
  565. {
  566. return (string) $input;
  567. }
  568. /**
  569. * Casts input to int.
  570. *
  571. * @param mixed $input
  572. * @return int
  573. */
  574. public function intFilter($input)
  575. {
  576. return (int) $input;
  577. }
  578. /**
  579. * Casts input to bool.
  580. *
  581. * @param mixed $input
  582. * @return bool
  583. */
  584. public function boolFilter($input)
  585. {
  586. return (bool) $input;
  587. }
  588. /**
  589. * Casts input to float.
  590. *
  591. * @param mixed $input
  592. * @return float
  593. */
  594. public function floatFilter($input)
  595. {
  596. return (float) $input;
  597. }
  598. /**
  599. * Casts input to array.
  600. *
  601. * @param mixed $input
  602. * @return array
  603. */
  604. public function arrayFilter($input)
  605. {
  606. return (array) $input;
  607. }
  608. /**
  609. * @return mixed
  610. */
  611. public function translate()
  612. {
  613. return $this->grav['language']->translate(func_get_args());
  614. }
  615. /**
  616. * Translate Strings
  617. *
  618. * @param $args
  619. * @param array|null $languages
  620. * @param bool $array_support
  621. * @param bool $html_out
  622. * @return mixed
  623. */
  624. public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false)
  625. {
  626. return $this->grav['language']->translate($args, $languages, $array_support, $html_out);
  627. }
  628. /**
  629. * @param $key
  630. * @param $index
  631. * @param null $lang
  632. *
  633. * @return mixed
  634. */
  635. public function translateArray($key, $index, $lang = null)
  636. {
  637. return $this->grav['language']->translateArray($key, $index, $lang);
  638. }
  639. /**
  640. * Repeat given string x times.
  641. *
  642. * @param string $input
  643. * @param int $multiplier
  644. *
  645. * @return string
  646. */
  647. public function repeatFunc($input, $multiplier)
  648. {
  649. return str_repeat($input, $multiplier);
  650. }
  651. /**
  652. * Return URL to the resource.
  653. *
  654. * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }}
  655. *
  656. * @param string $input Resource to be located.
  657. * @param bool $domain True to include domain name.
  658. *
  659. * @return string|null Returns url to the resource or null if resource was not found.
  660. */
  661. public function urlFunc($input, $domain = false)
  662. {
  663. return Utils::url($input, $domain);
  664. }
  665. /**
  666. * This function will evaluate Twig $twig through the $environment, and return its results.
  667. *
  668. * @param array $context
  669. * @param string $twig
  670. * @return mixed
  671. */
  672. public function evaluateTwigFunc($context, $twig ) {
  673. $loader = new \Twig_Loader_Filesystem('.');
  674. $env = new \Twig_Environment($loader);
  675. $template = $env->createTemplate($twig);
  676. return $template->render($context);
  677. }
  678. /**
  679. * This function will evaluate a $string through the $environment, and return its results.
  680. *
  681. * @param $context
  682. * @param $string
  683. * @return mixed
  684. */
  685. public function evaluateStringFunc($context, $string )
  686. {
  687. return $this->evaluateTwigFunc($context, "{{ $string }}");
  688. }
  689. /**
  690. * Based on Twig_Extension_Debug / twig_var_dump
  691. * (c) 2011 Fabien Potencier
  692. *
  693. * @param \Twig_Environment $env
  694. * @param $context
  695. */
  696. public function dump(\Twig_Environment $env, $context)
  697. {
  698. if (!$env->isDebug() || !$this->debugger) {
  699. return;
  700. }
  701. $count = func_num_args();
  702. if (2 === $count) {
  703. $data = [];
  704. foreach ($context as $key => $value) {
  705. if (is_object($value)) {
  706. if (method_exists($value, 'toArray')) {
  707. $data[$key] = $value->toArray();
  708. } else {
  709. $data[$key] = "Object (" . get_class($value) . ")";
  710. }
  711. } else {
  712. $data[$key] = $value;
  713. }
  714. }
  715. $this->debugger->addMessage($data, 'debug');
  716. } else {
  717. for ($i = 2; $i < $count; $i++) {
  718. $this->debugger->addMessage(func_get_arg($i), 'debug');
  719. }
  720. }
  721. }
  722. /**
  723. * Output a Gist
  724. *
  725. * @param string $id
  726. * @param string|bool $file
  727. *
  728. * @return string
  729. */
  730. public function gistFunc($id, $file = false)
  731. {
  732. $url = 'https://gist.github.com/' . $id . '.js';
  733. if ($file) {
  734. $url .= '?file=' . $file;
  735. }
  736. return '<script src="' . $url . '"></script>';
  737. }
  738. /**
  739. * Generate a random string
  740. *
  741. * @param int $count
  742. *
  743. * @return string
  744. */
  745. public function randomStringFunc($count = 5)
  746. {
  747. return Utils::generateRandomString($count);
  748. }
  749. /**
  750. * Pad a string to a certain length with another string
  751. *
  752. * @param $input
  753. * @param $pad_length
  754. * @param string $pad_string
  755. * @param int $pad_type
  756. *
  757. * @return string
  758. */
  759. public static function padFilter($input, $pad_length, $pad_string = " ", $pad_type = STR_PAD_RIGHT)
  760. {
  761. return str_pad($input, (int)$pad_length, $pad_string, $pad_type);
  762. }
  763. /**
  764. * Workaround for twig associative array initialization
  765. * Returns a key => val array
  766. *
  767. * @param string $key key of item
  768. * @param string $val value of item
  769. * @param array $current_array optional array to add to
  770. *
  771. * @return array
  772. */
  773. public function arrayKeyValueFunc($key, $val, $current_array = null)
  774. {
  775. if (empty($current_array)) {
  776. return array($key => $val);
  777. }
  778. $current_array[$key] = $val;
  779. return $current_array;
  780. }
  781. /**
  782. * Wrapper for array_intersect() method
  783. *
  784. * @param $array1
  785. * @param $array2
  786. * @return array
  787. */
  788. public function arrayIntersectFunc($array1, $array2)
  789. {
  790. if ($array1 instanceof Collection && $array2 instanceof Collection) {
  791. return $array1->intersect($array2);
  792. }
  793. return array_intersect($array1, $array2);
  794. }
  795. /**
  796. * Returns a string from a value. If the value is array, return it json encoded
  797. *
  798. * @param $value
  799. *
  800. * @return string
  801. */
  802. public function stringFunc($value)
  803. {
  804. if (is_array($value)) { //format the array as a string
  805. return json_encode($value);
  806. }
  807. return $value;
  808. }
  809. /**
  810. * Translate a string
  811. *
  812. * @return string
  813. */
  814. public function translateFunc()
  815. {
  816. return $this->grav['language']->translate(func_get_args());
  817. }
  818. /**
  819. * Authorize an action. Returns true if the user is logged in and
  820. * has the right to execute $action.
  821. *
  822. * @param string|array $action An action or a list of actions. Each
  823. * entry can be a string like 'group.action'
  824. * or without dot notation an associative
  825. * array.
  826. * @return bool Returns TRUE if the user is authorized to
  827. * perform the action, FALSE otherwise.
  828. */
  829. public function authorize($action)
  830. {
  831. /** @var User $user */
  832. $user = $this->grav['user'];
  833. if (!$user->authenticated || (isset($user->authorized) && !$user->authorized)) {
  834. return false;
  835. }
  836. $action = (array) $action;
  837. foreach ($action as $key => $perms) {
  838. $prefix = is_int($key) ? '' : $key . '.';
  839. $perms = $prefix ? (array) $perms : [$perms => true];
  840. foreach ($perms as $action2 => $authenticated) {
  841. if ($user->authorize($prefix . $action2)) {
  842. return $authenticated;
  843. }
  844. }
  845. }
  846. return false;
  847. }
  848. /**
  849. * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action.
  850. *
  851. * For maximum protection, ensure that the string representing the action is as specific as possible
  852. *
  853. * @param string $action the action
  854. * @param string $nonceParamName a custom nonce param name
  855. *
  856. * @return string the nonce input field
  857. */
  858. public function nonceFieldFunc($action, $nonceParamName = 'nonce')
  859. {
  860. $string = '<input type="hidden" name="' . $nonceParamName . '" value="' . Utils::getNonce($action) . '" />';
  861. return $string;
  862. }
  863. /**
  864. * Decodes string from JSON.
  865. *
  866. * @param string $str
  867. * @param bool $assoc
  868. * @param int $depth
  869. * @param int $options
  870. * @return array
  871. */
  872. public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0)
  873. {
  874. return json_decode(html_entity_decode($str), $assoc, $depth, $options);
  875. }
  876. /**
  877. * Used to retrieve a cookie value
  878. *
  879. * @param string $key The cookie name to retrieve
  880. *
  881. * @return mixed
  882. */
  883. public function getCookie($key)
  884. {
  885. return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_STRING);
  886. }
  887. /**
  888. * Twig wrapper for PHP's preg_replace method
  889. *
  890. * @param mixed $subject the content to perform the replacement on
  891. * @param mixed $pattern the regex pattern to use for matches
  892. * @param mixed $replace the replacement value either as a string or an array of replacements
  893. * @param int $limit the maximum possible replacements for each pattern in each subject
  894. *
  895. * @return mixed the resulting content
  896. */
  897. public function regexReplace($subject, $pattern, $replace, $limit = -1)
  898. {
  899. return preg_replace($pattern, $replace, $subject, $limit);
  900. }
  901. /**
  902. * Twig wrapper for PHP's preg_grep method
  903. *
  904. * @param $array
  905. * @param $regex
  906. * @param int $flags
  907. * @return array
  908. */
  909. public function regexFilter($array, $regex, $flags = 0) {
  910. return preg_grep($regex, $array, $flags);
  911. }
  912. /**
  913. * redirect browser from twig
  914. *
  915. * @param string $url the url to redirect to
  916. * @param int $statusCode statusCode, default 303
  917. */
  918. public function redirectFunc($url, $statusCode = 303)
  919. {
  920. header('Location: ' . $url, true, $statusCode);
  921. exit();
  922. }
  923. /**
  924. * Generates an array containing a range of elements, optionally stepped
  925. *
  926. * @param int $start Minimum number, default 0
  927. * @param int $end Maximum number, default `getrandmax()`
  928. * @param int $step Increment between elements in the sequence, default 1
  929. *
  930. * @return array
  931. */
  932. public function rangeFunc($start = 0, $end = 100, $step = 1)
  933. {
  934. return range($start, $end, $step);
  935. }
  936. /**
  937. * Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest,
  938. * in which case we may unsafely assume ajax. Non critical use only.
  939. *
  940. * @return true if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest
  941. */
  942. public function isAjaxFunc()
  943. {
  944. return (
  945. !empty($_SERVER['HTTP_X_REQUESTED_WITH'])
  946. && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest');
  947. }
  948. /**
  949. * Get's the Exif data for a file
  950. *
  951. * @param $image
  952. * @param bool $raw
  953. * @return mixed
  954. */
  955. public function exifFunc($image, $raw = false)
  956. {
  957. if (isset($this->grav['exif'])) {
  958. /** @var UniformResourceLocator $locator */
  959. $locator = $this->grav['locator'];
  960. if ($locator->isStream($image)) {
  961. $image = $locator->findResource($image);
  962. }
  963. $exif_reader = $this->grav['exif']->getReader();
  964. if (file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) {
  965. $exif_data = $exif_reader->read($image);
  966. if ($exif_data) {
  967. if ($raw) {
  968. return $exif_data->getRawData();
  969. }
  970. return $exif_data->getData();
  971. }
  972. }
  973. }
  974. return null;
  975. }
  976. /**
  977. * Simple function to read a file based on a filepath and output it
  978. *
  979. * @param $filepath
  980. * @return bool|string
  981. */
  982. public function readFileFunc($filepath)
  983. {
  984. /** @var UniformResourceLocator $locator */
  985. $locator = $this->grav['locator'];
  986. if ($locator->isStream($filepath)) {
  987. $filepath = $locator->findResource($filepath);
  988. }
  989. if (file_exists($filepath)) {
  990. return file_get_contents($filepath);
  991. }
  992. return false;
  993. }
  994. /**
  995. * Process a folder as Media and return a media object
  996. *
  997. * @param $media_dir
  998. * @return Media|null
  999. */
  1000. public function mediaDirFunc($media_dir)
  1001. {
  1002. /** @var UniformResourceLocator $locator */
  1003. $locator = $this->grav['locator'];
  1004. if ($locator->isStream($media_dir)) {
  1005. $media_dir = $locator->findResource($media_dir);
  1006. }
  1007. if (file_exists($media_dir)) {
  1008. return new Media($media_dir);
  1009. }
  1010. return null;
  1011. }
  1012. /**
  1013. * Dump a variable to the browser
  1014. *
  1015. * @param $var
  1016. */
  1017. public function vardumpFunc($var)
  1018. {
  1019. var_dump($var);
  1020. }
  1021. /**
  1022. * Returns a nicer more readable filesize based on bytes
  1023. *
  1024. * @param $bytes
  1025. * @return string
  1026. */
  1027. public function niceFilesizeFunc($bytes)
  1028. {
  1029. if ($bytes >= 1073741824)
  1030. {
  1031. $bytes = number_format($bytes / 1073741824, 2) . ' GB';
  1032. }
  1033. elseif ($bytes >= 1048576)
  1034. {
  1035. $bytes = number_format($bytes / 1048576, 2) . ' MB';
  1036. }
  1037. elseif ($bytes >= 1024)
  1038. {
  1039. $bytes = number_format($bytes / 1024, 1) . ' KB';
  1040. }
  1041. elseif ($bytes > 1)
  1042. {
  1043. $bytes = $bytes . ' bytes';
  1044. }
  1045. elseif ($bytes == 1)
  1046. {
  1047. $bytes = $bytes . ' byte';
  1048. }
  1049. else
  1050. {
  1051. $bytes = '0 bytes';
  1052. }
  1053. return $bytes;
  1054. }
  1055. /**
  1056. * Returns a nicer more readable number
  1057. *
  1058. * @param int|float $n
  1059. * @return bool|string
  1060. */
  1061. public function niceNumberFunc($n)
  1062. {
  1063. // first strip any formatting;
  1064. $n = 0 + str_replace(',', '', $n);
  1065. // is this a number?
  1066. if (!is_numeric($n)) {
  1067. return false;
  1068. }
  1069. // now filter it;
  1070. if ($n > 1000000000000) {
  1071. return round(($n/1000000000000), 2).' t';
  1072. }
  1073. if ($n > 1000000000) {
  1074. return round(($n/1000000000), 2).' b';
  1075. }
  1076. if ($n > 1000000) {
  1077. return round(($n/1000000), 2).' m';
  1078. }
  1079. if ($n > 1000) {
  1080. return round(($n/1000), 2).' k';
  1081. }
  1082. return number_format($n);
  1083. }
  1084. /**
  1085. * Get a theme variable
  1086. *
  1087. * @param $var
  1088. * @param bool $default
  1089. * @return string
  1090. */
  1091. public function themeVarFunc($var, $default = null)
  1092. {
  1093. $header = $this->grav['page']->header();
  1094. $header_classes = isset($header->$var) ? $header->$var : null;
  1095. return $header_classes ?: $this->config->get('theme.' . $var, $default);
  1096. }
  1097. /**
  1098. * takes an array of classes, and if they are not set on body_classes
  1099. * look to see if they are set in theme config
  1100. *
  1101. * @param $classes
  1102. * @return string
  1103. */
  1104. public function bodyClassFunc($classes)
  1105. {
  1106. $header = $this->grav['page']->header();
  1107. $body_classes = isset($header->body_classes) ? $header->body_classes : '';
  1108. foreach ((array)$classes as $class) {
  1109. if (!empty($body_classes) && Utils::contains($body_classes, $class)) {
  1110. continue;
  1111. }
  1112. $val = $this->config->get('theme.' . $class, false) ? $class : false;
  1113. $body_classes .= $val ? ' ' . $val : '';
  1114. }
  1115. return $body_classes;
  1116. }
  1117. /**
  1118. * Look for a page header variable in an array of pages working its way through until a value is found
  1119. *
  1120. * @param $var
  1121. * @param null $pages
  1122. * @return mixed
  1123. */
  1124. public function pageHeaderVarFunc($var, $pages = null)
  1125. {
  1126. if ($pages === null) {
  1127. $pages = $this->grav['page'];
  1128. }
  1129. // Make sure pages are an array
  1130. if (!is_array($pages)) {
  1131. $pages = array($pages);
  1132. }
  1133. // Loop over pages and look for header vars
  1134. foreach ($pages as $page) {
  1135. if (is_string($page)) {
  1136. $page = $this->grav['pages']->find($page);
  1137. }
  1138. if ($page) {
  1139. $header = $page->header();
  1140. if (isset($header->$var)) {
  1141. return $header->$var;
  1142. }
  1143. }
  1144. }
  1145. return null;
  1146. }
  1147. /**
  1148. * Dump/Encode data into YAML format
  1149. *
  1150. * @param $data
  1151. * @param $inline integer number of levels of inline syntax
  1152. * @return mixed
  1153. */
  1154. public function yamlEncodeFilter($data, $inline = 10)
  1155. {
  1156. return Yaml::dump($data, $inline);
  1157. }
  1158. /**
  1159. * Decode/Parse data from YAML format
  1160. *
  1161. * @param $data
  1162. * @return mixed
  1163. */
  1164. public function yamlDecodeFilter($data)
  1165. {
  1166. return Yaml::parse($data);
  1167. }
  1168. }