<?php
final class View {
	private $rootDir; //当前项目所在目录（物理路径）
	private $styleDir;
	private $dir; //当前项目相对于域名的目录路径,例如 http://www.jxcms.com/app/,设置成/app/
	private $sourceDir = array();
	private static $getInitial;

	private function __construct() {
	}
	private function set($styleDir, $rootDir, $dir) {
		$this -> rootDir = $rootDir;
		$this -> styleDir = $styleDir;
		$this -> dir = $dir;
	}

	public static function getInitial($styleDir, $rootDir, $dir = '/') {
		if (self :: $getInitial == null) self :: $getInitial = new View();
		self :: $getInitial -> set($styleDir, $rootDir, $dir);
		return self :: $getInitial;
	}

	public function template($tplFile, $objFile) {
		$objDir = dirname($objFile);
		$this -> checkDir($objDir); //模板目录检测
		$cacheDir = strstr(dirname($objDir), 'Templates');
		$this -> sourceDir = Cache :: read('SourceDir', $this -> styleDir, 0, $cacheDir);
		// 检测资源目录 start
		if (empty($this -> sourceDir)) {
			$tplDir = $this -> rootDir . 'templates/' . $this -> styleDir . '/';
			foreach(glob($tplDir . '*', GLOB_ONLYDIR) as $subDir) {
				$this -> sourceDir[] = substr(strrchr($subDir, '/'), 1);
			}
			Cache :: write('SourceDir', $this -> sourceDir, $this -> styleDir, $cacheDir);
		}
		// 检测资源目录 end
		// 解析模板 start
		$str = file_get_contents($tplFile);
		$this -> check($str); //模板安全性检测
		$str = $this -> parse($str);
		file_put_contents($objFile, $str);
		// 解析模板 end
		return true;
	}
	/**
	 * 循环创建目录函数
	 */
	private function checkDir($dir) {
		if (($dir != '' or $dir != '.') && !is_dir($dir)) {
			$rootlen = strlen($this -> rootDir);
			if (strcmp(substr($dir, 0, $rootlen), $this -> rootDir) == 0) {
				$dir = substr($dir, $rootlen);
				$curDir = $this -> rootDir;
			} else $curDir = '';
			$tmp = explode('/', $dir);
			$cnt = count($tmp);
			for($i = 0; $i < $cnt; $i++) {
				$curDir .= $tmp[$i] . '/';
				if (!is_dir($curDir))@mkdir($curDir, 0777);
			}
		}
	}
	/**
	 * 解析模板文件[parse template]
	 *
	 * @return string
	 */
	private function parse($str) {
		$str = preg_replace("/\<\?.*\?\>/sU", '', $str); //除去PHP程序
		$str = preg_replace("/\<\%.*\%\>/sU", '', $str); //除去ASP程序
		$str = preg_replace("/\<\!\-\-\{:(.+?)\}\-\-\>/s", '{:\\1}', $str); //去掉模板注释
		$str = preg_replace("/\}(\\\$[a-zA-Z0-9_\-\>\[\]\'\"\$,\.\x7f-\xff]+)\{:/s", "}{:\\1}{:", $str); //补充简化方式的模板代码
		$str = preg_replace("/\{:tpl\s+(.+?)\}/is", "<?php include App :: tpl(\"\\1\"); ?>\n", $str);
		$str = preg_replace("/\{:if\s+(.+?)\}/is", "<?php if(\\1) { ?>", $str);
		$str = preg_replace("/\{:else\}/i", "<?php } else { ?>", $str);
		$str = preg_replace("/\{:elseif\s+(.+?)\}/is", "<?php } elseif (\\1) { ?>", $str);
		$str = preg_replace("/\{:\/if\}/i", "<?php } ?>", $str);
		// 函数(方法)循环匹配
		$str = preg_replace("/\{:loop\s+([^\(\$]+\((?:(?!{:).)*?\))\s+(\S+?)\}/is", "<?php \$tplTmpData = \\1; if(is_array(\$tplTmpData) && !empty(\$tplTmpData)) foreach(\$tplTmpData as \\2) { ?>", $str);
		$str = preg_replace("/\{:loop\s+([^\(\$]+\((?:(?!{:).)*?\))\s+(\S+)\s+(\S+?)\}/i", "<?php \$tplTmpData = \\1; if(is_array(\$tplTmpData) && !empty(\$tplTmpData)) foreach(\$tplTmpData as \\2 => \\3) { ?>", $str);
		// 变量(常量)数组循环匹配
		$str = preg_replace("/\{:loop\s+(\S+)\s+(\S+?)\}/is", "<?php if(is_array(\\1) && !empty(\\1)) foreach(\\1 as \\2) { ?>", $str);
		$str = preg_replace("/\{:loop\s+(\S+)\s+(\S+)\s+(\S+?)\}/is", "<?php if(is_array(\\1) && !empty(\\1)) foreach(\\1 as \\2 => \\3) { ?>", $str);
		$str = preg_replace("/\{:loopelse\}/i", "<?php } else { ?>", $str);
		$str = preg_replace("/\{:\/loop\}/i", "<?php } ?>", $str);
		// 对象方式方法匹配
		$str = preg_replace("/\{:(\\\$[a-zA-Z0-9_\x7f-\xff]+\s*->\s*[a-zA-Z0-9_\x7f-\xff]+\(.*?\))\}/s", "<?php echo \\1; ?>", $str);
		// 作用域方式方法匹配
		$str = preg_replace("/\{:([a-zA-Z0-9_\x7f-\xff]+\s*::\s*[a-zA-Z0-9_\x7f-\xff]+\(.*?\))\}/s", "<?php echo \\1; ?>", $str);
		// 函数匹配
		$str = preg_replace("/\{:([a-zA-Z0-9_\x7f-\xff]+\(.*?\))\}/s", "<?php echo \\1; ?>", $str);
		// 常量匹配
		$str = preg_replace("/\{:([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/s", "<?php echo \\1; ?>", $str);
		// 变量匹配
		$str = preg_replace("/\{:(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\}/s", "<?php echo \\1; ?>", $str);
		// 对象属性匹配
		$str = preg_replace("/\{:(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+\s*->\s*[a-zA-Z0-9_\x7f-\xff]+)\}/s", "<?php echo \\1; ?>", $str);
		// 赋值匹配
		$str = preg_replace("/\{:(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff]+)\s*=\s*(.+?)\}/s", "<?php \\1 = \\2; ?>", $str);
		// 资源目录转换
		if (is_array($this -> sourceDir)) foreach($this -> sourceDir as $sourceDir) {
			$str = preg_replace("/([=|\(][\"|'| ]*)$sourceDir\//i", "\\1" . $this -> dir . 'templates/' . $this -> styleDir . '/' . $sourceDir . '/', $str);
		}
		// 多个../目录匹配
		$str = preg_replace("/([=|\(|:][\"|'| ]?)(\.\.\/){2,}/", "\\1" . $this -> dir, $str);
		// 单个../目录匹配
		$str = preg_replace("/([=|\(|:][\"|'| ]?)\.\.\//", "\\1" . $this -> dir . 'templates/', $str);
		// 模板转义
		$str = preg_replace("/\{#:(.+?)\}/s", "{:\\1}", $str);
		$str = "<?php defined('IN_JXCMS') or exit('Access Denied'); ?>" . $str;
		$str = preg_replace("/ \?\>[\n\r]*\<\?php /s", '', $str);
		return $str;
	}
	/**
	 * 检测模板中是否调用了非法函数[check resource file]
	 */
	private function check($str) {
		preg_match_all("/\{:(.+?)\}/s", $str, $tmpArray);
		$str = implode(' ', array_unique($tmpArray[1]));
		$Err = 0;
		preg_match_all("/([a-zA-Z0-9_\x7f-\xff]+)\(/", $str, $tmpArray);
		$tmpArray = array_unique($tmpArray[1]);
		$disableFunc = array('phpinfo', 'system', 'exec', 'shell_exec', 'passthru', 'proc_open', 'proc_close', ' proc_get_status', 'checkdnsrr', 'getmxrr', 'getservbyname', 'getservbyport', ' syslog', 'popen', 'show_source', 'highlight_file', 'dl', 'socket_listen', 'socket_create', 'socket_bind', 'socket_accept', ' socket_connect', ' stream_socket_server', ' stream_socket_accept', 'stream_socket_client', 'ftp_connect', ' ftp_login', 'ftp_pasv', 'ftp_get', 'sys_getloadavg', 'disk_total_space', ' disk_free_space', 'posix_ctermid', 'posix_get_last_error', 'posix_getcwd', ' posix_getegid', 'posix_geteuid', 'posix_getgid', ' posix_getgrgid', 'posix_getgrnam', 'posix_getgroups', 'posix_getlogin', 'posix_getpgid', 'posix_getpgrp', 'posix_getpid', ' posix_getppid', 'posix_getpwnam', 'posix_getpwuid', ' posix_getrlimit', ' posix_getsid', 'posix_getuid', 'posix_isatty', ' posix_kill', 'posix_mkfifo', 'posix_setegid', 'posix_seteuid', 'posix_setgid', ' posix_setpgid', 'posix_setsid', 'posix_setuid', 'posix_strerror', 'posix_times', 'posix_ttyname', 'posix_uname');
		if (is_array($tmpArray)) {
			foreach($tmpArray as $v) {
				if (in_array($v, $disableFunc)) {
					echo '<font color="red">' . $v . '</font><br />';
					$Err++;
				}
			}
			if ($Err) exit('Error, Function is not allowed!');
		}
		return true;
	}
}
