View.class.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK IT ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2009 http://thinkphp.cn All rights reserved.
  6. // +----------------------------------------------------------------------
  7. // | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
  8. // +----------------------------------------------------------------------
  9. // | Author: liu21st <liu21st@gmail.com>
  10. // +----------------------------------------------------------------------
  11. // $Id$
  12. /**
  13. +------------------------------------------------------------------------------
  14. * ThinkPHP 视图输出
  15. * 支持缓存和页面压缩
  16. +------------------------------------------------------------------------------
  17. * @category Think
  18. * @package Think
  19. * @subpackage Core
  20. * @author liu21st <liu21st@gmail.com>
  21. * @version $Id$
  22. +------------------------------------------------------------------------------
  23. */
  24. class View extends Think
  25. {
  26. protected $tVar = array(); // 模板输出变量
  27. protected $trace = array(); // 页面trace变量
  28. protected $templateFile = ''; // 模板文件名
  29. /**
  30. +----------------------------------------------------------
  31. * 模板变量赋值
  32. +----------------------------------------------------------
  33. * @access public
  34. +----------------------------------------------------------
  35. * @param mixed $name
  36. * @param mixed $value
  37. +----------------------------------------------------------
  38. */
  39. public function assign($name,$value=''){
  40. if(is_array($name)) {
  41. $this->tVar = array_merge($this->tVar,$name);
  42. }elseif(is_object($name)){
  43. foreach($name as $key =>$val)
  44. $this->tVar[$key] = $val;
  45. }else {
  46. $this->tVar[$name] = $value;
  47. }
  48. }
  49. /**
  50. +----------------------------------------------------------
  51. * Trace变量赋值
  52. +----------------------------------------------------------
  53. * @access public
  54. +----------------------------------------------------------
  55. * @param mixed $name
  56. * @param mixed $value
  57. +----------------------------------------------------------
  58. */
  59. public function trace($title,$value='') {
  60. if(is_array($title))
  61. $this->trace = array_merge($this->trace,$title);
  62. else
  63. $this->trace[$title] = $value;
  64. }
  65. /**
  66. +----------------------------------------------------------
  67. * 取得模板变量的值
  68. +----------------------------------------------------------
  69. * @access public
  70. +----------------------------------------------------------
  71. * @param string $name
  72. +----------------------------------------------------------
  73. * @return mixed
  74. +----------------------------------------------------------
  75. */
  76. public function get($name){
  77. if(isset($this->tVar[$name]))
  78. return $this->tVar[$name];
  79. else
  80. return false;
  81. }
  82. /**
  83. +----------------------------------------------------------
  84. * 加载模板和页面输出 可以返回输出内容
  85. +----------------------------------------------------------
  86. * @access public
  87. +----------------------------------------------------------
  88. * @param string $templateFile 模板文件名 留空为自动获取
  89. * @param string $charset 模板输出字符集
  90. * @param string $contentType 输出类型
  91. +----------------------------------------------------------
  92. * @return mixed
  93. +----------------------------------------------------------
  94. */
  95. public function display($templateFile='',$charset='',$contentType='text/html')
  96. {
  97. $this->fetch($templateFile,$charset,$contentType,true);
  98. }
  99. /**
  100. +----------------------------------------------------------
  101. * 输出布局模板
  102. +----------------------------------------------------------
  103. * @access protected
  104. +----------------------------------------------------------
  105. * @param string $charset 输出编码
  106. * @param string $contentType 输出类型
  107. * @param string $display 是否直接显示
  108. +----------------------------------------------------------
  109. * @return mixed
  110. +----------------------------------------------------------
  111. */
  112. protected function layout($content,$charset='',$contentType='text/html')
  113. {
  114. if(false !== strpos($content,'<!-- layout')) {
  115. // 查找布局包含的页面
  116. $find = preg_match_all('/<!-- layout::(.+?)::(.+?) -->/is',$content,$matches);
  117. if($find) {
  118. for ($i=0; $i< $find; $i++) {
  119. // 读取相关的页面模板替换布局单元
  120. if(0===strpos($matches[1][$i],'$'))
  121. // 动态布局
  122. $matches[1][$i] = $this->get(substr($matches[1][$i],1));
  123. if(0 != $matches[2][$i] ) {
  124. // 设置了布局缓存
  125. // 检查布局缓存是否有效
  126. $guid = md5($matches[1][$i]);
  127. $cache = S($guid);
  128. if($cache) {
  129. $layoutContent = $cache;
  130. }else{
  131. $layoutContent = $this->fetch($matches[1][$i],$charset,$contentType);
  132. S($guid,$layoutContent,$matches[2][$i]);
  133. }
  134. }else{
  135. $layoutContent = $this->fetch($matches[1][$i],$charset,$contentType);
  136. }
  137. $content = str_replace($matches[0][$i],$layoutContent,$content);
  138. }
  139. }
  140. }
  141. return $content;
  142. }
  143. /**
  144. +----------------------------------------------------------
  145. * 加载模板和页面输出
  146. +----------------------------------------------------------
  147. * @access public
  148. +----------------------------------------------------------
  149. * @param string $templateFile 模板文件名 留空为自动获取
  150. * @param string $charset 模板输出字符集
  151. * @param string $contentType 输出类型
  152. * @param string $display 是否直接显示
  153. +----------------------------------------------------------
  154. * @return mixed
  155. +----------------------------------------------------------
  156. */
  157. public function fetch($templateFile='',$charset='',$contentType='text/html',$display=false)
  158. {
  159. $GLOBALS['_viewStartTime'] = microtime(TRUE);
  160. if(null===$templateFile)
  161. // 使用null参数作为模版名直接返回不做任何输出
  162. return ;
  163. if(empty($charset)) $charset = C('DEFAULT_CHARSET');
  164. // 网页字符编码
  165. header("Content-Type:".$contentType."; charset=".$charset);
  166. header("Cache-control: private"); //支持页面回跳
  167. //页面缓存
  168. ob_start();
  169. ob_implicit_flush(0);
  170. if(!file_exists_case($templateFile))
  171. // 自动定位模板文件
  172. $templateFile = $this->parseTemplateFile($templateFile);
  173. $engine = strtolower(C('TMPL_ENGINE_TYPE'));
  174. if('php'==$engine) {
  175. // 模板阵列变量分解成为独立变量
  176. extract($this->tVar, EXTR_OVERWRITE);
  177. // 直接载入PHP模板
  178. include $templateFile;
  179. }elseif('think'==$engine && $this->checkCache($templateFile)) {
  180. // 如果是Think模板引擎并且缓存有效 分解变量并载入模板缓存
  181. extract($this->tVar, EXTR_OVERWRITE);
  182. //载入模版缓存文件
  183. include C('CACHE_PATH').md5($templateFile).C('TMPL_CACHFILE_SUFFIX');
  184. }else{
  185. // 模板文件需要重新编译 支持第三方模板引擎
  186. // 调用模板引擎解析和输出
  187. $className = 'Template'.ucwords($engine);
  188. require_cache(THINK_PATH.'/Lib/Think/Util/Template/'.$className.'.class.php');
  189. $tpl = new $className;
  190. $tpl->fetch($templateFile,$this->tVar,$charset);
  191. }
  192. $this->templateFile = $templateFile;
  193. // 获取并清空缓存
  194. $content = ob_get_clean();
  195. // 模板内容替换
  196. $content = $this->templateContentReplace($content);
  197. // 布局模板解析
  198. $content = $this->layout($content,$charset,$contentType);
  199. // 输出模板文件
  200. return $this->output($content,$display);
  201. }
  202. /**
  203. +----------------------------------------------------------
  204. * 检查缓存文件是否有效
  205. * 如果无效则需要重新编译
  206. +----------------------------------------------------------
  207. * @access public
  208. +----------------------------------------------------------
  209. * @param string $tmplTemplateFile 模板文件名
  210. +----------------------------------------------------------
  211. * @return boolen
  212. +----------------------------------------------------------
  213. */
  214. protected function checkCache($tmplTemplateFile)
  215. {
  216. if (!C('TMPL_CACHE_ON')) // 优先对配置设定检测
  217. return false;
  218. $tmplCacheFile = C('CACHE_PATH').md5($tmplTemplateFile).C('TMPL_CACHFILE_SUFFIX');
  219. if(!is_file($tmplCacheFile)){
  220. return false;
  221. }elseif (filemtime($tmplTemplateFile) > filemtime($tmplCacheFile)) {
  222. // 模板文件如果有更新则缓存需要更新
  223. return false;
  224. }elseif (C('TMPL_CACHE_TIME') != -1 && time() > filemtime($tmplCacheFile)+C('TMPL_CACHE_TIME')) {
  225. // 缓存是否在有效期
  226. return false;
  227. }
  228. //缓存有效
  229. return true;
  230. }
  231. /**
  232. +----------------------------------------------------------
  233. * 创建静态页面
  234. +----------------------------------------------------------
  235. * @access public
  236. +----------------------------------------------------------
  237. * @htmlfile 生成的静态文件名称
  238. * @htmlpath 生成的静态文件路径
  239. * @param string $templateFile 指定要调用的模板文件
  240. * 默认为空 由系统自动定位模板文件
  241. * @param string $charset 输出编码
  242. * @param string $contentType 输出类型
  243. +----------------------------------------------------------
  244. * @return string
  245. +----------------------------------------------------------
  246. */
  247. public function buildHtml($htmlfile,$htmlpath='',$templateFile='',$charset='',$contentType='text/html') {
  248. $content = $this->fetch($templateFile,$charset,$contentType);
  249. $htmlpath = !empty($htmlpath)?$htmlpath:HTML_PATH;
  250. $htmlfile = $htmlpath.$htmlfile.C('HTML_FILE_SUFFIX');
  251. if(!is_dir(dirname($htmlfile)))
  252. // 如果静态目录不存在 则创建
  253. mk_dir(dirname($htmlfile));
  254. if(false === file_put_contents($htmlfile,$content))
  255. throw_exception(L('_CACHE_WRITE_ERROR_'));
  256. return $content;
  257. }
  258. /**
  259. +----------------------------------------------------------
  260. * 输出模板
  261. +----------------------------------------------------------
  262. * @access protected
  263. +----------------------------------------------------------
  264. * @param string $content 模板内容
  265. * @param boolean $display 是否直接显示
  266. +----------------------------------------------------------
  267. * @return mixed
  268. +----------------------------------------------------------
  269. */
  270. protected function output($content,$display) {
  271. if(C('HTML_CACHE_ON')) HtmlCache::writeHTMLCache($content);
  272. if($display) {
  273. if(C('SHOW_RUN_TIME')){
  274. $runtime = '<div id="think_run_time" class="think_run_time">'.$this->showTime().'</div>';
  275. if(strpos($content,'{__RUNTIME__}'))
  276. $content = str_replace('{__RUNTIME__}',$runtime,$content);
  277. else
  278. $content .= $runtime;
  279. }
  280. echo $content;
  281. if(C('SHOW_PAGE_TRACE')) $this->showTrace();
  282. return null;
  283. }else {
  284. return $content;
  285. }
  286. }
  287. /**
  288. +----------------------------------------------------------
  289. * 模板内容替换
  290. +----------------------------------------------------------
  291. * @access protected
  292. +----------------------------------------------------------
  293. * @param string $content 模板内容
  294. +----------------------------------------------------------
  295. * @return string
  296. +----------------------------------------------------------
  297. */
  298. protected function templateContentReplace($content) {
  299. // 系统默认的特殊变量替换
  300. $replace = array(
  301. '../Public' => APP_PUBLIC_PATH,// 项目公共目录
  302. '__PUBLIC__' => WEB_PUBLIC_PATH,// 站点公共目录
  303. '__TMPL__' => APP_TMPL_PATH, // 项目模板目录
  304. '__ROOT__' => __ROOT__, // 当前网站地址
  305. '__APP__' => __APP__, // 当前项目地址
  306. '__URL__' => __URL__, // 当前模块地址
  307. '__ACTION__' => __ACTION__, // 当前操作地址
  308. '__SELF__' => __SELF__, // 当前页面地址
  309. );
  310. if(C('TOKEN_ON')) {
  311. if(strpos($content,'{__TOKEN__}')) {
  312. // 指定表单令牌隐藏域位置
  313. $replace['{__TOKEN__}'] = $this->buildFormToken();
  314. }elseif(strpos($content,'{__NOTOKEN__}')){
  315. // 标记为不需要令牌验证
  316. $replace['{__NOTOKEN__}'] = '';
  317. }elseif(preg_match('/<\/form(\s*)>/is',$content,$match)) {
  318. // 智能生成表单令牌隐藏域
  319. $replace[$match[0]] = $this->buildFormToken().$match[0];
  320. }
  321. }
  322. // 允许用户自定义模板的字符串替换
  323. if(is_array(C('TMPL_PARSE_STRING')) )
  324. $replace = array_merge($replace,C('TMPL_PARSE_STRING'));
  325. $content = str_replace(array_keys($replace),array_values($replace),$content);
  326. return $content;
  327. }
  328. /**
  329. +----------------------------------------------------------
  330. * 创建表单令牌隐藏域
  331. +----------------------------------------------------------
  332. * @access private
  333. +----------------------------------------------------------
  334. * @return string
  335. +----------------------------------------------------------
  336. */
  337. private function buildFormToken() {
  338. // 开启表单验证自动生成表单令牌
  339. $tokenName = C('TOKEN_NAME');
  340. $tokenType = C('TOKEN_TYPE');
  341. $tokenValue = $tokenType(microtime(TRUE));
  342. $token = '<input type="hidden" name="'.$tokenName.'" value="'.$tokenValue.'" />';
  343. $_SESSION[$tokenName] = $tokenValue;
  344. return $token;
  345. }
  346. /**
  347. +----------------------------------------------------------
  348. * 自动定位模板文件
  349. +----------------------------------------------------------
  350. * @access private
  351. +----------------------------------------------------------
  352. * @param string $templateFile 文件名
  353. +----------------------------------------------------------
  354. * @return string
  355. +----------------------------------------------------------
  356. * @throws ThinkExecption
  357. +----------------------------------------------------------
  358. */
  359. private function parseTemplateFile($templateFile) {
  360. if(''==$templateFile) {
  361. // 如果模板文件名为空 按照默认规则定位
  362. $templateFile = C('TMPL_FILE_NAME');
  363. }elseif(strpos($templateFile,'@')){
  364. // 引入其它主题的操作模板 必须带上模块名称 例如 blue@User:add
  365. $templateFile = TMPL_PATH.str_replace(array('@',':'),'/',$templateFile).C('TMPL_TEMPLATE_SUFFIX');
  366. }elseif(strpos($templateFile,':')){
  367. // 引入其它模块的操作模板
  368. $templateFile = TEMPLATE_PATH.'/'.str_replace(':','/',$templateFile).C('TMPL_TEMPLATE_SUFFIX');
  369. }elseif(!is_file($templateFile)) {
  370. // 引入当前模块的其它操作模板
  371. $templateFile = dirname(C('TMPL_FILE_NAME')).'/'.$templateFile.C('TMPL_TEMPLATE_SUFFIX');
  372. }
  373. if(!file_exists_case($templateFile))
  374. throw_exception(L('_TEMPLATE_NOT_EXIST_').'['.$templateFile.']');
  375. return $templateFile;
  376. }
  377. /**
  378. +----------------------------------------------------------
  379. * 显示运行时间、数据库操作、缓存次数、内存使用信息
  380. +----------------------------------------------------------
  381. * @access private
  382. +----------------------------------------------------------
  383. * @return string
  384. +----------------------------------------------------------
  385. */
  386. private function showTime() {
  387. // 显示运行时间
  388. $startTime = $GLOBALS['_viewStartTime'];
  389. $endTime = microtime(TRUE);
  390. $total_run_time = number_format(($endTime - $GLOBALS['_beginTime']), 3);
  391. $showTime = 'Process: '.$total_run_time.'s ';
  392. if(C('SHOW_ADV_TIME')) {
  393. // 显示详细运行时间
  394. $_load_time = number_format(($GLOBALS['_loadTime'] -$GLOBALS['_beginTime'] ), 3);
  395. $_init_time = number_format(($GLOBALS['_initTime'] -$GLOBALS['_loadTime'] ), 3);
  396. $_exec_time = number_format(($startTime -$GLOBALS['_initTime'] ), 3);
  397. $_parse_time = number_format(($endTime - $startTime), 3);
  398. $showTime .= '( Load:'.$_load_time.'s Init:'.$_init_time.'s Exec:'.$_exec_time.'s Template:'.$_parse_time.'s )';
  399. }
  400. if(C('SHOW_DB_TIMES') && class_exists('Db',false) ) {
  401. // 显示数据库操作次数
  402. $db = Db::getInstance();
  403. $showTime .= ' | DB :'.$db->Q().' queries '.$db->W().' writes ';
  404. }
  405. if(C('SHOW_CACHE_TIMES') && class_exists('Cache',false)) {
  406. // 显示缓存读写次数
  407. $cache = Cache::getInstance();
  408. $showTime .= ' | Cache :'.$cache->Q().' gets '.$cache->W().' writes ';
  409. }
  410. if(MEMORY_LIMIT_ON && C('SHOW_USE_MEM')) {
  411. // 显示内存开销
  412. $startMem = array_sum(explode(' ', $GLOBALS['_startUseMems']));
  413. $endMem = array_sum(explode(' ', memory_get_usage()));
  414. $showTime .= ' | UseMem:'. number_format(($endMem - $startMem)/1024).' kb';
  415. }
  416. return $showTime;
  417. }
  418. /**
  419. +----------------------------------------------------------
  420. * 显示页面Trace信息
  421. +----------------------------------------------------------
  422. * @access private
  423. +----------------------------------------------------------
  424. */
  425. private function showTrace(){
  426. // 显示页面Trace信息 读取Trace定义文件
  427. // 定义格式 return array('当前页面'=>$_SERVER['PHP_SELF'],'通信协议'=>$_SERVER['SERVER_PROTOCOL'],...);
  428. $traceFile = CONFIG_PATH.'trace.php';
  429. $_trace = is_file($traceFile)? include $traceFile : array();
  430. // 系统默认显示信息
  431. $this->trace('当前页面', $_SERVER['REQUEST_URI']);
  432. $this->trace('模板缓存', C('CACHE_PATH').md5($this->templateFile).C('TMPL_CACHFILE_SUFFIX'));
  433. $this->trace('请求方法', $_SERVER['REQUEST_METHOD']);
  434. $this->trace('通信协议', $_SERVER['SERVER_PROTOCOL']);
  435. $this->trace('请求时间', date('Y-m-d H:i:s',$_SERVER['REQUEST_TIME']));
  436. $this->trace('用户代理', $_SERVER['HTTP_USER_AGENT']);
  437. $this->trace('会话ID' , session_id());
  438. $log = Log::$log;
  439. $this->trace('日志记录',count($log)?count($log).'条日志<br/>'.implode('<br/>',$log):'无日志记录');
  440. $files = get_included_files();
  441. $this->trace('加载文件', count($files).str_replace("\n",'<br/>',substr(substr(print_r($files,true),7),0,-2)));
  442. $_trace = array_merge($_trace,$this->trace);
  443. // 调用Trace页面模板
  444. include C('TMPL_TRACE_FILE');
  445. }
  446. }//
  447. ?>