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.

Template.php 62KB

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