VMXTemplate/VMXTemplate.php

877 lines
27 KiB
PHP
Raw Permalink Normal View History

<?php
2006-09-27 20:00:00 +04:00
/**
* "Ох уж эти перлисты... что ни пишут - всё Template Toolkit получается!"
* "Oh, those perlists... they could write anything, and a result is another Template Toolkit"
* Rewritten 4 times: phpbb -> regex -> index() -> recursive descent -> LIME LALR(1)
*
* Homepage: http://yourcmc.ru/wiki/VMX::Template
* License: GNU GPLv3 or later
2020-01-01 17:30:23 +03:00
* Author: Vitaliy Filippov, 2006-2020
* Version: V3 (LALR), 2020-01-01
*
* The template engine is split into two parts:
* (1) This file - always used when running templates
2018-09-13 02:32:00 +03:00
* (2) VMXTemplateCompiler.php - used only when compiling new templates
*/
2011-01-06 03:37:21 +03:00
/**
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
2013-04-20 03:42:30 +04:00
# TODO For perl version - rewrite it and prevent auto-vivification on a.b
2010-02-16 03:07:25 +03:00
2011-09-15 02:11:15 +04:00
if (!defined('TS_UNIX'))
{
2012-09-26 00:58:18 +04:00
// Global timestamp format constants
2011-09-15 02:11:15 +04:00
define('TS_UNIX', 0);
define('TS_DB', 1);
define('TS_DB_DATE', 2);
define('TS_MW', 3);
define('TS_EXIF', 4);
define('TS_ORACLE', 5);
define('TS_ISO_8601', 6);
define('TS_RFC822', 7);
}
2011-01-18 16:22:33 +03:00
class VMXTemplate
{
2017-02-24 14:38:31 +03:00
// Loaded template class names
public static $loadedClasses = [];
2010-02-16 03:07:25 +03:00
static $Mon, $mon, $Wday;
2011-01-18 16:22:33 +03:00
static $cache_type = NULL;
static $cache = array();
2013-06-01 15:55:38 +04:00
static $safe_tags = 'div|blockquote|span|a|b|i|u|p|h1|h2|h3|h4|h5|h6|strike|strong|small|big|blink|center|ol|pre|sub|sup|font|br|table|tr|td|th|tbody|tfoot|thead|tt|ul|li|em|img|marquee|cite';
2011-01-18 16:22:33 +03:00
2012-09-26 00:58:18 +04:00
// Timestamp format constants
2011-01-18 16:22:33 +03:00
const TS_UNIX = 0;
const TS_DB = 1;
const TS_DB_DATE = 2;
const TS_MW = 3;
const TS_EXIF = 4;
const TS_ORACLE = 5;
const TS_ISO_8601 = 6;
const TS_RFC822 = 7;
2010-02-16 03:07:25 +03:00
2012-09-26 00:58:18 +04:00
// Version of code classes, saved into static $version
const CODE_VERSION = 5;
2012-09-26 00:58:18 +04:00
// Data passed to the template
var $tpldata = array();
// Parent 'VMXTemplate' object for compiled templates
// parse_anything() functions are always called on $this->parent
var $parent = NULL;
// Failed-to-load filenames, saved to skip them during the request
var $failed = array();
// Search path for template functions (filenames indexed by function name)
var $function_search_path = array();
// Options, compiler objects
var $options, $compiler;
2012-09-26 00:58:18 +04:00
/**
* Constructor
*
* @param array $options Options
*/
function __construct($options)
{
$this->options = new VMXTemplateOptions($options);
}
2012-09-26 00:58:18 +04:00
/**
* Clear template data
*/
2010-02-16 03:07:25 +03:00
function clear()
{
$this->tpldata = array();
return true;
}
2012-09-26 00:58:18 +04:00
/**
* Shortcut for $this->vars()
*/
2012-07-30 03:06:38 +04:00
function assign_vars($new = NULL, $value = NULL)
{
2012-09-26 00:58:18 +04:00
$this->vars($new, $value);
2012-07-30 03:06:38 +04:00
}
2012-09-26 00:58:18 +04:00
/**
* Set template data value/values.
* $obj->vars($key, $value);
* or
* $obj->vars(array(key => value, ...));
*/
2010-02-16 03:07:25 +03:00
function vars($new = NULL, $value = NULL)
{
2010-02-16 03:07:25 +03:00
if (is_array($new))
2012-09-26 00:58:18 +04:00
{
2010-02-16 03:07:25 +03:00
$this->tpldata = array_merge($this->tpldata, $new);
2012-09-26 00:58:18 +04:00
}
elseif ($new && $value !== NULL)
{
2010-02-16 03:07:25 +03:00
$this->tpldata[$new] = $value;
2012-09-26 00:58:18 +04:00
}
}
2012-09-26 00:58:18 +04:00
/*** Cache support - XCache/APC/eAccelerator ***/
2010-02-16 03:07:25 +03:00
static function cache_check_type()
{
2010-02-16 03:07:25 +03:00
if (is_null(self::$cache_type))
{
2010-02-16 03:07:25 +03:00
if (function_exists('xcache_get'))
self::$cache_type = 'x';
2012-09-26 00:58:18 +04:00
elseif (function_exists('apc_store'))
self::$cache_type = 'a';
elseif (function_exists('eaccelerator_get'))
2010-02-16 03:07:25 +03:00
self::$cache_type = 'e';
else
self::$cache_type = '';
}
}
2012-09-26 00:58:18 +04:00
2010-02-16 03:07:25 +03:00
static function cache_get($key)
{
2010-02-16 03:07:25 +03:00
self::cache_check_type();
if (!array_key_exists($key, self::$cache))
{
2010-02-16 03:07:25 +03:00
if (self::$cache_type == 'x')
self::$cache[$key] = xcache_get($key);
2012-09-26 00:58:18 +04:00
elseif (self::$cache_type == 'a')
self::$cache[$key] = apc_fetch($key);
elseif (self::$cache_type == 'e')
2010-02-16 03:07:25 +03:00
self::$cache[$key] = eaccelerator_get($key);
}
2013-07-31 01:41:09 +04:00
return @self::$cache[$key];
}
2012-09-26 00:58:18 +04:00
2010-02-16 03:07:25 +03:00
static function cache_del($key)
{
2010-02-16 03:07:25 +03:00
self::cache_check_type();
unset(self::$cache[$key]);
if (self::$cache_type == 'x')
xcache_unset($key);
2012-09-26 00:58:18 +04:00
elseif (self::$cache_type == 'a')
apc_delete($key);
elseif (self::$cache_type == 'e')
2010-02-16 03:07:25 +03:00
eaccelerator_rm($key);
}
2012-09-26 00:58:18 +04:00
2010-02-16 03:07:25 +03:00
static function cache_set($key, $value)
{
self::cache_check_type();
self::$cache[$key] = $value;
if (self::$cache_type == 'x')
xcache_set($key, $value);
2012-09-26 00:58:18 +04:00
elseif (self::$cache_type == 'a')
apc_store($key, $value);
elseif (self::$cache_type == 'e')
2010-02-16 03:07:25 +03:00
eaccelerator_put($key, $value);
}
2012-09-26 00:58:18 +04:00
/*** Parse functions ***/
2012-09-26 00:58:18 +04:00
/**
* Normal (main) parse function.
* Use it to run the template.
*
* @param string $filename Template filename
* @param array $vars Optional data, will override $this->tpldata
*/
function parse($filename, $vars = NULL)
{
2012-09-26 04:02:57 +04:00
return $this->parse_real($filename, NULL, 'main', $vars);
}
2012-09-26 00:58:18 +04:00
/**
* Call template block (= macro/function)
*
* @param string $filename Template filename
* @param string $function Function name
* @param array $vars Optional data
*/
function exec_from($filename, $function, $vars = NULL)
{
2012-09-26 00:58:18 +04:00
return $this->parse_real($filename, NULL, $function, $vars);
}
2012-09-26 00:58:18 +04:00
/**
* Should not be used without great need.
* Run template passed as argument.
*/
function parse_inline($code, $vars = NULL)
{
2012-09-26 04:02:57 +04:00
return $this->parse_real(NULL, $code, 'main', $vars);
}
2012-09-26 00:58:18 +04:00
/**
* Should not be used without great need.
* Execute a function from the code passed as argument.
*/
function exec_from_inline($code, $function, $vars = NULL)
{
return $this->parse_real(NULL, $code, $function, $vars);
}
/**
* parse_real variant that does not require $vars to be an lvalue
* and does not run filters on output
*/
protected function parse_discard($fn, $inline, $func, $vars = NULL)
{
return $this->parse_real($fn, $inline, $func, $vars, false);
}
2012-09-26 00:58:18 +04:00
/**
* "Real" parse function, handles all parse_*()
*/
protected function parse_real($fn, $inline, $func, &$vars = NULL, $run_filters = true)
2010-02-16 03:07:25 +03:00
{
if (!$fn)
2010-02-16 03:07:25 +03:00
{
if (!strlen($inline))
2010-02-16 03:07:25 +03:00
return '';
$class = 'Template_X'.md5($inline);
if (!class_exists($class))
{
if (!($file = $this->compile($inline, '')))
return NULL;
include $file;
}
2010-02-16 03:07:25 +03:00
}
else
{
if (substr($fn, 0, 1) != '/')
2012-09-26 00:58:18 +04:00
$fn = $this->options->root.$fn;
/* Don't reload already loaded classes - optimal for multiple parse() calls.
But if we would like to reload templates during ONE request some day... */
2011-01-06 03:37:19 +03:00
$class = 'Template_'.md5($fn);
if (!class_exists($class))
{
2012-09-26 00:58:18 +04:00
if (isset($this->failed[$fn]))
2011-01-06 03:37:19 +03:00
{
2012-09-26 00:58:18 +04:00
// Fail recorded, don't retry until next request
2011-01-06 03:37:19 +03:00
return NULL;
}
if (!($text = $this->loadfile($fn)))
{
2013-02-21 02:43:54 +04:00
$e = error_get_last();
$this->options->error("couldn't load template file '$fn': ".$e['message'], true);
2011-01-06 03:37:19 +03:00
$this->failed[$fn] = true;
return NULL;
}
if (!($file = $this->compile($text, $fn)))
{
$this->failed[$fn] = true;
return NULL;
}
2011-01-13 01:11:40 +03:00
$r = include($file);
if ($r !== 1)
{
2013-02-21 02:28:58 +04:00
$this->options->error("error including compiled template for '$fn'", true);
2011-01-13 01:11:40 +03:00
$this->failed[$fn] = true;
return NULL;
}
2012-09-26 00:58:18 +04:00
if (!class_exists($class) || !isset($class::$version) || $class::$version < self::CODE_VERSION)
2011-01-13 01:11:40 +03:00
{
2013-06-18 02:52:35 +04:00
// Force recompile
$file = $this->compile($text, $fn, true);
$this->options->error(
"Invalid or stale cache '$file' for template '$fn'. Caused by one of:".
" template upgrade (error should go away on next run), two templates with same content (change or merge), or an MD5 collision :)", true
);
2011-01-13 01:11:40 +03:00
return NULL;
}
foreach ($class::$functions as $loaded_function => $args)
2012-09-26 00:58:18 +04:00
{
2012-09-26 04:02:57 +04:00
// FIXME Do it better
// Remember functions during file loading
$this->function_search_path[$loaded_function][] = array($fn, $args);
2012-09-26 00:58:18 +04:00
}
}
2010-02-16 03:07:25 +03:00
}
if (!isset($class::$functions[$func]))
{
2013-06-18 02:52:35 +04:00
$this->options->error("No function '$func' found in ".($fn ? "template $fn" : 'inline template'), true);
return NULL;
}
2012-09-26 04:02:57 +04:00
$func = "fn_$func";
2011-01-13 01:11:40 +03:00
$tpl = new $class($this);
2011-01-06 03:37:19 +03:00
if ($vars)
2012-09-26 04:02:57 +04:00
{
2011-01-06 03:37:19 +03:00
$tpl->tpldata = &$vars;
2012-09-26 04:02:57 +04:00
}
2012-10-10 02:20:40 +04:00
$old = error_reporting();
if ($old & E_NOTICE)
{
error_reporting($old & ~E_NOTICE);
}
2011-01-06 03:37:19 +03:00
$t = $tpl->$func();
2012-10-10 02:20:40 +04:00
if ($old & E_NOTICE)
{
error_reporting($old);
}
if ($run_filters && $this->options->filters)
2011-01-06 03:37:19 +03:00
{
$filters = $this->options->filters;
if (is_callable($filters) || is_string($filters) && is_callable(array(__CLASS__, "filter_$filters")))
{
$filters = array($filters);
}
foreach ($filters as $w)
2012-09-26 00:58:18 +04:00
{
if (is_string($w) && is_callable(array(__CLASS__, "filter_$w")))
{
$w = array(__CLASS__, "filter_$w");
}
elseif (!is_callable($w))
{
continue;
}
call_user_func_array($w, array(&$t));
2012-09-26 00:58:18 +04:00
}
2011-01-06 03:37:19 +03:00
}
2010-02-16 03:07:25 +03:00
return $t;
}
2017-02-24 14:07:33 +03:00
/**
* Translate template file line number from stack frame $frame (taken from debug_backtrace())
*/
2017-02-24 14:38:31 +03:00
public function translateLine(&$frame)
2017-02-24 14:07:33 +03:00
{
2017-02-24 14:38:31 +03:00
if (isset(VMXTemplate::$loadedClasses[$frame['file']]))
2017-02-24 14:07:33 +03:00
{
2017-02-24 14:38:31 +03:00
$class = VMXTemplate::$loadedClasses[$frame['file']];
2017-02-24 14:07:33 +03:00
if (isset($class::$smap))
{
$l = $frame['line'];
$s = 0;
$e = count($class::$smap);
while ($e > $s+1)
{
if ($l < $class::$smap[($e+$s)>>1][0])
$e = ($e+$s)>>1;
else
$s = ($e+$s)>>1;
}
$frame['file'] = $class::$template_filename;
2017-02-24 14:38:31 +03:00
$frame['line'] = $class::$smap[$s][1];
2017-02-24 14:07:33 +03:00
}
}
2017-02-24 14:38:31 +03:00
if (!empty($frame['class']) && substr($frame['class'], 0, 9) == 'Template_')
{
$class = $frame['class'];
$frame['class'] = $class::$template_filename;
$frame['type'] = '->';
if (substr($frame['function'], 0, 3) == 'fn_')
$frame['function'] = substr($frame['function'], 3);
}
2017-02-24 14:07:33 +03:00
}
2012-09-26 00:58:18 +04:00
/**
2012-10-10 02:20:40 +04:00
* Load file (with caching)
2012-09-26 00:58:18 +04:00
*
* @param string $fn Filename
*/
2010-02-16 03:07:25 +03:00
function loadfile($fn)
{
$load = false;
2012-09-26 00:58:18 +04:00
if (!($text = self::cache_get("U$fn")) || $this->options->reload)
2010-02-16 03:07:25 +03:00
{
2013-02-21 02:43:54 +04:00
$mtime = @stat($fn);
2010-02-16 03:07:25 +03:00
$mtime = $mtime[9];
if (!$text)
2012-09-26 00:58:18 +04:00
{
2010-02-16 03:07:25 +03:00
$load = true;
2012-09-26 00:58:18 +04:00
}
2010-02-16 03:07:25 +03:00
else
{
$ctime = self::cache_get("T$fn");
if ($ctime < $mtime)
2012-09-26 00:58:18 +04:00
{
2010-02-16 03:07:25 +03:00
$load = true;
2012-09-26 00:58:18 +04:00
}
2010-02-16 03:07:25 +03:00
}
}
2012-09-26 00:58:18 +04:00
// Reload if file changed
2010-02-16 03:07:25 +03:00
if ($load)
{
2013-02-21 02:43:54 +04:00
if ($fp = @fopen($fn, "rb"))
2010-02-16 03:07:25 +03:00
{
fseek($fp, 0, SEEK_END);
$t = ftell($fp);
fseek($fp, 0, SEEK_SET);
$text = fread($fp, $t);
fclose($fp);
}
else
2012-09-26 00:58:18 +04:00
{
2010-02-16 03:07:25 +03:00
return NULL;
2012-09-26 00:58:18 +04:00
}
2012-10-10 02:20:40 +04:00
// Different keys may expire separately, but that's not a problem here
2010-02-16 03:07:25 +03:00
self::cache_set("T$fn", $mtime);
self::cache_set("U$fn", $text);
}
2010-02-16 03:07:25 +03:00
return $text;
}
2012-09-26 00:58:18 +04:00
/**
* Compile code into a file and return its filename.
* This file, evaluated, will create the "Template_XXX" class
*
* $file = $this->compile($code, $fn);
* require $file;
*/
2011-01-13 01:11:40 +03:00
function compile($code, $fn, $reload = false)
2010-02-16 03:07:25 +03:00
{
$md5 = md5($code);
2012-09-26 00:58:18 +04:00
$file = $this->options->cache_dir . 'tpl' . $md5 . '.php';
2013-04-20 15:12:35 +04:00
if (file_exists($file) && !$reload)
2012-09-26 00:58:18 +04:00
{
2010-02-16 03:07:25 +03:00
return $file;
2012-09-26 00:58:18 +04:00
}
2010-02-16 03:07:25 +03:00
2011-01-08 03:10:06 +03:00
if (!$fn)
{
2012-09-26 00:58:18 +04:00
// Mock filename for inline code
2011-01-08 03:10:06 +03:00
$func_ns = 'X' . $md5;
$c = debug_backtrace();
$c = $c[2];
$fn = '(inline template at '.$c['file'].':'.$c['line'].')';
}
else
2012-09-26 00:58:18 +04:00
{
2011-01-08 03:10:06 +03:00
$func_ns = md5($fn);
2012-09-26 00:58:18 +04:00
}
2016-05-29 17:35:58 +03:00
if ($this->options->strip_space)
self::filter_strip_space($code);
if (!$this->compiler)
{
2018-09-13 02:32:00 +03:00
require_once(dirname(__FILE__).'/VMXTemplateCompiler.php');
$this->compiler = new VMXTemplateCompiler($this->options);
}
$compiled = $this->compiler->parse_all($code, $fn, $func_ns);
2017-02-24 14:38:31 +03:00
$compiled .= "VMXTemplate::\$loadedClasses['".addcslashes(realpath(dirname($file)).'/'.basename($file), '\\\'')."'] = 'Template_$func_ns';\n";
2012-09-26 00:58:18 +04:00
if (!file_put_contents($file, $compiled))
{
throw new VMXTemplateException("Failed writing $file");
}
return $file;
}
/*** Built-in filters ***/
/**
* Strips space from the beginning and ending of each line
*/
static function filter_strip_space(&$text)
{
2013-04-09 19:01:39 +04:00
$text = preg_replace('/^[ \t]+/m', '', $text);
$text = preg_replace('/[ \t]+$/m', '', $text);
}
2012-09-26 00:58:18 +04:00
/*** Function implementations ***/
2013-04-20 03:42:30 +04:00
/**
* Call template block / "function" from the template where it was defined
*/
function call_block($block, $args, $errorinfo)
{
if (isset($this->function_search_path[$block]))
{
// FIXME maybe do it better!
$fn = $this->function_search_path[$block][0][0];
return $this->parse_real($fn, NULL, $block, $args, false);
2013-04-20 03:42:30 +04:00
}
throw new VMXTemplateException("Unknown block '$block'$errorinfo");
}
function call_block_list($block, $args, $errorinfo)
{
if (isset($this->function_search_path[$block]))
{
$fun = $this->function_search_path[$block][0];
$args = array_combine($fun[1], array_pad(array_slice($args, 0, count($fun[1])), count($fun[1]), NULL));
return $this->parse_real($fun[0], NULL, $block, $args, false);
}
throw new VMXTemplateException("Unknown block or function '$block'$errorinfo");
2013-04-20 03:42:30 +04:00
}
// No-op, just returns the single argument. Needed to workaround ($expression)['key'] and ($expression)->m() issues.
static function noop($a)
{
return $a;
}
2013-02-19 14:50:22 +04:00
// Guess if the array is associative based on the first key (for performance)
2010-02-16 03:07:25 +03:00
static function is_assoc($a)
{
2013-02-19 14:50:22 +04:00
reset($a);
2013-02-19 14:53:35 +04:00
return $a && !is_int(key($a));
2010-02-16 03:07:25 +03:00
}
2013-02-19 14:50:22 +04:00
// Merge all scalar and list arguments into one list
static function merge_to_array()
2010-02-16 03:07:25 +03:00
{
$args = func_get_args();
2013-02-19 14:50:22 +04:00
$aa = (array) array_shift($args);
if (self::is_assoc($aa))
$aa = array($aa);
2010-02-16 03:07:25 +03:00
foreach ($args as $a)
{
2010-02-16 03:07:25 +03:00
if (is_array($a) && !self::is_assoc($a))
foreach ($a as $v)
$aa[] = $v;
else
$aa[] = $a;
}
2013-02-19 14:50:22 +04:00
return $aa;
2010-02-16 03:07:25 +03:00
}
2012-07-30 03:06:38 +04:00
// Returns count of elements for arrays and 0 for others
2010-02-16 03:07:25 +03:00
static function array_count($a)
{
if (is_array($a))
return count($a);
return 0;
}
2012-07-30 03:06:38 +04:00
// Perlish OR operator - returns first true value
2010-03-10 03:09:21 +03:00
static function perlish_or()
{
$a = func_get_args();
2012-10-10 02:20:40 +04:00
$last = array_pop($a);
2010-03-10 03:09:21 +03:00
foreach ($a as $v)
if ($v)
return $v;
2012-10-10 02:20:40 +04:00
return $last;
2010-03-10 03:09:21 +03:00
}
2012-07-30 03:06:38 +04:00
// Call a function
function exec_call($f, $sub, $args)
{
if (is_callable($sub))
return call_user_func_array($sub, $args);
2013-02-21 02:28:58 +04:00
$this->parent->options->error("Unknown function: '$f'");
return NULL;
}
2012-07-30 03:06:38 +04:00
// Extract values from an array by modulus of their indexes
// exec_subarray_divmod([], 2)
// exec_subarray_divmod([], 2, 1)
static function exec_subarray_divmod($array, $div, $mod)
{
if (!$div || !is_array($array))
return $array;
if (!$mod)
$mod = 0;
$i = 0;
$r = array();
foreach ($array as $k => $v)
if (($i % $div) == $mod)
$r[$k] = $v;
return $r;
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// Executes subst()
static function exec_subst($str)
{
$args = func_get_args();
2016-10-31 17:42:19 +03:00
$str = preg_replace_callback(
'/(?<!\\\\)((?:\\\\\\\\)*)\$(?:([1-9]\d*)|\{([1-9]\d*)\})/is',
function($m) use($args)
{
return $args[$m[2] ? $m[2] : $m[3]];
},
$str
);
2012-07-30 03:06:38 +04:00
return $str;
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// Normal sort, but returns the sorted array
static function exec_sort($array)
{
sort($array);
return $array;
}
2010-02-16 03:07:25 +03:00
2012-07-30 03:06:38 +04:00
// Returns array item
static function exec_get($array, $key)
{
return $array[$key];
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// Creates hash from an array
static function exec_hash($array)
{
$hash = array();
$l = count($array);
for ($i = 0; $i < $l; $i += 2)
$hash[$array[$i]] = $array[$i+1];
return $hash;
}
2011-01-06 03:37:21 +03:00
2012-07-30 03:06:38 +04:00
// For a hash, returns an array with pairs { key => 'key', value => 'value' }
static function exec_pairs($array, $kf = 'key', $vf = 'value')
2012-07-30 03:06:38 +04:00
{
$r = array();
foreach ($array as $k => $v)
$r[] = array($kf => $k, $vf => $v);
2012-07-30 03:06:38 +04:00
return $r;
}
2010-02-16 03:07:25 +03:00
2012-07-30 03:06:38 +04:00
// Limit string length, cut it on space boundary and add '...' if length is over
static function strlimit($str, $maxlen, $dots = '...')
{
if (!$maxlen || $maxlen < 1 || strlen($str) <= $maxlen)
return $str;
$str = substr($str, 0, $maxlen);
$p = strrpos($str, ' ');
if (!$p || ($pt = strrpos($str, "\t")) > $p)
$p = $pt;
if ($p)
$str = substr($str, 0, $p);
return $str . $dots;
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// UTF-8 (mb_internal_encoding() really) variant of strlimit()
static function mb_strlimit($str, $maxlen, $dots = '...')
{
if (!$maxlen || $maxlen < 1 || mb_strlen($str) <= $maxlen)
return $str;
$str = mb_substr($str, 0, $maxlen);
$p = mb_strrpos($str, ' ');
if (!$p || ($pt = mb_strrpos($str, "\t")) > $p)
$p = $pt;
if ($p)
$str = mb_substr($str, 0, $p);
return $str . $dots;
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// UTF-8 lcfirst()
static function mb_lcfirst($str)
{
2015-01-17 21:56:32 +03:00
return mb_strtolower(mb_substr($str, 0, 1)) . mb_substr($str, 1);
2012-07-30 03:06:38 +04:00
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
// UTF-8 ucfirst()
static function mb_ucfirst($str)
{
2015-01-17 21:56:32 +03:00
return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
2012-07-30 03:06:38 +04:00
}
2010-02-16 03:07:25 +03:00
2013-06-01 15:55:38 +04:00
// Replace tags with whitespace
static function strip_tags($str, $allowed = false)
{
$allowed = $allowed ? '(?!/?('.$allowed.'))' : '';
return preg_replace('#(<'.$allowed.'/?[a-z][a-z0-9-]*(\s+[^<>]*)?>\s*)+#is', ' ', $str);
}
2013-04-23 23:08:59 +04:00
// Ignore result
function void($a)
{
return '';
}
// Select one of 3 plural forms for russian language
2013-03-09 03:25:06 +04:00
static function plural_ru($count, $one, $few, $many)
{
$sto = $count % 100;
if ($sto >= 10 && $sto <= 20)
return $many;
switch ($count % 10)
{
case 1: return $one;
case 2:
case 3:
case 4: return $few;
}
return $many;
}
2012-07-30 03:06:38 +04:00
// Limited-edition timestamp parser
static function timestamp($ts = 0, $format = 0)
{
if (!self::$Mon)
{
self::$Mon = explode(' ', 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec');
self::$mon = array_reverse(explode(' ', 'jan feb mar apr may jun jul aug sep oct nov dec'));
self::$Wday = explode(' ', 'Sun Mon Tue Wed Thu Fri Sat');
}
if (!strcmp(intval($ts), $ts))
{
// TS_UNIX or Epoch
if (!$ts)
$ts = time();
2012-07-30 03:06:38 +04:00
}
elseif (preg_match('/^\D*(\d{4,})\D*(\d{2})\D*(\d{2})\D*(?:(\d{2})\D*(\d{2})\D*(\d{2})\D*([\+\- ]\d{2}\D*)?)?$/s', $ts, $m))
{
// TS_DB, TS_DB_DATE, TS_MW, TS_EXIF, TS_ISO_8601
2013-04-22 21:35:33 +04:00
$ts = mktime(0+@$m[4], 0+@$m[5], 0+@$m[6], $m[2], $m[3], $m[1]);
2012-07-30 03:06:38 +04:00
}
elseif (preg_match('/^\s*(\d\d?)-(...)-(\d\d(?:\d\d)?)\s*(\d\d)\.(\d\d)\.(\d\d)/s', $ts, $m))
{
// TS_ORACLE
$ts = mktime($m[4], $m[5], $m[6], $mon[strtolower($m[2])]+1, intval($m[1]), $m[3] < 100 ? $m[3]+1900 : $m[3]);
}
elseif (preg_match('/^\s*..., (\d\d?) (...) (\d{4,}) (\d\d):(\d\d):(\d\d)\s*([\+\- ]\d\d)\s*$/s', $ts, $m))
{
// TS_RFC822
$ts = mktime($m[4], $m[5], $m[6], $mon[strtolower($m[2])]+1, intval($m[1]), $m[3]);
}
else
{
// Bogus value, return NULL
return NULL;
}
2011-01-08 03:09:57 +03:00
2012-07-30 03:06:38 +04:00
if (!$format)
{
// TS_UNIX
return $ts;
}
elseif ($format == self::TS_MW)
{
return strftime("%Y%m%d%H%M%S", $ts);
}
elseif ($format == self::TS_DB)
{
return strftime("%Y-%m-%d %H:%M:%S", $ts);
}
elseif ($format == self::TS_DB_DATE)
{
return strftime("%Y-%m-%d", $ts);
}
elseif ($format == self::TS_ISO_8601)
{
return strftime("%Y-%m-%dT%H:%M:%SZ", $ts);
}
elseif ($format == self::TS_EXIF)
{
return strftime("%Y:%m:%d %H:%M:%S", $ts);
}
elseif ($format == self::TS_RFC822)
{
$l = localtime($ts);
return strftime($Wday[$l[6]].", %d ".$Mon[$l[4]]." %Y %H:%M:%S %z", $ts);
}
elseif ($format == self::TS_ORACLE)
{
$l = localtime($ts);
return strftime("%d-".$Mon[$l[4]]."-%Y %H.%M.%S %p", $ts);
}
return $ts;
}
2013-04-20 03:42:30 +04:00
}
/**
* Template exception classes
*/
class VMXTemplateException extends Exception {}
/**
* Options class
*/
class VMXTemplateOptions
{
var $begin_code = '<!--'; // instruction start
var $end_code = '-->'; // instruction end
var $begin_subst = '{'; // substitution start (may be turned off via false)
var $end_subst = '}'; // substitution end (may be turned off via false)
2013-04-20 03:42:30 +04:00
var $no_code_subst = false; // do not substitute expressions in instructions
var $eat_code_line = true; // remove the "extra" lines which contain instructions only
var $root = '.'; // directory with templates
var $cache_dir = false; // compiled templates cache directory
var $reload = 1; // 0 means to not check for new versions of cached templates
var $filters = array(); // filter to run on output of every template
var $use_utf8 = true; // use UTF-8 for all string operations on template variables
var $raise_error = false; // die() on fatal template errors
var $log_error = false; // send errors to standard error output
var $print_error = false; // print fatal template errors
var $strip_space = false; // strip spaces from beginning and end of each line
2013-04-23 23:08:58 +04:00
var $auto_escape = false; // "safe mode" (try 's' for HTML) - automatically escapes substituted
// values via this functions if not escaped explicitly
2013-04-20 03:42:30 +04:00
var $compiletime_functions = array(); // custom compile-time functions (code generators)
// Logged errors (not an option)
var $input_filename;
var $errors;
function __construct($options = array())
{
$this->set($options);
$this->errors = array();
}
function set($options)
{
foreach ($options as $k => $v)
{
if (isset($this->$k))
{
$this->$k = $v;
}
}
if (!$this->begin_subst || !$this->end_subst)
{
$this->begin_subst = false;
$this->end_subst = false;
$this->no_code_subst = false;
}
2017-02-24 14:38:31 +03:00
$this->cache_dir = preg_replace('!([^/])/*$!s', '\1/', $this->cache_dir);
2013-04-20 03:42:30 +04:00
if (!is_writable($this->cache_dir))
{
throw new VMXTemplateException('VMXTemplate: cache_dir='.$this->cache_dir.' is not writable');
}
2017-02-24 14:38:31 +03:00
$this->root = preg_replace('!([^/])/*$!s', '\1/', $this->root);
2013-04-20 03:42:30 +04:00
}
function __destruct()
{
if ($this->print_error && $this->errors && PHP_SAPI != 'cli')
{
print '<div id="template-errors" style="display: block; border: 1px solid black; padding: 8px; background: #fcc">'.
'VMXTemplate errors:<ul><li>'.
2014-11-20 02:52:44 +03:00
implode('</li><li>', array_map('nl2br', array_map('htmlspecialchars', $this->errors))).
2013-04-20 03:42:30 +04:00
'</li></ul>';
$fp = fopen("php://stderr", 'a');
fputs($fp, "VMXTemplate errors:\n".implode("\n", $this->errors));
2013-04-20 03:42:30 +04:00
fclose($fp);
}
}
/**
* Log an error or a warning
*/
function error($e, $fatal = false)
{
$this->errors[] = $e;
if ($this->raise_error && $fatal)
die("VMXTemplate error: $e");
if ($this->log_error)
error_log("VMXTemplate error: $e");
elseif ($this->print_error && PHP_SAPI == 'cli')
print("VMXTemplate error: $e\n");
}
}