|
- <?php
-
- namespace Kuxin;
-
- use Kuxin\Helper\Arr;
- use Kuxin\Helper\OpenCC;
-
- /**
- * Class View
- *
- * @package Kuxin
- * @author Pakey <pakey@qq.com>
- */
- 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 = '<?php defined(\'KX_ROOT\') || exit(\'Permission denied\');?>' . self::replace($content);
- $storage->write($compiledFile, $content);
- }
- return $storage->getPath($compiledFile);
- }
-
- /**
- * 模版输出替换
- *
- * @param $content
- * @return string
- */
- protected static function replace(string $content)
- {
- $replace = [
- '__RUNINFO__' => '<?php echo \Kuxin\Response::runinfo();?>', // 站点公共目录
- '__SELF__' => '<?php echo $_SERVER[\'REQUEST_URI\'];?>', // 站点公共目录
- ];
- $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, '<?xml') !== false) {
- $content = str_replace('<?xml', '<?php echo "<?xml";?>', $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', '<?php else:?>', $content);
- $content = preg_replace('/' . $left . 'sectionelse\s*' . $right . '/i', '<?php endforeach;else:foreach(array(1) as $__loop):?>', $content);
- $content = preg_replace('/' . $left . '\/if\s*' . $right . '/i', '<?php endif;?>', $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', '<?php endforeach; endif;?>', $content);
- // 还原代码
- $content = preg_replace_callback('/' . chr(2) . '(.*?)' . chr(3) . '/', ['self', 'parseDecode'], $content);
- // 内容后续处理
-
- /*if (!APP_DEBUG) {
- $content = preg_replace_callback('/<style[^>]*>([^<]*)<\/style>/isU', array('self', 'parseCss'), $content);
- $content = preg_replace_callback('/<script[^>]*>([^<]+?)<\/script>/isU', array('self', 'parseJs'), $content);
- $content = preg_replace(array("/>\s+</"), array('> <'), $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 '<style type = "text/css">' . self::compressCss($match[1]) . '</style>';
- }
-
- /**
- * 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 "<?php include \\Kuxin\\View::parseTpl($variable);?>";
- } 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 "<?php echo $variable;?>";
- }
-
-
- /**
- * 解析载入
- *
- * @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 "<?php $operate $expression;?>";
- }
-
- /**
- * 解析判断
- *
- * @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 "<?php $judge($condition):?>";
- }
-
- /**
- * @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 "<?php echo \\Kuxin\\Helper\\Url::build(\"{$attribute['_type_']}\",[" . implode(',', $var) . "],{$attribute['responsetype']});?>";
- }
-
- /**
- * @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 "<?php echo \\Kuxin\\Block::show('{$attribute['method']}', [" . implode(',', $var) . "]);?>";
- } else {
- $name = '$' . $attribute['name'];
- return "<?php $name=\\Kuxin\\Block::show('{$attribute['method']}', [" . implode(',', $var) . "]);?>";
- }
- }
-
- /**
- * 解析循环
- *
- * @param $matches
- * @return string
- */
- private static function parseLoop($matches)
- {
- $loop = empty($matches[2]) ? '$list' : (self::parseVar($matches[2]));
- return "<?php if(is_array($loop)): foreach($loop as \$key =>\$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 "<?php if(is_array({$attribute['loop']}) && (array()!={$attribute['loop']})): $name=array(); {$name}['loop']=array_slice({$attribute['loop']},{$attribute['skip']},{$attribute['limit']},true); {$name}['total']=count({$attribute['loop']}); {$name}['count']=count({$name}['loop']); {$name}['cols']={$attribute['cols']}; {$name}['add']={$name}['count']%{$attribute['cols']}?{$attribute['cols']}-{$name}['count']%{$attribute['cols']}:0; {$name}['order']=0; {$name}['row']=1;{$name}['col']=0;foreach(array_pad({$name}['loop'],{$name}['add'],array()) as {$name}['index']=>{$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' ? "<?php {$matches[2]};?>" : 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 "<script type=\"text/javascript\" src=\"/" . Config::get('ad.path', 'ad') . "/{$key}.js\"></script>";
- }
-
- public static function getSuffix()
- {
- return self::$suffix;
- }
-
- public static function setSuffix($suffix)
- {
- self::$suffix = $suffix;
- }
- }
|