Açıklama Yok
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TemplateView.php 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. <?php
  2. class LtTemplateView
  3. {
  4. public $layout;
  5. public $layoutDir;
  6. public $template;
  7. public $templateDir;
  8. public $compiledDir;
  9. public $autoCompile; // bool
  10. public $component; // bool
  11. private $tpl_include_files;
  12. public function __construct()
  13. {
  14. /**
  15. * 自动编译通过对比文件修改时间确定是否编译,
  16. * 当禁止自动编译时, 需要手工删除编译后的文件来重新编译.
  17. *
  18. * 支持component include自动编译
  19. */
  20. $this->autoCompile = true;
  21. $this->component = false;
  22. }
  23. public function render()
  24. {
  25. if (empty($this->compiledDir))
  26. {
  27. $this->compiledDir = dirname($this->templateDir) . "/viewTpl/";
  28. }
  29. if (!empty($this->layout))
  30. {
  31. include $this->template(true);
  32. }
  33. else if ($this->component)
  34. {
  35. return; // 模板内使用{component module action}合并文件
  36. }
  37. else
  38. {
  39. include $this->template();
  40. }
  41. }
  42. /**
  43. * 返回编译后的模板路径, 如果不存在则编译生成并返回路径.
  44. * 如果文件存在且允许自动编译, 则对比模板文件和编译后的文件修改时间
  45. * 当修改模板后自支重新编译
  46. *
  47. * @param bool $islayout 是否使用布局
  48. * @return string 返回编译后的模板路径
  49. */
  50. public function template($islayout = false)
  51. {
  52. $this->layoutDir = rtrim($this->layoutDir, '\\/') . '/';
  53. $this->compiledDir = rtrim($this->compiledDir, '\\/') . '/';
  54. $this->templateDir = rtrim($this->templateDir, '\\/') . '/';
  55. if ($islayout)
  56. {
  57. $tplfile = $this->layoutDir . $this->layout . '.php';
  58. $objfile = $this->compiledDir . 'layout/' . $this->layout . '@' . $this->template . '.php';
  59. }
  60. else
  61. {
  62. $tplfile = $this->templateDir . $this->template . '.php';
  63. $objfile = $this->compiledDir . $this->template . '.php';
  64. }
  65. if (is_file($objfile))
  66. {
  67. if ($this->autoCompile)
  68. {
  69. $iscompile = true;
  70. $tpl_include_files = include($objfile);
  71. $last_modified_time = array();
  72. foreach($tpl_include_files as $f)
  73. {
  74. $last_modified_time[] = filemtime($f);
  75. }
  76. if (filemtime($objfile) == max($last_modified_time))
  77. {
  78. $iscompile = false;
  79. }
  80. }
  81. else
  82. {
  83. $iscompile = false;
  84. }
  85. }
  86. else
  87. {
  88. // 目标文件不存在,编译模板
  89. $iscompile = true;
  90. }
  91. if ($iscompile)
  92. {
  93. $this->tpl_include_files[] = $objfile;
  94. $this->tpl_include_files[] = $tplfile;
  95. $dir = pathinfo($objfile, PATHINFO_DIRNAME);
  96. if (!is_dir($dir))
  97. {
  98. if (!mkdir($dir, 0777, true))
  99. {
  100. trigger_error("Can not create $dir");
  101. }
  102. }
  103. $str = file_get_contents($tplfile);
  104. if (!$str)
  105. {
  106. trigger_error('Template file Not found or have no access!', E_USER_ERROR);
  107. }
  108. $str = $this->parse($str);
  109. if ($this->autoCompile)
  110. {
  111. $prefix = "<?php\r\nif(isset(\$iscompile)&&true==\$iscompile)\r\nreturn " . var_export(array_unique($this->tpl_include_files), true) . ";?>";
  112. $prefix = preg_replace("/([\r\n])+/", "\r\n", $prefix);
  113. $postfix = "\r\n<!--Template compilation time : " . date('Y-m-d H:i:s') . "-->\r\n";
  114. }
  115. else
  116. {
  117. $prefix = '';
  118. $postfix = '';
  119. }
  120. $str = $prefix . $str . $postfix;
  121. if (!file_put_contents($objfile, $str))
  122. {
  123. if (file_put_contents($objfile . '.tmp', $str))
  124. {
  125. copy($objfile . '.tmp', $objfile); // win下不能重命名已经存在的文件
  126. unlink($objfile . '.tmp');
  127. }
  128. }
  129. @chmod($objfile,0777);
  130. }
  131. return $objfile;
  132. }
  133. /**
  134. * 解析{}内字符串,替换php代码
  135. *
  136. * @param string $str
  137. * @return string
  138. */
  139. protected function parse($str)
  140. {
  141. $str = $this->removeComments($str);
  142. $str = $this->parseIncludeComponent($str);
  143. return $str; // 疑似这里有漏洞,国家漏洞库反馈 by 小虎哥
  144. // 回车 换行
  145. $str = str_replace("{CR}", "<?php echo \"\\r\";?>", $str);
  146. $str = str_replace("{LF}", "<?php echo \"\\n\";?>", $str);
  147. // if else elseif
  148. $str = preg_replace("/\{if\s+(.+?)\}/", "<?php if(\\1) { ?>", $str);
  149. $str = preg_replace("/\{else\}/", "<?php } else { ?>", $str);
  150. $str = preg_replace("/\{elseif\s+(.+?)\}/", "<?php } elseif (\\1) { ?>", $str);
  151. $str = preg_replace("/\{\/if\}/", "<?php } ?>", $str);
  152. // loop
  153. $str = preg_replace("/\{loop\s+(\S+)\s+(\S+)\}/e", "\$this->addquote('<?php if(isset(\\1) && is_array(\\1)) foreach(\\1 as \\2) { ?>')", $str);
  154. $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);
  155. $str = preg_replace("/\{\/loop\}/", "<?php } ?>", $str);
  156. // url生成
  157. $str = preg_replace("/\{url\(([^}]+)\)\}/", "<?php echo LtObjectUtil::singleton('LtUrl')->generate(\\1);?>", $str);
  158. // 函数
  159. $str = preg_replace("/\{([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\s*\(([^{}]*)\))\}/", "<?php echo \\1;?>", $str);
  160. $str = preg_replace("/\{\\$([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff:]*\(([^{}]*)\))\}/", "<?php echo \$\\1;?>", $str);
  161. // 变量
  162. /**
  163. * 放弃支持$name.name.name
  164. * $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);
  165. */
  166. // 其它变量
  167. $str = preg_replace("/\{(\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str);
  168. $str = preg_replace("/\{(\\$[a-zA-Z0-9_\.\[\]\'\"\$\x7f-\xff]+)\}/e", "\$this->addquote('<?php echo \\1;?>')", $str);
  169. // 类->属性 类->方法
  170. $str = preg_replace("/\{(\\\$[a-zA-Z0-9_\[\]\'\"\$\x7f-\xff][+\-\>\$\'\"\,\[\]\(\)a-zA-Z0-9_\x7f-\xff]+)\}/es", "\$this->addquote('<?php echo \\1;?>')", $str);
  171. // 常量
  172. $str = preg_replace("/\{([A-Z_\x7f-\xff][A-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str);
  173. // 静态变量
  174. $str = preg_replace("/\{([a-zA-Z0-9_]*::?\\\$[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)\}/", "<?php echo \\1;?>", $str);
  175. $str = preg_replace("/\{([a-zA-Z0-9_]*::?\\\$[a-zA-Z0-9_\.\[\]\'\"\$\x7f-\xff]+)\}/e", "\$this->addquote('<?php echo \\1;?>')", $str);
  176. // 合并相邻php标记
  177. $str = preg_replace("/\?\>\s*\<\?php[\r\n\t ]*/", "", $str);
  178. /**
  179. * 删除空行
  180. * Dos和windows采用回车+换行CR/LF表示下一行,
  181. * 而UNIX/Linux采用换行符LF表示下一行,
  182. * 苹果机(MAC OS系统)则采用回车符CR表示下一行.
  183. * CR用符号 '\r'表示, 十进制ASCII代码是13, 十六进制代码为0x0D;
  184. * LF使用'\n'符号表示, ASCII代码是10, 十六制为0x0A.
  185. * 所以Windows平台上换行在文本文件中是使用 0d 0a 两个字节表示,
  186. * 而UNIX和苹果平台上换行则是使用0a或0d一个字节表示.
  187. *
  188. * 这里统一替换成windows平台回车换行, 第二参数考虑 \\1 保持原有
  189. */
  190. $str = preg_replace("/([\r\n])+/", "\r\n", $str);
  191. // 删除第一行
  192. $str = preg_replace("/^[\r\n]+/", "", $str);
  193. // write
  194. $str = trim($str);
  195. return $str;
  196. }
  197. /**
  198. * 变量加上单引号
  199. * 如果是数字就不加单引号, 如果已经加上单引号或者双引号保持不变
  200. */
  201. protected function addquote($var)
  202. {
  203. preg_match_all("/\[([a-zA-Z0-9_\-\.\x7f-\xff]+)\]/s", $var, $vars);
  204. foreach($vars[1] as $k => $v)
  205. {
  206. if (is_numeric($v))
  207. {
  208. $var = str_replace($vars[0][$k], "[$v]", $var);
  209. }
  210. else
  211. {
  212. $var = str_replace($vars[0][$k], "['$v']", $var);
  213. }
  214. }
  215. return str_replace("\\\"", "\"", $var);
  216. }
  217. /**
  218. * 模板中第一行可以写exit函数防止浏览
  219. * 删除行首尾空白, html javascript css注释
  220. */
  221. protected function removeComments($str, $clear = false)
  222. {
  223. $str = str_replace(array('<?php exit?>', '<?php exit;?>'), array('', ''), $str);
  224. // 删除行首尾空白
  225. $str = preg_replace("/([\r\n]+)[\t ]+/s", "\\1", $str);
  226. $str = preg_replace("/[\t ]+([\r\n]+)/s", "\\1", $str);
  227. // 删除 {} 前后的 html 注释 <!-- -->
  228. $str = preg_replace("/\<\!\-\-\s*\{(.+?)\}\s*\-\-\>/s", "{\\1}", $str);
  229. $str = preg_replace("/\<\!\-\-\s*\-\-\>/s", "", $str);
  230. // 删除 html注释 存在 < { 就不删除
  231. $str = preg_replace("/\<\!\-\-\s*[^\<\{]*\s*\-\-\>/s", "", $str);
  232. if ($clear)
  233. {
  234. $str = $this->clear($str);
  235. }
  236. return $str;
  237. }
  238. /**
  239. * 清除一部分 style script内的注释
  240. * 多行注释内部存在 / 字符就不会清除
  241. */
  242. protected function clear($str)
  243. {
  244. preg_match_all("|<script[^>]*>(.*)</script>|Usi", $str, $tvar);
  245. foreach($tvar[0] as $k => $v)
  246. {
  247. // 删除单行注释
  248. $v = preg_replace("/\/\/\s*[a-zA-Z0-9_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/", "", $v);
  249. // 删除多行注释
  250. $v = preg_replace("/\/\*[^\/]*\*\//s", "", $v);
  251. $str = str_replace($tvar[0][$k], $v, $str);
  252. }
  253. preg_match_all("|<style[^>]*>(.*)</style>|Usi", $str, $tvar);
  254. foreach($tvar[0] as $k => $v)
  255. {
  256. // 删除多行注释
  257. $v = preg_replace("/\/\*[^\/]*\*\//s", "", $v);
  258. $str = str_replace($tvar[0][$k], $v, $str);
  259. }
  260. return $str;
  261. }
  262. /**
  263. *
  264. * @todo 注意相互引用的模板嵌套会导致死循环
  265. */
  266. protected function parseIncludeComponent($str)
  267. {
  268. $count_include_component = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);
  269. $count_include_component += preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);
  270. unset($tvar);
  271. while ($count_include_component > 0)
  272. {
  273. $str = $this->parseInclude($str);
  274. $str = $this->parseComponent($str);
  275. $count_include_component = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);
  276. $count_include_component += preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);
  277. unset($tvar);
  278. }
  279. $str = $this->removeComments($str);
  280. return $str;
  281. }
  282. /**
  283. * 解析多个{include path/file}合并成一个文件
  284. *
  285. * @example {include 'debug_info'}
  286. * {include 'debug_info.php'}
  287. * {include "debug_info"}
  288. * {include "debug_info.php"}
  289. * {include $this->templateDir . $this->template}
  290. */
  291. private function parseInclude($str)
  292. {
  293. $countSubTpl = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);
  294. while ($countSubTpl > 0)
  295. {
  296. foreach($tvar[1] as $k => $subfile)
  297. {
  298. eval("\$subfile = $subfile;");
  299. if (is_file($subfile))
  300. {
  301. $findfile = $subfile;
  302. }
  303. else if (is_file($subfile . '.php'))
  304. {
  305. $findfile = $subfile . '.php';
  306. }
  307. else if (is_file($this->templateDir . $subfile))
  308. {
  309. $findfile = $this->templateDir . $subfile;
  310. }
  311. else if (is_file($this->templateDir . $subfile . '.php'))
  312. {
  313. $findfile = $this->templateDir . $subfile . '.php';
  314. }
  315. else
  316. {
  317. $findfile = '';
  318. }
  319. if (!empty($findfile))
  320. {
  321. $subTpl = file_get_contents($findfile);
  322. $this->tpl_include_files[] = $findfile;
  323. }
  324. else
  325. {
  326. // 找不到文件
  327. $subTpl = 'SubTemplate not found:' . $subfile;
  328. }
  329. $str = str_replace($tvar[0][$k], $subTpl, $str);
  330. }
  331. $countSubTpl = preg_match_all("/\{include\s+(.+)\}/", $str, $tvar);
  332. }
  333. return $str;
  334. }
  335. /**
  336. * 解析多个{component module action}合并成一个文件
  337. */
  338. private function parseComponent($str)
  339. {
  340. $countCom = preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);
  341. while ($countCom > 0)
  342. {
  343. $i = 0;
  344. while ($i < $countCom)
  345. {
  346. $comfile = $this->templateDir . "component/" . $tvar[1][$i] . '-' . $tvar[2][$i] . '.php';
  347. if (is_file($comfile))
  348. {
  349. $subTpl = file_get_contents($comfile);
  350. $this->tpl_include_files[] = $comfile;
  351. }
  352. else
  353. {
  354. $subTpl = 'SubTemplate not found:' . $comfile;
  355. }
  356. ////////////////////////////////////////////////////////////////////////////
  357. $module = $tvar[1][$i];
  358. $action = $tvar[2][$i];
  359. $subTpl = "<?php
  360. \$dispatcher = LtObjectUtil::singleton('LtDispatcher');
  361. \$dispatcher->dispatchComponent('$module', '$action', \$this->context);
  362. \$comdata = \$dispatcher->data;
  363. unset(\$dispatcher);
  364. ?>
  365. " . $subTpl;
  366. ////////////////////////////////////////////////////////////////////////////
  367. $str = str_replace($tvar[0][$i], $subTpl, $str);
  368. $i++;
  369. }
  370. $countCom = preg_match_all("/\{component\s+([a-zA-Z0-9\.\-_]+)\s+([a-zA-Z0-9\.\-_]+)\}/", $str, $tvar);
  371. }
  372. return $str;
  373. }
  374. }