<?php
/**
 * 模板类
 * @copyright reginx.com
 * $Id: tpl.class.php 107 2013-02-21 15:42:46Z reginx $
 */
class tpl{
    /**
     * 值
     *
     * @var unknown_type
     */
    private $_val = array();

    /**
     * 配置
     *
     * @var unknown_type
     */
    private $_conf = array();

    /**
     * 是否开启输出缓冲
     *
     * @var unknown_type
     */
    private $_ob = true;

    /**
     * 当前模块
     *
     * @var unknown_type
     */
    private $_curm = '';

    /**
     * 架构函数
     *
     * @param unknown_type $param
     */
    private function __construct($app){
        $this->_conf = $app;
        $this->_conf['out_dir'] = $app['temp_path'] . $app['app_dir'];
        $this->_conf['tpl_dir'] = $app['app_tpl_path'] . $app['tpl'] . '/';
        if(!is_dir($this->_conf['out_dir'])){
            core::makedir($this->_conf['out_dir']);
        }
    }

    /**
     * 设置是否开启缓冲
     *
     * @param unknown_type $bool
     */
    public function setob($bool){
        $this->_ob = (bool)$bool;
    }

    /**
     * 获取模板对象
     *
     * @param String $out_dir
     * @param mixed $param
     * @return Object
     */
    static function getTpl($conf, $param = array()){
        static $tplobj = NULL;
        if(empty($tplobj)){
            $tplobj = new tpl($conf, $param);
        }
        return $tplobj;
    }

    /**
     * 模板赋值
     *
     * @param unknown_type $k
     * @param unknown_type $v
     */
    function assign($k, $v){
        $this->_val[$k] = $v;
    }

    /**
     * 输出模板
     *
     * @param unknown_type $tplfile
     * @param unknown_type $doctype
     * @param unknown_type $cdbuf 是否清除缓存区内容
     */
    function display($tplfile, $doctype = 'text/html' , $cdbuf = true){
        echo ($this->fetch($tplfile, $this->_conf['charset'], $doctype, true));
        exit();
    }

    /**
     * 解析引用的模板文件DIR
     *
     * @example user@default:index.html
     *          App @ style : tpl file
     * @param unknown_type $tplfile
     */
    private function _parsetpldir($tplfile){
        $ret = array($tplfile , $this->_conf['tpl_dir'] . $tplfile);
        if(preg_match('/^(.*?)@(.*?)\:(.*?)$/', $tplfile)){
            $tmp = array();
            preg_match('/^(.*?)@(.*?)\:(.*?)$/', $tplfile, $tmp);
            if($tmp[1] == 'plugin'){
                $this->_conf['tpl_dir'] = DATA_PATH . 'plugin/' .  ($tmp[2] == '' ? 'default/' : ($tmp[2] . '/')) . 'template/';
            }else{
                $this->_conf['tpl_dir'] = $this->_conf['base_path'] . ($tmp[1] == '' ? '' : ($tmp[1] . '/')) . 'template/' . ($tmp[2] == '' ? 'default/' : ($tmp[2] . '/'));
            }
            $ret[0] = $tmp[3];
            $ret[1] = $this->_conf['tpl_dir'] . $tmp[3];
        }
        return $ret;
    }

    /**
     * 设置模板文件目录
     *
     * @param unknown_type $dir
     */
    public function settpldir($dir){
        $dir = is_dir($dir) ? $dir : ($GLOBALS['_APP']['app_tpl_path'] . $dir);
        if(is_dir($dir)){
            $this->_conf['tpl_dir'] = $dir;
        }else{
            core::error(core::L('no-tpl-dir', $dir));
        }
    }

    /**
     * 获取页面html内容
     *
     * @param unknown_type $tplFile
     * @param unknown_type $charset
     * @param unknown_type $contentType
     * @param unknown_type $display
     * @return unknown
     */
    public function &fetch($sfile, $charset = 'utf-8', $contentType = 'text/html', $display = false , $cdbuf = true){
        list($sfile , $tplfile) = $this->_parsetpldir($sfile);
        $tmpfile = $this->_conf['out_dir'] . $this->_getcachetpl($sfile);
        !is_dir(dirname($tmpfile)) && core::makeDir(dirname($tmpfile));
        !file_exists($tplfile) && core::error(core::L('not-found', str_replace(BASE_PATH, '', $tplfile)), 'Tpl::fetch', 1);
        if(empty($charset)){
            $charset = $GLOBALS['_APP']['charset'];
        }
        // 非Debug模式下. 清空一切非模板输出
        !IS_DEBUG && $cdbuf && ob_end_clean();

        // 默认开启缓冲
        if($this->_ob){
            ob_start();
            ob_implicit_flush(0);
        }

        if($display){
            header('X-Powered-By: REGINX v' . REGINX_VERSION);
            header("Content-Type: " . $contentType . "; charset=" . $charset);
        }

        extract($this->_val);
        !$this->_available($tmpfile, $tplfile) && $this->_parsetpl($tmpfile, $tplfile);
        include ($tmpfile);

        if($this->_ob){
            $content = ob_get_clean();
            return $content;
        }
    }

    /**
     * 编译模板
     */
    private function _parsetpl($tempfile, $tplfile){
        $rule = array(
                array(
                        0 => '/\{\$([^\|]*?)\}/i',
                        1 => '/\{\s?foreach\s+\$?(.*?)\s+\$?(.*?)\s+\$?(.*?)\s*\}/i',
                        2 => '/\{\/foreach\s*\}/i',
                        3 => '/\{\s?if\s*?(.+?)\s*\}/i',
                        4 => '/\{\s?else\s*if(.*?)\s?\}/i',
                        5 => '/\{\s?else\s?\s*\}/i',
                        6 => '/\{\/if\s*?\}\s*/i',
                        7 => '/\{\s?__([\w\-_]*?)__\s?\}/ies',
                        8 => '/\{\s?for\s+\$?(.*?)\s+in\s+range\(\s*([^\s\,]*?)\s*,\s*(.*?)\)\s*\}/i',
                        9 => '/\{\s?for\s+\$?(.*?)\s+in\s+range\(\s*([^\s\,]*?)\s*,\s*([^\s\,]*?)\s*,\s*([^\s]*?)\s*\)\s*\}/i',
                        10 => '/\{\/for\s*\}/i',
                        11 => '/\{lang\:(.*?)\}/ies',
                        12 => '/\{\s?:(.*?)\s*?\}/i',
                        13 => '/\{\s?\$([^\|]+?)\|(\d+)\s*?\}/i',
                        14 => '/\{\s?:(.*?)\s*?\;?\s*?\}/i',
                        15 => '/\{\s*url\:\(?(.*?)\)?\}/i',
                        16 => '/\{\s?\@TIME(.*?)\s*?\}/i',
                        17 => '/\{\s?\$([^\|]+?)\|html\s*?\}/i',
                        18 => '/\{\s?\$([^\|]+?)\|thumb\s*?\}/i',
                        19 => '/\{\s?_([\w\-_]+?)_\s?\}/is',
                        20 => '/\{\s?eval\s+(.*?)\s*\}/is',
                        21 => '/\{\s*hook\:\(?(.*?)\)?\}/i',
                        22 => '/\{\s*\$([^\|]+?)\|qhtml\s*?\}/i',
                        23 => '/\{block\s*\:\s*\$([^\s\}]*)(?:\s*,\s*)?([^\}]+?)?\}/is',
                        
                ),
                array(
                        0 => '<?php echo($\1);?>',
                        1 => '<?php $\2_index = 0;foreach((array)$\1 as $\2 => $\3):$\2_index++;?>',
                        2 => '<?php endforeach;?> ',
                        3 => '<?php if(\1):?>',
                        4 => '<?php elseif(\1):?>',
                        5 => '<?php else:?>',
                        6 => '<?php endif;?>',
                        7 => 'defined("\1") ? \1 : (isset($GLOBALS["_APP"][strtolower("\1")]) ? $GLOBALS["_APP"][strtolower("\1")] : \1)',
                        8 => '<?php for($\1=\2;$\1<\3;$\1++):?>',
                        9 => '<?php for($\1=\2;(\3>\2 ? ($\1<\3) : ($\1>\3));$\1+=\4):?>',
                        10 => '<?php endfor;?> ',
                        11 => "\$this->lang('\\1')",
                        12 => '<?php echo(\1);?>',
                        13 => '<?php echo(mb_substr(filter::text($\1) , 0 , \2 , "utf-8"));?>',
                        14 => '<?php echo(\1);?>',
                        15 => "<?php echo(core::url(\\1)); ?>",
                        16 => '<?php printf("%.3f",(microtime(TRUE) - $GLOBALS["_MISC"]["STIME"])\1); ?>',
                        17 => '<?php echo(htmlspecialchars_decode($\1 , ENT_QUOTES)); ?>',
                        18 => '<?php echo(core::thumburl($\1)); ?>',
                        19 => '<?php echo(isset($GLOBALS["_APP"][strtolower("\1")]) ? $GLOBALS["_APP"][strtolower("\1")] : strtolower("\1")); ?>',
                        20 => '<?php \1?>',
                        21 => "<?php plugin::runhook(\\1); ?>",
                        22 => '<?php echo(htmlspecialchars($\1 , ENT_QUOTES)); ?>',
                        23 => '<?php \$\1 = $GLOBALS[\'_BLOCK\']->get\1(\2); ?>',
                )
        );
        $html = file_get_contents($tplfile);
        $html = preg_replace('/\s*\<\!\-\-' . preg_quote($GLOBALS['_APP']['tpl_pre']) . '\s*(.+?)\s*' . preg_quote($GLOBALS['_APP']['tpl_suf']) . '\-\-\>\s*/i', '{\1}', $html);
        $this->_getinctpl($html);
        $html = preg_replace($rule[0], $rule[1], $html);
        $html = preg_replace('/(\s)+\<\?php(.*?)\?\>/i', "\\1<?php\\2?>", $html);
        $html = preg_replace('/\<\?php(.*?)\?\>(\s)+/i', "<?php\\1?>\\2", $html);
        $html = "<?php !defined('IN_REGINX') && exit('Access Denied'); unset(\$this);?>\r\n" . $html;
        file_put_contents($tempfile, $html , LOCK_EX);
        $html = NULL;
        unset($html);
    }

    /**
     * 处理 Include 语句
     *
     * 支持包含其他App模板文件
     * {include admin@default:top.html} 则包含admin应用下default样式下的top.html
     * {include @style1:top.html} 包含主应用下的style1目录下的top.html
     * {include 应用ID@样式名称:模板文件名称}
     *
     * @abstract
     *
     *
     * @param unknown_type $html
     */
    private function _getinctpl(&$html, $limit = 1){
        $inc = $tmp = array();
        preg_match_all('/\{\s*?include\s+(.*?)\s*?\}/i', $html, $inc);
        for($j = 0; $j < sizeof($inc[1]); $j++){
            $incfile = $this->_conf['tpl_dir'] . $inc[1][$j];
            if(preg_match("/^(.*?)@.*?$/i", $inc[1][$j])){
                preg_match('/^(.*?)@(.*?)\:(.*?)$/', $inc[1][$j], $tmp);
                $incfile = $GLOBALS['_APP']['base_path'] . 
                        ($tmp[1] == '' ? '' : ($tmp[1] . '/')) . 'template/' .
                                ($tmp[2] == '' ? ($GLOBALS['_APP']['tpl'] . '/') : ($tmp[2] . '/')) . $tmp[3];
            }
            !file_exists($incfile) && core::error(core::L('not-found', $incfile), 'Tpl::_getinctpl', 1);
            $s = str_replace('/', '\/', str_replace('\\', '/', $inc[1][$j]));
            $temp = file_get_contents($incfile);
            $temp = preg_replace('/\s*\<\!\-\-' . preg_quote($GLOBALS['_APP']['tpl_pre']) . '\s*(.+?)\s*' . preg_quote($GLOBALS['_APP']['tpl_suf']) . '\-\-\>\s*/i', '{\1}', $temp);
            $html = preg_replace('/\{\s*?include\s+' . $s . '\s*?\}/is', $temp, $html);
        }
        /**
         * 支持最多3层 *
         */
        if(preg_match('/\{\s*?include\s+(.*?)\s*?\}/i', $html) && $limit <= 3){
            $this->_getinctpl($html, $limit + 1);
        }
    }

    /**
     * 检查缓存模板文件是否可用
     *
     * @param unknown_type $tmpfile
     * @param unknown_type $sfile
     * @return unknown
     */
    private function _available($tmpfile, $sfile){
        $ret = true;
        if(!file_exists($tmpfile)){
            $ret = false;
        }else{
            if(IS_DEBUG){
                $ret = false;
            }elseif($GLOBALS['_APP']['tpl_ttl']){
                $ret = filemtime($tmpfile) > filemtime($sfile);
            }
        }
        return $ret;
    }

    /**
     * 获取缓存模板文件名称
     *
     * @param unknown_type $key
     * @return unknown
     */
    private function _getcachetpl($key){
        $mod = $this->_curm ? $this->_curm : $GLOBALS['_MOD'];
        $key = $GLOBALS['_APP']['app_name'] . '_' . $GLOBALS['_APP']['lang'] . '_' . $mod . '_' . $this->_conf['tpl'] . $key;
        return sprintf('%X', crc32($key)) . '.php';
    }

    /**
     * 设置当前输出模板所属mod
     *
     * @param unknown_type $mod
     */
    public function setcurm($mod = false){
        $this->_curm = $mod;
    }

    /**
     * 语言解析
     */
    public function lang($args){
        $keys = is_array($args) ? $args : explode(',', preg_replace('/[\s|\'|\"]*/e', '', $args));
        if(!empty($keys)){
            for($i = 0; $i < sizeof($keys); $i++){
                if(!empty($keys[$i]) && $keys[$i] != ''){
                    $keys[$i] = isset($GLOBALS['_LANG'][$keys[$i]]) ? $GLOBALS['_LANG'][$keys[$i]] : $keys[$i];
                }
            }
            $format = array_shift($keys);
            $flen = sizeof(preg_split('/\%\w/i', $format));
            while(sizeof($keys) < $flen){ // 使数组元素总数与占位符数目相对应,否则会报错
                $keys[] = '';
            }
            return vsprintf($format, $keys);
        }
        return empty($keys) ? '' : join('-', $keys);
    }
}
?>