VMXTemplate/template.php

1093 lines
39 KiB
PHP
Raw Normal View History

<?php
2006-09-27 20:00:00 +04:00
2010-02-16 03:07:25 +03:00
# "Ох уж эти перлисты... что ни пишут - всё Template Toolkit получается!"
# Компилятор переписан уже 2 раза - сначала на regexы, потом на index() :-)
# А обратная совместимость по синтаксису, как ни странно, до сих пор цела.
class TemplateState
{
var $blocks = array();
var $in = array();
var $included = array();
var $in_set = 0;
}
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);
class Template
{
2010-02-16 03:07:25 +03:00
static $Mon, $mon, $Wday;
static $cache_type = NULL;
static $cache = array();
static $safe_tags = '<div> <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>';
var $errors = array(); // содержит последние ошибки
var $root = '.'; // каталог с шаблонами
var $reload = 1; // если 0, шаблоны не будут перечитываться с диска, и вызовов stat() происходить не будет
var $wrapper = false; // фильтр, вызываемый перед выдачей результата parse
var $tpldata = array(); // сюда будут сохранены: данные
var $cache_dir = false; // необязательный кэш, ускоряющий работу только в случае частых инициализаций интерпретатора
var $use_utf8 = true; // шаблоны в UTF-8 и с флагом UTF-8
var $begin_code = '<!--'; // начало кода
var $end_code = '-->'; // конец кода
var $eat_code_line = true; // съедать "лишний" перевод строки, если в строке только инструкция?
var $begin_subst = '{'; // начало подстановки (необязательно)
var $end_subst = '}'; // конец подстановки (необязательно)
var $strict_end = false; // жёстко требовать имя блока в его завершающей инструкции (<!-- end block -->)
var $raise_error = false; // говорить die() при ошибках в шаблонах
var $print_error = false; // печатать фатальные ошибки
function __construct($args)
{
foreach ($args as $k => $v)
$this->$k = $v;
$this->cache_dir = preg_replace('!/*$!s', '/', $this->cache_dir);
if (!is_writable($this->cache_dir))
$this->error('Template: cache_dir='.$this->cache_dir.' is not writable', true);
$this->root = preg_replace('!/*$!s', '/', $this->root);
}
2010-02-16 03:07:25 +03:00
// Сохранить ошибку
function error($e, $fatal = false)
{
2010-02-16 03:07:25 +03:00
$this->errors[] = $e;
if ($this->raise_error && $fatal)
die(__CLASS__."::error: $e");
elseif ($this->print_error)
print __CLASS__."::error: $e\n";
}
2010-02-16 03:07:25 +03:00
// Функция уничтожает данные шаблона
function clear()
{
$this->tpldata = array();
return true;
}
2010-02-16 03:07:25 +03:00
// Подлить в огонь переменных. Возвращает новый массив.
function assign_vars($new = NULL, $value = NULL) { return $this->vars($new, $value); }
function vars($new = NULL, $value = NULL)
{
2010-02-16 03:07:25 +03:00
if (is_array($new))
$this->tpldata = array_merge($this->tpldata, $new);
else if ($new && $value !== NULL)
$this->tpldata[$new] = $value;
return $this->tpldata;
}
2010-02-16 03:07:25 +03:00
// Кэш (xcache, eaccelerator)
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';
else if (function_exists('eaccelerator_get'))
self::$cache_type = 'e';
else
self::$cache_type = '';
}
}
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);
else if (self::$cache_type == 'e')
self::$cache[$key] = eaccelerator_get($key);
}
2010-02-16 03:07:25 +03:00
return self::$cache[$key];
}
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);
else if (self::$cache_type == 'e')
eaccelerator_rm($key);
}
static function cache_set($key, $value)
{
self::cache_check_type();
self::$cache[$key] = $value;
if (self::$cache_type == 'x')
xcache_set($key, $value);
else if (self::$cache_type == 'e')
eaccelerator_put($key, $value);
}
2010-02-16 03:07:25 +03:00
// Функция загружает, компилирует и возвращает результат для хэндла
// $page = $obj->parse( 'file/name.tpl' );
// $page = $obj->parse( 'template {CODE}', true );
function parse($fn, $inline = false)
{
$this->errors = array();
if ($inline)
{
$text = $fn;
$fn = '';
if (!$text)
return '';
}
else
{
if (!strlen($fn))
{
$this->error("Template: empty filename '$fn'", true);
return NULL;
}
if (substr($fn, 0, 1) != '/')
$fn = $this->root.$fn;
if (!($text = $this->loadfile($fn)))
{
2010-02-16 03:07:25 +03:00
$this->error("Template: couldn't load template file '$fn'", true);
return NULL;
}
2010-02-16 03:07:25 +03:00
}
if (!($file = $this->compile($text, $fn)))
return NULL;
$stack = array();
include $file;
$w = $this->wrapper;
if (is_callable($w))
$w(&$t);
return $t;
}
2010-02-16 03:07:25 +03:00
// Функция загружает файл с кэшированием
// $textref = $obj->loadfile($file)
function loadfile($fn)
{
$load = false;
if (!($text = self::cache_get("U$fn")) || $this->reload)
{
$mtime = stat($fn);
$mtime = $mtime[9];
if (!$text)
$load = true;
else
{
$ctime = self::cache_get("T$fn");
if ($ctime < $mtime)
$load = true;
}
}
2010-02-16 03:07:25 +03:00
// если файл изменился - перезасасываем
if ($load)
{
2010-02-16 03:07:25 +03:00
if ($fp = fopen($fn, "rb"))
{
fseek($fp, 0, SEEK_END);
$t = ftell($fp);
fseek($fp, 0, SEEK_SET);
$text = fread($fp, $t);
fclose($fp);
}
else
return NULL;
self::cache_set("T$fn", $mtime);
self::cache_set("U$fn", $text);
}
2010-02-16 03:07:25 +03:00
return $text;
}
2010-02-16 03:07:25 +03:00
// Функция компилирует код.
// $file = $this->compile($code, $fn);
// require $file;
// print $t;
function compile($code, $fn)
{
$md5 = md5($code);
$file = $this->cache_dir . 'tpl' . $md5 . '.php';
if (file_exists($file))
return $file;
// начала/концы спецстрок
$bc = $this->begin_code;
if (!$bc)
$bc = '<!--';
$ec = $this->end_code;
if (!$ec)
$ec = '-->';
// маркер начала, маркер конца, обработчик, съедать ли начало и конец строки
$blk = array(array($bc, $ec, 'compile_code_fragment', $this->{eat_code_line}));
if ($this->begin_subst && $this->end_subst)
$blk[] = array($this->{begin_subst}, $this->{end_subst}, 'compile_substitution');
foreach ($blk as &$v)
{
$v[4] = strlen($v[0]);
$v[5] = strlen($v[1]);
}
$st = new TemplateState();
// ищем фрагменты кода - на регэкспах-то было не очень правильно, да и медленно!
$r = '';
$pp = 0;
$l = strlen($code);
while ($code && $pp < $l)
{
$p = array();
$b = NULL;
// ищем ближайшее
foreach ($blk as $i => $bi)
if (($p[$i] = strpos($code, $bi[0], $pp)) !== false &&
(is_null($b) || $p[$i] < $p[$b]))
$b = $i;
if (!is_null($b))
{
2010-02-16 03:07:25 +03:00
/* это означает, что в случае отсутствия корректной инструкции
в найденной позиции надо пропустить ТОЛЬКО её начало и попробовать
найти что-нибудь снова! */
$pp = $p[$b]+$blk[$b][4];
$e = strpos($code, $blk[$b][1], $pp);
if ($e >= 0)
{
$frag = substr($code, $p[$b]+$blk[$b][4], $e-$p[$b]-$blk[$b][4]);
$f = $blk[$b][2];
$t = $frag;
if (!preg_match('/^\s*\n/s', $frag))
$frag = $this->$f($st, $frag);
else
$frag = NULL;
if (!is_null($frag))
{
// есть инструкция
$pp -= $blk[$b][4];
if ($pp > 0)
{
$text = substr($code, 0, $pp);
$code = substr($code, $pp);
$text = addcslashes($text, '\\\'');
// съедаем перевод строки, если надо
if ($blk[$b][5])
$text = preg_replace('/\r?\n\r?[ \t]*$/s', '', $text);
if (strlen($text))
$r .= "\$t.='$text';\n";
$pp = 0;
}
$r .= $frag;
$code = substr($code, $e+$blk[$b][5]-$p[$b]);
}
}
}
else
{
// финиш
$code = addcslashes($code, '\\\'');
$r .= "\$t.='$code';\n";
$code = '';
}
}
2010-02-16 03:07:25 +03:00
// дописываем начало и конец кода
if (!$fn)
{
2010-02-16 03:07:25 +03:00
$c = debug_backtrace();
$c = $c[2];
$fn = 'inline code in '.$c['class'].$c['type'].$c['function'].'() at '.$c['file'].':'.$c['line'];
}
2010-02-16 03:07:25 +03:00
$code = "<?php // $fn\n\$t = '';\n$r\n";
$r = '';
// записываем в файл
$fp = fopen($file, 'wb');
fwrite($fp, $code);
fclose($fp);
// возвращаем имя файла
return $file;
}
2010-02-16 03:07:25 +03:00
// ELSE
// ELSE IF expression
function compile_code_fragment_else($st, $kw, $t)
{
2010-02-16 03:07:25 +03:00
if (preg_match('/^IF\s+(.*)$/is', $t, $m))
return $this->compile_code_fragment_if($st, 'elsif', $m[1]);
return $t ? NULL : "} else {";
}
2010-02-16 03:07:25 +03:00
// IF expression
// ELSIF expression
function compile_code_fragment_if($st, $kw, $t)
{
2010-02-16 03:07:25 +03:00
$e = $this->compile_expression($t);
if ($e === NULL)
{
2010-02-16 03:07:25 +03:00
$this->error("Invalid expression in $kw: '$t'");
return NULL;
}
2010-02-23 02:46:01 +03:00
$cf_if = array('elseif' => "} else", 'elsif' => "} else", 'if' => "");
2010-02-16 03:07:25 +03:00
$kw = $cf_if[$kw];
if (!$kw)
$st->in[] = array('if');
return $kw . "if ($e) {\n";
}
2010-02-16 03:07:25 +03:00
function compile_code_fragment_elsif($st, $kw, $t)
{
2010-02-16 03:07:25 +03:00
return $this->compile_code_fragment_if($st, $kw, $t);
}
2010-02-16 03:07:25 +03:00
function compile_code_fragment_elseif($st, $kw, $t)
{
2010-02-16 03:07:25 +03:00
return $this->compile_code_fragment_if($st, $kw, $t);
}
2010-02-16 03:07:25 +03:00
// END [block]
function compile_code_fragment_end($st, $kw, $t)
{
if (!count($st->in))
{
$this->error("END $t without BEGIN, IF or SET");
return NULL;
}
$in = array_pop($st->in);
$w = $in[0];
if ($this->strict_end &&
($t && ($w != 'begin' || !$in[1] || $in[1] != $t) ||
!$t && $w == 'begin' && $in[1]))
{
$st->in[] = $in;
$this->error(strtoupper($kw)." $t after ".strtoupper($w)." ".$in[1]);
return NULL;
}
if ($w == 'set')
{
$st->in_set--;
return $this->varref($in[1]) . " = \$t;\n\$t = array_pop(\$stack);\n";
}
elseif ($w == 'function')
{
$st->in_set--;
return "EOF);\n";
}
2010-02-16 03:07:25 +03:00
elseif ($w == 'begin' || $w == 'for')
{
2010-02-16 03:07:25 +03:00
if ($w == 'begin')
array_pop($st->blocks);
$v = $this->varref($in[2]);
$v_i = $this->varref($in[2].'#');
return "}
array_pop(\$stack);
$v_i = array_pop(\$stack);
$v = array_pop(\$stack);
";
}
return "}\n";
}
2010-02-16 03:07:25 +03:00
// SET varref ... END
// FUNCTION varref ... END
2010-02-16 03:07:25 +03:00
// SET varref = expression
function compile_code_fragment_set($st, $kw, $t)
{
if (!preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/is', $t, $m))
return NULL;
if ($m[3])
{
if ($kw != 'set')
{
$this->error("Only SET is allowed for inline expressions, but '".strtoupper($kw)."' given (expression = $m[3])");
return NULL;
}
2010-02-16 03:07:25 +03:00
$e = $this->compile_expression($m[3]);
if ($e === NULL)
2010-02-16 03:07:25 +03:00
{
$this->error("Invalid expression in $kw: ($m[3])");
return NULL;
}
2010-02-19 01:03:08 +03:00
return $this->varref($m[1]) . ' = ' . $e . ";\n";
2010-02-16 03:07:25 +03:00
}
$st->in[] = array($kw, $m[1]);
2010-02-16 03:07:25 +03:00
$st->in_set++;
2010-06-01 01:39:51 +04:00
/* FIXME не работает в PHP < 5.3
да и вообще-то не очень хорош механизм функций,
ибо не кэшируется */
if ($kw == 'function')
2010-06-01 01:39:51 +04:00
return $this->varref($m[1]) . " = create_function(<<<'EOF'\n";
2010-02-16 03:07:25 +03:00
return "\$stack[] = \$t;\n\$t = '';\n";
}
2010-06-01 01:39:51 +04:00
function compile_code_fragment_function($st, $kw, $t)
{
return $this->compile_code_fragment_set($st, $kw, $t);
}
2010-02-16 03:07:25 +03:00
// INCLUDE template.tpl
function compile_code_fragment_include($st, $kw, $t)
{
$t = addcslashes($t, '\\\'');
return "\$t.=\$this->parse('$t');\n";
}
2010-02-16 03:07:25 +03:00
static function array1($a)
{
2010-02-19 01:03:08 +03:00
if (is_null($a))
return array();
if (is_array($a) && !self::is_assoc($a))
return $a;
return array($a);
}
2010-02-16 03:07:25 +03:00
// FOR[EACH] varref = array
// или
// FOR[EACH] varref (тогда записывается в себя)
function compile_code_fragment_for($st, $kw, $t, $in = false)
{
2010-02-16 03:07:25 +03:00
if (preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/s', $t, $m))
{
2010-02-16 03:07:25 +03:00
if (!$in)
$st->in[] = array('for', $t, $m[1]);
$v = $this->varref($m[1]);
$v_i = $this->varref($m[1].'#');
if (substr($v_i,-1) == substr($v,-1))
{
$iset = "$v_i = \$stack[count(\$stack)-1]++;\n";
}
else
2010-02-16 03:07:25 +03:00
{
// небольшой хак для $1 =~ \.\d+$
$iset = '';
}
$t = $m[3] ? $this->compile_expression($m[3]) : $v;
return
"\$stack[] = $v;
\$stack[] = $v_i;
\$stack[] = 0;
foreach (self::array1($t) as \$item) {
$v = \$item;
$iset";
}
2010-02-16 03:07:25 +03:00
return NULL;
}
2010-02-16 03:07:25 +03:00
function compile_code_fragment_foreach($st, $kw, $t)
{
return $this->compile_code_fragment_for($st, $kw, $t);
}
2010-02-16 03:07:25 +03:00
// BEGIN block [AT e] [BY e] [TO e]
// тоже legacy, но пока оставлю...
function compile_code_fragment_begin($st, $kw, $t)
{
2010-07-23 01:28:00 +04:00
if (preg_match('/^([a-z_][a-z0-9_]*)(?:\s+AT\s+(.+))?(?:\s+BY\s+(.+))?(?:\s+TO\s+(.+))?\s*$/is', $t, $m))
2010-02-16 03:07:25 +03:00
{
$st->blocks[] = $m[1];
$t = implode('.', $st->blocks);
$st->in[] = array('begin', $m[1], $t);
$e = $t;
if ($m[2])
{
$e = "array_slice($e, $m[2]";
if ($m[4])
$e .= ", $m[4]";
$e .= ")";
}
if ($m[3])
{
$e = "self::exec_subarray_divmod($e, $m[3])";
}
if ($e != $t)
{
$e = "$t = $e";
}
return $this->compile_code_fragment_for($st, 'for', $e, 1);
}
return NULL;
}
2010-02-16 03:07:25 +03:00
// компиляция фрагмента кода <!-- ... -->. это может быть:
// 1) [ELSE] IF выражение
// 2) BEGIN/FOR/FOREACH имя блока
// 3) END [имя блока]
// 4) SET переменная
// 5) SET переменная = выражение
// 6) INCLUDE имя_файлааблона
// 7) выражение
function compile_code_fragment($st, $e)
{
$e = ltrim($e, " \t\r");
$e = rtrim($e);
if (substr($e, 0, 1) == '#')
{
// комментарий!
return '';
}
if (preg_match('/^(?:(ELS)(?:E\s*)?)?IF!\s+(.*)$/s', $e, $m))
{
$e = $m[1].'IF NOT '.$m[2];
// обратная совместимость... нафига она нужна?...
// но пока пусть останется...
$this->error("Legacy IF! used, consider changing it to IF NOT");
}
list($kw, $t) = preg_split('/\s+/', $e, 2);
$kw = strtolower($kw);
if (!preg_match('/\W/s', $kw) &&
method_exists($this, $sub = "compile_code_fragment_$kw") &&
!is_null($r = $this->$sub($st, $kw, $t)))
return $r;
else if (!is_null($t = $this->compile_expression($e)))
return "\$t.=$t;\n";
return NULL;
}
2010-02-16 03:07:25 +03:00
// компиляция подстановки переменной {...} это просто выражение
function compile_substitution($st, $e)
{
$e = $this->compile_expression($e);
if ($e !== NULL)
2010-02-16 03:07:25 +03:00
return "\$t.=$e;\n";
return NULL;
}
2010-02-16 03:07:25 +03:00
// компиляция выражения. это может быть:
// 1) "строковой литерал"
// 2) 123.123 или 0123 или 0x123
// 3) переменная
// 4) функция(выражение,выражение,...,выражение)
// 5) функция выражение
// 6) для legacy mode: переменная/имя_функции
function compile_expression($e, $after = NULL)
{
if ($after && (!is_array($after) || !count($after)))
$after = NULL;
$e = ltrim($e, " \t\r");
if ($after)
$after[0] = '';
else
$e = rtrim($e);
// строковой или числовой литерал
2010-02-23 23:52:04 +03:00
if (preg_match('/^((\")(?:[^\"\\\\]+|\\\\.)*\"|\'(?:[^\'\\\\]+|\\\\.)*\'|-?0\d+|-?[0-9]\d*(\.\d+)?|-?0x\d+)\s*(.*)$/is', $e, $m))
{
2010-02-16 03:07:25 +03:00
if ($m[4])
{
2010-02-16 03:07:25 +03:00
if (!$after)
return NULL;
$after[0] = $m[4];
}
2010-02-16 03:07:25 +03:00
$e = $m[1];
if ($m[2])
$e = str_replace('$', '\\$', $e);
return $e;
}
// функция нескольких аргументов или вызов замыкания
else if (preg_match('/^([a-z_][a-z0-9_]*((?:\.[a-z0-9_]+)*))\s*\((.*)$/is', $e, $m))
2010-02-16 03:07:25 +03:00
{
$f = strtolower($m[1]);
if ($m[2] || !method_exists($this, "function_$f"))
2010-06-01 01:39:51 +04:00
$varref = $this->varref($m[1]);
$a = $m[3];
2010-02-16 03:07:25 +03:00
$args = array();
2010-02-23 23:52:04 +03:00
while (!is_null($e = $this->compile_expression($a, array(&$a))))
{
2010-02-16 03:07:25 +03:00
$args[] = $e;
if (preg_match('/^\s*\)/s', $a))
break;
else if ($a == ($b = preg_replace('/^\s*,/s', '', $a)))
{
2010-02-19 01:03:08 +03:00
$this->error("Unexpected token: '$a' in $f($m[2] parameter list");
2010-02-16 03:07:25 +03:00
return NULL;
}
2010-02-16 03:07:25 +03:00
$a = $b;
}
2010-02-16 03:07:25 +03:00
if ($a == ($b = preg_replace('/^\s*\)\s*/', '', $a)))
{
2010-02-19 01:03:08 +03:00
$this->error("Unexpected token: '$a' in the end of $f($m[2] parameter list");
2010-02-16 03:07:25 +03:00
return NULL;
}
2010-02-16 03:07:25 +03:00
$a = $b;
if ($a)
{
2010-02-16 03:07:25 +03:00
if (!$after)
return NULL;
$after[0] = $a;
}
if ($varref)
return "\$this->exec_call('$f', $varref, array(".implode(",", $args)."))";
2010-02-16 03:07:25 +03:00
return call_user_func_array(array($this, "function_$f"), $args);
}
// функция одного аргумента
else if (preg_match('/^([a-z_][a-z0-9_]*)\s+(?=\S)(.*)$/is', $e, $m))
{
$f = strtolower($m[1]);
if (!method_exists($this, "function_$f"))
{
2010-02-16 03:07:25 +03:00
$this->error("Unknown function: '$f' in '$e'");
return NULL;
}
2010-02-16 03:07:25 +03:00
$a = $m[2];
$arg = $this->compile_expression($a, array(&$a));
if ($arg === NULL)
{
2010-02-16 03:07:25 +03:00
$this->error("Invalid expression: ($e)");
return NULL;
}
2010-02-16 03:07:25 +03:00
$a = ltrim($a);
if ($a)
{
2010-02-16 03:07:25 +03:00
if (!$after)
return NULL;
$after[0] = $a;
}
2010-02-16 03:07:25 +03:00
$f = "function_$f";
return $this->$f($arg);
}
2010-02-16 03:07:25 +03:00
// переменная плюс legacy-mode переменная/функция
else if (preg_match('/^((?:[a-z0-9_]+\.)*(?:[a-z0-9_]+\#?))(?:\/([a-z]+))?\s*(.*)$/is', $e, $m))
{
if ($m[3])
{
if (!$after)
return NULL;
$after[0] = $m[3];
}
$e = $this->varref($m[1]);
if ($m[2])
{
$f = strtolower($m[2]);
if (!method_exists($this, "function_$f"))
{
$this->error("Unknown function: '$f' called in legacy mode ($m[0])");
return NULL;
}
$f = "function_$f";
$e = $this->$f($e);
}
return $e;
}
return NULL;
}
2010-02-16 03:07:25 +03:00
// генерация ссылки на переменную
function varref($e)
{
2010-02-16 03:07:25 +03:00
if (!$e)
return "";
$e = explode('.', $e);
$t = '$this->tpldata';
foreach ($e as $el)
{
2010-02-16 03:07:25 +03:00
if (preg_match('/^\d+$/', $el))
{
$t .= "[$el]";
}
else
{
$el = addcslashes($el, '\\\'');
$t .= "['$el']";
}
}
2010-02-16 03:07:25 +03:00
return $t;
}
// операция над аргументами
static function fmop($op, $args)
{
return "((" . join(") $op (", $args) . "))";
}
static function is_assoc($a)
{
foreach (array_keys($a) as $k)
if (!is_int($k))
return true;
return false;
}
// вспомогательная функция - вызов функции с раскрытием аргументов
static function call_array_func()
{
$args = func_get_args();
$cb = array_shift($args);
$aa = array();
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;
}
2010-02-16 03:07:25 +03:00
return call_user_func_array($cb, $args);
}
static function array_count($a)
{
if (is_array($a))
return count($a);
return 0;
}
// вызов функции с аргументами и раскрытием массивов
static function fearr($f, $args)
{
$e = "self::call_array_func($f";
foreach ($args as $a)
$e .= ", $a";
$e .= ")";
return $e;
}
2010-03-10 03:09:21 +03:00
// перлоподобный OR-оператор, который возвращает первое истинное значение
static function perlish_or()
{
$a = func_get_args();
foreach ($a as $v)
if ($v)
return $v;
return false;
}
// вызов своей функции
function exec_call($f, $sub, $args)
{
if (is_callable($sub))
return call_user_func_array($sub, $args);
$this->error("Unknown function: '$f'");
return NULL;
}
2010-02-16 03:07:25 +03:00
/* функции */
/* "или", "и", +, -, *, /, конкатенация */
2010-03-10 03:09:21 +03:00
function function_or() { $a = func_get_args(); return "self::perlish_or(".join(",", $a).")"; }
2010-02-16 03:07:25 +03:00
function function_and() { $a = func_get_args(); return $this->fmop('&&', $a); }
function function_add() { $a = func_get_args(); return $this->fmop('+', $a); }
function function_sub() { $a = func_get_args(); return $this->fmop('-', $a); }
function function_mul() { $a = func_get_args(); return $this->fmop('*', $a); }
function function_div() { $a = func_get_args(); return $this->fmop('/', $a); }
function function_mod($a,$b) { return "(($a) % ($b))"; }
function function_concat() { $a = func_get_args(); return $this->fmop('.', $a); }
/* логарифм, количество элементов, "не", "чётное?", "нечётное?", приведение к целому */
function function_log($e) { return "log($e)"; }
function function_count($e) { return "self::array_count($e)"; }
function function_not($e) { return "!($e)"; }
function function_even($e) { return "!(($e) & 1)"; }
function function_odd($e) { return "(($e) & 1)"; }
function function_int($e) { return "intval($e)"; }
function function_i($e) { return "intval($e)"; }
/* сравнения: == != > < >= <= */
function function_eq($a,$b) { return "(($a) == ($b))"; }
function function_ne($a,$b) { return "(($a) != ($b))"; }
function function_gt($a,$b) { return "(($a) > ($b))"; }
function function_lt($a,$b) { return "(($a) < ($b))"; }
function function_ge($a,$b) { return "(($a) >= ($b))"; }
function function_le($a,$b) { return "(($a) <= ($b))"; }
2010-06-06 02:55:26 +04:00
function function_yesno($a,$b,$c) { return "(($a) ? ($b) : ($c))"; }
2010-02-16 03:07:25 +03:00
/* нижний регистр */
2010-06-02 19:25:31 +04:00
function function_lc($e) { return "mb_strtolower($e)"; }
function function_lower($e) { return "mb_strtolower($e)"; }
function function_lowercase($e) { return "mb_strtolower($e)"; }
2010-02-16 03:07:25 +03:00
/* верхний регистр */
2010-06-02 19:25:31 +04:00
function function_uc($e) { return "mb_strtoupper($e)"; }
function function_upper($e) { return "mb_strtoupper($e)"; }
function function_uppercase($e) { return "mb_strtoupper($e)"; }
2010-02-23 02:46:01 +03:00
function function_strlimit($s,$l){ return "self::strlimit($s,$l)"; }
2010-02-16 03:07:25 +03:00
/* экранирование символов, специльных для регулярок */
function function_requote($e) { return "preg_quote($e)"; }
function function_re_quote($e) { return "preg_quote($e)"; }
function function_preg_quote($e) { return "preg_quote($e)"; }
/* замены - по регулярке и по подстроке */
function function_replace($re, $sub, $v)
{
return "preg_replace('#'.str_replace('#','\\\\#',$re).'#s', $sub, $v)";
}
function function_str_replace($s, $sub, $v)
{
return "str_replace($s, $sub, $v)";
}
2010-06-02 19:25:31 +04:00
/* длина строки */
function function_strlen($s) { return "mb_strlen($s)"; }
/* подстрока */
function function_substr($s, $start, $length = NULL)
{
2010-06-02 19:25:31 +04:00
return "mb_substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")";
}
function function_substring($s, $start, $length = NULL)
{
2010-06-02 19:25:31 +04:00
return "mb_substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")";
}
2010-02-16 03:07:25 +03:00
/* разбиение строки по регулярному выражению */
function function_split($re, $v, $limit = -1)
2010-02-16 03:07:25 +03:00
{
return "preg_split('#'.str_replace('#','\\\\#',$re).'#s', $v, $limit)";
}
/* экранирование кавычек */
function function_quote($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; }
function function_addslashes($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; }
function function_q($e) { return "str_replace(\"\\n\",\"\\\\n\",addslashes($e))"; }
2010-02-16 03:07:25 +03:00
/* преобразование символов <>&'" в HTML-сущности &lt; &gt; &amp; &apos; &quot; */
function function_htmlspecialchars($e) { return "htmlspecialchars($e,ENT_QUOTES)"; }
function function_html($e) { return "htmlspecialchars($e,ENT_QUOTES)"; }
function function_s($e) { return "htmlspecialchars($e,ENT_QUOTES)"; }
2010-06-27 23:01:01 +04:00
function function_nl2br($s) { return "nl2br($s)"; }
2010-02-16 03:07:25 +03:00
/* экранирование в стиле URI */
function function_uriquote($e) { return "urlencode($e)"; }
function function_uri_escape($e) { return "urlencode($e)"; }
function function_urlencode($e) { return "urlencode($e)"; }
/* удаление всех, заданных или "небезопасных" HTML-тегов */
2010-03-10 03:09:21 +03:00
function function_strip($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; }
function function_strip_tags($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; }
2010-03-10 03:09:21 +03:00
function function_t($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; }
2010-02-19 01:03:08 +03:00
function function_strip_unsafe($e) { return "strip_tags($e, self::\$safe_tags)"; }
function function_h($e) { return "strip_tags($e, self::\$safe_tags)"; }
2010-02-16 03:07:25 +03:00
/* объединение всех скаляров и всех элементов аргументов-массивов */
function function_join() { $a = func_get_args(); return self::fearr("'join'", $a); }
function function_implode() { $a = func_get_args(); return self::fearr("'join'", $a); }
/* подставляет на места $1, $2 и т.п. в строке аргументы */
function function_subst() { $a = func_get_args(); return self::fearr("'VMX_Template::exec_subst'", $a); }
/* sprintf */
function function_sprintf() { $a = func_get_args(); return self::fearr("'sprintf'", $a); }
/* создание хеша */
function function_hash()
{
$s = "array(";
$i = 0;
$d = '';
foreach (func_get_args() as $v)
{
$s .= $d;
$s .= $v;
$i++;
if ($i & 1)
$d = '=>';
else
$d = ',';
}
$s .= ")";
return $s;
}
2010-06-02 19:25:31 +04:00
/* ключи хеша или массива */
function function_keys($a) { return "array_keys($a)"; }
function function_hash_keys($a) { return "array_keys($a)"; }
function function_array_keys($a) { return "array_keys($a)"; }
2010-02-16 03:07:25 +03:00
// создание массива
function function_array()
{
$a = func_get_args();
return "array(" . join(",", $a) . ")";
}
// подмассив по номерам элементов
function function_subarray() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; }
function function_array_slice() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; }
// подмассив по кратности номеров элементов
function function_subarray_divmod() { $a = func_get_args(); return "self::exec_subarray_divmod(" . join(",", $a) . ")"; }
// объединение массивов
function function_array_merge() { $a = func_get_args(); return "array_merge(" . join(",", $a) . ")"; }
2010-02-16 03:07:25 +03:00
// получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5)))
// по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п.
function function_get($a, $k) { return $a."[$k]"; }
function function_hget($a, $k) { return $a."[$k]"; }
function function_aget($a, $k) { return $a."[$k]"; }
// shift, unshift, pop, push
function function_shift($a) { return "array_shift($a)"; }
function function_pop($a) { return "array_pop($a)"; }
function function_unshift($a, $v) { return "array_unshift($a, $v)"; }
function function_push($a, $v) { return "array_push($a, $v)"; }
2010-06-01 01:39:51 +04:00
// игнорирование результата (а-ля js)
function function_void($a) { return "self::void($a)"; }
function void($a) { return ''; }
2010-02-16 03:07:25 +03:00
/* map() */
function function_map($f)
{
if (!method_exists($this, "function_$f"))
{
$this->error("Unknown function specified for map(): $f");
return NULL;
}
$f = "function_$f";
$f = $this->$f('$arg');
$args = func_get_args();
array_shift($args);
array_unshift($args, "create_function('$arg',$f)");
return self::fearr("array_map", $args);
}
2010-05-28 01:40:07 +04:00
/* дамп переменной */
function function_var_dump($var)
{
return "self::exec_dump($var)";
}
function function_dump($var)
{
return "self::exec_dump($var)";
}
/* включение другого файла */
function function_include($file)
{
return "\$this->parse($file)";
}
function function_parse($file)
{
return "\$this->parse($file)";
}
// дамп переменной
static function exec_dump($var)
{
ob_start();
var_dump($var);
$var = ob_get_contents();
ob_end_clean();
return $var;
}
2010-02-16 03:07:25 +03:00
// подмассив по кратности номеров элементов
// 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;
}
2010-02-16 03:07:25 +03:00
// strftime
function function_strftime($fmt, $date, $time = '')
{
$e = $time ? "($date).' '.($time)" : $date;
return "strftime($fmt, self::timestamp($e))";
}
// выполняет подстановку function_subst
static function exec_subst($str)
{
$args = func_get_args();
$str = preg_replace_callback('/(?<!\\\\)((?:\\\\\\\\)*)\$(?:([1-9]\d*)|\{([1-9]\d*)\})/is', create_function('$m', 'return $args[$m[2]?$m[2]:$m[3]];'), $str);
return $str;
}
2010-02-23 02:46:01 +03:00
// ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что.
static function strlimit($str, $maxlen)
{
2010-06-02 19:25:31 +04:00
if (!$maxlen || $maxlen < 1 || mb_strlen($str) <= $maxlen)
2010-02-23 02:46:01 +03:00
return $str;
2010-06-02 19:25:31 +04:00
$str = mb_substr($str, 0, $maxlen);
$p = mb_strrpos($str, ' ');
if (!$p || ($pt = mb_strrpos($str, "\t")) > $p)
2010-02-23 02:46:01 +03:00
$p = $pt;
if ($p)
2010-06-02 19:25:31 +04:00
$str = mb_substr($str, 0, $p);
2010-02-23 02:46:01 +03:00
return $str . '...';
}
2010-02-16 03:07:25 +03:00
// ограниченная распознавалка дат
function timestamp($ts = 0, $format = 0)
{
if (!self::$Mon)
{
self::$Mon = split(' ', 'Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec');
self::$mon = array_reverse(split(' ', 'jan feb mar apr may jun jul aug sep oct nov dec'));
self::$Wday = split(' ', 'Sun Mon Tue Wed Thu Fri Sat');
}
if (!strcmp(intval($ts), $ts))
2010-02-16 03:07:25 +03:00
{
// TS_UNIX or Epoch
if (!$ts)
$ts = time;
}
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
$ts = mktime(0+$m[4], 0+$m[5], 0+$m[6], $m[2], $m[3], $m[1]);
2010-02-16 03:07:25 +03: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;
}
if (!$format)
{
// TS_UNIX
return $ts;
}
elseif ($format == TS_MW)
{
return strftime("%Y%m%d%H%M%S", $ts);
}
elseif ($format == TS_DB)
{
return strftime("%Y-%m-%d %H:%M:%S", $ts);
}
elseif ($format == TS_DB_DATE)
{
return strftime("%Y-%m-%d", $ts);
}
elseif ($format == TS_ISO_8601)
{
return strftime("%Y-%m-%dT%H:%M:%SZ", $ts);
}
elseif ($format == TS_EXIF)
{
return strftime("%Y:%m:%d %H:%M:%S", $ts);
}
elseif ($format == TS_RFC822)
{
$l = localtime($ts);
return strftime($Wday[$l[6]].", %d ".$Mon[$l[4]]." %Y %H:%M:%S %z", $ts);
}
elseif ($format == TS_ORACLE)
{
$l = localtime($ts);
return strftime("%d-".$Mon[$l[4]]."-%Y %H.%M.%S %p", $ts);
}
return $ts;
}
}