<?php
/* * *********************************************************
 * [cml] (C)2012 - 3000 cml http://linhecheng.com
 * @Author  linhecheng<linhechengbush@live.com>
 * @Date: 13-6-26 上午11:23
 * @version  1.0 
 * cml框架 视图
 * *********************************************************** */
defined('CML_PATH') || exit();

class CmlView
{
	public $args = array();//要传到模板的参数

	/**
	 * @var CmlView
	 */
	private static $_instance;

	/**
	 * @var array 模板参数信息
	 */
	private $_options = array();

	/**
	 * 调用CmlView单例
	 *
	 * @access static
	 *
	 * @return CmlView
	 */
	public static function getInstance()
	{
		if (!self::$_instance instanceof self) self::$_instance = new self();
		return self::$_instance;
	}

	/**
	 * 构造方法
	 *
	 * @return void
	 */
	private function __construct()
	{
		$this->_options = array(
			'templateDir' => 'templates' . DIR_SEP, //模板文件所在目录
			'cacheDir' => 'templates' . DIR_SEP . 'cache' . DIR_SEP, //缓存文件存放目录
			'autoUpdate' => true, //当模板文件改动时是否重新生成缓存
			'leftDeper' => $this->filterDeper(C('TMPL_LEFT_DEPER')),
			'rightDeper' => $this->filterDeper(C('TMPL_RIGHT_DEPER'))
		);
	}

	/**
	 * 转义定界符
	 *
	 * @param $deper
	 *
	 * @return str
	 */
	private function filterDeper($deper)
	{
		return preg_replace('#([><}{\-!])#', '\\\\${1}', $deper);
	}

	/**
	 * 设定模板参数信息
	 *
	 * @param  array $options 参数数组
	 *
	 * @return void
	 */
	public function setOptions(array $options)
	{
		foreach ($options as $name => $value) {
			$this->set($name, $value);
		}
	}

	/**
	 * 设定模板参数
	 *
	 * @param  string $name  参数名称
	 * @param  mixed  $value 参数值
	 *
	 * @return void
	 */
	public function set($name, $value)
	{
		$this->_options[$name] = $value;
	}

	/**
	 * 通过魔术方法设定模板参数
	 *
	 * @param  string $name  参数名称
	 * @param  mixed  $value 参数值
	 *
	 * @return void
	 */
	public function __set($name, $value)
	{
		$this->set($name, $value);
	}

	/**
	 * 获取模板文件缓存
	 *
	 * @param  string $file 模板文件名称
	 * @param int $type 缓存类型0当前操作的模板的缓存 1包含的模板的缓存
	 *
	 * @return string
	 */
	public function getFile($file, $type = 0)
	{
		if ($this->_options['autoUpdate']) {
			$tplFile = $this->_getTplFile($file, $type);
			$cacheFile = $this->_getCacheFile($file, $type);
			is_readable($tplFile) || throw_exception(L('_TEMPLATE_FILE_NOT_FOUND_', null, $tplFile));
			if(!is_file($cacheFile)) {
				$this->compile($tplFile, $cacheFile);
				return $cacheFile;
			}
			$compile = false;
			$tplMtime = filemtime($tplFile);
			$cacheMtime = filemtime($cacheFile);
			if($cacheMtime && $tplMtime) {
				($cacheMtime < $tplMtime) && $compile = true;
			} else {//获取mtime失败
				$compile = true;
			}
			$compile && $this->compile($tplFile, $cacheFile);
			return $cacheFile;
		}
	}

	/**
	 * 对模板文件进行缓存
	 *
	 * @param  string  $tplFile    模板文件名
	 * @param  string $cacheFile 模板缓存文件名
	 *
	 * @return void
	 */
	public function compile($tplFile, $cacheFile)
	{
		$leftDeper = $this->_options['leftDeper'];
		$rightDeper = $this->_options['rightDeper'];

		//取得模板内容
		$template = file_get_contents($tplFile);

		//替换 PHP 换行符
		$template = str_replace("{LF}", '<?php echo PHP_EOL;?>', $template);

		//要替换的标签
		$exp = array(
			'#\<\?(=|php)(.+?)\?\>#s',
			"#$leftDeper(\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*?\\[\S+?\\]\\[\S+?\\]|\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*?\\[\S+?\\]|\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*?);?$rightDeper#",
			"#$leftDeper(\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*?)\\.([a-zA-Z0-9_\x7f-\xff]+);?$rightDeper#",
			"#$leftDeper(\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*?)\\.([a-zA-Z0-9_\x7f-\xff]+)\\.([a-zA-Z0-9_\x7f-\xff]+);?$rightDeper#",
			'#'.$leftDeper.'template\s+([a-z0-9A-Z_\.\/]+);?'.$rightDeper.'[\n\r\t]*#',
			'#'.$leftDeper.'eval\s+(.+?)'.$rightDeper.'#s',
			'#'.$leftDeper.'echo\s+(.+?)'.$rightDeper.'#s',
			'#'.$leftDeper.'if\s+(.+?)'.$rightDeper.'#s',
			'#'.$leftDeper.'(elseif|else if)\s+(.+?)'.$rightDeper.'#s',
			'#'.$leftDeper.'else'.$rightDeper.'#',
			'#'.$leftDeper.'\/if'.$rightDeper.'#',
			'#'.$leftDeper.'(loop|foreach)\s+(\S+)\s+(\S+)'.$rightDeper.'#s',
			'#'.$leftDeper.'(loop|foreach)\s+(\S+)\s+(\S+)\s+(\S+)'.$rightDeper.'#s',
			'#'.$leftDeper.'\/(loop|foreach)'.$rightDeper.'#',
			'#'.$leftDeper.'hook\s+(\w+?)\s*'.$rightDeper.'#i',
			'#'.$leftDeper.' \\?\\>[\n\r]*\\<\\?'.$rightDeper.'#',
		);

		//替换后的内容
		$replace = array(
			'&lt;?${1}${2}?&gt',
			'<?php echo ${1};?>',
			'<?php echo ${1}[\'${2}\'];?>',
			'<?php echo ${1}[\'${2}\'][\'${3}\'];?>',
			'<?php include(CmlView::getInstance()->getFile(\'${1}\',1)); ?>',
			'<?php ${1};?>',
			'<?php echo ${1};?>',
			'<?php if(${1}) { ?>',
			'<?php } else if(${2}) { ?>',
			'<?php } else { ?>',
			'<?php } ?>',
			'<?php if(is_array(${2})) { foreach(${2} as ${3}) { ?>',
			'<?php if(is_array(${2})) { foreach(${2} as ${3} => ${4}) { ?>',
			'<?php } } ?>',
			'<?php CmlBase::hook("${1}");?>',
			'',
		);
		//执行替换
		$template = preg_replace($exp, $replace, $template);

		//加入全局常量替换
		$constant = array(
			'/__PUBLIC__/i' => '<?php echo __PUBLIC__;?>',
			'/__ROOT__/i' => '<?php echo __ROOT__;?>',
			'/__MODULE__/i'=>'<?php echo __MODULE__;?>',
			'/__CONTROLLER__/i' => '<?php echo __CONTROLLER__;?>',
			'/__ACTION__/i' =>  '<?php echo __ACTION__;?>',
			'/__SELF__/i' => '<?php echo __SELF__;?>',
			'/__APP__/i' => '<?php echo __APP__;?>',
			'/__TOKEN__/i'=>'<input type="hidden" name="CML_TOKEN" value="<?php echo __TOKEN__ ;?>" />',
			'/CML_APP_NAME/' => '<?php echo CML_APP_NAME;?>',
			'/CML_MODULE_NAME/' => '<?php echo CML_MODULE_NAME;?>',
			'/CML_CONTROLLER_NAME/' => '<?php echo CML_CONTROLLER_NAME;?>',
			'/CML_ACTION_NAME/' => '<?php echo CML_ACTION_NAME;?>'
		);
		$template = preg_replace(array_keys($constant), array_values($constant), $template);  

		if(!$GLOBALS['APP_DEBUG']) {
			$find = array('~>\s+<~','~>(\s+\n|\r)~');
			$replace = array('><','>');
			$template = preg_replace($find, $replace, $template);
			$template = str_replace('?><?php','',$template);
		}

		//添加 头信息

		$template = '<?php if (!class_exists(\'CmlView\')) die(\'Access Denied\');'
				  . "?>\r\n{$template}";

		//写入缓存文件
		$this->_makePath($cacheFile);
		file_put_contents($cacheFile, $template);
	}

	/**
	 * 获取模板文件名及路径
	 *
	 * @param  string $file 模板文件名称
	 * @param int $type 模板类型0当前操作的模板 1载入的模板
	 *
	 * @return string
	 */
	private function _getTplFile($file, $type)
	{
		$dir = $type == 1 ? ( PROJECT_PATH.DIR_SEP.APP_PATH.DIR_SEP.'Views'.DIR_SEP.(C('TMPL_THEME') != '' ? C('TMPL_THEME').DIR_SEP : '') ) :  $this->_options['templateDir'];
		return $dir.$file;
	}

	/**
	 * 获取模板缓存文件名及路径
	 *
	 * @param  string $file 模板文件名称
	 * @param int $type 模板类型0当前操作的模板 1载入的模板
	 *
	 * @return string
	 */
	private function _getCacheFile($file, $type)
	{
		$file = explode(C('TMPL_TEMPLATE_SUFFIX'), $file);
		(!isset($file[1]) && !isset($this->_options['isSystemTpl']) ) && throw_exception(L('_TEMPLATE_SUFFIX_ERROR_'));
		$file = $file[0].'.cache.php';
		$dir = $type == 1 ? RUNTIME_CACHE_PATH.DIR_SEP.'Views'.DIR_SEP.APP_PATH.DIR_SEP.(C('TMPL_THEME') != '' ? C('TMPL_THEME').DIR_SEP : '') : $dir = $this->_options['cacheDir'];
		return $dir.$file;
	}

	/**
	 * 根据指定的路径创建不存在的文件夹
	 *
	 * @param  string  $path 路径/文件夹名称
	 *
	 * @return string
	 */
	private function _makePath($path)
	{
		$path = dirname($path);
		if(!is_dir($path) && !mkdir($path, 0700, true)) throw_exception(L('_CREATE_DIR_ERROR_')."[{$path}]");
		return true;
	}

	/**
	 * 模板变量赋值
	 *
	 * @param mixed $name 要显示的模板变量
	 * @param mixed $value 变量的值
	 *
	 * @return void
	 */
	public static  function assign($name,$value = '')
	{
		self::getInstance()->args[$name] = $value;
	}

	/**
	 * 模板显示 调用内置的模板引擎显示方法，
	 *
	 * @param string $templateFile 指定要调用的模板文件 默认为空 由系统自动定位模板文件
	 * @param string $isSystem 是否为系统提示页面 默认为0， 1为是
	 *
	 * @return void
	 */
	public static function display($templateFile ='', $isSystem = 0)
	{
		if($isSystem != 1) {
			if(C('FORM_TOKEN')) {
				CmlSecure::seToken();
				define('__TOKEN__', CmlSecure::getToken());
			}

			$addModule = C('APP_MODULE') ? $_GET[C('VAR_MODULE')].DIR_SEP : '';
			$templateDir = PROJECT_PATH.DIR_SEP.APP_PATH.DIR_SEP.'Views'.DIR_SEP.(C('TMPL_THEME') != '' ? C('TMPL_THEME').DIR_SEP : '');
			if($templateFile == '') {
				$addDir = $addModule.$_GET[C('VAR_CONTROLLER')].DIR_SEP;
				$file = $_GET[C('VAR_ACTION')].C('TMPL_TEMPLATE_SUFFIX');
			} else {
				$tmp = explode('/', $templateFile);
				if(isset($tmp[2])) { //Home/User/index
					$addDir = $tmp[0].'/'.$tmp[1].'/';
					$file = $tmp[2].C('TMPL_TEMPLATE_SUFFIX');
				} else if(isset($tmp[1])) { // User/index
					$addDir = $addModule.$tmp[0].DIR_SEP;
					$file = $tmp[1].C('TMPL_TEMPLATE_SUFFIX');
				} else {
					$addDir = $addModule.$_GET[C('VAR_CONTROLLER')].DIR_SEP;
					$file = $tmp[0].C('TMPL_TEMPLATE_SUFFIX');
				}
			}
		} else { //系统提示
			$templateDir = dirname($templateFile).DIR_SEP;
			$file = basename($templateFile);
			$addDir = '';
		}

		$options = array(
			'templateDir' => $templateDir.$addDir, //指定模板文件存放目录
			'cacheDir' => RUNTIME_CACHE_PATH.DIR_SEP.'Views'.DIR_SEP.APP_PATH.DIR_SEP.(C('TMPL_THEME') != '' ? C('TMPL_THEME').DIR_SEP : '').$addDir, //指定缓存文件存放目录
			'autoUpdate' => true, //当模板修改时自动更新缓存
		);
		if($isSystem == 1) {
			$options['cacheDir'] = RUNTIME_CACHE_PATH.DIR_SEP.'Views'.DIR_SEP.'CML'.DIR_SEP;
			$options['leftDeper'] = '{';
			$options['rightDeper'] = '}';
			$options['isSystemTpl'] = true;
		}
		$CmlView = self::getInstance();
		$CmlView->setOptions($options);
		if(!empty($CmlView->args)) {
			extract($CmlView->args, EXTR_PREFIX_SAME, "xxx");
			$CmlView->args = array();
		}

		header('Cache-control: '.C('HTTP_CACHE_CONTROL'));  // 页面缓存控制

		require $CmlView->getFile($file);
		if($GLOBALS['APP_DEBUG']) {
			CmlDebug::stop();
		} else {
			$deBugLogData = dump('', 1);
			if(!empty($deBugLogData)) require CML_LIB_BASE_PATH.DIR_SEP.'CmlConsoleLog.php';
			CML_OB_START && ob_end_flush();
			exit();
		}
	}

}