1524 lines
56 KiB
PHP
1524 lines
56 KiB
PHP
<?php
|
||
|
||
# "Ох уж эти перлисты... что ни пишут - всё Template Toolkit получается!"
|
||
# Компилятор переписан уже 2 раза - сначала на regexы, потом на index() :-)
|
||
# А обратная совместимость по синтаксису, как ни странно, до сих пор цела.
|
||
|
||
# Homepage: http://yourcmc.ru/wiki/VMX::Template
|
||
# Author: Vitaliy Filippov, 2006-2011
|
||
# $Id$
|
||
|
||
class VMXTemplateState
|
||
{
|
||
var $blocks = array();
|
||
var $in = array();
|
||
var $functions = array();
|
||
var $output_position = 0;
|
||
var $input_filename = '';
|
||
}
|
||
|
||
class VMXTemplate
|
||
{
|
||
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>';
|
||
|
||
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;
|
||
|
||
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 для операций со строками
|
||
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; // печатать фатальные ошибки
|
||
var $compiletime_functions = array(); // дополнительные функции времени компиляции
|
||
var $parent = NULL; // сюда сохраняется объект, от которого отпочкован объект класса конкретного шаблона
|
||
// компилятор всегда дёргается в рамках объекта Template, а не объекта конкретного шаблона
|
||
|
||
var $failed = array(); // сюда сохраняются имена файлов, загрузка которых не удалась,
|
||
// чтобы не долбиться в один и тот же кривой шаблон много раз за запрос
|
||
|
||
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);
|
||
}
|
||
|
||
// Сохранить ошибку
|
||
function error($e, $fatal = false)
|
||
{
|
||
$this->errors[] = $e;
|
||
if ($this->raise_error && $fatal)
|
||
die(__CLASS__."::error: $e");
|
||
elseif ($this->print_error)
|
||
print __CLASS__."::error: $e<br />";
|
||
}
|
||
|
||
// Функция уничтожает данные шаблона
|
||
function clear()
|
||
{
|
||
$this->tpldata = array();
|
||
return true;
|
||
}
|
||
|
||
// Подлить в огонь переменных. Возвращает новый массив.
|
||
function assign_vars($new = NULL, $value = NULL) { return $this->vars($new, $value); }
|
||
function vars($new = NULL, $value = NULL)
|
||
{
|
||
if (is_array($new))
|
||
$this->tpldata = array_merge($this->tpldata, $new);
|
||
else if ($new && $value !== NULL)
|
||
$this->tpldata[$new] = $value;
|
||
return $this->tpldata;
|
||
}
|
||
|
||
// Кэш (xcache, eaccelerator)
|
||
static function cache_check_type()
|
||
{
|
||
if (is_null(self::$cache_type))
|
||
{
|
||
if (function_exists('xcache_get'))
|
||
self::$cache_type = 'x';
|
||
else if (function_exists('eaccelerator_get'))
|
||
self::$cache_type = 'e';
|
||
else
|
||
self::$cache_type = '';
|
||
}
|
||
}
|
||
static function cache_get($key)
|
||
{
|
||
self::cache_check_type();
|
||
if (!array_key_exists($key, self::$cache))
|
||
{
|
||
if (self::$cache_type == 'x')
|
||
self::$cache[$key] = xcache_get($key);
|
||
else if (self::$cache_type == 'e')
|
||
self::$cache[$key] = eaccelerator_get($key);
|
||
}
|
||
return self::$cache[$key];
|
||
}
|
||
static function cache_del($key)
|
||
{
|
||
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);
|
||
}
|
||
|
||
// Вызов функции из шаблона
|
||
function exec_from($filename, $function, $vars = NULL)
|
||
{
|
||
return $this->parse_real($filename, NULL, $function, $vars);
|
||
}
|
||
|
||
// Вызов функции из кода шаблона
|
||
// Совсем не рекомендовано, но возможно
|
||
function exec_from_inline($code, $function, $vars = NULL)
|
||
{
|
||
return $this->parse_real(NULL, $code, $function, $vars);
|
||
}
|
||
|
||
// Обработка главного блока шаблона
|
||
// $page = $obj->parse($filename);
|
||
// $page = $obj->parse($filename, $tpldata);
|
||
function parse($fn, $vars = NULL)
|
||
{
|
||
return $this->parse_real($fn, NULL, '_main', $vars);
|
||
}
|
||
|
||
// Обработка явно переданного кода шаблона
|
||
// Менее рекомендовано, но возможно
|
||
function parse_inline($code, $vars = NULL)
|
||
{
|
||
return $this->parse_real(NULL, $code, '_main', $vars);
|
||
}
|
||
|
||
// "Реальная" функция, обрабатывающая все вызовы типа parse
|
||
function parse_real($fn, $inline, $func, $vars = NULL)
|
||
{
|
||
$this->errors = array();
|
||
if (!$fn)
|
||
{
|
||
if (!strlen($inline))
|
||
return '';
|
||
$class = 'Template_X'.md5($inline);
|
||
if (!($file = $this->compile($inline, '')))
|
||
return NULL;
|
||
include $file;
|
||
}
|
||
else
|
||
{
|
||
if (substr($fn, 0, 1) != '/')
|
||
$fn = $this->root.$fn;
|
||
/* Пока что, если класс существует - просто используем его.
|
||
Однако если внезапно потребуется перезагружать шаблоны
|
||
в рамках ОДНОГО запроса - надо будет добавить stat()... FIXME?
|
||
Зато можно не бояться многократно вызывать какой-нибудь блок. */
|
||
$class = 'Template_'.md5($fn);
|
||
if (!class_exists($class))
|
||
{
|
||
if ($this->failed[$fn])
|
||
{
|
||
/* Если один раз за запрос загрузить не смогли,
|
||
то больше не пытаемся */
|
||
return NULL;
|
||
}
|
||
if (!($text = $this->loadfile($fn)))
|
||
{
|
||
$this->error("couldn't load template file '$fn'", true);
|
||
$this->failed[$fn] = true;
|
||
return NULL;
|
||
}
|
||
if (!($file = $this->compile($text, $fn)))
|
||
{
|
||
$this->failed[$fn] = true;
|
||
return NULL;
|
||
}
|
||
$r = include($file);
|
||
if ($r !== 1)
|
||
{
|
||
$this->error("error including compiled template for '$fn'", true);
|
||
$this->failed[$fn] = true;
|
||
return NULL;
|
||
}
|
||
if (!class_exists($class))
|
||
{
|
||
/* кэш от старой версии, нужно сбросить
|
||
FIXME в будущем совместимость со старым кэшем будет убрана */
|
||
$this->error("Please, clear template cache path after upgrading VMX::Template", true);
|
||
$this->failed[$fn] = true;
|
||
return NULL;
|
||
}
|
||
}
|
||
}
|
||
$func = "__$func";
|
||
$tpl = new $class($this);
|
||
if ($vars)
|
||
$tpl->tpldata = &$vars;
|
||
$t = $tpl->$func();
|
||
/* FIXME Кусочек legacy, но тоже пока оставлен */
|
||
if ($this->wrapper)
|
||
{
|
||
$w = $this->wrapper;
|
||
if (is_callable($w))
|
||
$w(&$t);
|
||
}
|
||
return $t;
|
||
}
|
||
|
||
// Функция загружает файл с кэшированием
|
||
// $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;
|
||
}
|
||
}
|
||
// если файл изменился - перезасасываем
|
||
if ($load)
|
||
{
|
||
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);
|
||
}
|
||
return $text;
|
||
}
|
||
|
||
// Функция компилирует код.
|
||
// $file = $this->compile($code, $fn);
|
||
// require $file;
|
||
// --> class Template_...
|
||
function compile($code, $fn, $reload = false)
|
||
{
|
||
$md5 = md5($code);
|
||
$file = $this->cache_dir . 'tpl' . $md5 . '.php';
|
||
if (file_exists($file) && !$reload)
|
||
return $file;
|
||
|
||
// "имя" файла для кода не из файла
|
||
if (!$fn)
|
||
{
|
||
$func_ns = 'X' . $md5;
|
||
$c = debug_backtrace();
|
||
$c = $c[2];
|
||
$fn = '(inline template at '.$c['file'].':'.$c['line'].')';
|
||
}
|
||
else
|
||
$func_ns = md5($fn);
|
||
|
||
// начала/концы спецстрок
|
||
$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 VMXTemplateState();
|
||
$st->input_filename = $fn;
|
||
|
||
// ищем фрагменты кода - на регэкспах-то было не очень правильно, да и медленно!
|
||
$r = '';
|
||
$pp = 0;
|
||
while ($code)
|
||
{
|
||
$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))
|
||
{
|
||
/* это означает, что в случае отсутствия корректной инструкции
|
||
в найденной позиции надо пропустить ТОЛЬКО её начало и попробовать
|
||
найти что-нибудь снова! */
|
||
$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];
|
||
if (!preg_match('/^\s*\n/s', $frag))
|
||
{
|
||
/* Некоторые инструкции хотят видеть позицию в выходном потоке.
|
||
Например, FUNCTION и END. Поэтому преобразуем текст
|
||
до вызова обработчика. */
|
||
$x_pp = $pp - $blk[$b][4];
|
||
$l = 0;
|
||
if ($x_pp > 0)
|
||
{
|
||
$text = substr($code, 0, $x_pp);
|
||
$text = addcslashes($text, '\\\'');
|
||
// съедаем перевод строки, если надо
|
||
if ($blk[$b][5])
|
||
$text = preg_replace('/\r?\n\r?[ \t]*$/s', '', $text);
|
||
if ($l = strlen($text))
|
||
$l += 8;
|
||
}
|
||
// записываем позицию
|
||
$st->output_position = $l + strlen($r);
|
||
// вызываем обработчик
|
||
$frag = $this->$f($st, $frag);
|
||
}
|
||
else
|
||
$frag = NULL;
|
||
if (!is_null($frag))
|
||
{
|
||
// есть инструкция
|
||
$pp = $x_pp;
|
||
if ($pp > 0)
|
||
{
|
||
if (strlen($text))
|
||
$r .= "\$t.='$text';\n"; // длина как раз этого = $l+8
|
||
$code = substr($code, $pp);
|
||
$pp = 0;
|
||
}
|
||
$r .= $frag;
|
||
$code = substr($code, $e+$blk[$b][5]-$p[$b]);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// финиш
|
||
$code = addcslashes($code, '\\\'');
|
||
$r .= "\$t.='$code';\n";
|
||
$code = '';
|
||
}
|
||
}
|
||
|
||
// перемещаем функции в конец кода
|
||
$code = '';
|
||
for ($i = count($st->functions)-1; $i >= 0; $i--)
|
||
{
|
||
$f = $st->functions[$i];
|
||
$f = substr_replace($r, '', $f[0], $f[1]-$f[0]);
|
||
$code .= $f;
|
||
}
|
||
|
||
// заворачиваем основной код в _main()
|
||
$rfn = addcslashes($fn, '\\\'');
|
||
$code = "<?php // $fn
|
||
class Template_$func_ns extends ".__CLASS__." {
|
||
static \$template_filename = '$rfn';
|
||
function __construct(\$t) {
|
||
\$this->tpldata = &\$t->tpldata;
|
||
\$this->parent = &\$t;
|
||
}
|
||
function ___main() {
|
||
\$stack = array();
|
||
\$t = '';
|
||
$r
|
||
return \$t;
|
||
}
|
||
$code
|
||
}
|
||
";
|
||
$r = '';
|
||
|
||
// записываем в файл
|
||
$fp = fopen($file, 'wb');
|
||
fwrite($fp, $code);
|
||
fclose($fp);
|
||
|
||
// возвращаем имя файла
|
||
return $file;
|
||
}
|
||
|
||
// ELSE
|
||
// ELSE IF expression
|
||
function compile_code_fragment_else($st, $kw, $t)
|
||
{
|
||
if (preg_match('/^IF\s+(.*)$/is', $t, $m))
|
||
return $this->compile_code_fragment_if($st, 'elsif', $m[1]);
|
||
return $t ? NULL : "} else {";
|
||
}
|
||
|
||
// IF expression
|
||
// ELSIF expression
|
||
function compile_code_fragment_if($st, $kw, $t)
|
||
{
|
||
$e = $this->compile_expression($t);
|
||
if ($e === NULL)
|
||
{
|
||
$this->error("Invalid expression in $kw: '$t'");
|
||
return NULL;
|
||
}
|
||
$cf_if = array('elseif' => "} else", 'elsif' => "} else", 'if' => "");
|
||
$kw = $cf_if[$kw];
|
||
if (!$kw)
|
||
$st->in[] = array('if');
|
||
return $kw . "if ($e) {\n";
|
||
}
|
||
function compile_code_fragment_elsif($st, $kw, $t)
|
||
{
|
||
return $this->compile_code_fragment_if($st, $kw, $t);
|
||
}
|
||
function compile_code_fragment_elseif($st, $kw, $t)
|
||
{
|
||
return $this->compile_code_fragment_if($st, $kw, $t);
|
||
}
|
||
|
||
// END [block]
|
||
function compile_code_fragment_end($st, $kw, $t)
|
||
{
|
||
if (!count($st->in))
|
||
{
|
||
$this->error("END $t without begin directive");
|
||
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')
|
||
{
|
||
return $this->varref($in[1]) . " = \$t;\n\$t = array_pop(\$stack);\n";
|
||
}
|
||
elseif ($w == 'function')
|
||
{
|
||
$s = "return \$t;\n}\n";
|
||
foreach (array('blocks', 'in') as $k)
|
||
$st->$k = $in[2][$k];
|
||
$st->functions[count($st->functions)-1][] = $st->output_position+strlen($s);
|
||
return $s;
|
||
}
|
||
elseif ($w == 'begin' || $w == 'for')
|
||
{
|
||
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";
|
||
}
|
||
|
||
// SET varref ... END
|
||
// SET varref = expression
|
||
function compile_code_fragment_set($st, $kw, $t)
|
||
{
|
||
if (!preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/is', $t, $m))
|
||
return NULL;
|
||
if (strlen($m[3]))
|
||
{
|
||
$e = $this->compile_expression($m[3]);
|
||
if ($e === NULL)
|
||
{
|
||
$this->error("Invalid expression in $kw: ($m[3])");
|
||
return NULL;
|
||
}
|
||
return $this->varref($m[1]) . ' = ' . $e . ";\n";
|
||
}
|
||
$st->in[] = array($kw, $m[1]);
|
||
return "\$stack[] = \$t;\n\$t = '';\n";
|
||
}
|
||
|
||
// FUNCTION|BLOCK|MACRO name ... END
|
||
// FUNCTION|BLOCK|MACRO name = expression
|
||
function compile_code_fragment_function($st, $kw, $t)
|
||
{
|
||
if (!preg_match('/^([^=]*)(=\s*(.*))?/is', $t, $m))
|
||
return NULL;
|
||
if (!preg_match('/^[^\W\d]\w*$/', $m[1]) || $m[1] == '_main')
|
||
{
|
||
$this->error("Template function names:
|
||
* must start with a letter
|
||
* must consist of alphanumeric characters
|
||
* must not be equal to '_main'
|
||
I see 'FUNCTION $m[1]' instead.");
|
||
return NULL;
|
||
}
|
||
if ($st->functions && count($st->functions[count($st->functions)-1]) == 1)
|
||
{
|
||
$this->error("Template functions cannot be nested");
|
||
return NULL;
|
||
}
|
||
/* при первом обращении к шаблону все его функции,
|
||
включая "основную" _main, становятся членами класса шаблона.
|
||
при последующих они просто вызываются без дополнительных затрат.
|
||
слишком много функций в классе не появится, т.к. PHP всё равно
|
||
сбрасывается при каждом запросе. */
|
||
$s = "function __$m[1] () {\n";
|
||
if (strlen($m[3]))
|
||
{
|
||
$e = $this->compile_expression($m[3]);
|
||
if ($e === NULL)
|
||
{
|
||
$this->error("Invalid expression in $kw: ($m[3])");
|
||
return NULL;
|
||
}
|
||
$s .= "return $e;\n}\n";
|
||
$st->functions[] = array(
|
||
$st->output_position,
|
||
$st->output_position+strlen($s)
|
||
);
|
||
return $s;
|
||
}
|
||
/* блоки сохраняются и сбрасываются */
|
||
$st->in = array(array('function', $m[1], array('in' => $st->in, 'blocks' => $st->blocks)));
|
||
$st->blocks = array();
|
||
/* запоминаем положение в выходном потоке
|
||
для последующего разбиения его на функции */
|
||
$st->functions[] = array($st->output_position);
|
||
return $s . "\$stack = array();\n\$t = '';\n";
|
||
}
|
||
function compile_code_fragment_block($st, $kw, $t)
|
||
{
|
||
return $this->compile_code_fragment_function($st, $kw, $t);
|
||
}
|
||
function compile_code_fragment_macro($st, $kw, $t)
|
||
{
|
||
return $this->compile_code_fragment_function($st, $kw, $t);
|
||
}
|
||
|
||
// INCLUDE template.tpl
|
||
// legacy, в новом варианте можно использовать с кавычками, и это уже идёт как функция
|
||
function compile_code_fragment_include($st, $kw, $t)
|
||
{
|
||
$t = preg_replace('/^[a-z0-9_\.]+$/', '\'\0\'', $t);
|
||
if (!is_null($t = $this->compile_expression("include $t")))
|
||
return "\$t.=$t;\n";
|
||
return NULL;
|
||
}
|
||
|
||
static function array1($a)
|
||
{
|
||
if (is_null($a))
|
||
return array();
|
||
if (is_array($a) && !self::is_assoc($a))
|
||
return $a;
|
||
return array($a);
|
||
}
|
||
|
||
// FOR[EACH] varref = array
|
||
// или
|
||
// FOR[EACH] varref (тогда записывается в себя)
|
||
function compile_code_fragment_for($st, $kw, $t, $in = false)
|
||
{
|
||
if (preg_match('/^((?:\w+\.)*\w+)(\s*=\s*(.*))?/s', $t, $m))
|
||
{
|
||
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
|
||
{
|
||
// небольшой хак для $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";
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
function compile_code_fragment_foreach($st, $kw, $t)
|
||
{
|
||
return $this->compile_code_fragment_for($st, $kw, $t);
|
||
}
|
||
|
||
// BEGIN block [AT e] [BY e] [TO e]
|
||
// тоже legacy, но пока оставлю...
|
||
function compile_code_fragment_begin($st, $kw, $t)
|
||
{
|
||
if (preg_match('/^([a-z_][a-z0-9_]*)(?:\s+AT\s+(.+))?(?:\s+BY\s+(.+))?(?:\s+TO\s+(.+))?\s*$/is', $t, $m))
|
||
{
|
||
$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;
|
||
}
|
||
|
||
// компиляция фрагмента кода <!-- ... -->. это может быть:
|
||
// 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;
|
||
elseif (!is_null($t = $this->compile_expression($e)))
|
||
{
|
||
// если заданы маркеры подстановок (по умолчанию { ... }),
|
||
// то выражения, вычисляемые в директивах (по умолчанию <!-- ... -->),
|
||
// не подставляются в результат
|
||
if ($this->begin_subst && $this->end_subst &&
|
||
!preg_match('/^(parse|process|include|exec)/is', $e))
|
||
return "$t;\n";
|
||
return "\$t.=$t;\n";
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// компиляция подстановки переменной {...} это просто выражение
|
||
function compile_substitution($st, $e)
|
||
{
|
||
$e = $this->compile_expression($e);
|
||
if ($e !== NULL)
|
||
return "\$t.=$e;\n";
|
||
return NULL;
|
||
}
|
||
|
||
// компиляция выражения. это может быть:
|
||
// 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);
|
||
// строковой или числовой литерал
|
||
if (preg_match('/^((\")(?:[^\"\\\\]+|\\\\.)*\"|\'(?:[^\'\\\\]+|\\\\.)*\'|-?0\d+|-?[0-9]\d*(\.\d+)?|-?0x\d+)\s*(.*)$/is', $e, $m))
|
||
{
|
||
if ($m[4])
|
||
{
|
||
if (!$after)
|
||
return NULL;
|
||
$after[0] = $m[4];
|
||
}
|
||
$e = $m[1];
|
||
if ($m[2])
|
||
$e = str_replace('$', '\\$', $e);
|
||
return $e;
|
||
}
|
||
// функция нескольких аргументов или вызов метода объекта
|
||
elseif (preg_match('/^([a-z_][a-z0-9_]*((?:\.[a-z0-9_]+)*))\s*\((.*)$/is', $e, $m))
|
||
{
|
||
/* вызов методов по цепочке типа obj.method().other_method()
|
||
не поддерживаем, потому что к таким цепочкам без сохранения звеньев
|
||
нервно относится сам PHP */
|
||
$f = strtolower($m[1]);
|
||
if ($m[2])
|
||
{
|
||
/* вызов метода объекта obj.method() */
|
||
$p = strrpos($m[1], '.');
|
||
$method = substr(substr_replace($m[1], '', $p+1), 1);
|
||
if (preg_match('/^[^a-z_]/is', $method))
|
||
{
|
||
$this->error("Object method name cannot start with a number: '$method' of '$m[1]'");
|
||
return NULL;
|
||
}
|
||
$varref = $this->varref($m[1]).'->'.$method;
|
||
}
|
||
elseif ($this->compiletime_functions[$f])
|
||
$ct_callable = $this->compiletime_functions[$f];
|
||
else
|
||
{
|
||
if (!($a = self::$function_aliases[$f]))
|
||
$a = $f;
|
||
if (!method_exists($this, "function_$a"))
|
||
{
|
||
$this->error("Unknown function: '$f' in '$e'");
|
||
return NULL;
|
||
}
|
||
$ct_callable = array($this, "function_$a");
|
||
}
|
||
/* разбираем аргументы */
|
||
$a = $m[3];
|
||
$args = array();
|
||
while (!is_null($e = $this->compile_expression($a, array(&$a))))
|
||
{
|
||
$args[] = $e;
|
||
if (preg_match('/^\s*((,|=>)\s*)?\)/s', $a))
|
||
break;
|
||
elseif ($a == ($b = preg_replace('/^\s*(,|=>)/s', '', $a)))
|
||
{
|
||
$this->error("Unexpected token: '$a' in $f($m[2] parameter list");
|
||
return NULL;
|
||
}
|
||
$a = $b;
|
||
}
|
||
if ($a == ($b = preg_replace('/^\s*((,|=>)\s*)?\)\s*/', '', $a)))
|
||
{
|
||
$this->error("Unexpected token: '$a' in the end of $f($m[2] parameter list");
|
||
return NULL;
|
||
}
|
||
$a = $b;
|
||
/* записываем остатки в $after */
|
||
if ($a)
|
||
{
|
||
if (!$after)
|
||
return NULL;
|
||
$after[0] = $a;
|
||
}
|
||
/* вызов метода объекта или компиляция вызова функции */
|
||
if ($varref)
|
||
return "$varref(".implode(",", $args).")";
|
||
return call_user_func_array($ct_callable, $args);
|
||
}
|
||
// функция одного аргумента
|
||
elseif (preg_match('/^([a-z_][a-z0-9_]*)\s+(?=\S)(.*)$/is', $e, $m))
|
||
{
|
||
$f = strtolower($m[1]);
|
||
if (!($a = self::$function_aliases[$f]))
|
||
$a = $f;
|
||
if (!method_exists($this, "function_$a"))
|
||
{
|
||
$this->error("Unknown function: '$f' in '$e'");
|
||
return NULL;
|
||
}
|
||
$function_name = "function_$a";
|
||
$a = $m[2];
|
||
$arg = $this->compile_expression($a, array(&$a));
|
||
if ($arg === NULL)
|
||
{
|
||
$this->error("Invalid expression: ($e)");
|
||
return NULL;
|
||
}
|
||
$a = ltrim($a);
|
||
if ($a)
|
||
{
|
||
if (!$after)
|
||
return NULL;
|
||
$after[0] = $a;
|
||
}
|
||
return $this->$function_name($arg);
|
||
}
|
||
// переменная плюс legacy-mode переменная/функция
|
||
elseif (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 (!($a = self::$function_aliases[$f]))
|
||
$a = $f;
|
||
if (!method_exists($this, "function_$a"))
|
||
{
|
||
$this->error("Unknown function: '$f' called in legacy mode ($m[0])");
|
||
return NULL;
|
||
}
|
||
$f = "function_$a";
|
||
$e = $this->$f($e);
|
||
}
|
||
return $e;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
// генерация ссылки на переменную
|
||
function varref($e)
|
||
{
|
||
if (!$e)
|
||
return "";
|
||
$e = explode('.', $e);
|
||
$t = '$this->tpldata';
|
||
foreach ($e as $el)
|
||
{
|
||
if (preg_match('/^\d+$/', $el))
|
||
{
|
||
$t .= "[$el]";
|
||
}
|
||
else
|
||
{
|
||
$el = addcslashes($el, '\\\'');
|
||
$t .= "['$el']";
|
||
}
|
||
}
|
||
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)
|
||
{
|
||
if (is_array($a) && !self::is_assoc($a))
|
||
foreach ($a as $v)
|
||
$aa[] = $v;
|
||
else
|
||
$aa[] = $a;
|
||
}
|
||
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;
|
||
}
|
||
|
||
// перлоподобный 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;
|
||
}
|
||
|
||
/*** Функции ***/
|
||
|
||
/** Алиасы */
|
||
static $aliases = array(
|
||
'i' => 'int',
|
||
'intval' => 'int',
|
||
'lower' => 'lc',
|
||
'lowercase' => 'lc',
|
||
'upper' => 'uc',
|
||
'uppercase' => 'uc',
|
||
'addslashes' => 'quote',
|
||
'q' => 'quote',
|
||
'sq' => 'sql_quote',
|
||
're_quote' => 'requote',
|
||
'preg_quote' => 'requote',
|
||
'uri_escape' => 'urlencode',
|
||
'uriquote' => 'urlencode',
|
||
'substring' => 'substr',
|
||
'htmlspecialchars' => 'html',
|
||
's' => 'html',
|
||
'strip_tags' => 'strip',
|
||
't' => 'strip',
|
||
'h' => 'strip_unsafe',
|
||
'implode' => 'join',
|
||
'truncate' => 'strlimit',
|
||
'hash_keys' => 'keys',
|
||
'array_keys' => 'keys',
|
||
'array_slice' => 'subarray',
|
||
'hget' => 'get',
|
||
'aget' => 'get',
|
||
'var_dump' => 'dump',
|
||
'process' => 'parse',
|
||
'include' => 'parse',
|
||
'process_inline' => 'parse_inline',
|
||
'include_inline' => 'parse_inline',
|
||
);
|
||
|
||
/** Числа, логические операции **/
|
||
|
||
/* логические операции */
|
||
function function_or() { $a = func_get_args(); return "self::perlish_or(".join(",", $a).")"; }
|
||
function function_and() { $a = func_get_args(); return $this->fmop('&&', $a); }
|
||
function function_not($e) { return "!($e)"; }
|
||
|
||
/* арифметические операции */
|
||
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_log($e) { return "log($e)"; }
|
||
|
||
/* чётный, нечётный */
|
||
function function_even($e) { return "!(($e) & 1)"; }
|
||
function function_odd($e) { return "(($e) & 1)"; }
|
||
|
||
/* приведение к целому числу */
|
||
function function_int($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))"; }
|
||
|
||
/* сравнения: == != > < >= <= (аргументов как строк) */
|
||
function function_seq($a,$b) { return "((\"$a\") == (\"$b\"))"; }
|
||
function function_sne($a,$b) { return "((\"$a\") != (\"$b\"))"; }
|
||
function function_sgt($a,$b) { return "((\"$a\") > (\"$b\"))"; }
|
||
function function_slt($a,$b) { return "((\"$a\") < (\"$b\"))"; }
|
||
function function_sge($a,$b) { return "((\"$a\") >= (\"$b\"))"; }
|
||
function function_sle($a,$b) { return "((\"$a\") <= (\"$b\"))"; }
|
||
|
||
/* сравнения: == != > < >= <= (аргументов как чисел) */
|
||
function function_neq($a,$b) { return "((0+$a) == ($b))"; }
|
||
function function_nne($a,$b) { return "((0+$a) != ($b))"; }
|
||
function function_ngt($a,$b) { return "((0+$a) > ($b))"; }
|
||
function function_nlt($a,$b) { return "((0+$a) < ($b))"; }
|
||
function function_nge($a,$b) { return "((0+$a) >= ($b))"; }
|
||
function function_nle($a,$b) { return "((0+$a) <= ($b))"; }
|
||
|
||
/* тернарный оператор $1 ? $2 : $3 */
|
||
function function_yesno($a,$b,$c) { return "(($a) ? ($b) : ($c))"; }
|
||
|
||
/** Строки **/
|
||
|
||
/* нижний регистр */
|
||
function function_lc($e) { return ($this->use_utf8 ? "mb_" : "") . "strtolower($e)"; }
|
||
|
||
/* верхний регистр */
|
||
function function_uc($e) { return ($this->use_utf8 ? "mb_" : "") . "strtoupper($e)"; }
|
||
|
||
/* нижний регистр первого символа */
|
||
function function_lcfirst($e) { return ($this->use_utf8 ? "self::mb_" : "") . "lcfirst($e)"; }
|
||
|
||
/* верхний регистр первого символа */
|
||
function function_ucfirst($e) { return ($this->use_utf8 ? "self::mb_" : "") . "ucfirst($e)"; }
|
||
|
||
/* экранирование кавычек */
|
||
function function_quote($e) { return "str_replace(array(\"\\n\",\"\\r\"),array(\"\\\\n\",\"\\\\r\"),addslashes($e))"; }
|
||
|
||
/* экранирование кавычек в SQL- или CSV- стиле (кавычка " превращается в двойную кавычку "") */
|
||
function function_sql_quote($e) { return "str_replace('\"','\"\"',$e)"; }
|
||
|
||
/* экранирование символов, специальных для регулярного выражения */
|
||
function function_requote($e) { return "preg_quote($e)"; }
|
||
|
||
/* экранирование в стиле URL */
|
||
function function_urlencode($e) { return "urlencode($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)";
|
||
}
|
||
|
||
/* длина строки */
|
||
function function_strlen($s) { return ($this->use_utf8 ? "mb_" : "") . "strlen($s)"; }
|
||
|
||
/* подстрока */
|
||
function function_substr($s, $start, $length = NULL)
|
||
{
|
||
return ($this->use_utf8 ? "mb_" : "") . "substr($s, $start" . ($length !== NULL ? ", $length" : "") . ")";
|
||
}
|
||
|
||
/* убиение пробелов в начале и конце */
|
||
function function_trim($s) { return "trim($s)"; }
|
||
|
||
/* разбиение строки по регулярному выражению */
|
||
function function_split($re, $v, $limit = -1)
|
||
{
|
||
return "preg_split('#'.str_replace('#','\\\\#',$re).'#s', $v, $limit)";
|
||
}
|
||
|
||
/* преобразование символов <>&'" в HTML-сущности < > & ' " */
|
||
function function_html($e) { return "htmlspecialchars($e,ENT_QUOTES)"; }
|
||
|
||
/* удаление всех или заданных тегов */
|
||
function function_strip($e, $t='') { return "strip_tags($e".($t?",$t":"").")"; }
|
||
|
||
/* удаление "небезопасных" HTML-тегов */
|
||
function function_strip_unsafe($e) { return "strip_tags($e, self::\$safe_tags)"; }
|
||
|
||
/* заменить \n на <br /> */
|
||
function function_nl2br($s) { return "nl2br($s)"; }
|
||
|
||
/* конкатенация строк */
|
||
function function_concat() { $a = func_get_args(); return $this->fmop('.', $a); }
|
||
|
||
/* объединение всех скаляров и всех элементов аргументов-массивов */
|
||
function function_join() { $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); }
|
||
|
||
/* strftime */
|
||
function function_strftime($fmt, $date, $time = '')
|
||
{
|
||
$e = $time ? "($date).' '.($time)" : $date;
|
||
return "strftime($fmt, self::timestamp($e))";
|
||
}
|
||
|
||
/* ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что. */
|
||
/* strlimit(string, length, dots = '...') */
|
||
function function_strlimit($a)
|
||
{
|
||
$a = func_get_args();
|
||
return "self::" . ($this->use_utf8 ? "mb_" : "") . "strlimit(".join(",", $a).")";
|
||
}
|
||
|
||
/** Массивы и хеши **/
|
||
|
||
/* создание хеша */
|
||
function function_hash()
|
||
{
|
||
$a = func_get_args();
|
||
if (count($a) == 1)
|
||
return "self::exec_hash(".$a[0].")";
|
||
$s = "array(";
|
||
$i = 0;
|
||
$d = '';
|
||
foreach ($a as $v)
|
||
{
|
||
$s .= $d;
|
||
$s .= $v;
|
||
$i++;
|
||
if ($i & 1)
|
||
$d = '=>';
|
||
else
|
||
$d = ',';
|
||
}
|
||
$s .= ")";
|
||
return $s;
|
||
}
|
||
|
||
/* ключи хеша или массива */
|
||
function function_keys($a) { return "array_keys(is_array($a) ? $a : array())"; }
|
||
|
||
/* сортировка массива */
|
||
function function_sort() { $a = func_get_args(); return self::fearr("'VMX_Template::exec_sort'", $a); }
|
||
|
||
/* пары id => ключ, name => значение для ассоциативного массива */
|
||
function function_pairs($a) { return "self::exec_pairs(is_array($a) ? $a : array())"; }
|
||
|
||
/* создание массива */
|
||
function function_array()
|
||
{
|
||
$a = func_get_args();
|
||
return "array(" . join(",", $a) . ")";
|
||
}
|
||
|
||
/* диапазон от $1 до $2 */
|
||
function function_range($a, $b) { return "range($a,$b)"; }
|
||
|
||
/* проверка, массив это или нет? */
|
||
function function_is_array($a) { return "is_array($a)"; }
|
||
|
||
/* число элементов в массиве */
|
||
function function_count($e) { return "self::array_count($e)"; }
|
||
|
||
/* подмассив по номерам элементов */
|
||
function function_subarray() { $a = func_get_args(); return "array_slice(" . join(",", $a) . ")"; }
|
||
|
||
/* подмассив по кратности номеров элементов */
|
||
function function_subarray_divmod() { $a = func_get_args(); return "self::exec_subarray_divmod(" . join(",", $a) . ")"; }
|
||
|
||
/* 0) получить "корневую" переменную по неконстантному ключу
|
||
1) получить элемент хеша/массива по неконстантному ключу (например get(iteration.array, rand(5)))
|
||
по-моему, это лучше, чем Template Toolkit'овский ад - hash.key.${another.hash.key}.зюка.хрюка и т.п.
|
||
2) получить элемент выражения-массива - ибо в PHP не работает (...expression...)['key'],
|
||
к примеру не работает range(1,10)[0]
|
||
но у нас-то можно написать get(range(1,10), 0), поэтому мы должны это поддерживать
|
||
хотя это и не будет lvalue */
|
||
function function_get($a, $k=NULL)
|
||
{
|
||
if ($k === NULL)
|
||
return "\$this->tpldata[$a]";
|
||
/* проверяем синтаксис выражения */
|
||
if (@eval('return true; '.$a.'[0];'))
|
||
return $a."[$k]";
|
||
return "self::exec_get($a, $k)";
|
||
}
|
||
|
||
/* присваивание (только lvalue) */
|
||
function function_set($l, $r) { return "($l = $r)"; }
|
||
|
||
/* объединение массивов */
|
||
function function_array_merge() { $a = func_get_args(); return "array_merge(" . join(",", $a) . ")"; }
|
||
|
||
/* 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)"; }
|
||
|
||
/** Прочее **/
|
||
|
||
/* игнорирование результата (а-ля js) */
|
||
function function_void($a) { return "self::void($a)"; }
|
||
function void($a) { return ''; }
|
||
|
||
/* дамп переменной */
|
||
function function_dump($var)
|
||
{
|
||
return "self::exec_dump($var)";
|
||
}
|
||
|
||
/* JSON-кодирование */
|
||
function function_json($v) { return "json_encode($v)"; }
|
||
|
||
/* Аргументы для функций включения
|
||
аргументы ::= hash(ключ => значение, ...) | ключ => значение, ...
|
||
*/
|
||
function auto_hash($args)
|
||
{
|
||
if (!($n = count($args)))
|
||
$args = NULL;
|
||
elseif ($n == 1)
|
||
$args = ", ".$args[0];
|
||
else
|
||
$args = ", ".call_user_func_array(array($this, 'function_hash'), $args);
|
||
return $args;
|
||
}
|
||
|
||
/* включение другого файла: parse('файл'[, аргументы]) */
|
||
function function_parse()
|
||
{
|
||
$args = func_get_args();
|
||
$file = array_shift($args);
|
||
$args = $this->auto_hash($args);
|
||
return "\$this->parent->parse_real($file, NULL, '_main'$args)";
|
||
}
|
||
|
||
/* включение блока из текущего файла: exec('блок'[, аргументы]) */
|
||
function function_exec()
|
||
{
|
||
$args = func_get_args();
|
||
$block = array_shift($args);
|
||
$args = $this->auto_hash($args);
|
||
return "\$this->parent->parse_real(self::\$template_filename, NULL, $block$args)";
|
||
}
|
||
|
||
/* включение блока из другого файла: exec_from('файл', 'блок'[, аргументы]) */
|
||
function function_exec_from()
|
||
{
|
||
$args = func_get_args();
|
||
$file = array_shift($args);
|
||
$block = array_shift($args);
|
||
$args = $this->auto_hash($args);
|
||
return "\$this->parent->parse_real($file, NULL, $block$args)";
|
||
}
|
||
|
||
/* parse не из файла, хотя и не рекомендуется */
|
||
function function_parse_inline()
|
||
{
|
||
$args = func_get_args();
|
||
$code = array_shift($args);
|
||
$args = $this->auto_hash($args);
|
||
return "\$this->parent->parse_real(NULL, $code, '_main'$args)";
|
||
}
|
||
|
||
/* сильно не рекомендуется, но возможно:
|
||
включение блока не из файла:
|
||
exec_from_inline('код', 'блок'[, аргументы]) */
|
||
function function_exec_from_inline()
|
||
{
|
||
$args = func_get_args();
|
||
$code = array_shift($args);
|
||
$block = array_shift($args);
|
||
$args = $this->auto_hash($args);
|
||
return "\$this->parent->parse_real(NULL, $code, $block$args)";
|
||
}
|
||
|
||
/* вызов функции объекта по вычисляемому имени:
|
||
call(object, "method", arg1, arg2, ...) или
|
||
call_array(object, "method", array(arg1, arg2, ...)) */
|
||
function function_call()
|
||
{
|
||
$a = func_get_args();
|
||
$o = array_shift($a);
|
||
$m = array_shift($a);
|
||
return "call_user_func_array(array($o, $m), array(".implode(", ", $a)."))";
|
||
}
|
||
function function_call_array($o, $m, $a = NULL)
|
||
{
|
||
return "call_user_func_array(array($o, $m), ".($a ? $a : "array()").")";
|
||
}
|
||
|
||
/* 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);
|
||
}
|
||
|
||
/*** Реализации функций ***/
|
||
|
||
// дамп переменной
|
||
static function exec_dump($var)
|
||
{
|
||
ob_start();
|
||
var_dump($var);
|
||
$var = ob_get_contents();
|
||
ob_end_clean();
|
||
return $var;
|
||
}
|
||
|
||
// подмассив по кратности номеров элементов
|
||
// 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;
|
||
}
|
||
|
||
// выполняет подстановку 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;
|
||
}
|
||
|
||
// выполняет сортировку и возвращает сортированный массив
|
||
static function exec_sort($array)
|
||
{
|
||
sort($array);
|
||
return $array;
|
||
}
|
||
|
||
// возвращает элемент массива
|
||
static function exec_get($array, $key)
|
||
{
|
||
return $array[$key];
|
||
}
|
||
|
||
// делает хеш из массива
|
||
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;
|
||
}
|
||
|
||
// возвращает пары вида { key => ключ, value => значение }
|
||
static function exec_pairs($array)
|
||
{
|
||
$r = array();
|
||
foreach ($array as $k => $v)
|
||
$r[] = array('key' => $k, 'value' => $v);
|
||
return $r;
|
||
}
|
||
|
||
// ограничение длины строки $maxlen символами на границе пробелов и добавление '...', если что.
|
||
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;
|
||
}
|
||
|
||
// то же, но в UTF-8 (точнее, в текущей mb_internal_encoding())
|
||
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;
|
||
}
|
||
|
||
// lcfirst() и ucfirst() для UTF-8
|
||
static function mb_lcfirst($str)
|
||
{
|
||
return mb_strtolower(mb_substr($str, 0, 1)) . mb_substr($str, 0, 1);
|
||
}
|
||
|
||
// lcfirst() и ucfirst() для UTF-8
|
||
static function mb_ucfirst($str)
|
||
{
|
||
return mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 0, 1);
|
||
}
|
||
|
||
// ограниченная распознавалка дат
|
||
static 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))
|
||
{
|
||
// 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]);
|
||
}
|
||
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 == 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;
|
||
}
|
||
}
|