grav = Grav::instance(); $this->debugger = $this->grav['debugger'] ?? null; $this->config = $this->grav['config']; } /** * Register some standard globals * * @return array */ public function getGlobals() { return [ 'grav' => $this->grav, ]; } /** * Return a list of all filters. * * @return array */ public function getFilters() { return [ new \Twig_SimpleFilter('*ize', [$this, 'inflectorFilter']), new \Twig_SimpleFilter('absolute_url', [$this, 'absoluteUrlFilter']), new \Twig_SimpleFilter('contains', [$this, 'containsFilter']), new \Twig_SimpleFilter('chunk_split', [$this, 'chunkSplitFilter']), new \Twig_SimpleFilter('nicenumber', [$this, 'niceNumberFunc']), new \Twig_SimpleFilter('nicefilesize', [$this, 'niceFilesizeFunc']), new \Twig_SimpleFilter('nicetime', [$this, 'nicetimeFunc']), new \Twig_SimpleFilter('defined', [$this, 'definedDefaultFilter']), new \Twig_SimpleFilter('ends_with', [$this, 'endsWithFilter']), new \Twig_SimpleFilter('fieldName', [$this, 'fieldNameFilter']), new \Twig_SimpleFilter('ksort', [$this, 'ksortFilter']), new \Twig_SimpleFilter('ltrim', [$this, 'ltrimFilter']), new \Twig_SimpleFilter('markdown', [$this, 'markdownFunction'], ['needs_context' => true, 'is_safe' => ['html']]), new \Twig_SimpleFilter('md5', [$this, 'md5Filter']), new \Twig_SimpleFilter('base32_encode', [$this, 'base32EncodeFilter']), new \Twig_SimpleFilter('base32_decode', [$this, 'base32DecodeFilter']), new \Twig_SimpleFilter('base64_encode', [$this, 'base64EncodeFilter']), new \Twig_SimpleFilter('base64_decode', [$this, 'base64DecodeFilter']), new \Twig_SimpleFilter('randomize', [$this, 'randomizeFilter']), new \Twig_SimpleFilter('modulus', [$this, 'modulusFilter']), new \Twig_SimpleFilter('rtrim', [$this, 'rtrimFilter']), new \Twig_SimpleFilter('pad', [$this, 'padFilter']), new \Twig_SimpleFilter('regex_replace', [$this, 'regexReplace']), new \Twig_SimpleFilter('safe_email', [$this, 'safeEmailFilter']), new \Twig_SimpleFilter('safe_truncate', ['\Grav\Common\Utils', 'safeTruncate']), new \Twig_SimpleFilter('safe_truncate_html', ['\Grav\Common\Utils', 'safeTruncateHTML']), new \Twig_SimpleFilter('sort_by_key', [$this, 'sortByKeyFilter']), new \Twig_SimpleFilter('starts_with', [$this, 'startsWithFilter']), new \Twig_SimpleFilter('truncate', ['\Grav\Common\Utils', 'truncate']), new \Twig_SimpleFilter('truncate_html', ['\Grav\Common\Utils', 'truncateHTML']), new \Twig_SimpleFilter('json_decode', [$this, 'jsonDecodeFilter']), new \Twig_SimpleFilter('array_unique', 'array_unique'), new \Twig_SimpleFilter('basename', 'basename'), new \Twig_SimpleFilter('dirname', 'dirname'), new \Twig_SimpleFilter('print_r', 'print_r'), new \Twig_SimpleFilter('yaml_encode', [$this, 'yamlEncodeFilter']), new \Twig_SimpleFilter('yaml_decode', [$this, 'yamlDecodeFilter']), new \Twig_SimpleFilter('nicecron', [$this, 'niceCronFilter']), // Translations new \Twig_SimpleFilter('t', [$this, 'translate'], ['needs_environment' => true]), new \Twig_SimpleFilter('tl', [$this, 'translateLanguage']), new \Twig_SimpleFilter('ta', [$this, 'translateArray']), // Casting values new \Twig_SimpleFilter('string', [$this, 'stringFilter']), new \Twig_SimpleFilter('int', [$this, 'intFilter'], ['is_safe' => ['all']]), new \Twig_SimpleFilter('bool', [$this, 'boolFilter']), new \Twig_SimpleFilter('float', [$this, 'floatFilter'], ['is_safe' => ['all']]), new \Twig_SimpleFilter('array', [$this, 'arrayFilter']), // Object Types new \Twig_SimpleFilter('get_type', [$this, 'getTypeFunc']), new \Twig_SimpleFilter('of_type', [$this, 'ofTypeFunc']) ]; } /** * Return a list of all functions. * * @return array */ public function getFunctions() { return [ new \Twig_SimpleFunction('array', [$this, 'arrayFilter']), new \Twig_SimpleFunction('array_key_value', [$this, 'arrayKeyValueFunc']), new \Twig_SimpleFunction('array_key_exists', 'array_key_exists'), new \Twig_SimpleFunction('array_unique', 'array_unique'), new \Twig_SimpleFunction('array_intersect', [$this, 'arrayIntersectFunc']), new \Twig_SimpleFunction('authorize', [$this, 'authorize']), new \Twig_SimpleFunction('debug', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), new \Twig_SimpleFunction('dump', [$this, 'dump'], ['needs_context' => true, 'needs_environment' => true]), new \Twig_SimpleFunction('vardump', [$this, 'vardumpFunc']), new \Twig_SimpleFunction('print_r', 'print_r'), new \Twig_SimpleFunction('http_response_code', 'http_response_code'), new \Twig_SimpleFunction('evaluate', [$this, 'evaluateStringFunc'], ['needs_context' => true]), new \Twig_SimpleFunction('evaluate_twig', [$this, 'evaluateTwigFunc'], ['needs_context' => true]), new \Twig_SimpleFunction('gist', [$this, 'gistFunc']), new \Twig_SimpleFunction('nonce_field', [$this, 'nonceFieldFunc']), new \Twig_SimpleFunction('pathinfo', 'pathinfo'), new \Twig_SimpleFunction('random_string', [$this, 'randomStringFunc']), new \Twig_SimpleFunction('repeat', [$this, 'repeatFunc']), new \Twig_SimpleFunction('regex_replace', [$this, 'regexReplace']), new \Twig_SimpleFunction('regex_filter', [$this, 'regexFilter']), new \Twig_SimpleFunction('string', [$this, 'stringFunc']), new \Twig_SimpleFunction('url', [$this, 'urlFunc']), new \Twig_SimpleFunction('json_decode', [$this, 'jsonDecodeFilter']), new \Twig_SimpleFunction('get_cookie', [$this, 'getCookie']), new \Twig_SimpleFunction('redirect_me', [$this, 'redirectFunc']), new \Twig_SimpleFunction('range', [$this, 'rangeFunc']), new \Twig_SimpleFunction('isajaxrequest', [$this, 'isAjaxFunc']), new \Twig_SimpleFunction('exif', [$this, 'exifFunc']), new \Twig_SimpleFunction('media_directory', [$this, 'mediaDirFunc']), new \Twig_SimpleFunction('body_class', [$this, 'bodyClassFunc']), new \Twig_SimpleFunction('theme_var', [$this, 'themeVarFunc']), new \Twig_SimpleFunction('header_var', [$this, 'pageHeaderVarFunc']), new \Twig_SimpleFunction('read_file', [$this, 'readFileFunc']), new \Twig_SimpleFunction('nicenumber', [$this, 'niceNumberFunc']), new \Twig_SimpleFunction('nicefilesize', [$this, 'niceFilesizeFunc']), new \Twig_SimpleFunction('nicetime', [$this, 'nicetimeFunc']), new \Twig_SimpleFunction('cron', [$this, 'cronFunc']), new \Twig_SimpleFunction('xss', [$this, 'xssFunc']), // Translations new \Twig_SimpleFunction('t', [$this, 'translate'], ['needs_environment' => true]), new \Twig_SimpleFunction('tl', [$this, 'translateLanguage']), new \Twig_SimpleFunction('ta', [$this, 'translateArray']), // Object Types new \Twig_SimpleFunction('get_type', [$this, 'getTypeFunc']), new \Twig_SimpleFunction('of_type', [$this, 'ofTypeFunc']) ]; } /** * @return array */ public function getTokenParsers() { return [ new TwigTokenParserRender(), new TwigTokenParserThrow(), new TwigTokenParserTryCatch(), new TwigTokenParserScript(), new TwigTokenParserStyle(), new TwigTokenParserMarkdown(), new TwigTokenParserSwitch(), ]; } /** * Filters field name by changing dot notation into array notation. * * @param string $str * * @return string */ public function fieldNameFilter($str) { $path = explode('.', rtrim($str, '.')); return array_shift($path) . ($path ? '[' . implode('][', $path) . ']' : ''); } /** * Protects email address. * * @param string $str * * @return string */ public function safeEmailFilter($str) { $email = ''; for ($i = 0, $len = strlen($str); $i < $len; $i++) { $j = random_int(0, 1); $email .= $j === 0 ? '&#' . ord($str[$i]) . ';' : $str[$i]; } return str_replace('@', '@', $email); } /** * Returns array in a random order. * * @param array $original * @param int $offset Can be used to return only slice of the array. * * @return array */ public function randomizeFilter($original, $offset = 0) { if (!\is_array($original)) { return $original; } if ($original instanceof \Traversable) { $original = iterator_to_array($original, false); } $sorted = []; $random = array_slice($original, $offset); shuffle($random); $sizeOf = \count($original); for ($x = 0; $x < $sizeOf; $x++) { if ($x < $offset) { $sorted[] = $original[$x]; } else { $sorted[] = array_shift($random); } } return $sorted; } /** * Returns the modulus of an integer * * @param string|int $number * @param int $divider * @param array $items array of items to select from to return * * @return int */ public function modulusFilter($number, $divider, $items = null) { if (\is_string($number)) { $number = strlen($number); } $remainder = $number % $divider; if (\is_array($items)) { return $items[$remainder] ?? $items[0]; } return $remainder; } /** * Inflector supports following notations: * * `{{ 'person'|pluralize }} => people` * `{{ 'shoes'|singularize }} => shoe` * `{{ 'welcome page'|titleize }} => "Welcome Page"` * `{{ 'send_email'|camelize }} => SendEmail` * `{{ 'CamelCased'|underscorize }} => camel_cased` * `{{ 'Something Text'|hyphenize }} => something-text` * `{{ 'something_text_to_read'|humanize }} => "Something text to read"` * `{{ '181'|monthize }} => 5` * `{{ '10'|ordinalize }} => 10th` * * @param string $action * @param string $data * @param int $count * * @return string */ public function inflectorFilter($action, $data, $count = null) { $action .= 'ize'; $inflector = $this->grav['inflector']; if (\in_array( $action, ['titleize', 'camelize', 'underscorize', 'hyphenize', 'humanize', 'ordinalize', 'monthize'], true )) { return $inflector->{$action}($data); } if (\in_array($action, ['pluralize', 'singularize'], true)) { return $count ? $inflector->{$action}($data, $count) : $inflector->{$action}($data); } return $data; } /** * Return MD5 hash from the input. * * @param string $str * * @return string */ public function md5Filter($str) { return md5($str); } /** * Return Base32 encoded string * * @param string $str * @return string */ public function base32EncodeFilter($str) { return Base32::encode($str); } /** * Return Base32 decoded string * * @param string $str * @return bool|string */ public function base32DecodeFilter($str) { return Base32::decode($str); } /** * Return Base64 encoded string * * @param string $str * @return string */ public function base64EncodeFilter($str) { return base64_encode($str); } /** * Return Base64 decoded string * * @param string $str * @return bool|string */ public function base64DecodeFilter($str) { return base64_decode($str); } /** * Sorts a collection by key * * @param array $input * @param string $filter * @param int $direction * @param int $sort_flags * * @return array */ public function sortByKeyFilter($input, $filter, $direction = SORT_ASC, $sort_flags = SORT_REGULAR) { return Utils::sortArrayByKey($input, $filter, $direction, $sort_flags); } /** * Return ksorted collection. * * @param array $array * * @return array */ public function ksortFilter($array) { if (null === $array) { $array = []; } ksort($array); return $array; } /** * Wrapper for chunk_split() function * * @param string $value * @param int $chars * @param string $split * @return string */ public function chunkSplitFilter($value, $chars, $split = '-') { return chunk_split($value, $chars, $split); } /** * determine if a string contains another * * @param string $haystack * @param string $needle * * @return bool */ public function containsFilter($haystack, $needle) { if (empty($needle)) { return $haystack; } return (strpos($haystack, (string) $needle) !== false); } /** * Gets a human readable output for cron syntax * * @param $at * @return string */ public function niceCronFilter($at) { $cron = new Cron($at); return $cron->getText('en'); } /** * Get Cron object for a crontab 'at' format * * @param string $at * @return CronExpression */ public function cronFunc($at) { return CronExpression::factory($at); } /** * displays a facebook style 'time ago' formatted date/time * * @param string $date * @param bool $long_strings * * @param bool $show_tense * @return bool */ public function nicetimeFunc($date, $long_strings = true, $show_tense = true) { if (empty($date)) { return $this->grav['language']->translate('GRAV.NICETIME.NO_DATE_PROVIDED', null, true); } if ($long_strings) { $periods = [ 'NICETIME.SECOND', 'NICETIME.MINUTE', 'NICETIME.HOUR', 'NICETIME.DAY', 'NICETIME.WEEK', 'NICETIME.MONTH', 'NICETIME.YEAR', 'NICETIME.DECADE' ]; } else { $periods = [ 'NICETIME.SEC', 'NICETIME.MIN', 'NICETIME.HR', 'NICETIME.DAY', 'NICETIME.WK', 'NICETIME.MO', 'NICETIME.YR', 'NICETIME.DEC' ]; } $lengths = ['60', '60', '24', '7', '4.35', '12', '10']; $now = time(); // check if unix timestamp if ((string)(int)$date === (string)$date) { $unix_date = $date; } else { $unix_date = strtotime($date); } // check validity of date if (empty($unix_date)) { return $this->grav['language']->translate('GRAV.NICETIME.BAD_DATE', null, true); } // is it future date or past date if ($now > $unix_date) { $difference = $now - $unix_date; $tense = $this->grav['language']->translate('GRAV.NICETIME.AGO', null, true); } elseif ($now == $unix_date) { $difference = $now - $unix_date; $tense = $this->grav['language']->translate('GRAV.NICETIME.JUST_NOW', null, false); } else { $difference = $unix_date - $now; $tense = $this->grav['language']->translate('GRAV.NICETIME.FROM_NOW', null, true); } for ($j = 0; $difference >= $lengths[$j] && $j < count($lengths) - 1; $j++) { $difference /= $lengths[$j]; } $difference = round($difference); if ($difference != 1) { $periods[$j] .= '_PLURAL'; } if ($this->grav['language']->getTranslation($this->grav['language']->getLanguage(), $periods[$j] . '_MORE_THAN_TWO') ) { if ($difference > 2) { $periods[$j] .= '_MORE_THAN_TWO'; } } $periods[$j] = $this->grav['language']->translate('GRAV.'.$periods[$j], null, true); if ($now == $unix_date) { return $tense; } $time = "{$difference} {$periods[$j]}"; $time .= $show_tense ? " {$tense}" : ''; return $time; } /** * Allow quick check of a string for XSS Vulnerabilities * * @param string|array $data * @return bool|string|array */ public function xssFunc($data) { if (!\is_array($data)) { return Security::detectXss($data); } $results = Security::detectXssFromArray($data); $results_parts = array_map(function($value, $key) { return $key.': \''.$value . '\''; }, array_values($results), array_keys($results)); return implode(', ', $results_parts); } /** * @param string $string * * @return mixed */ public function absoluteUrlFilter($string) { $url = $this->grav['uri']->base(); $string = preg_replace('/((?:href|src) *= *[\'"](?!(http|ftp)))/i', "$1$url", $string); return $string; } /** * @param string $string * * @param array $context * @param bool $block Block or Line processing * @return mixed|string */ public function markdownFunction($context, $string, $block = true) { $page = $context['page'] ?? null; return Utils::processMarkdown($string, $block, $page); } /** * @param string $haystack * @param string $needle * * @return bool */ public function startsWithFilter($haystack, $needle) { return Utils::startsWith($haystack, $needle); } /** * @param string $haystack * @param string $needle * * @return bool */ public function endsWithFilter($haystack, $needle) { return Utils::endsWith($haystack, $needle); } /** * @param mixed $value * @param null $default * * @return null */ public function definedDefaultFilter($value, $default = null) { return null !== $value ? $value : $default; } /** * @param string $value * @param null $chars * * @return string */ public function rtrimFilter($value, $chars = null) { return rtrim($value, $chars); } /** * @param string $value * @param null $chars * * @return string */ public function ltrimFilter($value, $chars = null) { return ltrim($value, $chars); } /** * Casts input to string. * * @param mixed $input * @return string */ public function stringFilter($input) { return (string) $input; } /** * Casts input to int. * * @param mixed $input * @return int */ public function intFilter($input) { return (int) $input; } /** * Casts input to bool. * * @param mixed $input * @return bool */ public function boolFilter($input) { return (bool) $input; } /** * Casts input to float. * * @param mixed $input * @return float */ public function floatFilter($input) { return (float) $input; } /** * Casts input to array. * * @param mixed $input * @return array */ public function arrayFilter($input) { return (array) $input; } /** * @return string */ public function translate(\Twig_Environment $twig) { // shift off the environment $args = func_get_args(); array_shift($args); // If admin and tu filter provided, use it if (isset($this->grav['admin'])) { $numargs = count($args); $lang = null; if (($numargs === 3 && is_array($args[1])) || ($numargs === 2 && !is_array($args[1]))) { $lang = array_pop($args); } elseif ($numargs === 2 && is_array($args[1])) { $subs = array_pop($args); $args = array_merge($args, $subs); } return $this->grav['admin']->translate($args, $lang); } // else use the default grav translate functionality return $this->grav['language']->translate($args); } /** * Translate Strings * * @param string|array $args * @param array|null $languages * @param bool $array_support * @param bool $html_out * @return string */ public function translateLanguage($args, array $languages = null, $array_support = false, $html_out = false) { /** @var Language $language */ $language = $this->grav['language']; return $language->translate($args, $languages, $array_support, $html_out); } /** * @param string $key * @param string $index * @param array|null $lang * @return string */ public function translateArray($key, $index, $lang = null) { /** @var Language $language */ $language = $this->grav['language']; return $language->translateArray($key, $index, $lang); } /** * Repeat given string x times. * * @param string $input * @param int $multiplier * * @return string */ public function repeatFunc($input, $multiplier) { return str_repeat($input, $multiplier); } /** * Return URL to the resource. * * @example {{ url('theme://images/logo.png')|default('http://www.placehold.it/150x100/f4f4f4') }} * * @param string $input Resource to be located. * @param bool $domain True to include domain name. * * @return string|null Returns url to the resource or null if resource was not found. */ public function urlFunc($input, $domain = false) { return Utils::url($input, $domain); } /** * This function will evaluate Twig $twig through the $environment, and return its results. * * @param array $context * @param string $twig * @return mixed */ public function evaluateTwigFunc($context, $twig ) { $loader = new \Twig_Loader_Filesystem('.'); $env = new \Twig_Environment($loader); $template = $env->createTemplate($twig); return $template->render($context); } /** * This function will evaluate a $string through the $environment, and return its results. * * @param array $context * @param string $string * @return mixed */ public function evaluateStringFunc($context, $string ) { return $this->evaluateTwigFunc($context, "{{ $string }}"); } /** * Based on Twig_Extension_Debug / twig_var_dump * (c) 2011 Fabien Potencier * * @param \Twig_Environment $env * @param string $context */ public function dump(\Twig_Environment $env, $context) { if (!$env->isDebug() || !$this->debugger) { return; } $count = func_num_args(); if (2 === $count) { $data = []; foreach ($context as $key => $value) { if (is_object($value)) { if (method_exists($value, 'toArray')) { $data[$key] = $value->toArray(); } else { $data[$key] = "Object (" . get_class($value) . ")"; } } else { $data[$key] = $value; } } $this->debugger->addMessage($data, 'debug'); } else { for ($i = 2; $i < $count; $i++) { $this->debugger->addMessage(func_get_arg($i), 'debug'); } } } /** * Output a Gist * * @param string $id * @param string|bool $file * * @return string */ public function gistFunc($id, $file = false) { $url = 'https://gist.github.com/' . $id . '.js'; if ($file) { $url .= '?file=' . $file; } return ''; } /** * Generate a random string * * @param int $count * * @return string */ public function randomStringFunc($count = 5) { return Utils::generateRandomString($count); } /** * Pad a string to a certain length with another string * * @param string $input * @param int $pad_length * @param string $pad_string * @param int $pad_type * * @return string */ public static function padFilter($input, $pad_length, $pad_string = ' ', $pad_type = STR_PAD_RIGHT) { return str_pad($input, (int)$pad_length, $pad_string, $pad_type); } /** * Workaround for twig associative array initialization * Returns a key => val array * * @param string $key key of item * @param string $val value of item * @param array $current_array optional array to add to * * @return array */ public function arrayKeyValueFunc($key, $val, $current_array = null) { if (empty($current_array)) { return array($key => $val); } $current_array[$key] = $val; return $current_array; } /** * Wrapper for array_intersect() method * * @param array $array1 * @param array $array2 * @return array */ public function arrayIntersectFunc($array1, $array2) { if ($array1 instanceof Collection && $array2 instanceof Collection) { return $array1->intersect($array2); } return array_intersect($array1, $array2); } /** * Returns a string from a value. If the value is array, return it json encoded * * @param array|string $value * * @return string */ public function stringFunc($value) { if (is_array($value)) { //format the array as a string return json_encode($value); } return $value; } /** * Translate a string * * @return string */ public function translateFunc() { return $this->grav['language']->translate(func_get_args()); } /** * Authorize an action. Returns true if the user is logged in and * has the right to execute $action. * * @param string|array $action An action or a list of actions. Each * entry can be a string like 'group.action' * or without dot notation an associative * array. * @return bool Returns TRUE if the user is authorized to * perform the action, FALSE otherwise. */ public function authorize($action) { /** @var UserInterface|null $user */ $user = $this->grav['user'] ?? null; if (!$user || !$user->authenticated || (isset($user->authorized) && !$user->authorized)) { return false; } $action = (array) $action; foreach ($action as $key => $perms) { $prefix = is_int($key) ? '' : $key . '.'; $perms = $prefix ? (array) $perms : [$perms => true]; foreach ($perms as $action2 => $authenticated) { if ($user->authorize($prefix . $action2)) { return $authenticated; } } } return false; } /** * Used to add a nonce to a form. Call {{ nonce_field('action') }} specifying a string representing the action. * * For maximum protection, ensure that the string representing the action is as specific as possible * * @param string $action the action * @param string $nonceParamName a custom nonce param name * * @return string the nonce input field */ public function nonceFieldFunc($action, $nonceParamName = 'nonce') { $string = ''; return $string; } /** * Decodes string from JSON. * * @param string $str * @param bool $assoc * @param int $depth * @param int $options * @return array */ public function jsonDecodeFilter($str, $assoc = false, $depth = 512, $options = 0) { return json_decode(html_entity_decode($str), $assoc, $depth, $options); } /** * Used to retrieve a cookie value * * @param string $key The cookie name to retrieve * * @return mixed */ public function getCookie($key) { return filter_input(INPUT_COOKIE, $key, FILTER_SANITIZE_STRING); } /** * Twig wrapper for PHP's preg_replace method * * @param mixed $subject the content to perform the replacement on * @param mixed $pattern the regex pattern to use for matches * @param mixed $replace the replacement value either as a string or an array of replacements * @param int $limit the maximum possible replacements for each pattern in each subject * * @return string|string[]|null the resulting content */ public function regexReplace($subject, $pattern, $replace, $limit = -1) { return preg_replace($pattern, $replace, $subject, $limit); } /** * Twig wrapper for PHP's preg_grep method * * @param array $array * @param string $regex * @param int $flags * @return array */ public function regexFilter($array, $regex, $flags = 0) { return preg_grep($regex, $array, $flags); } /** * redirect browser from twig * * @param string $url the url to redirect to * @param int $statusCode statusCode, default 303 */ public function redirectFunc($url, $statusCode = 303) { header('Location: ' . $url, true, $statusCode); exit(); } /** * Generates an array containing a range of elements, optionally stepped * * @param int $start Minimum number, default 0 * @param int $end Maximum number, default `getrandmax()` * @param int $step Increment between elements in the sequence, default 1 * * @return array */ public function rangeFunc($start = 0, $end = 100, $step = 1) { return range($start, $end, $step); } /** * Check if HTTP_X_REQUESTED_WITH has been set to xmlhttprequest, * in which case we may unsafely assume ajax. Non critical use only. * * @return bool True if HTTP_X_REQUESTED_WITH exists and has been set to xmlhttprequest */ public function isAjaxFunc() { return ( !empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'); } /** * Get the Exif data for a file * * @param string $image * @param bool $raw * @return mixed */ public function exifFunc($image, $raw = false) { if (isset($this->grav['exif'])) { /** @var UniformResourceLocator $locator */ $locator = $this->grav['locator']; if ($locator->isStream($image)) { $image = $locator->findResource($image); } $exif_reader = $this->grav['exif']->getReader(); if ($image && file_exists($image) && $this->config->get('system.media.auto_metadata_exif') && $exif_reader) { $exif_data = $exif_reader->read($image); if ($exif_data) { if ($raw) { return $exif_data->getRawData(); } return $exif_data->getData(); } } } return null; } /** * Simple function to read a file based on a filepath and output it * * @param string $filepath * @return bool|string */ public function readFileFunc($filepath) { /** @var UniformResourceLocator $locator */ $locator = $this->grav['locator']; if ($locator->isStream($filepath)) { $filepath = $locator->findResource($filepath); } if ($filepath && file_exists($filepath)) { return file_get_contents($filepath); } return false; } /** * Process a folder as Media and return a media object * * @param string $media_dir * @return Media|null */ public function mediaDirFunc($media_dir) { /** @var UniformResourceLocator $locator */ $locator = $this->grav['locator']; if ($locator->isStream($media_dir)) { $media_dir = $locator->findResource($media_dir); } if ($media_dir && file_exists($media_dir)) { return new Media($media_dir); } return null; } /** * Dump a variable to the browser * * @param mixed $var */ public function vardumpFunc($var) { var_dump($var); } /** * Returns a nicer more readable filesize based on bytes * * @param int $bytes * @return string */ public function niceFilesizeFunc($bytes) { return Utils::prettySize($bytes); } /** * Returns a nicer more readable number * * @param int|float|string $n * @return string|bool */ public function niceNumberFunc($n) { if (!\is_float($n) && !\is_int($n)) { if (!\is_string($n) || $n === '') { return false; } // Strip any thousand formatting and find the first number. $list = array_filter(preg_split("/\D+/", str_replace(',', '', $n))); $n = reset($list); if (!\is_numeric($n)) { return false; } $n = (float)$n; } // now filter it; if ($n > 1000000000000) { return round($n/1000000000000, 2).' t'; } if ($n > 1000000000) { return round($n/1000000000, 2).' b'; } if ($n > 1000000) { return round($n/1000000, 2).' m'; } if ($n > 1000) { return round($n/1000, 2).' k'; } return number_format($n); } /** * Get a theme variable * * @param string $var * @param bool $default * @return string */ public function themeVarFunc($var, $default = null) { $header = $this->grav['page']->header(); $header_classes = $header->{$var} ?? null; return $header_classes ?: $this->config->get('theme.' . $var, $default); } /** * takes an array of classes, and if they are not set on body_classes * look to see if they are set in theme config * * @param string|string[] $classes * @return string */ public function bodyClassFunc($classes) { $header = $this->grav['page']->header(); $body_classes = $header->body_classes ?? ''; foreach ((array)$classes as $class) { if (!empty($body_classes) && Utils::contains($body_classes, $class)) { continue; } $val = $this->config->get('theme.' . $class, false) ? $class : false; $body_classes .= $val ? ' ' . $val : ''; } return $body_classes; } /** * Look for a page header variable in an array of pages working its way through until a value is found * * @param string $var * @param string|string[]|null $pages * @return mixed */ public function pageHeaderVarFunc($var, $pages = null) { if ($pages === null) { $pages = $this->grav['page']; } // Make sure pages are an array if (!\is_array($pages)) { $pages = [$pages]; } // Loop over pages and look for header vars foreach ($pages as $page) { if (\is_string($page)) { $page = $this->grav['pages']->find($page); } if ($page) { $header = $page->header(); if (isset($header->{$var})) { return $header->{$var}; } } } return null; } /** * Dump/Encode data into YAML format * * @param array $data * @param int $inline integer number of levels of inline syntax * @return string */ public function yamlEncodeFilter($data, $inline = 10) { return Yaml::dump($data, $inline); } /** * Decode/Parse data from YAML format * * @param string $data * @return array */ public function yamlDecodeFilter($data) { return Yaml::parse($data); } /** * Function/Filter to return the type of variable * * @param mixed $var * @return string */ public function getTypeFunc($var) { return gettype($var); } /** * Function/Filter to test type of variable * * @param mixed $var * @param string|null $typeTest * @param string|null $className * @return bool */ public function ofTypeFunc($var, $typeTest=null, $className=null) { switch ($typeTest) { default: return false; break; case 'array': return is_array($var); break; case 'bool': return is_bool($var); break; case 'class': return is_object($var) === true && get_class($var) === $className; break; case 'float': return is_float($var); break; case 'int': return is_int($var); break; case 'numeric': return is_numeric($var); break; case 'object': return is_object($var); break; case 'scalar': return is_scalar($var); break; case 'string': return is_string($var); break; } } }