<?php
//
// +----------------------------------------------------------------------+
// | PHP version > 4.3.4 & 5.x                                            |
// +----------------------------------------------------------------------+
// | Copyright (c) 2006-2007 toplee.com                                   |
// +----------------------------------------------------------------------+
// | 本文件包含PEAR的PHPLIB模板类扩展功能类定义                           |
// | 本类包含对PEAR的PHPLIB Template类继承,并实现cache、utf8等支持       |
// +----------------------------------------------------------------------+
//
// $Id: MyTemplate.class.php,v 1.0 2006/08/28
//
 
/**
 * 使用帮助:
 * 实例化类     $TPL = new MyTemplate(array $tpl_config)
 * 调用模板     $TPL->setFile($handle, $filename="")
 * 设置block    $TPL->setBlock($parent, $handle, $name="")
 * 解析变量     $TPL->setVar($varname, $value="", $append=false)
 * 解析页面     $TPL->parse($target, $handle, $append=false)
 * 输出页面     $TPL->p($varname) = echo $TPL->get($varname);
 * 检查cache    $TPL->cacheCheck()
 * 写入cache    $TPL->cache($data)
 * 输出cache    $TPL->pCache()
 *
 * 实例化模板类时配置选项$tpl_config格式和说明
 * $tpl_config = array(
 *  'debug'     => false,   //是否显示debug信息
 *  'root'      => 'tpl',   //模板存放路径,目前是相对路径,末尾不包含/
 *  'unknowns'  => 'remove',//模板中未解释的标记是否保留输出
 *  'cache'     => array(
 *      'cache'     => true,        //是否打开cache支持
 *      'root'      => 'tpl_cache/',//cache存放路径,目前支持相对路进,末尾包含/
 *      'hash'      => 3,           //cache缓存文件散列目录级数,比如 a/3/d 
 *      'life_time' => 10,          //cache文件默认失效时间,单位秒
 *      'file_ext'  => 'tpl.html',  //cache文件扩展名
 *      ),
 *  );
 */
 
require_once('HTML/Template/PHPLIB.php');
 
class MyTemplate extends Template_PHPLIB
{
    var $cache = array();   //和cache相关的配置信息,在tpl_config里面设置
 
    var $cache_dir = '';    //当前请求对应的cache存放目录,相对路径末尾包含 /
    var $cache_md5 = '';    //根据script_path得到的md5值,用于路径和cache文件名
    var $cache_file = '';   //包含完整路径的cache文件
    var $cache_ini = '';    //包含网站路径的cache配置文件
 
    var $root_path = '';    //从当前路径开始相对于网站根目录的路径信息
    var $script_path = '';  //当前请求页面的绝对路径,如/test.php?a=b,支持POST
    var $php_self = '';     //当前请求页面的绝对路径,如/test.php
 
    /**
     * $cache_parse = array(
     *          'md5'       => '',
     *          'tpl_count' => 2,
     *          0   => array(
     *                  'tpl' => 'aa.tpl',
     *                  'md5' => 'md51',
     *                  ),
     *          1   => array(
     *                  'tpl' => 'bb.tpl',
     *                  'md5' => 'md52',
     *                  ),
     *          );
     */
    var $cache_parse = array();
 
    /**
     * @Purpose:构造函数
     * @Param array $tpl_config
     * @Author Michael Lee <[email protected]>
     * @Return: NULL
     */
    function MyTemplate($tpl_config="")
    {
        $debug = isset($tpl_config['debug']) ? $tpl_config['debug'] : false;
        $root = isset($tpl_config['root']) ? $tpl_config['root'] : ".";
        $unknowns = isset($tpl_config['unknowns']) ? $tpl_config['unknowns'] : 'remove';
        $this->cache = $tpl_config['cache'];
 
        $this->Template_PHPLIB($root,$unknowns);
        $this->debug = $debug;
 
        //仅在打开了cache支持的配置情况下才启用和cache相关的功能
        if ($this->cache['cache']) {
            //初始化得到一些当前访问请求对应的路径信息
            $this->_getScriptPath();
            $this->_getRootpath();
            $this->_getPhpSelf();
 
            //取得当前请求对应的cache_dir和cache_md5
            $this->_getCacheFile();
        }
 
        if ($this->debug) {
            if ($this->cache['cache'])
                echo '<font color=red>Current Cache md5:'.$this->cache_md5.'<br></font>';
            else
                echo '<font color=red>Cache Disabled! Just use PHPLIB!<br></font>';
        }
    }
 
 
    /**
     * 把当前访问请求结果页面写入cache
     * 首先要得到当前页面根据php_self得到的md5_file值
     * 然后根据$this->file数组得到各个模板文件名和路径,分别得到他们的md5_file值
     * 把以上值写入到cache的ini配置文件,同时把cache页面内容写入cache文件
     *
     * @access public
     * @param string $data
     * @return true
     */
    function cache($data)
    {
        if (!$this->cache['cache']) return;
 
        $ini = "";
        $file = $this->php_self;
        //为了支持cli-cgi模式的php运行环境
        if (substr($file,0,1) == "/") $file = $this->root_path.$file;
        $md5 = md5_file($file);
        $ini .= "md5 = $md5rn";
 
        $count = count($this->file);
        $ini .= "tpl_count = $countrn";
 
        $i = 0;
        foreach ($this->file AS $k=>$v) {
            $ini .= "[$i]rn";
            $ini .= "tpl = "$v"rn";
            $ini .= "md5 = "".md5_file($v).""rn";
            $i++;
        }
        //echo $ini; exit;  //for debug
 
        $this->_mkdir2($this->cache_dir);
        if (!$this->_writeToFile($this->cache_ini,$ini)) return false;
        if (!$this->_writeToFile($this->cache_file,$data)) return false;
 
        return true;
    }
 
 
 
    /**
     * 检查当前请求相关的cache是否可用
     * 1.$this->cache['cache']是否为true 2.是否存在 3.是否过期
     * 4.是否当前php程序文件有改变 5.是否相关的tpl文件有变动
     *
     * @access public
     * @return bool true/false
     */
    function checkCache()
    {
        //1.判断当前是否允许cache
        if (!$this->cache['cache']) {
            if (DEBUG) echo 'cache disabled';
            return FALSE;
        }
 
        //2.判断当前cache_file是否存在
        if (!file_exists($this->cache_file) || !file_exists($this->cache_ini)) {
            if (DEBUG) echo 'cache not exists';
            return FALSE;
        }
 
        //3.判断是否过期
        $now = time();
        $orig = filemtime($this->cache_file);
        $chk = $now-$orig;
        if ($chk > $this->cache['life_time']) {
            if (DEBUG) echo 'cache expired';
            return FALSE;
        }
 
 
        //解析cache的ini配置文件
        $this->_parseCacheIni();
 
 
        //判断当前php文件是否有改变
        //取得当前php文件的md5_file值
        $php_self = $this->root_path.substr($this->php_self,1);
        $md5_script = @md5_file($php_self);
        if ($md5_script != $this->cache_parse['md5']) {
            if ($this->debug) echo 'md5 bad';
            return false;
        }
 
 
        //判断相关的tpl文件是否有变化,方法和前面类似
        for($i=0;$i<$this->cache_parse['tpl_count'];$i++) {
            $tpl_file = $this->cache_parse[$i]['tpl'];
            $tpl_md5 = $this->cache_parse[$i]['md5'];
            $tpl_md5_cur = @md5_file($tpl_file);
            if ($tpl_md5_cur != $tpl_md5) {
                if ($this->debug) echo 'tpl md5 bad: '.$tpl_file.$tpl_md5_cur.'#'.$tpl_md5;
                return false;
            }
        }
 
        return true;
    }
 
 
    /**
     * 取得cache,用于页面输出
     * 去掉cache前面的配置行
     *
     * @access public
     * @return true
     */
    function pCache()
    {
        @readfile($this->cache_file);
        return true;
    }
 
    /**
     * 删除cache
     *
     * @access public
     * @return NULL
     */
    function rmCache()
    {
        @unlink($this->cache_file);
        @unlink($this->cache_ini);
        return;
    }
 
 
    /**
     * 得到cache文件名,包括路径信息,如./cache/ab/cd/ef/abcdef.....tpl.html
     *
     * @access private
     */
    function _getCacheFile()
    {
        if (empty($this->script_path)) $this->_getScriptPath();
        $md5_string = md5($this->script_path);
        $path = "";
        for ($i=0;$i<$this->cache['hash'];$i++)
            $path .= substr($md5_string,$i,1).'/';
 
        $path = $this->cache['root'].$path;
        $this->cache_dir    = $path;
        $this->cache_md5    = $md5_string;
        $this->cache_file   = $path.$md5_string.'.'.$this->cache['file_ext'];
        $this->cache_ini    = $path.$md5_string.'.ini';
    }
 
 
    /**
     * 解析cache文件的头四行,得到下列信息
     * $this->cache_file_parse['php_self_md5'] 当前php程序的原始md5值
     * $this->cache_file_parse['tpls_count']和['tpls']
     *
     * @access private
     */
    function _parseCacheIni()
    {
        $this->cache_parse = @parse_ini_file($this->cache_ini,true);
        return true;
    }
 
 
    /**
     * 把内容写入指定文件
     *
     * @access private
     */
    function _writeToFile($file,$content,$mode='w')
    {
        $oldmask = umask(0);
        $fp = fopen($file, $mode);
        if (!$fp) return false;
        @fwrite($fp,$content);
        @fclose($fp);
        @umask($oldmask);
        return true;
    }
 
    /**
     * 创建多级目录
     * 
     * @access private
     */
    function _mkdir2($dir)
    {
        $dir = @preg_replace("/\/","/",$dir);
        $dir = @preg_replace("//{2,}/","/",$dir);
        $dir = @explode('/',$dir);
    
        $path = "";
        for ($i=0;$i<count($dir);$i++) {
            $path .= $dir[$i].'/';
            if (!is_dir($path)) @mkdir($path,0700);
        }
        return true;
    }
 
 
    /**
     * 取得当前请求的完整路径,用于标示当前cache的唯一性
     * 目前使用public_setting.inc.php里面取得的SCRIPT_PATH值
     * 注意:如果web请求通过POST方式进行的提交,那么可能造成结果页面cache有问题
     * 处理POST的方法,就是在SCRIPT_PATH的基础上,判断$_POST变量是否为空
     * 如果不为空,则进行serialize()处理,保证post的唯一性
     *
     * @access private
     */
    function _getScriptPath()
    {
        global $_SERVER,$_POST,$_ENV;
        if ($_ENV['REQUEST_URI'] OR $_SERVER['REQUEST_URI']) {
        $sp = $_SERVER['REQUEST_URI'] ? $_SERVER['REQUEST_URI'] : $_ENV['REQUEST_URI'];
        } else {
            if ($_ENV['PATH_INFO'] OR $_SERVER['PATH_INFO']) {
                $sp = $_SERVER['PATH_INFO'] ? $_SERVER['PATH_INFO']: $_ENV['PATH_INFO'];
            } else if ($_ENV['REDIRECT_URL'] OR $_SERVER['REDIRECT_URL']) {
                $sp = $_SERVER['REDIRECT_URL'] ? $_SERVER['REDIRECT_URL']: $_ENV['REDIRECT_URL'];
            } else {
                $sp = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_ENV['PHP_SELF'];
            }
        
            if ($_ENV['QUERY_STRING'] OR $_SERVER['QUERY_STRING']) {
                $sp .= '?' . ($_SERVER['QUERY_STRING'] ? $_SERVER['QUERY_STRING'] : $_ENV['QUERY_STRING']);
            }
        }
    
        $sp = preg_replace("//{2,}/","/",$sp);
        $find = array('"', '<', '>');
        $replace = array('&quot;', '&lt;', '&gt;');
        $sp = str_replace($find, $replace, $sp);
        $sp = xss_clean($sp);
        
        if ($_SERVER['REQUEST_METHOD'] == 'POST') {
            $p = @serialize($_POST);
            $sp .= "?$p";
        }
        return $sp;
    }
 
    /**
     * 取得当前请求的脚本文件绝对路径
     * 用于得到当前文件的md5值,判断当前文件是否被修改过
     * 当前使用public_setting.inc.php里面得到的PHP_SELF值
     *
     * @access private
     */
    function _getPhpSelf()
    {
        global $_ENV,$_SERVER;
        
        if ($_ENV['PHP_SELF'] OR $_SERVER['PHP_SELF'])
            $p = $_ENV['PHP_SELF'] ? $_ENV['PHP_SELF'] : $_SERVER['PHP_SELF'];
        elseif ($_ENV['SCRIPT_NAME'] OR $_SERVER['SCRIPT_NAME'])
            $p = $_ENV['SCRIPT_NAME'] ? $_ENV['SCRIPT_NAME'] : $_SERVER['SCRIPT_NAME'];
        else
            $p = preg_replace('#(?.*)#', '', $this->script_path);
            
        return $p;
    }
 
    /**
     * 取得当前请求的脚本文件相对于根目录的相对路径,如 ../../
     * 当前使用public_setting.inc.php里面得到的ROOT_PATH值
     *
     * @access private
     */
    function _getRootPath()
    {
        $a = @explode("/",$this->script_path);
        $c = @count($a);
        $p = "";
        for ($i=0; $i < $c-2; $i++) $p = "../".$p;
    
        if ($p == "") $p="./";
        
        return $p;
    }
}
?>