diff --git a/convention.php b/convention.php index 0e7d3904..fa1409cb 100644 --- a/convention.php +++ b/convention.php @@ -95,7 +95,7 @@ return [ // +---------------------------------------------------------------------- // 默认跳转页面对应的模板文件 - 'dispatch_jump_tmpl' => THINK_PATH . 'tpl/dispatch_jump.tpl', + 'dispatch_jump_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', // 默认的模板引擎 'template_engine' => 'Think', @@ -104,7 +104,7 @@ return [ // +---------------------------------------------------------------------- // 异常页面的模板文件 - 'exception_tmpl' => THINK_PATH . 'tpl/think_exception.tpl', + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', // 错误显示信息,非调试模式有效 'error_message' => '页面错误!请稍后再试~', // 错误定向页面 @@ -152,6 +152,7 @@ return [ // 是否启用多状态数据库配置 如果启用的话 需要跟随app_status配置不同的数据库信息 'use_db_switch' => false, + 'db_fields_strict' => true, 'database' => [ // 数据库类型 'type' => 'mysql', diff --git a/library/think/Model.php b/library/think/Model.php index 7982b3c2..c2cce2c2 100644 --- a/library/think/Model.php +++ b/library/think/Model.php @@ -190,14 +190,18 @@ class Model } } } + $fields = $this->getDbFields(); // 检查非数据字段 - if (!empty($this->fields)) { + if (!empty($fields)) { foreach ($data as $key => $val) { - if (!in_array($key, $this->fields, true)) { + if (!in_array($key, $fields, true)) { + if (Config::get('db_fields_strict')) { + throw new Exception(' fields not exists :[' . $key . '=>' . $val . ']'); + } unset($data[$key]); } elseif (is_scalar($val) && empty($this->options['bind'][':' . $key])) { // 字段类型检查 - $this->_parseType($data, $key); + $this->_parseType($data, $key, $this->options['bind']); } } } @@ -777,7 +781,7 @@ class Model $key = trim($key); if (in_array($key, $fields, true)) { if (is_scalar($val) && empty($options['bind'][':' . $key])) { - $this->_parseType($options['where'], $key); + $this->_parseType($options['where'], $key, $options['bind']); } } } @@ -799,18 +803,21 @@ class Model * @param string $key 字段名 * @return void */ - protected function _parseType(&$data, $key) + protected function _parseType(&$data, $key, &$bind) { - if (!isset($this->options['bind'][':' . $key]) && isset($this->fields['_type'][$key])) { + if (!isset($bind[':' . $key]) && isset($this->fields['_type'][$key])) { $fieldType = strtolower($this->fields['_type'][$key]); if (false !== strpos($fieldType, 'enum')) { // 支持ENUM类型优先检测 } elseif (false === strpos($fieldType, 'bigint') && false !== strpos($fieldType, 'int')) { - $data[$key] = intval($data[$key]); + $bind[':' . $key] = [$data[$key], \PDO::PARAM_INT]; + $data[$key] = ':' . $key; } elseif (false !== strpos($fieldType, 'float') || false !== strpos($fieldType, 'double')) { - $data[$key] = floatval($data[$key]); + $bind[':' . $key] = [$data[$key], \PDO::PARAM_INT]; + $data[$key] = ':' . $key; } elseif (false !== strpos($fieldType, 'bool')) { - $data[$key] = (bool) $data[$key]; + $bind[':' . $key] = [$data[$key], \PDO::PARAM_BOOL]; + $data[$key] = ':' . $key; } elseif (false !== strpos($fieldType, 'json') && is_array($data[$key])) { $data[$key] = json_encode($data[$key]); } diff --git a/library/think/Template.php b/library/think/Template.php index 8a6da9fe..ffc263b9 100644 --- a/library/think/Template.php +++ b/library/think/Template.php @@ -593,40 +593,26 @@ class Template $flag = substr($str, 0, 1); switch ($flag) { case '$': // 解析模板变量 格式 {$varName} - $this->parseVar($str); - switch ($this->config['tpl_var_identify']) { - case 'array': - $begin = 0; - break; - case 'obj': - $begin = 1; - break; - default: - // 如果是自动识别.语法,则要查找:之后的?号 - $begin = strpos($str, ':'); - } // 是否带有?号 - if (false !== $pos = strpos($str, '?', $begin)) { + if (false !== $pos = strpos($str, '?')) { $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); - $name = trim($array[0]); + $name = $array[0]; + $this->parseVar($name); $this->parseVarFunction($name); $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); $first = substr($str, 0, 1); if (isset($array[1])) { - // 设置了判断条件 - // XXX: 加入这句原本是为解决变量末声明的问题,但$name中是多个条件时会解析错误,故注释掉 - /*if (strpos($name, '[')) { - $name = 'isset(' . $name . ') && ' . $name; - }*/ - $name .= $array[1] . trim($array[2]); + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; if ('=' == $first) { // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; + $str = ''; } else { - $str = ''; + $str = ''; } - } elseif ($begin || ')' == substr($name, -1, 1)) { + } elseif (')' == substr($name, -1, 1)) { // $name为对象或是自动识别,或者含有函数 switch ($first) { case '?': @@ -651,18 +637,19 @@ class Template break; case ':': // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = ''; + $str = ''; break; default: if (strpos($str, ':')) { // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b - $str = ''; + $str = ''; } else { $str = ''; } } } } else { + $this->parseVar($str); $this->parseVarFunction($str); $str = ''; } @@ -674,11 +661,14 @@ class Template break; case '~': // 执行某个函数 $str = substr($str, 1); + $this->parseVar($str); $str = ''; break; case '-': case '+': // 输出计算 - $str = ''; + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; break; case '/': // 注释标签 $flag2 = substr($str, 1, 1); @@ -777,9 +767,9 @@ class Template switch ($fun) { case 'default': // 特殊模板函数 if (false === strpos($name, '(')) { - $name = '((isset(' . $name . ') && (' . $name . ' !== \'\')?(' . $name . '):' . $args[1] . ')'; + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; } else { - $name = '((' . $name . ' !== \'\')?(' . $name . '):' . $args[1] . ')'; + $name = '(' . $name . ' !== \'\'?' . $name . ':' . $args[1] . ')'; } break; default: // 通用模板函数 @@ -981,9 +971,9 @@ class Template $begin = $this->config['tpl_begin']; $end = $this->config['tpl_end']; if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { - $regex = $begin . '((?:[\$\:\-\+][a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + $regex = $begin . '((?:[\$\:\-\+~][\$a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; } else { - $regex = $begin . '((?:[\$\:\-\+][a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + $regex = $begin . '((?:[\$\:\-\+~][\$a-wA-w_][\w\.\:\[\(\*\/\-\+\%_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; } break; } diff --git a/library/think/Url.php b/library/think/Url.php index 2f47a7e0..2fc5069a 100644 --- a/library/think/Url.php +++ b/library/think/Url.php @@ -24,17 +24,36 @@ class Url * @param boolean|string $domain 是否显示域名 或者直接传入域名 * @return string */ - public static function build($url = '', $vars = '', $suffix = true, $domain = true) + public static function build($url = '', $vars = '', $suffix = true, $domain = false) { + // 解析URL + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (false !== strpos($url, '@')) { + // 解析域名 + list($url, $domain) = explode('@', $info['path'], 2); + } + // 解析参数 if (is_string($vars)) { // aaa=1&bbb=2 转换成数组 parse_str($vars, $vars); } - if (strpos($url, '?')) { - list($url, $params) = explode('?', $url); - parse_str($params, $params); + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); $vars = array_merge($params, $vars); } @@ -63,22 +82,24 @@ class Url // URL后缀 $suffix = self::parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; // 参数组装 if (!empty($vars)) { // 添加参数 if (Config::get('url_common_param')) { $vars = urldecode(http_build_query($vars)); - $url .= $suffix . '?' . $vars; + $url .= $suffix . $anchor . '?' . $vars; } else { foreach ($vars as $var => $val) { if ('' !== trim($val)) { $url .= $depr . $var . $depr . urlencode($val); } } - $url .= $suffix; + $url .= $suffix . $anchor; } } else { - $url .= $suffix; + $url .= $suffix . $anchor; } // 检测域名 @@ -136,6 +157,8 @@ class Url } } } + } else { + $domain = $domain . (strpos($domain, '.') ? '' : strstr($_SERVER['HTTP_HOST'], '.')); } $domain = (self::isSsl() ? 'https://' : 'http://') . $domain; } else { @@ -226,7 +249,9 @@ class Url $key = array_shift($route); } $route = $route[0]; - if (strpos($route, '?')) { + if (is_array($route)) { + $route = implode('\\', $route); + } elseif (strpos($route, '?')) { $route = strstr($route, '?', true); } $var = self::parseVar($rule . '/' . $key); @@ -234,7 +259,9 @@ class Url } } else { $route = $val['route']; - if (strpos($route, '?')) { + if (is_array($route)) { + $route = implode('\\', $route); + } elseif (strpos($route, '?')) { $route = strstr($route, '?', true); } $var = self::parseVar($rule); diff --git a/library/think/db/Driver.php b/library/think/db/Driver.php index 0e0e8937..ba68ad96 100644 --- a/library/think/db/Driver.php +++ b/library/think/db/Driver.php @@ -41,23 +41,39 @@ abstract class Driver protected $_linkID = null; // 数据库连接参数配置 protected $config = [ - 'type' => '', // 数据库类型 - 'hostname' => '127.0.0.1', // 服务器地址 - 'database' => '', // 数据库名 - 'username' => '', // 用户名 - 'password' => '', // 密码 - 'hostport' => '', // 端口 - 'dsn' => '', // - 'params' => [], // 数据库连接参数 - 'charset' => 'utf8', // 数据库编码默认采用utf8 - 'prefix' => '', // 数据库表前缀 - 'debug' => false, // 数据库调试模式 - 'deploy' => 0, // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'rw_separate' => false, // 数据库读写是否分离 主从式有效 - 'master_num' => 1, // 读写分离后 主服务器数量 - 'slave_no' => '', // 指定从服务器序号 - 'db_like_fields' => '', // like字段自动替换为%%包裹 - 'debug' => false, // 是否调试 + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // like字段自动替换为%%包裹 + 'db_like_fields' => '', + // 是否开启数据库调试 + 'debug' => false, ]; // 数据库表达式 protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN']; @@ -153,7 +169,9 @@ abstract class Driver $this->queryStr = $str; if (!empty($this->bind)) { $that = $this; - $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return $that->quote($val);}, $this->bind)); + $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) { + return $that->quote(is_array($val) ? $val[0] : $val); + }, $this->bind)); } if ($fetchSql) { return $this->queryStr; @@ -212,7 +230,9 @@ abstract class Driver $this->queryStr = $str; if (!empty($this->bind)) { $that = $this; - $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) {return $that->quote($val);}, $this->bind)); + $this->queryStr = strtr($this->queryStr, array_map(function ($val) use ($that) { + return $that->quote(is_array($val) ? $val[0] : $val); + }, $this->bind)); } if ($fetchSql) { return $this->queryStr; @@ -409,7 +429,7 @@ abstract class Driver } elseif (is_scalar($val)) { // 过滤非标量数据 if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) { - $set[] = $this->parseKey($key) . '=' . $this->quote($val); + $set[] = $this->parseKey($key) . '=' . $val; } else { $name = count($this->bind); $set[] = $this->parseKey($key) . '=:' . $name; @@ -890,7 +910,7 @@ abstract class Driver // 过滤非标量数据 $fields[] = $this->parseKey($key); if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) { - $values[] = $this->parseValue($val); + $values[] = $val; } else { $name = count($this->bind); $values[] = ':' . $name; @@ -932,7 +952,7 @@ abstract class Driver $value[] = 'NULL'; } elseif (is_scalar($val)) { if (0 === strpos($val, ':') && in_array($val, array_keys($this->bind))) { - $value[] = $this->parseValue($val); + $value[] = $val; } else { $name = count($this->bind); $value[] = ':' . $name; diff --git a/library/think/template/TagLib.php b/library/think/template/TagLib.php index 58d2b9b9..be02d6a0 100644 --- a/library/think/template/TagLib.php +++ b/library/think/template/TagLib.php @@ -324,7 +324,10 @@ class TagLib $_taglibs[$tag][0] = strlen(ltrim($this->tpl->config('taglib_begin'), '\\') . $tag); $_taglibs[$tag][1] = strlen(ltrim($this->tpl->config('taglib_end'), '\\')); } - $result['expression'] = trim(substr($str, $_taglibs[$tag][0], -$_taglibs[$tag][1])); + $result['expression'] = substr($str, $_taglibs[$tag][0], -$_taglibs[$tag][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); } elseif (empty($this->tags[$tag]) || !empty($this->tags[$tag]['attr'])) { throw new Exception('_XML_TAG_ERROR_:' . $tag); } diff --git a/library/think/template/taglib/Cx.php b/library/think/template/taglib/Cx.php index 377cb0b6..27755697 100644 --- a/library/think/template/taglib/Cx.php +++ b/library/think/template/taglib/Cx.php @@ -253,7 +253,7 @@ class Cx extends Taglib { $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; $name = $this->autoBuildVar($name); - $parseStr = '' . $content . ''; + $parseStr = '' . $content . ''; return $parseStr; } @@ -270,20 +270,20 @@ class Cx extends Taglib $flag = substr($value, 0, 1); if ('$' == $flag || ':' == $flag) { $value = $this->autoBuildVar($value); - $value = 'case ' . $value . ': '; + $value = 'case ' . $value . ':'; } elseif (strpos($value, '|')) { $values = explode('|', $value); $value = ''; foreach ($values as $val) { - $value .= 'case "' . addslashes($val) . '": '; + $value .= 'case "' . addslashes($val) . '":'; } } else { - $value = 'case "' . $value . '": '; + $value = 'case "' . $value . '":'; } $parseStr = '' . $content; $isBreak = isset($tag['break']) ? $tag['break'] : ''; if ('' == $isBreak || $isBreak) { - $parseStr .= ''; + $parseStr .= ''; } return $parseStr; }