| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385 | <?phpclass LtTemplateView{	public $layout;	public $layoutDir;	public $template;	public $templateDir;	public $compiledDir;	public $autoCompile; // bool	public $component; // bool	private $tpl_include_files;	public function __construct()	{		/**		 * 自动编译通过对比文件修改时间确定是否编译,		 * 当禁止自动编译时, 需要手工删除编译后的文件来重新编译.		 * 		 * 支持component include自动编译		 */		$this->autoCompile = true;		$this->component = false;	}	public function render()	{		if (empty($this->compiledDir))		{			$this->compiledDir = dirname($this->templateDir) . "/viewTpl/";		}		if (!empty($this->layout))		{			include $this->template(true);		}		else if ($this->component)		{			return; // 模板内使用{component module action}合并文件		}		else		{			include $this->template();		}	}	/**	 * 返回编译后的模板路径, 如果不存在则编译生成并返回路径. 	 * 如果文件存在且允许自动编译, 则对比模板文件和编译后的文件修改时间 	 * 当修改模板后自支重新编译	 * 	 * @param bool $islayout 是否使用布局	 * @return string 返回编译后的模板路径	 */	public function template($islayout = false)	{		$this->layoutDir = rtrim($this->layoutDir, '\\/') . '/';		$this->compiledDir = rtrim($this->compiledDir, '\\/') . '/';		$this->templateDir = rtrim($this->templateDir, '\\/') . '/';		if ($islayout)		{			$tplfile = $this->layoutDir . $this->layout . '.php';			$objfile = $this->compiledDir . 'layout/' . $this->layout . '@' . $this->template . '.php';		}		else		{			$tplfile = $this->templateDir . $this->template . '.php';			$objfile = $this->compiledDir . $this->template . '.php';		}		if (is_file($objfile))		{			if ($this->autoCompile)			{				$iscompile = true;				$tpl_include_files = include($objfile);				$last_modified_time = array();				foreach($tpl_include_files as $f)				{					$last_modified_time[] = filemtime($f);				}				if (filemtime($objfile) == max($last_modified_time))				{					$iscompile = false;				}			}			else			{				$iscompile = false;			}		}		else		{ 			// 目标文件不存在,编译模板			$iscompile = true;		}		if ($iscompile)		{			$this->tpl_include_files[] = $objfile;			$this->tpl_include_files[] = $tplfile;			$dir = pathinfo($objfile, PATHINFO_DIRNAME);			if (!is_dir($dir))			{				if (!mkdir($dir, 0777, true))				{					trigger_error("Can not create $dir");				}			}			$str = file_get_contents($tplfile);			if (!$str)			{				trigger_error('Template file Not found or have no access!', E_USER_ERROR);			}			$str = $this->parse($str);			if ($this->autoCompile)			{				$prefix = "<?php\r\nif(isset(\$iscompile)&&true==\$iscompile)\r\nreturn " . var_export(array_unique($this->tpl_include_files), true) . ";?>";				$prefix = preg_replace("/([\r\n])+/", "\r\n", $prefix);				$postfix = "\r\n<!--Template compilation time : " . date('Y-m-d H:i:s') . "-->\r\n";			}			else			{				$prefix = '';				$postfix = '';			}			$str = $prefix . $str . $postfix;			if (!file_put_contents($objfile, $str))			{				if (file_put_contents($objfile . '.tmp', $str))				{					copy($objfile . '.tmp', $objfile); // win下不能重命名已经存在的文件					unlink($objfile . '.tmp');				}			}			@chmod($objfile,0777);		}		return $objfile;	}	/**	 * 解析{}内字符串,替换php代码	 * 	 * @param string $str 	 * @return string 	 */	protected function parse($str)	{		$str = $this->removeComments($str);		$str = $this->parseIncludeComponent($str); 		// 回车 换行		$str = str_replace("{CR}", "<?php echo \"\\r\";?>", $str);		$str = str_replace("{LF}", "<?php echo \"\\n\";?>", $str); 		// if else elseif		$str = preg_replace("/\{if\s+(.+?)\}/", "<?php if(\\1) { ?>", $str);		$str = preg_replace("/\{else\}/", "<?php } else { ?>", $str);		$str = preg_replace("/\{elseif\s+(.+?)\}/", "<?php } elseif (\\1) { ?>", $str);		$str = preg_replace("/\{\/if\}/", "<?php } ?>", $str); 		// loop		$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/e", "\$this->addquote('<?php if(isset(\\1) && is_array(\\1)) foreach(\\1 as \\2) { ?>')", $str);		$str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\s+(\S+)\}/e", "\$this->addquote('<?php if(isset(\\1) && is_array(\\1)) foreach(\\1 as \\2=>\\3) { ?>')", $str);		$str = preg_replace("/\{\/loop\}/", "<?php } ?>", $str); 		// url生成		$str = preg_replace("/\{url\(([^}]+)\)\}/", "<?php echo LtObjectUtil::singleton('LtUrl')->generate(\\1);?>", $str); 		// 函数		$str = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\s*\(([^{}]*)\))\}/", "<?php echo \\1;?>", $str);		$str = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "<?php echo \$\\1;?>", $str); 		// 变量		/**		 * 放弃支持$name.name.name		 * $str = preg_replace("/\{(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\.([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1['\\2'];?>", $str);		 */		// 其它变量		$str = preg_replace("/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str);		$str = preg_replace("/\{(\\$[a-zA-Z0-9_\.\[\]\'\"\$\x7f-\xff]+)\}/e", "\$this->addquote('<?php echo \\1;?>')", $str); 		// 类->属性  类->方法		$str = preg_replace("/\{(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff][+\-\>\$\'\"\,\[\]\(\)a-zA-Z0-9_\x7f-\xff]+)\}/es", "\$this->addquote('<?php echo \\1;?>')", $str); 		// 常量		$str = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str); 		// 静态变量		$str = preg_replace("/\{([a-zA-Z0-9_]*::?\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str);		$str = preg_replace("/\{([a-zA-Z0-9_]*::?\\\$[a-zA-Z0-9_\.\[\]\'\"\$\x7f-\xff]+)\}/e", "\$this->addquote('<?php echo \\1;?>')", $str); 		// 合并相邻php标记		$str = preg_replace("/\?\>\s*\<\?php[\r\n\t ]*/", "", $str);		/**		 * 删除空行		 * Dos和windows采用回车+换行CR/LF表示下一行,		 * 而UNIX/Linux采用换行符LF表示下一行,		 * 苹果机(MAC OS系统)则采用回车符CR表示下一行.		 * CR用符号 '\r'表示, 十进制ASCII代码是13, 十六进制代码为0x0D;		 * LF使用'\n'符号表示, ASCII代码是10, 十六制为0x0A.		 * 所以Windows平台上换行在文本文件中是使用 0d 0a 两个字节表示, 		 * 而UNIX和苹果平台上换行则是使用0a或0d一个字节表示.		 * 		 * 这里统一替换成windows平台回车换行, 第二参数考虑 \\1 保持原有		 */		$str = preg_replace("/([\r\n])+/", "\r\n", $str); 		// 删除第一行		$str = preg_replace("/^[\r\n]+/", "", $str); 		// write		$str = trim($str);		return $str;	}	/**	 * 变量加上单引号 	 * 如果是数字就不加单引号, 如果已经加上单引号或者双引号保持不变	 */	protected function addquote($var)	{		preg_match_all("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", $var, $vars);		foreach($vars[1] as $k => $v)		{			if (is_numeric($v))			{				$var = str_replace($vars[0][$k], "[$v]", $var);			}			else			{				$var = str_replace($vars[0][$k], "['$v']", $var);			}		}		return str_replace("\\\"", "\"", $var);	}	/**	 * 模板中第一行可以写exit函数防止浏览	 * 删除行首尾空白, html javascript css注释	 */	protected function removeComments($str, $clear = false)	{		$str = str_replace(array('<?php exit?>', '<?php exit;?>'), array('', ''), $str); 		// 删除行首尾空白		$str = preg_replace("/([\r\n]+)[\t ]+/s", "\\1", $str);		$str = preg_replace("/[\t ]+([\r\n]+)/s", "\\1", $str); 		// 删除 {} 前后的 html 注释 <!--  -->		$str = preg_replace("/\<\!\-\-\s*\{(.+?)\}\s*\-\-\>/s", "{\\1}", $str);		$str = preg_replace("/\<\!\-\-\s*\-\-\>/s", "", $str); 		// 删除 html注释 存在 < { 就不删除		$str = preg_replace("/\<\!\-\-\s*[^\<\{]*\s*\-\-\>/s", "", $str);		if ($clear)		{			$str = $this->clear($str);		}		return $str;	}	/**	 * 清除一部分 style script内的注释	 * 多行注释内部存在 / 字符就不会清除	 */	protected function clear($str)	{		preg_match_all("|<script[^>]*>(.*)</script>|Usi", $str, $tvar);		foreach($tvar[0] as $k => $v)		{ 			// 删除单行注释			$v = preg_replace("/\/\/\s*[a-zA-Z0-9_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/", "", $v); 			// 删除多行注释			$v = preg_replace("/\/\*[^\/]*\*\//s", "", $v);			$str = str_replace($tvar[0][$k], $v, $str);		}		preg_match_all("|<style[^>]*>(.*)</style>|Usi", $str, $tvar);		foreach($tvar[0] as $k => $v)		{ 			// 删除多行注释			$v = preg_replace("/\/\*[^\/]*\*\//s", "", $v);			$str = str_replace($tvar[0][$k], $v, $str);		}		return $str;	}	/**	 * 	 * @todo 注意相互引用的模板嵌套会导致死循环	 */	protected function parseIncludeComponent($str)	{		$count_include_component = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);		$count_include_component += preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);		unset($tvar);		while ($count_include_component > 0)		{			$str = $this->parseInclude($str);			$str = $this->parseComponent($str);			$count_include_component = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);			$count_include_component += preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);			unset($tvar);		}		$str = $this->removeComments($str);		return $str;	}	/**	 * 解析多个{include path/file}合并成一个文件	 * 	 * @example {include 'debug_info'}	 * {include 'debug_info.php'}	 * {include "debug_info"}	 * {include "debug_info.php"}	 * {include $this->templateDir . $this->template}	 */	private function parseInclude($str)	{		$countSubTpl = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);		while ($countSubTpl > 0)		{			foreach($tvar[1] as $k => $subfile)			{				eval("\$subfile = $subfile;");				if (is_file($subfile))				{					$findfile = $subfile;				}				else if (is_file($subfile . '.php'))				{					$findfile = $subfile . '.php';				}				else if (is_file($this->templateDir . $subfile))				{					$findfile = $this->templateDir . $subfile;				}				else if (is_file($this->templateDir . $subfile . '.php'))				{					$findfile = $this->templateDir . $subfile . '.php';				}				else				{					$findfile = '';				} 								if (!empty($findfile))				{					$subTpl = file_get_contents($findfile);					$this->tpl_include_files[] = $findfile;				}				else				{ 					// 找不到文件					$subTpl = 'SubTemplate not found:' . $subfile;				}				$str = str_replace($tvar[0][$k], $subTpl, $str);			}			$countSubTpl = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);		}		return $str;	}	/**	 * 解析多个{component module action}合并成一个文件	 */	private function parseComponent($str)	{		$countCom = preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);		while ($countCom > 0)		{			$i = 0;			while ($i < $countCom)			{				$comfile = $this->templateDir . "component/" . $tvar[1][$i] . '-' . $tvar[2][$i] . '.php';				if (is_file($comfile))				{					$subTpl = file_get_contents($comfile);					$this->tpl_include_files[] = $comfile;				}				else				{					$subTpl = 'SubTemplate not found:' . $comfile;				}////////////////////////////////////////////////////////////////////////////$module = $tvar[1][$i];$action = $tvar[2][$i];$subTpl = "<?php\$dispatcher = LtObjectUtil::singleton('LtDispatcher');\$dispatcher->dispatchComponent('$module', '$action', \$this->context);\$comdata = \$dispatcher->data;unset(\$dispatcher);?>" . $subTpl;////////////////////////////////////////////////////////////////////////////				$str = str_replace($tvar[0][$i], $subTpl, $str);				$i++;			}			$countCom = preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);		}		return $str;	}}
 |