Template.php 53 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | ThinkPHP [ WE CAN DO IT JUST THINK ]
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2006~2019 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. declare (strict_types=1);
  12. namespace think;
  13. use Exception;
  14. use Psr\SimpleCache\CacheInterface;
  15. /**
  16. * ThinkPHP分离出来的模板引擎
  17. * 支持XML标签和普通标签的模板解析
  18. * 编译型模板引擎 支持动态缓存
  19. */
  20. class Template
  21. {
  22. /**
  23. * 模板变量
  24. * @var array
  25. */
  26. protected $data = [];
  27. /**
  28. * 模板配置参数
  29. * @var array
  30. */
  31. protected $config = [
  32. 'view_path' => '', // 模板路径
  33. 'view_suffix' => 'html', // 默认模板文件后缀
  34. 'view_depr' => DIRECTORY_SEPARATOR,
  35. 'cache_path' => '',
  36. 'cache_suffix' => 'php', // 默认模板缓存后缀
  37. 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数
  38. 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码
  39. 'tpl_begin' => '{', // 模板引擎普通标签开始标记
  40. 'tpl_end' => '}', // 模板引擎普通标签结束标记
  41. 'strip_space' => false, // 是否去除模板文件里面的html空格与换行
  42. 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译
  43. 'compile_type' => 'file', // 模板编译类型
  44. 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变
  45. 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒)
  46. 'layout_on' => false, // 布局模板开关
  47. 'layout_name' => 'layout', // 布局模板入口文件
  48. 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识
  49. 'taglib_begin' => '{', // 标签库标签开始标记
  50. 'taglib_end' => '}', // 标签库标签结束标记
  51. 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测
  52. 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序
  53. 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔
  54. 'display_cache' => false, // 模板渲染缓存
  55. 'cache_id' => '', // 模板缓存ID
  56. 'tpl_replace_string' => [],
  57. 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别
  58. 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出
  59. ];
  60. /**
  61. * 保留内容信息
  62. * @var array
  63. */
  64. private $literal = [];
  65. /**
  66. * 扩展解析规则
  67. * @var array
  68. */
  69. private $extend = [];
  70. /**
  71. * 模板包含信息
  72. * @var array
  73. */
  74. private $includeFile = [];
  75. /**
  76. * 模板存储对象
  77. * @var object
  78. */
  79. protected $storage;
  80. /**
  81. * 查询缓存对象
  82. * @var CacheInterface
  83. */
  84. protected $cache;
  85. /**
  86. * 架构函数
  87. * @access public
  88. * @param array $config
  89. */
  90. public function __construct(array $config = [])
  91. {
  92. $this->config = array_merge($this->config, $config);
  93. $this->config['taglib_begin_origin'] = $this->config['taglib_begin'];
  94. $this->config['taglib_end_origin'] = $this->config['taglib_end'];
  95. $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/');
  96. $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/');
  97. $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/');
  98. $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/');
  99. // 初始化模板编译存储器
  100. $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File';
  101. $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type);
  102. $this->storage = new $class();
  103. }
  104. /**
  105. * 模板变量赋值
  106. * @access public
  107. * @param array $vars 模板变量
  108. * @return $this
  109. */
  110. public function assign(array $vars = [])
  111. {
  112. $this->data = array_merge($this->data, $vars);
  113. return $this;
  114. }
  115. /**
  116. * 模板引擎参数赋值
  117. * @access public
  118. * @param string $name
  119. * @param mixed $value
  120. */
  121. public function __set($name, $value)
  122. {
  123. $this->config[$name] = $value;
  124. }
  125. /**
  126. * 设置缓存对象
  127. * @access public
  128. * @param CacheInterface $cache 缓存对象
  129. * @return void
  130. */
  131. public function setCache(CacheInterface $cache): void
  132. {
  133. $this->cache = $cache;
  134. }
  135. /**
  136. * 模板引擎配置
  137. * @access public
  138. * @param array $config
  139. * @return $this
  140. */
  141. public function config(array $config)
  142. {
  143. $this->config = array_merge($this->config, $config);
  144. return $this;
  145. }
  146. /**
  147. * 获取模板引擎配置项
  148. * @access public
  149. * @param string $name
  150. * @return mixed
  151. */
  152. public function getConfig(string $name)
  153. {
  154. return $this->config[$name] ?? null;
  155. }
  156. /**
  157. * 模板变量获取
  158. * @access public
  159. * @param string $name 变量名
  160. * @return mixed
  161. */
  162. public function get(string $name = '')
  163. {
  164. if ('' == $name) {
  165. return $this->data;
  166. }
  167. $data = $this->data;
  168. foreach (explode('.', $name) as $key => $val) {
  169. if (isset($data[$val])) {
  170. $data = $data[$val];
  171. } else {
  172. $data = null;
  173. break;
  174. }
  175. }
  176. return $data;
  177. }
  178. /**
  179. * 扩展模板解析规则
  180. * @access public
  181. * @param string $rule 解析规则
  182. * @param callable $callback 解析规则
  183. * @return void
  184. */
  185. public function extend(string $rule, callable $callback = null): void
  186. {
  187. $this->extend[$rule] = $callback;
  188. }
  189. /**
  190. * 渲染模板文件
  191. * @access public
  192. * @param string $template 模板文件
  193. * @param array $vars 模板变量
  194. * @return void
  195. */
  196. public function fetch(string $template, array $vars = []): void
  197. {
  198. if ($vars) {
  199. $this->data = array_merge($this->data, $vars);
  200. }
  201. if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
  202. // 读取渲染缓存
  203. if ($this->cache->has($this->config['cache_id'])) {
  204. echo $this->cache->get($this->config['cache_id']);
  205. return;
  206. }
  207. }
  208. $template = $this->parseTemplateFile($template);
  209. if ($template) {
  210. $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.');
  211. if (!$this->checkCache($cacheFile)) {
  212. // 缓存无效 重新模板编译
  213. $content = file_get_contents($template);
  214. $this->compiler($content, $cacheFile);
  215. }
  216. // 页面缓存
  217. ob_start();
  218. if (PHP_VERSION > 8.0) {
  219. ob_implicit_flush(false);
  220. } else {
  221. ob_implicit_flush(0);
  222. }
  223. // 读取编译存储
  224. $this->storage->read($cacheFile, $this->data);
  225. // 获取并清空缓存
  226. $content = ob_get_clean();
  227. if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) {
  228. // 缓存页面输出
  229. $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']);
  230. }
  231. echo $content;
  232. }
  233. }
  234. /**
  235. * 检查编译缓存是否存在
  236. * @access public
  237. * @param string $cacheId 缓存的id
  238. * @return boolean
  239. */
  240. public function isCache(string $cacheId): bool
  241. {
  242. if ($cacheId && $this->cache && $this->config['display_cache']) {
  243. // 缓存页面输出
  244. return $this->cache->has($cacheId);
  245. }
  246. return false;
  247. }
  248. /**
  249. * 渲染模板内容
  250. * @access public
  251. * @param string $content 模板内容
  252. * @param array $vars 模板变量
  253. * @return void
  254. */
  255. public function display(string $content, array $vars = []): void
  256. {
  257. if ($vars) {
  258. $this->data = array_merge($this->data, $vars);
  259. }
  260. $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.');
  261. if (!$this->checkCache($cacheFile)) {
  262. // 缓存无效 模板编译
  263. $this->compiler($content, $cacheFile);
  264. }
  265. // 读取编译存储
  266. $this->storage->read($cacheFile, $this->data);
  267. }
  268. /**
  269. * 设置布局
  270. * @access public
  271. * @param mixed $name 布局模板名称 false 则关闭布局
  272. * @param string $replace 布局模板内容替换标识
  273. * @return $this
  274. */
  275. public function layout($name, string $replace = '')
  276. {
  277. if (false === $name) {
  278. // 关闭布局
  279. $this->config['layout_on'] = false;
  280. } else {
  281. // 开启布局
  282. $this->config['layout_on'] = true;
  283. // 名称必须为字符串
  284. if (is_string($name)) {
  285. $this->config['layout_name'] = $name;
  286. }
  287. if (!empty($replace)) {
  288. $this->config['layout_item'] = $replace;
  289. }
  290. }
  291. return $this;
  292. }
  293. /**
  294. * 检查编译缓存是否有效
  295. * 如果无效则需要重新编译
  296. * @access private
  297. * @param string $cacheFile 缓存文件名
  298. * @return bool
  299. */
  300. private function checkCache(string $cacheFile): bool
  301. {
  302. if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) {
  303. return false;
  304. }
  305. // 读取第一行
  306. $line = fgets($handle);
  307. if (false === $line) {
  308. return false;
  309. }
  310. preg_match('/\/\*(.+?)\*\//', $line, $matches);
  311. if (!isset($matches[1])) {
  312. return false;
  313. }
  314. $includeFile = unserialize($matches[1]);
  315. if (!is_array($includeFile)) {
  316. return false;
  317. }
  318. // 检查模板文件是否有更新
  319. foreach ($includeFile as $path => $time) {
  320. if (is_file($path) && filemtime($path) > $time) {
  321. // 模板文件如果有更新则缓存需要更新
  322. return false;
  323. }
  324. }
  325. // 检查编译存储是否有效
  326. return $this->storage->check($cacheFile, $this->config['cache_time']);
  327. }
  328. /**
  329. * 编译模板文件内容
  330. * @access private
  331. * @param string $content 模板内容
  332. * @param string $cacheFile 缓存文件名
  333. * @return void
  334. */
  335. private function compiler(string &$content, string $cacheFile): void
  336. {
  337. // 判断是否启用布局
  338. if ($this->config['layout_on']) {
  339. if (false !== strpos($content, '{__NOLAYOUT__}')) {
  340. // 可以单独定义不使用布局
  341. $content = str_replace('{__NOLAYOUT__}', '', $content);
  342. } else {
  343. // 读取布局模板
  344. $layoutFile = $this->parseTemplateFile($this->config['layout_name']);
  345. if ($layoutFile) {
  346. // 替换布局的主体内容
  347. $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile));
  348. }
  349. }
  350. } else {
  351. $content = str_replace('{__NOLAYOUT__}', '', $content);
  352. }
  353. // 模板解析
  354. $this->parse($content);
  355. if ($this->config['strip_space']) {
  356. /* 去除html空格与换行 */
  357. $find = ['~>\s+<~', '~>(\s+\n|\r)~'];
  358. $replace = ['><', '>'];
  359. $content = preg_replace($find, $replace, $content);
  360. }
  361. // 优化生成的php代码
  362. $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content);
  363. // 模板过滤输出
  364. $replace = $this->config['tpl_replace_string'];
  365. $content = str_replace(array_keys($replace), array_values($replace), $content);
  366. // 添加安全代码及模板引用记录
  367. $content = '<?php /*' . serialize($this->includeFile) . '*/ ?>' . "\n" . $content;
  368. // 编译存储
  369. $this->storage->write($cacheFile, $content);
  370. $this->includeFile = [];
  371. }
  372. /**
  373. * 模板解析入口
  374. * 支持普通标签和TagLib解析 支持自定义标签库
  375. * @access public
  376. * @param string $content 要解析的模板内容
  377. * @return void
  378. */
  379. public function parse(string &$content): void
  380. {
  381. // 内容为空不解析
  382. if (empty($content)) {
  383. return;
  384. }
  385. // 替换literal标签内容
  386. $this->parseLiteral($content);
  387. // 解析继承
  388. $this->parseExtend($content);
  389. // 解析布局
  390. $this->parseLayout($content);
  391. // 解析widget
  392. $this->parseWidgetsHead($content);
  393. $this->parseWidgetsBlock($content);
  394. $this->parseWidgetsScript($content);
  395. // 检查include语法
  396. $this->parseInclude($content);
  397. // 替换包含文件中literal标签内容
  398. $this->parseLiteral($content);
  399. // 检查PHP语法
  400. $this->parsePhp($content);
  401. // 获取需要引入的标签库列表
  402. // 标签库只需要定义一次,允许引入多个一次
  403. // 一般放在文件的最前面
  404. // 格式:<taglib name="html,mytag..." />
  405. // 当TAGLIB_LOAD配置为true时才会进行检测
  406. if ($this->config['taglib_load']) {
  407. $tagLibs = $this->getIncludeTagLib($content);
  408. if (!empty($tagLibs)) {
  409. // 对导入的TagLib进行解析
  410. foreach ($tagLibs as $tagLibName) {
  411. $this->parseTagLib($tagLibName, $content);
  412. }
  413. }
  414. }
  415. // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀
  416. if ($this->config['taglib_pre_load']) {
  417. $tagLibs = explode(',', $this->config['taglib_pre_load']);
  418. foreach ($tagLibs as $tag) {
  419. $this->parseTagLib($tag, $content);
  420. }
  421. }
  422. // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀
  423. $tagLibs = explode(',', $this->config['taglib_build_in']);
  424. foreach ($tagLibs as $tag) {
  425. $this->parseTagLib($tag, $content, true);
  426. }
  427. // 解析普通模板标签 {$tagName}
  428. $this->parseTag($content);
  429. // 还原被替换的Literal标签
  430. $this->parseLiteral($content, true);
  431. }
  432. /**
  433. * 检查PHP语法
  434. * @access private
  435. * @param string $content 要解析的模板内容
  436. * @return void
  437. * @throws Exception
  438. */
  439. private function parsePhp(string &$content): void
  440. {
  441. // 短标签的情况要将<?标签用echo方式输出 否则无法正常输出xml标识
  442. $content = preg_replace('/(<\?(?!php|=|$))/i', '<?php echo \'\\1\'; ?>' . "\n", $content);
  443. // PHP语法检查
  444. if ($this->config['tpl_deny_php'] && false !== strpos($content, '<?php')) {
  445. throw new Exception('not allow php tag');
  446. }
  447. }
  448. /**
  449. * 解析模板中的布局标签
  450. * @access private
  451. * @param string $content 要解析的模板内容
  452. * @return void
  453. */
  454. private function parseLayout(string &$content): void
  455. {
  456. // 读取模板中的布局标签
  457. if (preg_match($this->getRegex('layout'), $content, $matches)) {
  458. // 替换Layout标签
  459. $content = str_replace($matches[0], '', $content);
  460. // 解析Layout标签
  461. $array = $this->parseAttr($matches[0]);
  462. if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) {
  463. // 读取布局模板
  464. $layoutFile = $this->parseTemplateFile($array['name']);
  465. if ($layoutFile) {
  466. $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item'];
  467. // 替换布局的主体内容
  468. $content = str_replace($replace, $content, file_get_contents($layoutFile));
  469. }
  470. }
  471. } else {
  472. $content = str_replace('{__NOLAYOUT__}', '', $content);
  473. }
  474. }
  475. /**
  476. * 解析模板中的widgetsHead标签
  477. * @access private
  478. * @param string $content 要解析的模板内容
  479. * @return void
  480. */
  481. private function parseWidgetsHead(string &$content): void
  482. {
  483. $regex = $this->getRegex('widgetsHead');
  484. $result = preg_match($regex, $content, $matches);
  485. // 读取模板中的布局标签
  486. if ($result) {
  487. $widgetsHeadContent = '';
  488. $widgets = $this->get('_theme_widgets');
  489. if (!empty($widgets)) {
  490. foreach ($widgets as $widget) {
  491. if (empty($widget['display'])) {
  492. continue;
  493. }
  494. $template = "public@widgets/{$widget['name']}/head";
  495. try {
  496. $template = $this->parseTemplateFile($template);
  497. $widgetsHeadContent .= <<<hello
  498. <include file="$template"/>
  499. hello;
  500. } catch (\Exception $e) {
  501. }
  502. }
  503. $content = str_replace($matches[0], $widgetsHeadContent, $content);
  504. }
  505. }
  506. }
  507. /**
  508. * 解析模板中的widgetsBlock标签
  509. * @access private
  510. * @param string $content 要解析的模板内容
  511. * @return void
  512. */
  513. private function parseWidgetsBlock(string &$content): void
  514. {
  515. $regex = $this->getRegex('widgetsBlock');
  516. $result = preg_match_all($regex, $content, $matches, PREG_SET_ORDER);
  517. // 读取模板中的布局标签
  518. if ($result) {
  519. $widgetsBlocks = $this->get('theme_widgets_blocks');
  520. $designingTheme = cookie('cmf_design_theme');
  521. foreach ($matches as $match) {
  522. $array = $this->parseAttr($match[0]);
  523. $name = $array['name'];
  524. $tagName = $array['tag'];
  525. $attrsText = '';
  526. unset($array['tag']);
  527. unset($array['name']);
  528. $attrs = [];
  529. if ($designingTheme) {
  530. if (!isset($array['class'])) {
  531. $attrs[] = 'class="__cmf_widgets_block"';
  532. }
  533. $attrs[] = 'data-cmf_widgets_block_name="' . $name . '"';
  534. $attrs[] = 'data-cmf_theme_file_id="' . $widgetsBlocks[$name]['_file_id'] . '"';
  535. }
  536. foreach ($array as $attrName => $attrValue) {
  537. if (strpos($attrValue, '$') === 0) {
  538. $this->parseVar($attrValue);
  539. $attrValue = "<?php echo $attrValue ?>";
  540. } else {
  541. $attrValue = "{$attrValue}";
  542. }
  543. if ($attrName == 'class' && $designingTheme) {
  544. $attrValue = '__cmf_widgets_block ' . $attrValue;
  545. }
  546. $attrs[] = $attrName . '="' . $attrValue . '"';
  547. }
  548. $attrsText = ' ' . join(' ', $attrs);
  549. $widgetsBlockContent = '';
  550. if (!empty($widgetsBlocks[$name]['widgets'])) {
  551. $widgets = $widgetsBlocks[$name]['widgets'];
  552. if (!empty($widgets)) {
  553. foreach ($widgets as $key => $widget) {
  554. if (empty($widget['display'])) {
  555. continue;
  556. }
  557. $widgetsBlockContent .= <<<hello
  558. <?php
  559. \$widget= \$theme_widgets_blocks['{$name}']['widgets']['{$key}'];
  560. \$_theme_file_id='{$widgetsBlocks[$name]['_file_id']}';
  561. \$_widget_id='{$key}';
  562. ?>
  563. <include file="public@widgets/{$widget['name']}/widget"/>
  564. hello;
  565. }
  566. }
  567. $widgetsBlockContent = <<<hello
  568. <$tagName{$attrsText}>
  569. $widgetsBlockContent
  570. </$tagName>
  571. hello;
  572. } else {
  573. if ($designingTheme) {
  574. $widgetsBlockContent = <<<hello
  575. <$tagName{$attrsText}></$tagName>
  576. hello;
  577. }
  578. }
  579. $content = str_replace($match[0], "$widgetsBlockContent", $content);
  580. }
  581. }
  582. }
  583. /**
  584. * 解析模板中的widgetsScript标签
  585. * @access private
  586. * @param string $content 要解析的模板内容
  587. * @return void
  588. */
  589. private function parseWidgetsScript(string &$content): void
  590. {
  591. $regex = $this->getRegex('widgetsScript');
  592. $result = preg_match($regex, $content, $matches);
  593. // 读取模板中的布局标签
  594. if ($result) {
  595. $widgetsScriptContent = '';
  596. $widgets = $this->get('_theme_widgets');
  597. if (!empty($widgets)) {
  598. foreach ($widgets as $widget) {
  599. if (empty($widget['display'])) {
  600. continue;
  601. }
  602. $template = "public@widgets/{$widget['name']}/script";
  603. try {
  604. $template = $this->parseTemplateFile($template);
  605. $widgetsScriptContent .= <<<hello
  606. <include file="$template"/>
  607. hello;
  608. } catch (\Exception $e) {
  609. }
  610. }
  611. $content = str_replace($matches[0], $widgetsScriptContent, $content);
  612. }
  613. }
  614. }
  615. /**
  616. * 解析模板中的include标签
  617. * @access private
  618. * @param string $content 要解析的模板内容
  619. * @return void
  620. */
  621. private function parseInclude(string &$content): void
  622. {
  623. $regex = $this->getRegex('include');
  624. $func = function ($template) use (&$func, &$regex, &$content) {
  625. if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) {
  626. foreach ($matches as $match) {
  627. $array = $this->parseAttr($match[0]);
  628. $file = $array['file'];
  629. unset($array['file']);
  630. // 分析模板文件名并读取内容
  631. $parseStr = $this->parseTemplateName($file);
  632. foreach ($array as $k => $v) {
  633. // 以$开头字符串转换成模板变量
  634. if (0 === strpos($v, '$')) {
  635. $v = $this->get(substr($v, 1));
  636. }
  637. $parseStr = str_replace('[' . $k . ']', $v, $parseStr);
  638. }
  639. $content = str_replace($match[0], $parseStr, $content);
  640. // 再次对包含文件进行模板分析
  641. $func($parseStr);
  642. }
  643. unset($matches);
  644. }
  645. };
  646. // 替换模板中的include标签
  647. $func($content);
  648. }
  649. /**
  650. * 解析模板中的extend标签
  651. * @access private
  652. * @param string $content 要解析的模板内容
  653. * @return void
  654. */
  655. private function parseExtend(string &$content): void
  656. {
  657. $regex = $this->getRegex('extend');
  658. $array = $blocks = $baseBlocks = [];
  659. $extend = '';
  660. $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) {
  661. if (preg_match($regex, $template, $matches)) {
  662. if (!isset($array[$matches['name']])) {
  663. $array[$matches['name']] = 1;
  664. // 读取继承模板
  665. $extend = $this->parseTemplateName($matches['name']);
  666. // 递归检查继承
  667. $func($extend);
  668. // 取得block标签内容
  669. $blocks = array_merge($blocks, $this->parseBlock($template));
  670. return;
  671. }
  672. } else {
  673. // 取得顶层模板block标签内容
  674. $baseBlocks = $this->parseBlock($template, true);
  675. if (empty($extend)) {
  676. // 无extend标签但有block标签的情况
  677. $extend = $template;
  678. }
  679. }
  680. };
  681. $func($content);
  682. if (!empty($extend)) {
  683. if ($baseBlocks) {
  684. $children = [];
  685. foreach ($baseBlocks as $name => $val) {
  686. $replace = $val['content'];
  687. if (!empty($children[$name])) {
  688. // 如果包含有子block标签
  689. foreach ($children[$name] as $key) {
  690. $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace);
  691. }
  692. }
  693. if (isset($blocks[$name])) {
  694. // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖
  695. $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']);
  696. if (!empty($val['parent'])) {
  697. // 如果不是最顶层的block标签
  698. $parent = $val['parent'];
  699. if (isset($blocks[$parent])) {
  700. $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']);
  701. }
  702. $blocks[$name]['content'] = $replace;
  703. $children[$parent][] = $name;
  704. continue;
  705. }
  706. } elseif (!empty($val['parent'])) {
  707. // 如果子标签没有被继承则用原值
  708. $children[$val['parent']][] = $name;
  709. $blocks[$name] = $val;
  710. }
  711. if (!$val['parent']) {
  712. // 替换模板中的顶级block标签
  713. $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend);
  714. }
  715. }
  716. }
  717. $content = $extend;
  718. unset($blocks, $baseBlocks);
  719. }
  720. }
  721. /**
  722. * 替换页面中的literal标签
  723. * @access private
  724. * @param string $content 模板内容
  725. * @param boolean $restore 是否为还原
  726. * @return void
  727. */
  728. private function parseLiteral(string &$content, bool $restore = false): void
  729. {
  730. $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal');
  731. if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
  732. if (!$restore) {
  733. $count = count($this->literal);
  734. // 替换literal标签
  735. foreach ($matches as $match) {
  736. $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2]));
  737. $content = str_replace($match[0], "<!--###literal{$count}###-->", $content);
  738. $count++;
  739. }
  740. } else {
  741. // 还原literal标签
  742. foreach ($matches as $match) {
  743. $content = str_replace($match[0], $this->literal[$match[1]], $content);
  744. }
  745. // 清空literal记录
  746. $this->literal = [];
  747. }
  748. unset($matches);
  749. }
  750. }
  751. /**
  752. * 获取模板中的block标签
  753. * @access private
  754. * @param string $content 模板内容
  755. * @param boolean $sort 是否排序
  756. * @return array
  757. */
  758. private function parseBlock(string &$content, bool $sort = false): array
  759. {
  760. $regex = $this->getRegex('block');
  761. $result = [];
  762. if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) {
  763. $right = $keys = [];
  764. foreach ($matches as $match) {
  765. if (empty($match['name'][0])) {
  766. if (count($right) > 0) {
  767. $tag = array_pop($right);
  768. $start = $tag['offset'] + strlen($tag['tag']);
  769. $length = $match[0][1] - $start;
  770. $result[$tag['name']] = [
  771. 'begin' => $tag['tag'],
  772. 'content' => substr($content, $start, $length),
  773. 'end' => $match[0][0],
  774. 'parent' => count($right) ? end($right)['name'] : '',
  775. ];
  776. $keys[$tag['name']] = $match[0][1];
  777. }
  778. } else {
  779. // 标签头压入栈
  780. $right[] = [
  781. 'name' => $match[2][0],
  782. 'offset' => $match[0][1],
  783. 'tag' => $match[0][0],
  784. ];
  785. }
  786. }
  787. unset($right, $matches);
  788. if ($sort) {
  789. // 按block标签结束符在模板中的位置排序
  790. array_multisort($keys, $result);
  791. }
  792. }
  793. return $result;
  794. }
  795. /**
  796. * 搜索模板页面中包含的TagLib库
  797. * 并返回列表
  798. * @access private
  799. * @param string $content 模板内容
  800. * @return array|null
  801. */
  802. private function getIncludeTagLib(string &$content)
  803. {
  804. // 搜索是否有TagLib标签
  805. if (preg_match($this->getRegex('taglib'), $content, $matches)) {
  806. // 替换TagLib标签
  807. $content = str_replace($matches[0], '', $content);
  808. return explode(',', $matches['name']);
  809. }
  810. }
  811. /**
  812. * TagLib库解析
  813. * @access public
  814. * @param string $tagLib 要解析的标签库
  815. * @param string $content 要解析的模板内容
  816. * @param boolean $hide 是否隐藏标签库前缀
  817. * @return void
  818. */
  819. public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void
  820. {
  821. if (false !== strpos($tagLib, '\\')) {
  822. // 支持指定标签库的命名空间
  823. $className = $tagLib;
  824. $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1);
  825. } else {
  826. $className = '\\think\\template\\taglib\\' . ucwords($tagLib);
  827. }
  828. $tLib = new $className($this);
  829. $tLib->parseTag($content, $hide ? '' : $tagLib);
  830. }
  831. /**
  832. * 分析标签属性
  833. * @access public
  834. * @param string $str 属性字符串
  835. * @param string $name 不为空时返回指定的属性名
  836. * @return array
  837. */
  838. public function parseAttr(string $str, string $name = null): array
  839. {
  840. $regex = '/\s+(?>(?P<name>[\w-]+)\s*)=(?>\s*)([\"\'])(?P<value>(?:(?!\\2).)*)\\2/is';
  841. $array = [];
  842. if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) {
  843. foreach ($matches as $match) {
  844. $array[$match['name']] = $match['value'];
  845. }
  846. unset($matches);
  847. }
  848. if (!empty($name) && isset($array[$name])) {
  849. return $array[$name];
  850. }
  851. return $array;
  852. }
  853. /**
  854. * 模板标签解析
  855. * 格式: {TagName:args [|content] }
  856. * @access private
  857. * @param string $content 要解析的模板内容
  858. * @return void
  859. */
  860. private function parseTag(string &$content): void
  861. {
  862. $regex = $this->getRegex('tag');
  863. if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) {
  864. foreach ($matches as $match) {
  865. $str = stripslashes($match[1]);
  866. $flag = substr($str, 0, 1);
  867. switch ($flag) {
  868. case '$':
  869. // 解析模板变量 格式 {$varName}
  870. // 是否带有?号
  871. if (false !== $pos = strpos($str, '?')) {
  872. $array = preg_split('/([!=]={1,2}|(?<!-)[><]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE);
  873. $name = $array[0];
  874. $this->parseVar($name);
  875. //$this->parseVarFunction($name);
  876. $str = trim(substr($str, $pos + 1));
  877. $this->parseVar($str);
  878. $first = substr($str, 0, 1);
  879. if (strpos($name, ')')) {
  880. // $name为对象或是自动识别,或者含有函数
  881. if (isset($array[1])) {
  882. $this->parseVar($array[2]);
  883. $name .= $array[1] . $array[2];
  884. }
  885. switch ($first) {
  886. case '?':
  887. $this->parseVarFunction($name);
  888. $str = '<?php echo (' . $name . ') ? ' . $name . ' : ' . substr($str, 1) . '; ?>';
  889. break;
  890. case '=':
  891. $str = '<?php if(' . $name . ') echo ' . substr($str, 1) . '; ?>';
  892. break;
  893. default:
  894. $str = '<?php echo ' . $name . '?' . $str . '; ?>';
  895. }
  896. } else {
  897. if (isset($array[1])) {
  898. $express = true;
  899. $this->parseVar($array[2]);
  900. $express = $name . $array[1] . $array[2];
  901. } else {
  902. $express = false;
  903. }
  904. if (in_array($first, ['?', '=', ':'])) {
  905. $str = trim(substr($str, 1));
  906. if ('$' == substr($str, 0, 1)) {
  907. $str = $this->parseVarFunction($str);
  908. }
  909. }
  910. // $name为数组
  911. switch ($first) {
  912. case '?':
  913. // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx
  914. $str = '<?php echo ' . ($express ?: 'isset(' . $name . ')') . ' ? ' . $this->parseVarFunction($name) . ' : ' . $str . '; ?>';
  915. break;
  916. case '=':
  917. // {$varname?='xxx'} $varname为真时才输出xxx
  918. $str = '<?php if(' . ($express ?: '!empty(' . $name . ')') . ') echo ' . $str . '; ?>';
  919. break;
  920. case ':':
  921. // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx
  922. $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . ' ? ' . $this->parseVarFunction($name) . ' : ' . $str . '; ?>';
  923. break;
  924. default:
  925. if (strpos($str, ':')) {
  926. // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b
  927. $array = explode(':', $str, 2);
  928. $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0];
  929. $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1];
  930. $str = implode(' : ', $array);
  931. }
  932. $str = '<?php echo ' . ($express ?: '!empty(' . $name . ')') . ' ? ' . $str . '; ?>';
  933. }
  934. }
  935. } else {
  936. $this->parseVar($str);
  937. $this->parseVarFunction($str);
  938. $str = '<?php echo ' . $str . '; ?>';
  939. }
  940. break;
  941. case ':':
  942. // 输出某个函数的结果
  943. $str = substr($str, 1);
  944. $this->parseVar($str);
  945. $str = '<?php echo ' . $str . '; ?>';
  946. break;
  947. case '~':
  948. // 执行某个函数
  949. $str = substr($str, 1);
  950. $this->parseVar($str);
  951. $str = '<?php ' . $str . '; ?>';
  952. break;
  953. case '-':
  954. case '+':
  955. // 输出计算
  956. $this->parseVar($str);
  957. $str = '<?php echo ' . $str . '; ?>';
  958. break;
  959. case '/':
  960. // 注释标签
  961. $flag2 = substr($str, 1, 1);
  962. if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) {
  963. $str = '';
  964. }
  965. break;
  966. default:
  967. // 未识别的标签直接返回
  968. $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end'];
  969. break;
  970. }
  971. $content = str_replace($match[0], $str, $content);
  972. }
  973. unset($matches);
  974. }
  975. }
  976. /**
  977. * 模板变量解析,支持使用函数
  978. * 格式: {$varname|function1|function2=arg1,arg2}
  979. * @access public
  980. * @param string $varStr 变量数据
  981. * @return void
  982. */
  983. public function parseVar(string &$varStr): void
  984. {
  985. $varStr = trim($varStr);
  986. if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) {
  987. static $_varParseList = [];
  988. while ($matches[0]) {
  989. $match = array_pop($matches[0]);
  990. //如果已经解析过该变量字串,则直接返回变量值
  991. if (isset($_varParseList[$match[0]])) {
  992. $parseStr = $_varParseList[$match[0]];
  993. } else {
  994. if (strpos($match[0], '.')) {
  995. $vars = explode('.', $match[0]);
  996. $first = array_shift($vars);
  997. if (isset($this->extend[$first])) {
  998. $callback = $this->extend[$first];
  999. $parseStr = $callback($vars);
  1000. } elseif ('$Request' == $first) {
  1001. // 输出请求变量
  1002. $parseStr = $this->parseRequestVar($vars);
  1003. } elseif ('$Think' == $first) {
  1004. // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出
  1005. $parseStr = $this->parseThinkVar($vars);
  1006. } else {
  1007. switch ($this->config['tpl_var_identify']) {
  1008. case 'array': // 识别为数组
  1009. $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']';
  1010. break;
  1011. case 'obj': // 识别为对象
  1012. $parseStr = $first . '->' . implode('->', $vars);
  1013. break;
  1014. default: // 自动判断数组或对象
  1015. $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')';
  1016. }
  1017. }
  1018. } else {
  1019. $parseStr = str_replace(':', '->', $match[0]);
  1020. }
  1021. $_varParseList[$match[0]] = $parseStr;
  1022. }
  1023. $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0]));
  1024. }
  1025. unset($matches);
  1026. }
  1027. }
  1028. /**
  1029. * 对模板中使用了函数的变量进行解析
  1030. * 格式 {$varname|function1|function2=arg1,arg2}
  1031. * @access public
  1032. * @param string $varStr 变量字符串
  1033. * @param bool $autoescape 自动转义
  1034. * @return string
  1035. */
  1036. public function parseVarFunction(string &$varStr, bool $autoescape = true): string
  1037. {
  1038. if (!$autoescape && false === strpos($varStr, '|')) {
  1039. return $varStr;
  1040. } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) {
  1041. $varStr .= '|' . $this->config['default_filter'];
  1042. }
  1043. static $_varFunctionList = [];
  1044. $_key = md5($varStr);
  1045. //如果已经解析过该变量字串,则直接返回变量值
  1046. if (isset($_varFunctionList[$_key])) {
  1047. $varStr = $_varFunctionList[$_key];
  1048. } else {
  1049. $varArray = explode('|', $varStr);
  1050. // 取得变量名称
  1051. $name = trim(array_shift($varArray));
  1052. // 对变量使用函数
  1053. $length = count($varArray);
  1054. // 取得模板禁止使用函数列表
  1055. $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']);
  1056. for ($i = 0; $i < $length; $i++) {
  1057. $args = explode('=', $varArray[$i], 2);
  1058. // 模板函数过滤
  1059. $fun = trim($args[0]);
  1060. if (in_array($fun, $template_deny_funs)) {
  1061. continue;
  1062. }
  1063. switch (strtolower($fun)) {
  1064. case 'raw':
  1065. break;
  1066. case 'date':
  1067. $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')';
  1068. break;
  1069. case 'first':
  1070. $name = 'current(' . $name . ')';
  1071. break;
  1072. case 'last':
  1073. $name = 'end(' . $name . ')';
  1074. break;
  1075. case 'upper':
  1076. $name = 'strtoupper(' . $name . ')';
  1077. break;
  1078. case 'lower':
  1079. $name = 'strtolower(' . $name . ')';
  1080. break;
  1081. case 'format':
  1082. $name = 'sprintf(' . $args[1] . ',' . $name . ')';
  1083. break;
  1084. case 'default': // 特殊模板函数
  1085. if (false === strpos($name, '(')) {
  1086. $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')';
  1087. } else {
  1088. $name = '(' . $name . ' ?: ' . $args[1] . ')';
  1089. }
  1090. break;
  1091. default: // 通用模板函数
  1092. if (isset($args[1])) {
  1093. if (strstr($args[1], '###')) {
  1094. $args[1] = str_replace('###', $name, $args[1]);
  1095. $name = "$fun($args[1])";
  1096. } else {
  1097. $name = "$fun($name,$args[1])";
  1098. }
  1099. } else {
  1100. if (!empty($args[0])) {
  1101. $name = "$fun($name)";
  1102. }
  1103. }
  1104. }
  1105. }
  1106. $_varFunctionList[$_key] = $name;
  1107. $varStr = $name;
  1108. }
  1109. return $varStr;
  1110. }
  1111. /**
  1112. * 请求变量解析
  1113. * 格式 以 $Request. 打头的变量属于请求变量
  1114. * @access public
  1115. * @param array $vars 变量数组
  1116. * @return string
  1117. */
  1118. public function parseRequestVar(array $vars): string
  1119. {
  1120. $type = strtoupper(trim(array_shift($vars)));
  1121. $param = implode('.', $vars);
  1122. switch ($type) {
  1123. case 'SERVER':
  1124. $parseStr = '$_SERVER[\'' . $param . '\']';
  1125. break;
  1126. case 'GET':
  1127. $parseStr = '$_GET[\'' . $param . '\']';
  1128. break;
  1129. case 'POST':
  1130. $parseStr = '$_POST[\'' . $param . '\']';
  1131. break;
  1132. case 'COOKIE':
  1133. $parseStr = '$_COOKIE[\'' . $param . '\']';
  1134. break;
  1135. case 'SESSION':
  1136. $parseStr = '$_SESSION[\'' . $param . '\']';
  1137. break;
  1138. case 'ENV':
  1139. $parseStr = '$_ENV[\'' . $param . '\']';
  1140. break;
  1141. case 'REQUEST':
  1142. $parseStr = '$_REQUEST[\'' . $param . '\']';
  1143. break;
  1144. default:
  1145. $parseStr = '\'\'';
  1146. }
  1147. return $parseStr;
  1148. }
  1149. /**
  1150. * 特殊模板变量解析
  1151. * 格式 以 $Think. 打头的变量属于特殊模板变量
  1152. * @access public
  1153. * @param array $vars 变量数组
  1154. * @return string
  1155. */
  1156. public function parseThinkVar(array $vars): string
  1157. {
  1158. $type = strtoupper(trim(array_shift($vars)));
  1159. $param = implode('.', $vars);
  1160. switch ($type) {
  1161. case 'CONST':
  1162. $parseStr = strtoupper($param);
  1163. break;
  1164. case 'NOW':
  1165. $parseStr = "date('Y-m-d g:i a',time())";
  1166. break;
  1167. case 'LDELIM':
  1168. $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\'';
  1169. break;
  1170. case 'RDELIM':
  1171. $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\'';
  1172. break;
  1173. default:
  1174. $parseStr = defined($type) ? $type : '\'\'';
  1175. }
  1176. return $parseStr;
  1177. }
  1178. /**
  1179. * 分析加载的模板文件并读取内容 支持多个模板文件读取
  1180. * @access private
  1181. * @param string $templateName 模板文件名
  1182. * @return string
  1183. */
  1184. private function parseTemplateName(string $templateName): string
  1185. {
  1186. $array = explode(',', $templateName);
  1187. $parseStr = '';
  1188. foreach ($array as $templateName) {
  1189. if (empty($templateName)) {
  1190. continue;
  1191. }
  1192. if (0 === strpos($templateName, '$')) {
  1193. //支持加载变量文件名
  1194. $templateName = $this->get(substr($templateName, 1));
  1195. }
  1196. $template = $this->parseTemplateFile($templateName);
  1197. if ($template) {
  1198. // 获取模板文件内容
  1199. $parseStr .= file_get_contents($template);
  1200. }
  1201. }
  1202. return $parseStr;
  1203. }
  1204. /**
  1205. * 解析模板文件名
  1206. * @access private
  1207. * @param string $template 文件名
  1208. * @return string
  1209. */
  1210. private function parseTemplateFile(string $template): string
  1211. {
  1212. if ('' == pathinfo($template, PATHINFO_EXTENSION)) {
  1213. if (strpos($template, '@')) {
  1214. list($module, $template) = explode('@', $template);
  1215. }
  1216. if (0 !== strpos($template, '/')) {
  1217. $template = str_replace(['/', ':'], $this->config['view_depr'], $template);
  1218. } else {
  1219. $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1));
  1220. }
  1221. if (!empty($this->config['view_base'])) {
  1222. $module = isset($module) ? $module : app()->http->getName();
  1223. $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : '');
  1224. } else {
  1225. $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path'];
  1226. }
  1227. $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.');
  1228. }
  1229. if (is_file($template)) {
  1230. // 记录模板文件的更新时间
  1231. $this->includeFile[$template] = filemtime($template);
  1232. return $template;
  1233. }
  1234. throw new Exception('template not exists:' . $template);
  1235. }
  1236. /**
  1237. * 按标签生成正则
  1238. * @access private
  1239. * @param string $tagName 标签名
  1240. * @return string
  1241. */
  1242. private function getRegex(string $tagName): string
  1243. {
  1244. $regex = '';
  1245. if ('tag' == $tagName) {
  1246. $begin = $this->config['tpl_begin'];
  1247. $end = $this->config['tpl_end'];
  1248. if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) {
  1249. $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end;
  1250. } else {
  1251. $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end;
  1252. }
  1253. } else {
  1254. $begin = $this->config['taglib_begin'];
  1255. $end = $this->config['taglib_end'];
  1256. $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false;
  1257. switch ($tagName) {
  1258. case 'block':
  1259. if ($single) {
  1260. $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end;
  1261. } else {
  1262. $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P<name>[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end;
  1263. }
  1264. break;
  1265. case 'literal':
  1266. if ($single) {
  1267. $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')';
  1268. $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)';
  1269. $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
  1270. } else {
  1271. $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')';
  1272. $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)';
  1273. $regex .= '(' . $begin . '\/' . $tagName . $end . ')';
  1274. }
  1275. break;
  1276. case 'restoreliteral':
  1277. $regex = '<!--###literal(\d+)###-->';
  1278. break;
  1279. case 'include':
  1280. $name = 'file';
  1281. case 'taglib':
  1282. case 'layout':
  1283. case 'extend':
  1284. case 'widgetsBlock':
  1285. if (empty($name)) {
  1286. $name = 'name';
  1287. }
  1288. if ($single) {
  1289. $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end;
  1290. } else {
  1291. $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end;
  1292. }
  1293. break;
  1294. case 'widgetsHead':
  1295. case 'widgetsScript':
  1296. /* $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P<name>[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end;*/
  1297. $regex = $begin . $tagName . '\b\s*(?P<name>[\$\w\-\/\.\:@,\\]+)\1(?>[^' . $end . ']*)' . $end;
  1298. break;
  1299. }
  1300. }
  1301. return '/' . $regex . '/is';
  1302. }
  1303. public function __debugInfo()
  1304. {
  1305. $data = get_object_vars($this);
  1306. unset($data['storage']);
  1307. return $data;
  1308. }
  1309. }