*/ class View { // 模板存储变量 protected static $_vars = []; // 模版基地址 protected static $_path = ''; // 模版文件名 protected static $_file = null; // layout 开关 protected static $_layout = false; // layout 模板 protected static $_layoutFile = ''; // 文件后缀 protected static $suffix = '.phtml'; /** * 开启layout */ public static function enableLayout() { self::$_layout = true; } /** * 关闭layout */ public static function disableLayout() { self::$_layout = false; } /** * 配置layoutFile * @param string $file */ public static function setLayoutFile(string $file) { self::$_layoutFile = $file; } /** * @param $file */ public static function setFile(string $file) { self::$_file = $file; } /** * @param string $path */ public static function setPath(string $path) { self::$_path = $path; } /** * @return string */ public static function getPath() { if (empty(self::$_path)) { self::$_path = KX_ROOT . '/app/view'; } return self::$_path; } /** * 模板变量赋值 * * @access public * @param mixed $var * @param mixed $value */ public static function set($var, $value = null) { if (is_array($var)) { self::$_vars = Arr::merge(self::$_vars, $var); } else { self::$_vars[$var] = $value; } } /* * 获取模板变量值 */ public static function get(string $var = '') { if ($var == '') { return self::$_vars; } elseif (isset(self::$_vars[$var])) { return self::$_vars[$var]; } elseif (strpos($var, '.') !== false) { $arr = explode('.', $var); $tmp = self::$_vars; foreach ($arr as $v) { if (substr($v, 0, 1) === '$') { $v = self::get($v); } $tmp = $tmp[$v]; } if (!empty($tmp)) { return $tmp; } } return null; } /** * 加载并视图片段文件内容 * * @access public * @param string $file 视图片段文件名称 * @param array $data 附加数据 * @return string */ public static function make(?string $file = null, ?array $data = []) { $data['_kxcms_config'] = Config::getAll(); //复制参数 if ($data) { self::set($data); } //获取模板 $tplfilepath = self::getTplFilePath($file); if(Config::get('template.output_charset')!='utf-8'){ self::recursionHandleValueCharset(self::$_vars, Config::get('template.output_charset')); } extract(self::$_vars, EXTR_OVERWRITE); ob_start(); include self::checkCompile($tplfilepath); $content = ob_get_contents(); ob_end_clean(); return $content; } private static function recursionHandleValueCharset(&$data, $method) { foreach ($data as &$datum) { if (is_string($datum)) { switch ($method) { case 'gbk': $datum = iconv('utf-8', 'gbk', $datum); break; case 'big5': $datum = OpenCC::change($datum); break; } } elseif (is_array($datum)) { self::recursionHandleValueCharset($datum, $method); } } } /** * 获得模版位置 * * @param string $tpl 视图模板 * @return string */ protected static function getTplFilePath(string $tpl = null) { $tpl = $tpl === null ? self::$_file : $tpl; if ($tpl === null) { $filepath = self::getPath() . '/' . str_replace('\\', '/', Router::$controller) . '/' . Router::$action . self::$suffix; } elseif (substr($tpl, 0, 1) === '/') { $filepath = self::getPath() . $tpl . self::$suffix; } elseif (substr($tpl, 0, 1) === '@') { $filepath = self::getPath() . '/' . substr($tpl, 1) . self::$suffix; } else { $filepath = dirname(self::getPath() . '/' . str_replace('\\', '/', Router::$controller)) . '/' . $tpl . self::$suffix; } if (is_file($filepath)) { return realpath($filepath); } else { trigger_error("模版{$tpl}不存在[" . $filepath . ']', E_USER_ERROR); return false; } } /** * @param $tplfile * @return string */ protected static function checkCompile(string $tplfile) { $compiledName = ltrim(str_replace([KX_ROOT, '/app/views', '/template/'], '/', $tplfile), '/'); if (!$compiledName) { trigger_error('生成的模板缓存文件名为空 [' . $tplfile . ']', E_USER_ERROR); } $compiledFile = str_replace('/', ',', $compiledName); $storage = DI::Storage('template'); if (Config::get('app.debug') || !$storage->exist($compiledFile) || $storage->mtime($compiledFile) < filemtime($tplfile)) { // 获取模版内容 $content = file_get_contents($tplfile); // 解析模版 $content = self::compile($content); //判断是否开启layout if (self::$_layout && self::$_layoutFile) { $includeFile = self::getTplFilePath(self::$_layoutFile); $layout = self::compile(file_get_contents($includeFile)); $content = str_replace('__CONTENT__', $content, $layout); } $content = '' . self::replace($content); $storage->write($compiledFile, $content); } return $storage->getPath($compiledFile); } /** * 模版输出替换 * * @param $content * @return string */ protected static function replace(string $content) { $replace = [ '__RUNINFO__' => '', // 站点公共目录 '__SELF__' => '', // 站点公共目录 ]; $content = strtr($content, $replace); // 判断是否显示runtime info 信息 return $content; } /** * 编译解析 * * @param $content * @return mixed */ public static function compile(string $content) { $left = preg_quote('{', '/'); $right = preg_quote('}', '/'); if (strpos($content, '', $content); } if (!preg_match('/' . $left . '.*?' . $right . '/s', $content)) return $content; // 解析载入 $content = preg_replace_callback('/' . $left . 'include\s+file\s*\=\s*(\'|\")([^\}]*?)\1\s*' . $right . '/i', [ 'self', 'parseInlcude', ], $content); // 解析代码 $content = preg_replace_callback('/' . $left . '(code|php)' . $right . '(.*?)' . $left . '\/\1' . $right . '/is', [ 'self', 'parseEncode', ], $content); // 模板注释 $content = preg_replace('/' . $left . '\/\*.*?\*\/' . $right . '/s', '', $content); $content = preg_replace('/' . $left . '\/\/.*?' . $right . '/', '', $content); // 解析变量 $content = preg_replace_callback('/' . $left . '(\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.[\w\-]+)*))((?:\s*\|\s*[\w\:]+(?:\s*=\s*(?:@|"[^"]*"|\'[^\']*\'|#[\w\-]+|\$[\w\-]+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.[\w\-]+)*)|[^\|\:,"\'\s]*?)(?:\s*,\s*(?:@|"[^"]*"|\'[^\']*\'|#[\w\-]+|\$[\w\-]+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.[\w\-]+)*)|[^\|\:,"\'\s]*?))*)?)*)\s*' . $right . '/', [ 'self', 'parseVariable', ], $content); // 解析函数 $content = preg_replace_callback('/' . $left . '(\=|~)\s*(.+?)\s*' . $right . '/', [ 'self', 'parseFunction', ], $content); // 解析判断 $content = preg_replace_callback('/' . $left . '(if|else\s*if)\s+(.+?)\s*' . $right . '/', [ 'self', 'parseJudgment', ], $content); $content = preg_replace('/' . $left . 'else\s*' . $right . '/i', '', $content); $content = preg_replace('/' . $left . 'sectionelse\s*' . $right . '/i', '', $content); $content = preg_replace('/' . $left . '\/if\s*' . $right . '/i', '', $content); // 解析链接 $content = preg_replace_callback('/' . $left . 'link\=((?:"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^"\'\s]+?)(?:(?:\s+\w+\s*\=\s*(?:"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^"\'\s]+?))*?))\s*' . $right . '/i', [ 'self', 'parseLink', ], $content); // 解析微件 $content = preg_replace_callback('/' . $left . 'block((?:\s+\w+\s*\=\s*(?:"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^"\'\s]+?))+)\s*' . $right . '/i', [ 'self', 'parseBlock', ], $content); // 解析循环 $content = preg_replace_callback('/' . $left . 'loop\s*=([\'|"]?)(\$?\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*))\1\s*' . $right . '/i', [ 'self', 'parseLoop', ], $content); $content = preg_replace_callback('/' . $left . 'loop' . $right . '/i', ['self', 'parseLoop'], $content); $content = preg_replace_callback('/' . $left . 'section((?:\s+\w+\s*\=\s*(?:"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^"\'\s]+?))+)\s*' . $right . '/i', [ 'self', 'parseSection', ], $content); $content = preg_replace('/' . $left . '\/(?:loop|section)\s*' . $right . '/i', '', $content); // 还原代码 $content = preg_replace_callback('/' . chr(2) . '(.*?)' . chr(3) . '/', ['self', 'parseDecode'], $content); // 内容后续处理 /*if (!APP_DEBUG) { $content = preg_replace_callback('/]*>([^<]*)<\/style>/isU', array('self', 'parseCss'), $content); $content = preg_replace_callback('/]*>([^<]+?)<\/script>/isU', array('self', 'parseJs'), $content); $content = preg_replace(array("/>\s+ <'), $content); $content = preg_replace('/\?>\s*<\?php/', '', $content); $content = str_replace(array("\r\n", "\r", "\n", "\t", ' ', ' '), ' ', $content); $content = strip_whitespace($content); }*/ // 返回内容 return $content; } /** * css压缩 * * @param $match * @return string */ public static function parseCss(string $match) { return ''; } /** * js压缩 * * @param $march * @return mixed */ public static function parseJs(string $march) { return str_replace($march[1], self::compressJS($march[1]), $march[0]); } /** * 解析变量名 * * @param $var * @return array|mixed|string */ private static function parseVar($var) { $var = is_array($var) ? reset($var) : trim($var); if (substr($var, 0, 1) !== '$') { $var = '$' . $var; } if (preg_match('/^\$\w+(\.[\w\-]+)+$/', $var)) { if (substr($var, 0, 4) === '$kx.') { $vars = array_pad(explode('.', $var, 3), 3, ''); switch ($vars[1]) { case 'server': $var = '$_SERVER[\'' . strtoupper($vars[2]) . '\']'; break; case 'const': $var = strtoupper($vars[2]); break; case 'config': $var = '\Kuxin\Helper\Arr::getValue($_kxcms_config, "' . $vars[2] . '")'; break; case 'tplconfig': $var = 'self::getTplConfig("' . $vars[2] . '")'; break; case 'get': $var = '$_GET[\'' . $vars[2] . '\']'; break; case 'post': $var = '$_POST[\'' . $vars[2] . '\']'; break; case 'request': $var = '$_REQUEST[\'' . $vars[2] . '\']'; break; case 'cookie': $var = 'Cookie("' . $vars[2] . '")'; break; case 'ad': $var = 'self::getAd("' . $vars[2] . '")'; break; default: $var = strtoupper($vars[1]); break; } } else { $var = preg_replace('/\.(\w+)/', '[\'\1\']', strtolower($var)); } } else { $var = strtolower($var); } return $var; } /** * @param $string * @param $format * @return array * $format中值true则按照变量解析 其他为默认值 */ private static function parseAttribute(string $string, array $format) { $attribute = ['_etc' => []]; preg_match_all('/(?:^|\s+)(\w+)\s*\=\s*(?|(")([^"]*)"|(\')([^\']*)\'|(#)(\w+)|(\$)(\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*))|()([^"\'\s]+?))(?=\s+\w+\s*\=|$)/', $string, $match); foreach ($match[0] as $key => $value) { $name = strtolower($match[1][$key]); $value = strtolower(trim($match[3][$key])); if (isset($format[$name]) && is_bool($format[$name])) { $attribute[$name] = $format[$name] ? self::parseVar($value) : $value; } else { switch ($match[2][$key]) { case '#': $value = strtoupper($value); break; case '$': $value = self::parseVar($value); break; case '"': case '\'': $value = $match[2][$key] . $value . $match[2][$key]; break; default: $value = is_numeric($value) ? $value : var_export($value, true); } if (isset($format[$name])) { $attribute[$name] = $value; } else { $attribute['_etc'][$name] = $value; } } } return array_merge($format, $attribute); } /** * 解析变量 * * @param $matches * @return string */ private static function parseVariable($matches) { $variable = self::parseVar($matches[1]); if ($matches[2]) { preg_match_all('/\s*\|\s*([\w\:]+)(\s*=\s*(?:@|"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^\|\:,"\'\s]*?)(?:\s*,\s*(?:@|"[^"]*"|\'[^\']*\'|#\w+|\$\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*)|[^\|\:,"\'\s]*?))*)?(?=\||$)/', $matches[2], $match); foreach ($match[0] as $key => $value) { $function = $match[1][$key]; if (strtolower($function) == 'parsetpl') { return ""; } elseif (in_array($function, ['date', 'default'])) { $function = "\\Kuxin\\View::{$function}"; } elseif (in_array($function, ['truncate'])) { $function = "\\Kuxin\\Helper\\Str::{$function}"; } $param = [$variable]; preg_match_all('/(?:=|,)\s*(?|(@)|(")([^"]*)"|(\')([^\']*)\'|(#)(\w+)|(\$)(\w+(?:(?:\[(?:[^\[\]]+|(?R))*\])*|(?:\.\w+)*))|()([^\|\:,"\'\s]*?))(?=,|$)/', $match[2][$key], $mat); if (array_search('@', $mat[1]) !== false) $param = []; foreach ($mat[0] as $k => $v) { switch ($mat[1][$k]) { case '@': $param[] = $variable; break; case '#': $param[] = strtoupper($mat[2][$k]); break; case '$': $param[] = self::parseVar($mat[2][$k]); break; case '"': case '\'': $param[] = $mat[1][$k] . $mat[2][$k] . $mat[1][$k]; break; default: $param[] = is_numeric($mat[2][$k]) ? $mat[2][$k] : var_export($mat[2][$k], true); } } $variable = $function . '(' . implode(',', $param) . ')'; } } return ""; } /** * 解析载入 * * @param $matches * @return mixed|string */ private static function parseInlcude($matches) { //20141215 防止写空导致调用死循环 if ($matches['2']) { $includeFile = self::getTplFilePath($matches['2']); $truereturn = realpath($includeFile); if ($truereturn) { $content = file_get_contents($truereturn); return self::compile($content); } trigger_error("include参数有误,得不到设置的模版,参数[{$matches['2']}],解析模版路径[{$includeFile}]", E_USER_ERROR); } return ''; } /** * 解析函数 * * @param $matches * @return string */ private static function parseFunction($matches) { $operate = $matches[1] === '=' ? 'echo' : ''; $expression = preg_replace_callback('/\$\w+(?:\.\w+)+/', ['self', 'parseVar'], $matches[2]); return ""; } /** * 解析判断 * * @param $matches * @return string */ private static function parseJudgment($matches) { $judge = strtolower($matches[1]) === 'if' ? 'if' : 'elseif'; $condition = preg_replace_callback('/\$\w+(?:\.\w+)+/', ['self', 'parseVar'], $matches[2]); return ""; } /** * @param $matches * @return string */ private static function parseLink($matches) { $attribute = self::parseAttribute('_type_=' . $matches[1], ['_type_' => false, 'responsetype' => '""']); if (!is_string($attribute['_type_'])) return $matches[0]; $var = []; foreach ($attribute['_etc'] as $key => $value) { $var[] = "'$key'=>$value"; } return ""; } /** * @param $matches * @return string */ private static function parseBlock($matches) { $attribute = self::parseAttribute($matches[1], ['method' => false, 'name' => false]); $var = []; foreach ($attribute['_etc'] as $key => $value) { $var[] = "'$key'=>$value"; } if (empty($attribute['name']) || $attribute['name'] === false) { return ""; } else { $name = '$' . $attribute['name']; return ""; } } /** * 解析循环 * * @param $matches * @return string */ private static function parseLoop($matches) { $loop = empty($matches[2]) ? '$list' : (self::parseVar($matches[2])); return "\$loop):?>"; } /** * @param $matches * @return string */ private static function parseSection($matches) { $attribute = self::parseAttribute($matches[1], [ 'loop' => true, 'name' => true, 'item' => true, 'cols' => '1', 'skip' => '0', 'limit' => 'null', ]); if (!is_string($attribute['loop'])) return $matches[0]; $name = is_string($attribute['name']) ? $attribute['name'] : '$i'; $list = is_string($attribute['item']) ? $attribute['item'] : '$loop'; return "{$name}['list']): $list={$name}['list']; {$name}['order']++; {$name}['col']++; if({$name}['col']=={$attribute['cols']}): {$name}['col']=0; {$name}['row']++; endif; {$name}['first']={$name}['order']==1; {$name}['last']={$name}['order']=={$name}['count']; {$name}['extra']={$name}['order']>{$name}['count'];?>"; } /** * 保护代码 * * @param $matches * @return string */ private static function parseEncode($matches) { return chr(2) . base64_encode(strtolower($matches[1]) === 'php' ? "" : trim($matches[2])) . chr(3); } /** * 还原代码 * * @param $matches * @return bool|string */ private static function parseDecode($matches) { return base64_decode($matches[1]); } /** * @param $content * @return string */ public static function compressJS($content) { $lines = explode("\n", $content); foreach ($lines as &$line) { $line = trim($line) . "\n"; } return implode('', $lines); } /** * @param $content * @return mixed */ public static function compressCss($content) { $content = preg_replace('!/\*[^*]*\*+([^/][^*]*\*+)*/!', '', $content); //删除注释 $content = preg_replace('![ ]{2,}!', ' ', $content); //删除注释 $content = str_replace(["\r\n", "\r", "\n", "\t"], '', $content); //删除空白 return $content; } /** * 默认值函数 * * @return string */ public static function default() { $args = func_get_args(); $value = array_shift($args); if (is_bool($value)) { $value = intval($value); } if (!is_numeric($value)) { return $value; } elseif (isset($args[$value])) { return $args[$value]; } else { return ''; } } /** * 时间函数优化 * * @param $time * @param $format * @return mixed */ public static function date($time, $format) { if ($time == '0') return ''; return date($format, $time); } /** * @param string $content * @return string */ public static function parseTpl($content) { if ($content == '') return ''; $storage = DI::Storage('template'); $cachefile = 'parsetpl/' . md5($content . '_parseTpl') . '.php'; if (!$storage->exist($cachefile)) { $content = self::compile($content); $storage->write($cachefile, $content); } return $storage->getPath($cachefile); } public static function getTplConfig($key) { return Config::get('template.tplconfig')[basename(self::$_path)][$key]['value'] ?? ''; } public static function getAd($key) { return ""; } public static function getSuffix() { return self::$suffix; } public static function setSuffix($suffix) { self::$suffix = $suffix; } }