From dd420ba3b6ea44144fe74d70f691a5b344e2eb9e Mon Sep 17 00:00:00 2001 From: vitalif Date: Tue, 16 Feb 2010 00:07:25 +0000 Subject: [PATCH] First rewrite of template.php --- template.php | 1655 ++++++++++++++++++++++++++++---------------------- 1 file changed, 921 insertions(+), 734 deletions(-) diff --git a/template.php b/template.php index 6ff09d3..070c4da 100644 --- a/template.php +++ b/template.php @@ -1,782 +1,969 @@ and can be placed on one line - * {block_name.block_name.etc....varname} = varref varname of current iteration of block block_name.block_name.etc... - * {block_name.block_name.etc....varname|varoption} = varref varname if it's set or block_name.block_name.etc....varoption if not - * {block_name.#} = number of current iteration of block block_name - * = begin block block_name, blocks can be nested - * = end block - * = include file "filename" directly at this point (path relative to current template file path) - * = begin block_name at iteration #start and do #count iterations (or 1 by default) - * = do block_name iterations with numbers #no if (#no % #div) = #mod - * - * [[...]] = parse [[...]] only if varref {block_name.block_name.etc.[...].varname} is set and is true (if PHP thinks that it's true :-)) - * [[...]] = parse [[...]] only if varref {block_name.block_name.etc.[...].varname} is not set or is not true - * - * Документация: - * и могут быть даже на одной строке - * {VAR} - подстановка корневой переменной VAR - * = дословное включение файла "filename" - * = обработать в цикле все итерации блока "блок1" - * = обработать в цикле <количество> (или все оставшиеся если <количество> не задано) итераций блока "блок1" - * = обработать в цикле все итерации блока "блок1", номера которых по модулю <делитель> равны <остаток> - * = конец тела цикла - * Блоки могут быть вложенными, и могут быть расположены на одной строке. - * Подстановки внутриблоковых переменных работают внутри блока :) - * {блок1.блок2.и_так_далее.VAR} = подстановка переменной VAR блока "блок1.блок2.и_так_далее" - * {и_так_далее.#} = номер текущей итерации блока "блок1.блок2.и_так_далее", если "и_так_далее" - самый внутренний по вложенности блок - * {блок1.блок2.и_так_далее.VAR|VAR2} = подстановка переменной VAR, а если она не задана - то VAR2 блока "блок1.блок2.и_так_далее" - * - * [[...]] = показать [[...]] только если переменная VAR текущей итерации блока "блок1.блок2.и_так_далее" задана - * [[...]] = показать [[...]] только если переменная VAR текущей итерации блока "блок1.блок2.и_так_далее" НЕ задана - * - * Code example/пример кода: - * - * $template = new Template ("/directory/with/templates"); - * $template->cachedir = "/directory/for/caching/compiled/code"; # your script must have write access to this directory / выполняемый скрипт должен иметь доступ на запись в эту директорию - * $template->no_pparse = true; # highly recommended with previous versions, but unnecessary with this / важно с предыдущими версиями, но необязательно с этой - * - * $template->set_filenames(array('body' => 'hello.tpl')); - * $template->assign_vars(array( - * "HELLO" => "Hello, world!", - * "SHOW1" => 1, - * "TEXT1" => "I'll be visible (SHOW1 = true)", - * "TEXT2" => "And I not (SHOW2)", - * )); - * $template->assign_block_vars('language', array ( - * 'NAME' => 'PHP', - * 'URL' => 'http://php.net/' - * )); - * $template->assign_block_vars('language', array ( - * 'NAME' => 'Perl', - * 'URL' => 'http://perl.org/' - * )); - * - * $template->pparse('body'); # same as "echo $template->rparse('body');" if $template->no_pparse is true / то же, что и "echo $template->rparse('body');", если $template->no_pparse истинно - * - * Template example/пример шаблона: - * - * - * - * Hello, world! by template.php - * - * - * {HELLO}
- * {TEXT1}
- * {TEXT2}SHOW2 is false
- * - * {language.NAME} --> {language.URL}
- * - * - * - */ +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 { - var $classname = "Template"; + static $Mon, $mon, $Wday; + static $cache_type = NULL; + static $cache = array(); + static $safe_tags = '

        
    • '; - // set this variable to cache directory and template compiler will use caching - var $cachedir = false; + 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 $eat_code_line = true; // съедать "лишний" перевод строки, если в строке только инструкция? + var $begin_subst = '{'; // начало подстановки (необязательно) + var $end_subst = '}'; // конец подстановки (необязательно) + var $strict_end = false; // жёстко требовать имя блока в его завершающей инструкции () + var $raise_error = false; // говорить die() при ошибках в шаблонах + var $print_error = false; // печатать фатальные ошибки - // variable that holds all the data we'll be substituting into - // the compiled templates. - // ... - // This will end up being a multi-dimensional array like this: - // $this->_tpldata[block.][iteration#][child.][iteration#][child2.][iteration#][variablename] == value - // if it's a root-level variable, it'll be like this: - // $this->_tpldata[.][0][varname] == value - var $_tpldata = array(); - var $_tpldata_stack = array(); - - // Hash of filenames for each template handle. - var $files = array(); - - // Root template directory. - var $root = ""; - - // this will hash handle names to the compiled code for that handle. - var $compiled_code = array(); - - // This will hold the uncompiled code for that handle. - var $uncompiled_code = array(); - - // This option causes pparse() to be equal to "echo rparse()" - var $no_pparse = true; - - // Set this to the name of "wrapper" function, which is called by - // rparse() after compiling and running. - var $wrapper = false; - - /** - * - * $conv defines available conversions. $conv[0] define conversions that take 0 parameters, - * $conv[1] - 1. - * - * By default, the following conversions are available: - * T = strip_tags - * i = intval - * b = nl2br - * h = html_strip [requires lib.php by VMX] - * H = html_strip_pbr [requires lib.php by VMX] - * s = htmlspecialchars - * S = html_pbr [requires lib.php by VMX] - * l = strtolower - * u = strtoupper - * c### = strlimit (###) where ### is number of any length - * - */ - var $conv = array ( - 0 => array ( - 'T' => 'strip_tags', - 'i' => 'intval', - 'b' => 'nl2br', - 'h' => 'html_strip', - 'H' => 'html_strip_pbr', - 's' => 'htmlspecialchars', - 'S' => 'html_pbr', - 'l' => 'strtolower', - 'u' => 'strtoupper', - 'q' => 'addslashes', - 'l' => 'urlencode', - ), - 1 => array ( - 'c' => 'strlimit' - ) - ); - - /** - * Constructor. Simply sets the root dir. - */ - function Template($root = ".") + function __construct($args) { - $this->set_rootdir($root); + 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); } - /** - * Destroys this template object. Should be called when you're done with it, in order - * to clear out the template data so you can load/parse a new template set. - */ - function destroy() + // Сохранить ошибку + function error($e, $fatal = false) { - $this->_tpldata = array(); + $this->errors[] = $e; + if ($this->raise_error && $fatal) + die(__CLASS__."::error: $e"); + elseif ($this->print_error) + print __CLASS__."::error: $e\n"; } - /** - * Saves current template data in template data stack and then destroys data. - */ - function datapush() + // Функция уничтожает данные шаблона + function clear() { - array_push ($this->_tpldata_stack, $this->_tpldata); - } - - /** - * Restores last saved template data from template data stack. - */ - function datapop() - { - if (count ($this->_tpldata_stack)) - $this->_tpldata = array_pop ($this->_tpldata_stack); - } - - /** - * Sets the template root directory for this Template object. - */ - function set_rootdir($dir) - { - if (!is_dir($dir)) - return false; - $this->root = $dir; + $this->tpldata = array(); return true; } - /** - * Sets the template filenames for handles. $filename_array - * should be a hash of handle => filename pairs. - */ - function set_filenames($filename_array) + // Подлить в огонь переменных. Возвращает новый массив. + function assign_vars($new = NULL, $value = NULL) { return $this->vars($new, $value); } + function vars($new = NULL, $value = NULL) { - if (!is_array($filename_array)) - return false; - - reset($filename_array); - $a = $this->wrapper; - $this->wrapper = false; - while(list($handle, $filename) = each($filename_array)) - { - $this->files[$handle] = $this->make_filename($filename); - $this->uncompiled_code[$handle] = ''; - $this->compiled_code[$handle] = ''; - } - $this->wrapper = $a; - - return true; + if (is_array($new)) + $this->tpldata = array_merge($this->tpldata, $new); + else if ($new && $value !== NULL) + $this->tpldata[$new] = $value; + return $this->tpldata; } - /** - * Sets the template code for handle directly, without loading it from any files. - */ - function set_template_code ($handle, $code) + // Кэш (xcache, eaccelerator) + static function cache_check_type() { - $this->files[$handle] = false; - $this->uncompiled_code[$handle] = $code; - unset ($this->compiled_code[$handle]); - return true; - } - - /** - * Load the file for the handle, compile the file, - * and run the compiled code. This will print out - * the results of executing the template. - */ - function pparse($handle) - { - if ($this->no_pparse) + if (is_null(self::$cache_type)) { - echo $this->rparse ($handle); - return; - } - - if (!$this->loadfile($handle)) - die("Template->pparse(): Couldn't load template file for handle $handle"); - - // actually compile the template now. - if (!isset($this->compiled_code[$handle]) || empty($this->compiled_code[$handle])) - $this->compiled_code[$handle] = $this->compile($this->uncompiled_code[$handle]); - - //echo ($this->compiled_code[$handle]); - - // Run the compiled code. - eval($this->compiled_code[$handle]); - return true; - } - - /** - * Load the file for the handle, compile the file, - * and run the compiled code. This will RETURN - * the results of executing the template. - */ - function rparse ($handle) - { - if (!$this->loadfile($handle)) - die("Template->rparse(): Couldn't load template file for handle $handle"); - - // Compile it, with the "no echo statements" option on. - $_str = ""; - $code = $this->compile($this->uncompiled_code[$handle], true, '_str'); - - // evaluate the variable assignment. - eval($code); - - // call wrapper if it's set - if ($this->wrapper) - { - $fn = $this->wrapper; - $_str = $fn ($_str); - } - - // return the value of the generated variable. - return $_str; - } - - /** - * Inserts the uncompiled code for $handle as the - * value of $varname in the root-level. This can be used - * to effectively include a template in the middle of another - * template. - * Note that all desired assignments to the variables in $handle should be done - * BEFORE calling this function. - */ - function assign_var_from_handle($varname, $handle) - { - if (!$this->loadfile($handle)) - die("Template->assign_var_from_handle(): Couldn't load template file for handle $handle"); - - // Compile it, with the "no echo statements" option on. - $_str = ""; - $code = $this->compile($this->uncompiled_code[$handle], true, '_str'); - - // evaluate the variable assignment. - eval($code); - // assign the value of the generated variable to the given varname. - $this->assign_var($varname, $_str); - - return true; - } - - /** - * Block-level variable assignment. Adds a new block iteration with the given - * variable assignments. Note that this should only be called once per block - * iteration. - */ - function assign_block_vars($blockname, $vararray) - { - if ($blockname == '.' || !$blockname) - return $this->assign_vars ($vararray); - if ($blockname{0} == '.') - $blockname = substr ($blockname, 1); - $vararray = $this->array_do_conversions ($vararray); - if (strstr($blockname, '.')) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks) - 1; - $str = '$this->_tpldata'; - for ($i = 0; $i < $blockcount; $i++) - { - $str .= '[\'' . $blocks[$i] . '.\']'; - eval('$lastiteration = sizeof(' . $str . ') - 1;'); - $str .= '[' . $lastiteration . ']'; - } - // Now we add the block that we're actually assigning to. - // We're adding a new iteration to this block with the given - // variable assignments. - $str .= '[\'' . $blocks[$blockcount] . '.\'][] = $vararray;'; - - // Now we evaluate this assignment we've built up. - eval($str); - } - else - { - // Top-level block. - // Add a new iteration to this block with the variable assignments - // we were given. - $this->_tpldata[$blockname . '.'][] = $vararray; - } - return true; - } - - /** - * Block-level variable assignment. This function does not add any iterations - * to block, it only appends $vararray to last existing iteration. [VMX 2006] - */ - function append_block_vars($blockname, $vararray) - { - if ($blockname == '.' || !$blockname) - $this->assign_vars ($vararray); - if ($blockname{0} == '.') - $blockname = substr ($blockname, 1); - $vararray = $this->array_do_conversions ($vararray); - if (strstr($blockname, '.')) - { - // Nested block. - $blocks = explode('.', $blockname); - $blockcount = sizeof($blocks); - $str = '$this->_tpldata'; - for ($i = 0; $i < $blockcount; $i++) - { - $str .= '[\'' . $blocks[$i] . '.\']'; - eval('$lastiteration = sizeof(' . $str . ') - 1;'); - $str .= '[' . $lastiteration . ']'; - } - eval ($str .= ' = array_merge ('.$str.', $vararray);'); - } - else - { - // Top-level block. - if (($len = count ($this->_tpldata[$blockname . '.'])) > 0) - $this->_tpldata[$blockname.'.'][$len-1] = array_merge ($this->_tpldata[$blockname . '.'][$len-1], $vararray); - } - return true; - } - - /** - * Root-level variable assignment. Adds to current assignments, overriding - * any existing variable assignment with the same name. - */ - function assign_vars ($vararray) - { - $vararray = $this->array_do_conversions ($vararray); - foreach ($vararray as $key => $val) - $this->_tpldata['.'][0][$key] = $val; - return true; - } - - /** - * Root-level variable assignment. Adds to current assignments, overriding - * any existing variable assignment with the same name. - */ - function assign_var ($varname, $varval) - { - if (($p = strpos ($varname, '/')) !== false) - { - eval ('$varval = ' . $this->generate_conversion_ref ('$varval', substr ($varname, $p+1)) . ';'); - $varname = substr ($varname, 0, $p); - } - $this->_tpldata['.'][0][$varname] = $varval; - return true; - } - - /** - * Generates a full path+filename for the given filename, which can either - * be an absolute name, or a name relative to the rootdir for this Template - * object. - */ - function make_filename($filename) - { - // Check if it's an absolute or relative path. - if (substr($filename, 0, 1) != '/') - $filename = $this->root . '/' . $filename;//($rp_filename = phpbb_realpath($this->root . '/' . $filename)) ? $rp_filename : $filename; - - if (!file_exists($filename)) - die("Template->make_filename(): Error - file $filename does not exist"); - return $filename; - } - - /** - * If not already done, load the file for the given handle and populate - * the uncompiled_code[] hash with its code. Do not compile. - */ - function loadfile($handle) - { - // If the file for this handle is already loaded and compiled, do nothing. - if (isset($this->uncompiled_code[$handle]) && !empty($this->uncompiled_code[$handle])) - return true; - - // If we don't have a file assigned to this handle, die. - if (!isset($this->files[$handle])) - die("Template->loadfile(): No file specified for handle $handle"); - - // << VMX >> if $this->files[$handle] is false - template contents are specified directly - if ($this->files[$handle] !== false) - { - $filename = $this->files[$handle]; - $filepath = substr ($filename, 0, strrpos ($filename, '/')+1); - - $str = @file_get_contents ($filename); - if (empty($str)) - die("Template->loadfile(): File $filename for handle $handle is empty"); - - // Handle instructions - while (preg_match ('##', $str, $m)) - $str = str_replace ($m[0], @file_get_contents ($filepath . $m[1]), $str); - - $this->uncompiled_code[$handle] = $str; - } - return true; - } - - /** - * Compiles the given string of code, and returns - * the result in a string. - * If "do_not_echo" is true, the returned code will not be directly - * executable, but can be used as part of a variable assignment - * for use in assign_code_from_handle() or rparse(). - */ - function compile($code, $do_not_echo = false, $retvar = '') - { - if ($this->cachedir) - { - $sfile = $this->cachedir . ($do_not_echo ? 'alt_' . $retvar : '') . md5 ($code) . '.tps'; - if (($cached = @file_get_contents ($sfile)) !== false) - return $cached; - } - else - unset ($sfile); - $default_addmode = $do_not_echo ? '$' . $retvar . ' .= ' : 'echo '; - - // Сначала - $code = preg_replace ('/\s*/s', '', $code); - - // форматирование кода для красоты - $code = preg_replace ('/^\s*()\s*$/m', "\x01\\1\x01\n", $code); - $ncode = $code; - do { - $code = $ncode; - $ncode = preg_replace ("/([^\x01])()/m", "\\1\x01\\2", $code); - } while ($ncode != $code); - $ncode = $code; - do { - $code = $ncode; - $ncode = preg_replace ("/(?=[^\x01])/m", "\\0\x01", $code); - } while ($ncode != $code); - - // replace \ with \\ and then ' with \'. - $code = str_replace('\\', '\\\\', $code); - $code = str_replace('\'', '\\\'', $code); - - // VMX :: handle iteration numbers - $code = preg_replace ('/\{([a-z0-9\-_]+)\.#\}/', '\'.(1+(isset($_\1_i)?$_\1_i:0)).\'', $code); - - // change template varrefs into PHP varrefs - - // handle all varrefs (instead of only non-root) - $varrefs = array(); - preg_match_all ('#\{(([a-z0-9\-_]+?\.)+)?([a-z0-9\-_/]+?)(\|([a-z0-9\-_/]+?))?\}#is', $code, $varrefs); - $varcount = sizeof($varrefs[1]); - for ($i = 0; $i < $varcount; $i++) - { - $namespace = $varrefs[1][$i]; - $varname = $varrefs[3][$i]; - if (isset ($varrefs[5][$i])) - $varoption = $varrefs[5][$i]; + if (function_exists('xcache_get')) + self::$cache_type = 'x'; + else if (function_exists('eaccelerator_get')) + self::$cache_type = 'e'; else - $varoption = false; - $new = $this->generate_block_varref ($namespace, $varname, $varoption); - $code = str_replace ($varrefs[0][$i], $new, $code); + self::$cache_type = ''; } - - // // This will handle the remaining root-level varrefs - // $code = preg_replace('#\{([a-z0-9\-_]*?)\}#is', '\' . ( ( isset($this->_tpldata[\'.\'][0][\'\1\']) ) ? $this->_tpldata[\'.\'][0][\'\1\'] : \'\' ) . \'', $code); - - // replace \n with \n\x1 - $code = str_replace ("\n", "\n\x1", $code); - //$code = preg_replace ('#(?:\x1\s*)+\x1#', "\x1", $code); - - // Break code up into lines - $code_lines = explode("\x1", $code); - - $block_nesting_level = 0; - $block_names = array(); - $block_names[0] = "."; - $addmodes[0][0] = $default_addmode; - $addmodes[0][1] = ';'; - $addmodes[0][2] = 0; - - $line_count = sizeof($code_lines); - $in_set = false; - for ($i = 0; $i < $line_count; $i++) + } + static function cache_get($key) + { + self::cache_check_type(); + if (!array_key_exists($key, self::$cache)) { - if (strlen ($code_lines[$i]) == 0) - continue; - // Additional part will handle AT and MOD - if (preg_match('#^$#', $code_lines[$i], $m)) // ((APPEND|PREPEND) (([A-Za-z0-9\-_]+?\.)*)([A-Za-z0-9\-_]+?) )? - { - $b_not = $m[1]; - $b_bn = $m[2]; - $b_mod = $m[3]; - $n[0] = $m[0]; - $n[1] = $b_bn; - - // We have the start of a block. - $block_nesting_level++; - $block_names[$block_nesting_level] = $b_bn; - $addmodes[$block_nesting_level][0] = $default_addmode; - $addmodes[$block_nesting_level][1] = ';'; - $addmodes[$block_nesting_level][2] = 0; - $cbstart = 0; - $cbcount = 0; - $cbplus = '++'; - - if (preg_match ('#^[ \t]*AT ([0-9]+)[ \t]*(?:([0-9]+)[ \t]*)?$#', $b_mod, $nem)) - { - $cbstart = isset ($nem[1]) ? (0+$nem[1]) : 0; - $cbcount = (isset ($nem[2]) ? (0+$nem[2]) + $cbstart : 0); - } - if (preg_match ('#^[ \t]*MOD ([1-9][0-9]*) ([0-9]+)[ \t]*$#', $b_mod, $nem)) - { - $cbstart = $nem[2]; - $cbplus = '+='.$nem[1]; - } - - if ($block_nesting_level < 2) - { - // Block is not nested. - if (!$cbcount) - $code_lines[$i] = '$_' . $b_bn . '_count = ( isset($this->_tpldata[\'' . $b_bn . '.\']) ) ? sizeof($this->_tpldata[\'' . $b_bn . '.\']) : ' . $addmodes[$block_nesting_level][2] . ';'; - else $code_lines[$i] = '$_' . $b_bn . '_count = min (@sizeof($this->_tpldata[\'' . $b_bn . '.\']), ' . $cbcount . ');'; - } - else - { - // This block is nested. - - // Generate a namespace string for this block. - $namespace = implode('.', $block_names); - // strip leading period from root level.. - $namespace = substr($namespace, 2); - // Get a reference to the data array for this block that depends on the - // current indices of all parent blocks. - $varref = $this->generate_block_data_ref($namespace, false); - // $this->_tpldata['categories.'][$_categories_i]['modules.'] - // Create the for loop code to iterate over this block. - if (!$cbcount) - $code_lines[$i] = '$_' . $b_bn . '_count = ( isset(' . $varref . ') ) ? sizeof(' . $varref . ') : ' . $addmodes[$block_nesting_level][2] . ';'; - else $code_lines[$i] = '$_' . $b_bn . '_count = min (@sizeof(' . $varref . '), ' . $cbcount . ');'; - } - if (!$b_not) - $code_lines[$i] .= "\n" . 'for ($_' . $b_bn . '_i = ' . $cbstart . '; $_' . $b_bn . '_i < $_' . $b_bn . '_count; $_' . $b_bn . '_i' . $cbplus . ')'; - else - $code_lines[$i] .= "\n" . 'for ($_' . $b_bn . '_i = ' . $cbcount . '; $_' . $b_bn . '_i < ' . max($cbcount, 1) . '; $_' . $b_bn . '_i' . $cbplus . ')'; - $code_lines[$i] .= "\n" . '{'; - } - else if (preg_match('##', $code_lines[$i], $m)) - { - // Make strict check - only corresponds to - if ($block_nesting_level > 0 && trim ($m[1]) == $block_names[$block_nesting_level]) - { - unset($block_names[$block_nesting_level]); - $block_nesting_level--; - } - $code_lines[$i] = '} // END ' . $m[1]; - } - else if (preg_match('##is', $code_lines[$i], $m)) - { - $varref = $this->generate_block_data_ref (substr ($m[2], 0, strlen($m[2])-1), true) . '[\'' . $m[3] . '\']'; - $code_lines[$i] = 'if('.(empty($m[1]) ? '!' : '').'empty('.$varref.')) {'; - } - else if (preg_match('##is', $code_lines[$i], $m)) - $code_lines[$i] = '} else {'; - else if (!$in_set && preg_match('#^\s*\s*$#s', $code_lines[$i], $m)) - { - $varref = $this->generate_block_data_ref (substr ($m[1], 0, strlen($m[1])-1), true) . '[\'' . $m[2] . '\']'; - $code_lines[$i] = "\$${retvar}_tmp = \$$retvar;\n\$$retvar = '';\n$varref = eval(<<\s*$/s', $code_lines[$i], $m)) - { - $code_lines[$i] = "return \\\$_str;\nENDSET\n);\n\$$retvar = \$${retvar}_tmp;"; - $in_set = false; - } - else if (!preg_match('/^\s*$/', $code_lines[$i])) - { - // We have an ordinary line of code - $code_lines[$i] = $addmodes[$block_nesting_level][0] . '\'' . $code_lines[$i] . '\'' . $addmodes[$block_nesting_level][1]; - } - if ($in_set && !preg_match('/<<_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['varname'] . ' - * It's ready to be inserted into an "echo" line in one of the templates. - * NOTE: expects a trailing "." on the namespace. - */ - function generate_block_varref($namespace, $varname, $varoption = false) + // Функция загружает, компилирует и возвращает результат для хэндла + // $page = $obj->parse( 'file/name.tpl' ); + // $page = $obj->parse( 'template {CODE}', true ); + function parse($fn, $inline = false) { - $varconv = false; - if (($p = strpos ($varname, '/')) !== false) + $this->errors = array(); + if ($inline) { - $varconv = substr ($varname, $p+1); - $varname = substr ($varname, 0, $p); + $text = $fn; + $fn = ''; + if (!$text) + return ''; } - // Strip the trailing period. - $namespace = substr ($namespace, 0, strlen($namespace)-1); - // Get a reference to the data block for this namespace. - $varref = $this->generate_block_data_ref($namespace, true); - // Prepend the necessary code to stick this in an echo line. - if ($varoption === false) - $varoption = "''"; else - $varoption = '((isset(' . $varref . '[\'' . $varoption . '\']' . ')) ? ' . $varref . '[\'' . $varoption . '\'] : \'\')'; - // Append the variable reference. - $varref .= '[\'' . $varname . '\']'; - $varref = '((isset(' . $varref . ')) ? ' . $varref . ' : ' . $varoption . ')'; - if ($varconv) - $varref = $this->generate_conversion_ref ($varref, $varconv); - $varref = '\' . ' . $varref . ' . \''; - return $varref; - } - - /** - * Generates a reference to the array of data values for the given - * (possibly nested) block namespace. This is a string of the form: - * $this->_tpldata['parent'][$_parent_i]['$child1'][$_child1_i]['$child2'][$_child2_i]...['$childN'] - * - * If $include_last_iterator is true, then [$_childN_i] will be appended to the form shown above. - * NOTE: does not expect a trailing "." on the blockname. - */ - function generate_block_data_ref($blockname, $include_last_iterator) - { - // Added - VMX: function now can handle root-level - if ($blockname == '') - return '$this->_tpldata[\'.\']' . ($include_last_iterator ? '[0]' : ''); - // Get an array of the blocks involved. - $blocks = explode(".", $blockname); - $blockcount = sizeof($blocks) - 1; - $varref = '$this->_tpldata'; - // Build up the string with everything but the last child. - for ($i = 0; $i < $blockcount; $i++) - $varref .= '[\'' . $blocks[$i] . '.\'][$_' . $blocks[$i] . '_i]'; - // Add the block reference for the last child. - $varref .= '[\'' . $blocks[$blockcount] . '.\']'; - // Add the iterator for the last child if requried. - if ($include_last_iterator) - $varref .= '[$_' . $blocks[$blockcount] . '_i]'; - return $varref; - } - - /** - * Конвертирует те переменные массива $array, имена которых заданы как NAME/conv - */ - function array_do_conversions ($array) - { - $ra = array (); - foreach ($array as $key => $val) { - if (($p = strpos ($key, '/')) !== false) + if (!strlen($fn)) { - eval ('$val = ' . $this->generate_conversion_ref ('$val', substr ($key, $p+1)) . ';'); - $key = substr ($key, 0, $p); + $this->error("Template: empty filename '$fn'", true); + return NULL; + } + if (substr($fn, 0, 1) != '/') + $fn = $this->root.$fn; + if (!($text = $this->loadfile($fn))) + { + $this->error("Template: couldn't load template file '$fn'", true); + return NULL; } - $ra [$key] = $val; } - return $ra; + if (!($file = $this->compile($text, $fn))) + return NULL; + $stack = array(); + include $file; + $w = $this->wrapper; + if (is_callable($w)) + $w(&$t); + return $t; } - /** - * Generates function call series needed to perform conversion(s) according to $conv - */ - function generate_conversion_ref ($val, $conv) + // Функция загружает файл с кэшированием + // $textref = $obj->loadfile($file) + function loadfile($fn) { - $cv = array (); - $l = strlen ($conv); - for ($i = 0; $i < $l; $i++) + $load = false; + if (!($text = self::cache_get("U$fn")) || $this->reload) { - if (array_key_exists ($conv{$i}, $this->conv[0])) - $cv [$conv{$i}] = true; - else if (array_key_exists ($conv{$i}, $this->conv[1])) - $cv [$conv{$i}] = 0+substr ($conv,$i+1); + $mtime = stat($fn); + $mtime = $mtime[9]; + if (!$text) + $load = true; + else + { + $ctime = self::cache_get("T$fn"); + if ($ctime < $mtime) + $load = true; + } } - $cv = array_reverse ($cv); - $r = $val; - foreach ($cv as $k => $v) + // если файл изменился - перезасасываем + if ($load) { - if ($v === true) - $r = $this->conv[0][$k] . '(' . $r . ')'; - else if (is_numeric ($v)) - $r = $this->conv[1][$k] . '(' . $r . ',' . $v . ')'; + 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; + // 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 = ''; + + // маркер начала, маркер конца, обработчик, съедать ли начало и конец строки + $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)) + { + /* это означает, что в случае отсутствия корректной инструкции + в найденной позиции надо пропустить ТОЛЬКО её начало и попробовать + найти что-нибудь снова! */ + $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 = ''; + } + } + + // дописываем начало и конец кода + if (!$fn) + { + $c = debug_backtrace(); + $c = $c[2]; + $fn = 'inline code in '.$c['class'].$c['type'].$c['function'].'() at '.$c['file'].':'.$c['line']; + } + $code = "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) + { + $this->error("Invalid expression in $kw: '$t'"); + return NULL; + } + $cf_if = array('elseif' => "} els", 'elsif' => "} els", '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, 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 == '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 ($m[3]) + { + $e = $this->compile_expression($m[3]); + if (!$e) + { + $this->error("Invalid expression in $kw: ($m[3])"); + return NULL; + } + } + $st->in[] = array('set', $m[1]); + $st->in_set++; + return "\$stack[] = \$t;\n\$t = '';\n"; + } + + // INCLUDE template.tpl + function compile_code_fragment_include($st, $kw, $t) + { + $t = addcslashes($t, '\\\''); + return "\$t.=\$this->parse('$t');\n"; + } + + static function array1($a) + { + return is_array($a) && !self::is_assoc($a) ? $a : 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+(.+))?/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; + else if (!is_null($t = $this->compile_expression($e))) + return "\$t.=$t;\n"; + return NULL; + } + + // компиляция подстановки переменной {...} это просто выражение + function compile_substitution($st, $e) + { + $e = $this->compile_expression($e); + if ($e) + 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('/^((\")(?:[^\"\\\\]+|\\\\.)*\"|\'(?:[^\'\\\\]+|\\\\.)*\'|-?[1-9]\d*(\.\d+)?|-?0\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; + } + // функция нескольких аргументов + else if (preg_match('/^([a-z_][a-z0-9_]*)\s*\((.*)$/is', $e, $m)) + { + $f = strtolower($m[1]); + if (!method_exists($this, "function_$f")) + { + $this->error("Unknown function: '$f'"); + return NULL; + } + $a = $m[2]; + $args = array(); + while ($e = $this->compile_expression($a, array(&$a))) + { + $args[] = $e; + if (preg_match('/^\s*\)/s', $a)) + break; + else if ($a == ($b = preg_replace('/^\s*,/s', '', $a))) + { + $this->error("Unexpected token: '$a' in $f() parameter list"); + return NULL; + } + $a = $b; + } + if ($a == ($b = preg_replace('/^\s*\)\s*/', '', $a))) + { + $this->error("Unexpected token: '$a' in the end of $f() parameter list"); + return NULL; + } + $a = $b; + if ($a) + { + if (!$after) + return NULL; + $after[0] = $a; + } + 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")) + { + $this->error("Unknown function: '$f' in '$e'"); + return NULL; + } + $a = $m[2]; + $arg = $this->compile_expression($a, array(&$a)); + if (!$arg) + { + $this->error("Invalid expression: ($e)"); + return NULL; + } + $a = ltrim($a); + if ($a) + { + if (!$after) + return NULL; + $after[0] = $a; + } + $f = "function_$f"; + return $this->$f($arg); + } + // переменная плюс 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; + } + + // генерация ссылки на переменную + 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; + } + + /* функции */ + + /* "или", "и", +, -, *, /, конкатенация */ + function function_or() { $a = func_get_args(); return $this->fmop('||', $a); } + 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))"; } + + /* нижний регистр */ + function function_lc($e) { return "strtolower($e)"; } + function function_lower($e) { return "strtolower($e)"; } + function function_lowercase($e) { return "strtolower($e)"; } + + /* верхний регистр */ + function function_uc($e) { return "strtoupper($e)"; } + function function_upper($e) { return "strtoupper($e)"; } + function function_uppercase($e) { return "strtoupper($e)"; } + + /* экранирование символов, специльных для регулярок */ + 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)"; + } + + /* разбиение строки по регулярному выражению */ + function function_split($re, $v, $limit=-1) + { + return "preg_split('#'.str_replace('#','\\\\#',$re).'#s', $v, $limit)"; + } + + /* экранирование кавычек */ + function function_quote($e) { return "addslashes($e)"; } + function function_addslashes($e) { return "addslashes($e)"; } + function function_q($e) { return "addslashes($e)"; } + + /* преобразование символов <>&'" в HTML-сущности < > & ' " */ + 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)"; } + + /* экранирование в стиле URI */ + function function_uriquote($e) { return "urlencode($e)"; } + function function_uri_escape($e) { return "urlencode($e)"; } + function function_urlencode($e) { return "urlencode($e)"; } + + /* удаление всех, заданных или "небезопасных" HTML-тегов */ + function function_strip($e, $t) { return "strip_tags($e".($t?",$t":"").")"; } + function function_t($e, $t) { return "strip_tags($e".($t?",$t":"").")"; } + function function_strip_unsafe($e) { return "strip_tags($e, self::$safe_tags)"; } + function function_h($e) { return "strip_tags($e, self::$safe_tags)"; } + + /* объединение всех скаляров и всех элементов аргументов-массивов */ + 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; + } + + // создание массива + 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) . ")"; } + + // получить элемент хеша/массива по неконстантному ключу (например 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]"; } + + /* 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); + } + + // подмассив по кратности номеров элементов + // 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; } -} -?> + // 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('/(?