From 4f2d7a0afdd2e609d60458a51d6c5455a7812d80 Mon Sep 17 00:00:00 2001 From: hisiphp Date: Wed, 5 Sep 2018 23:43:03 +0800 Subject: [PATCH 01/32] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=88=B01.0.8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [修复] 多入口文件支持 [修复] 系统配置排序无效 [修复] 框架布局时,后台首页链接错误 [修复] 左侧菜单折叠显示异常 [更新] 云通信平台接口地址更新 [更新] 控制器分层支持 [更新] index、install模块控制器目录名称调整 [更新] layui升级到2.4.3 --- admin.php | 8 +- app/admin/controller/Admin.php | 1 + app/admin/controller/Upgrade.php | 12 +- app/admin/controller/User.php | 2 +- app/admin/model/AdminModule.php | 4 +- app/admin/model/AdminPlugins.php | 6 +- app/admin/view/block/menu.php | 6 +- app/admin/view/config/index.php | 2 +- app/common/behavior/Base.php | 4 +- app/common/behavior/Init.php | 10 +- app/common/controller/Plugins.php | 14 + app/common/model/AdminMember.php | 68 +-- app/common/taglib/Hisi.php | 108 +++++ app/common/util/Cloud.php | 4 +- app/common/util/Dir.php | 3 +- app/common/validate/AdminMember.php | 28 +- app/config.php | 4 +- app/index/{controller => home}/Error.php | 4 +- app/index/{controller => home}/Home.php | 2 +- app/index/{controller => home}/Plugins.php | 2 +- app/index/{controller => home}/Push.php | 2 +- app/install/{controller => home}/Error.php | 13 +- app/install/sql/install.sql | 12 +- index.php | 2 +- static/admin/css/style.css | 395 ++++++++---------- static/admin/js/global.js | 13 +- static/admin/js/layui/css/layui.css | 4 +- static/admin/js/layui/css/layui.mobile.css | 2 +- static/admin/js/layui/css/modules/code.css | 2 +- .../css/modules/laydate/default/laydate.css | 2 +- .../layui/css/modules/layer/default/layer.css | 2 +- static/admin/js/layui/font/iconfont.eot | Bin 40144 -> 40844 bytes static/admin/js/layui/font/iconfont.svg | 19 +- static/admin/js/layui/font/iconfont.ttf | Bin 39968 -> 40668 bytes static/admin/js/layui/font/iconfont.woff | Bin 26328 -> 26744 bytes static/admin/js/layui/lay/modules/carousel.js | 2 +- static/admin/js/layui/lay/modules/code.js | 2 +- .../admin/js/layui/lay/modules/colorpicker.js | 2 + static/admin/js/layui/lay/modules/element.js | 2 +- static/admin/js/layui/lay/modules/flow.js | 2 +- static/admin/js/layui/lay/modules/form.js | 4 +- static/admin/js/layui/lay/modules/jquery.js | 2 +- static/admin/js/layui/lay/modules/laydate.js | 4 +- static/admin/js/layui/lay/modules/layedit.js | 2 +- static/admin/js/layui/lay/modules/layer.js | 4 +- static/admin/js/layui/lay/modules/laypage.js | 2 +- static/admin/js/layui/lay/modules/laytpl.js | 2 +- static/admin/js/layui/lay/modules/mobile.js | 2 +- static/admin/js/layui/lay/modules/rate.js | 2 +- static/admin/js/layui/lay/modules/slider.js | 2 + static/admin/js/layui/lay/modules/table.js | 4 +- static/admin/js/layui/lay/modules/tree.js | 2 +- static/admin/js/layui/lay/modules/upload.js | 4 +- static/admin/js/layui/lay/modules/util.js | 4 +- static/admin/js/layui/layui.js | 4 +- version.php | 2 +- 56 files changed, 466 insertions(+), 350 deletions(-) create mode 100644 app/common/taglib/Hisi.php rename app/index/{controller => home}/Error.php (92%) rename app/index/{controller => home}/Home.php (98%) rename app/index/{controller => home}/Plugins.php (98%) rename app/index/{controller => home}/Push.php (99%) rename app/install/{controller => home}/Error.php (96%) create mode 100755 static/admin/js/layui/lay/modules/colorpicker.js create mode 100755 static/admin/js/layui/lay/modules/slider.js diff --git a/admin.php b/admin.php index 296fe6f..c2cdc64 100644 --- a/admin.php +++ b/admin.php @@ -4,7 +4,7 @@ // +---------------------------------------------------------------------- // | Copyright (c) 2016-2018 http://www.hisiphp.com // +---------------------------------------------------------------------- -// | HisiPHP承诺基础框架永久免费开源,您可用于学习和商用,但必须保留软件版权信息。 +// | HisiPHP提供个人非商业用途免费使用,商业需授权。 // +---------------------------------------------------------------------- // | Author: 橘子俊 <364666827@qq.com>,开发者QQ群:50304283 // +---------------------------------------------------------------------- @@ -12,18 +12,14 @@ header('Content-Type:text/html;charset=utf-8'); // 检测PHP环境 if(version_compare(PHP_VERSION,'5.5.0','<')) die('PHP版本过低,最少需要PHP5.5,请升级PHP版本!'); - // 定义应用目录 define('APP_PATH', __DIR__ . '/app/'); - // 定义入口为admin define('ENTRANCE', 'admin'); - // 检查是否安装 -if(!is_file(APP_PATH.'install/install.lock')) { +if(!is_file(APP_PATH.'install/install.lock') && !is_file(APP_PATH.'install.lock')) { header('Location: /'); exit; } - // 加载框架引导文件 require __DIR__ . '/thinkphp/start.php'; \ No newline at end of file diff --git a/app/admin/controller/Admin.php b/app/admin/controller/Admin.php index d3c2e59..7586b72 100644 --- a/app/admin/controller/Admin.php +++ b/app/admin/controller/Admin.php @@ -97,6 +97,7 @@ class Admin extends Common $this->assign('pages', ''); // 编辑页默认数据输出变量 $this->assign('data_info', ''); + $this->assign('form_data', ''); $this->assign('admin_user', $login); $this->assign('languages', model('AdminLanguage')->lists()); } diff --git a/app/admin/controller/Upgrade.php b/app/admin/controller/Upgrade.php index 8588972..811f48b 100644 --- a/app/admin/controller/Upgrade.php +++ b/app/admin/controller/Upgrade.php @@ -85,15 +85,19 @@ class Upgrade extends Admin $res = $this->cloud->data($data)->api('bind'); if (isset($res['code']) && $res['code'] == 1) { // 缓存站点标识 + $file = APP_PATH.'extra'.DS.'hs_cloud.php'; $str = " '".$res['data']."'];\n"; - file_put_contents(APP_PATH.'extra'.DS.'hs_cloud.php', $str); - if (is_file(APP_PATH.'extra'.DS.'hs_cloud.php')) { - $cloud = include_once APP_PATH.'extra'.DS.'hs_cloud.php'; + if (is_file($file)) { + unlink($file); + } + file_put_contents($file, $str); + if (is_file($file)) { + $cloud = include_once $file; if (isset($cloud['identifier']) && !empty($cloud['identifier'])) { return $this->success('恭喜您,已成功绑定云平台账号。'); } } - return $this->error('extra'.DS.'hs_cloud.php写入失败!'); + return $this->error('extra/hs_cloud.php写入失败!'); } return $this->error($res['msg'] ? $res['msg'] : '云平台绑定失败!(-0)'); } diff --git a/app/admin/controller/User.php b/app/admin/controller/User.php index 7a35223..8d1bb0b 100644 --- a/app/admin/controller/User.php +++ b/app/admin/controller/User.php @@ -101,7 +101,7 @@ class User extends Admin */ public function setTheme() { - $theme = input('param.theme', 'default'); + $theme = input('param.theme', 0); if (UserModel::setTheme($theme, true) === false) { return $this->error('设置失败'); } diff --git a/app/admin/model/AdminModule.php b/app/admin/model/AdminModule.php index 63bb41b..83fb0c9 100644 --- a/app/admin/model/AdminModule.php +++ b/app/admin/model/AdminModule.php @@ -221,7 +221,7 @@ class AdminModule extends Model rmdir($path.'controller'); // 生成后台默认控制器 if (is_dir($path.'admin')) { - $admin_contro = "fetch()'.";\n }\n}"; + $admin_contro = "fetch()'.";\n }\n}"; // 删除框架生成的html文件 @unlink($path . 'view'.DS.'index'.DS.'index.html'); file_put_contents($path . 'admin'.DS.'Index.php', $admin_contro); @@ -230,7 +230,7 @@ class AdminModule extends Model // 生成前台默认控制器 if (is_dir($path.'home')) { - $home_contro = "fetch()'.";\n }\n}"; + $home_contro = "fetch()'.";\n }\n}"; file_put_contents($path . 'home'.DS.'Index.php', $home_contro); file_put_contents(ROOT_PATH.'theme'.DS.$data['name'].DS.'default'.DS.'index'.DS.'index.php', ''); } diff --git a/app/admin/model/AdminPlugins.php b/app/admin/model/AdminPlugins.php index ad86b3d..b61485e 100755 --- a/app/admin/model/AdminPlugins.php +++ b/app/admin/model/AdminPlugins.php @@ -114,7 +114,7 @@ class AdminPlugins extends Model $data['status'] = 0; $data['icon'] = ROOT_DIR.'plugins/'.$data['name'].'/'.$data['name'].'.png'; // 验证 - $valid = Loader::validate('Plugins'); + $valid = Loader::validate('app\admin\validate\Plugins'); if($valid->check($data) !== true) { $this->error = $valid->getError(); return false; @@ -182,12 +182,12 @@ class AdminPlugins extends Model public function mkExample($path = '', $data = []) { if (is_dir($path.'admin')) { - $admin = "fetch()'.";\n }\n}"; + $admin = "fetch()'.";\n }\n}"; file_put_contents($path . 'admin'.DS.'Index.php', $admin); file_put_contents($path.'view'.DS.'admin'.DS.'index'.DS.'index.php', "\n{include file=\"admin@block/layui\" /}"); } if (is_dir($path.'home')) { - $home = "fetch()'.";\n }\n}"; + $home = "fetch()'.";\n }\n}"; file_put_contents($path . 'home'.DS.'Index.php', $home); file_put_contents($path.'view'.DS.'home'.DS.'index'.DS.'index.php', ''); } diff --git a/app/admin/view/block/menu.php b/app/admin/view/block/menu.php index ded3ab5..51306b3 100644 --- a/app/admin/view/block/menu.php +++ b/app/admin/view/block/menu.php @@ -47,13 +47,13 @@ {$vv['title']}
{if condition="$vv['title'] eq '快捷菜单'"} -
后台首页
+
后台首页
{volist name="vv['childs']" id="vvv"} -
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
+
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
{/volist} {else /} {volist name="vv['childs']" id="vvv"} -
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
+
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
{/volist} {/if}
diff --git a/app/admin/view/config/index.php b/app/admin/view/config/index.php index 6634206..1a58e6b 100644 --- a/app/admin/view/config/index.php +++ b/app/admin/view/config/index.php @@ -13,7 +13,7 @@ + + \ No newline at end of file diff --git a/app/admin/view/block/menu.php b/app/admin/view/block/menu.php index 51306b3..0ded75d 100644 --- a/app/admin/view/block/menu.php +++ b/app/admin/view/block/menu.php @@ -49,7 +49,7 @@ {if condition="$vv['title'] eq '快捷菜单'"}
后台首页
{volist name="vv['childs']" id="vvv"} -
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
+
{if condition="file_exists('.'.$vvv['icon'])"}{else /}{/if} {$vvv['title']}
{/volist} {else /} {volist name="vv['childs']" id="vvv"} diff --git a/app/admin/view/develop/edit.php b/app/admin/view/develop/edit.php index fba87fd..82e60c7 100644 --- a/app/admin/view/develop/edit.php +++ b/app/admin/view/develop/edit.php @@ -147,230 +147,6 @@ -
- - -
- {include file="block/layui" /} \ No newline at end of file diff --git a/app/admin/view/member/index.php b/app/admin/view/member/index.php index 8869c2f..8d0e3d5 100644 --- a/app/admin/view/member/index.php +++ b/app/admin/view/member/index.php @@ -1,10 +1,14 @@ +
-
+
- +
@@ -18,51 +22,47 @@
- - - - - - - - - - - - - - - - - - {php} - $level = config('hs_system.member_level'); - {/php} - {volist name="data_list" id="vo"} - - - - - - - - - - - {/volist} - -
会员等级&经验资金积分注册&登陆状态操作
- -

{$vo['username']} ({$vo['nick']})
手机:{$vo['mobile']}
邮箱:{$vo['email']}

-
{$level[$vo['level_id']]['name']}
经验值:{$vo['exper']}
余额:{$vo['money']}
冻结:{$vo['frozen_money']}
积分:{$vo['integral']}
冻结:{$vo['frozen_integral']}
注册:{$vo['ctime']}
登陆:{:date('Y-m-d H:i:s', $vo['last_login_time'])}
-
-
- - -
-
-
- {$pages} +
+ + +
-{include file="block/layui" /} \ No newline at end of file +{include file="block/layui" /} + \ No newline at end of file diff --git a/app/admin/view/module/index.php b/app/admin/view/module/index.php index d15426d..5833ea0 100644 --- a/app/admin/view/module/index.php +++ b/app/admin/view/module/index.php @@ -55,7 +55,7 @@ 配置 {/if} {if condition="$vo['app_id'] gt 0"} - 更新 + 更新 {else /} {/if} 卸载 diff --git a/app/admin/view/module/theme.php b/app/admin/view/module/theme.php index 0d62238..fc66809 100644 --- a/app/admin/view/module/theme.php +++ b/app/admin/view/module/theme.php @@ -7,7 +7,16 @@
作者:{$vo['author']}
版本:{$vo['version']}
时间:{$vo['time']}
-
{if condition="$vo['name'] eq $data_info['theme']"}默认主题{else /}设为默认{/if}{if condition="isset($vo['identifier']) && !empty($vo['identifier'])"}更新{/if}删除
+
+ {if condition="$vo['name'] eq $data_info['theme']"} + 默认主题 + {else /} + 设为默认 + {/if} + {if condition="isset($vo['identifier']) && !empty($vo['identifier'])"} + 更新 + {/if} + 删除
{/volist} diff --git a/app/admin/view/publics/icon.php b/app/admin/view/publics/icon.php index c142c13..4d731e2 100644 --- a/app/admin/view/publics/icon.php +++ b/app/admin/view/publics/icon.php @@ -25,6 +25,7 @@
    +
  • diff --git a/app/common/model/AdminMember.php b/app/common/model/AdminMember.php index 8d2b9d7..cbbfe45 100644 --- a/app/common/model/AdminMember.php +++ b/app/common/model/AdminMember.php @@ -55,19 +55,20 @@ class AdminMember extends Model // 有效期 public function setExpireTimeAttr($value) { - if ($value != 0) { - return strtotime($value); - } - return 0; + if (!$value) return 0; + return strtotime($value); } // 有效期 public function getExpireTimeAttr($value) { - if ($value != 0) { - return date('Y-m-d', $value); - } - return 0; + if (!$value) return 0; + return date('Y-m-d', $value); + } + + public function hasLevel() + { + return $this->hasOne('AdminMemberLevel', 'id', 'level_id'); } /** @@ -86,9 +87,8 @@ class AdminMember extends Model $map['nick'] = isset($data['nick']) ? $data['nick'] : ''; $map['avatar'] = isset($data['avatar']) ? $data['avatar'] : ''; $map['level_id'] = 0; - if (!isset($data['password']) || empty($data['password'])) { - $this->error = '密码为必填项!'; - return false; + if (isset($data['password'])) { + $map['password'] = $data['password']; } if (isset($data['account'])) { @@ -113,7 +113,7 @@ class AdminMember extends Model if (isset($data['username']) && is_username($data['username'])) { $map['username'] = $data['username']; } - $map['password'] = $data['password']; + $level = model('AdminMemberLevel')->where('default',1)->find(); if ($level) { @@ -121,9 +121,8 @@ class AdminMember extends Model $map['expire_time'] = $level['expire'] > 0 ? strtotime('+ '.$level['expire'].' days') : 0; } - $res = $this->validate('AdminMember')->isUpdate(false)->save(array_filter($map)); + $res = $this->validate('AdminMember.register')->isUpdate(false)->save($map); if (!$res) { - $this->error = $this->getError() ? $this->getError() : '注册失败!'; return false; } $map['id'] = $this->id; @@ -167,7 +166,7 @@ class AdminMember extends Model } $res = $this->create($map); if (!$res) { - $this->error = $this->getError() ? $this->getError() : '授权登录失败!'; + $this->error = $this->getError() ? : '授权登录失败!'; return false; } @@ -191,10 +190,6 @@ class AdminMember extends Model $account = trim($account); $password = trim($password); $field = trim($field, ','); - if (empty($account) || empty($password)) { - $this->error = '请输入账号和密码!'; - return false; - } $map = $rule = []; $map['status'] = 1; @@ -214,12 +209,14 @@ class AdminMember extends Model return false; } $rule['password'] = $password; + if ($token !== false) { $rule['__token__'] = input('param.__token__') ? input('param.__token__') : $token; $scene = 'login_token'; } else { $scene = 'login'; } + // 验证 if ($this->validateData($rule, 'AdminMember.'.$scene) != true) { $this->error = $this->getError(); @@ -231,28 +228,26 @@ class AdminMember extends Model $this->error = '用户不存在或被禁用!'; return false; } - + $member = $member->toArray(); // 密码校验 - if (!password_verify($password, $member->password)) { + if (!password_verify($password, $member['password'])) { $this->error = '登陆密码错误!'; return false; } - // 检查有效期 - if ($member->expire_time > 0 && $member->expire_time < request()->time()) { + if ($member['expire_time'] !== 0 && strtotime($member['expire_time']) < time()) { $this->error = '账号已过期!'; return false; } - $login = []; - $login['id'] = $member->id; - $login['level_id'] = $member->level_id; + $login['id'] = $member['id']; + $login['level_id'] = $member['level_id']; $fields = explode(',', $field); foreach ($fields as $v) { if ($v == 'password') { continue; } - $login[$v] = $member->$v; + $login[$v] = $member[$v]; } return self::autoLogin($login); } @@ -289,9 +284,9 @@ class AdminMember extends Model $this->error = '用户不存在或被禁用!'; return false; } - + $data = $data->toArray(); // 检查有效期 - if ($data['expire_time'] > 0 && $data['expire_time'] < request()->time()) { + if ($data['expire_time'] !== 0 && strtotime($data['expire_time']) < time()) { $this->error = '账号已过期!'; return false; } diff --git a/app/common/util/Cloud.php b/app/common/util/Cloud.php index 92aa8ed..6c9c224 100644 --- a/app/common/util/Cloud.php +++ b/app/common/util/Cloud.php @@ -159,9 +159,9 @@ class Cloud { return $result; } if($this->type == 'get'){ - $result = http::getRequest($this->api, $params); + $result = Http::get($this->api, $params); }elseif ($this->type == 'post') { - $result = http::postRequest($this->api, $params); + $result = Http::post($this->api, $params); } return self::_response($result); } diff --git a/app/common/util/Http.php b/app/common/util/Http.php index e82dd56..a2828f4 100644 --- a/app/common/util/Http.php +++ b/app/common/util/Http.php @@ -1,116 +1,184 @@ ,开发者QQ群:50304283 +// +---------------------------------------------------------------------- namespace app\common\util; class Http { /** - * 用CURL模拟获取网页页面内容 - * + * 用CURL模拟获取网页内容,兼容旧版 * @param string $url 所要获取内容的网址 - * @param array $data 所要提交的数据 + * @param array $params 所要提交的数据 * @param array $header 请求头信息 * @param string $charset 编码 * @param string $proxy 代理设置 * @param integer $expire 时间限制 - * @return string + * @return array|string + * @author 橘子俊 <364666827@qq.com> */ - public static function getRequest($url, $data = [], $header = [], $charset = 'UTF-8', $proxy = null, $expire = 30) { - if (!$url) { - return false; - } - if (!is_array($data)) { - $data = (array)$data; - } - //分析是否开启SSL加密 - $ssl = substr($url, 0, 8) == 'https://' ? true : false; - //读取网址内容 - $ch = curl_init(); - //设置代理 - if (!is_null($proxy)) { - curl_setopt ($ch, CURLOPT_PROXY, $proxy); - } - //分析网址中的参数 - $paramUrl = http_build_query($data, '', '&'); - $extStr = (strpos($url, '?') !== false) ? '&' : '?'; - $url = $url . (($paramUrl) ? $extStr . $paramUrl : ''); - - curl_setopt($ch, CURLOPT_URL, $url); - if ($ssl) { - // 对认证证书来源的检查 - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0); - // 从证书中检查SSL加密算法是否存在 - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); - } - if (!$header) { - $header = ["content-type: application/x-www-form-urlencoded;charset=".$charset]; - } - - curl_setopt($ch,CURLOPT_HTTPHEADER, $header); - //设置浏览器 - curl_setopt($ch, CURLOPT_USERAGENT, isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'); - curl_setopt($ch, CURLOPT_HEADER, 0); - //使用自动跳转 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, $expire); - - $content = curl_exec($ch); - curl_close($ch); - - return $content; + public static function getRequest($url, $params = [], $header = [], $charset = 'UTF-8', $proxy = null, $expire = 30) + { + return self::send($url, $params, 'GET', $header, $expire); } /** - * 用CURL模拟提交数据 - * + * 用CURL模拟提交数据,兼容旧版 * @param string $url post所要提交的网址 - * @param array $dat 所要提交的数据 + * @param array $params 所要提交的数据 * @param array $header 请求头信息 * @param string $charset 编码 * @param string $proxy 代理设置 * @param integer $expire 所用的时间限制 - * @return string + * @return array|string + * @author 橘子俊 <364666827@qq.com> */ - public static function postRequest($url, $data = [], $header = [], $charset= 'UTF-8', $proxy = null, $expire = 30) { - if (!$url) { - return false; - } - //cookie file - $cookieFile = CACHE_PATH . 'temp/' . md5(config('authkey')) . '.txt'; - //分析是否开启SSL加密 - $ssl = substr($url, 0, 8) == 'https://' ? true : false; - //读取网址内容 + public static function postRequest($url, $params = [], $header = [], $charset= 'UTF-8', $proxy = null, $expire = 30) + { + return self::send($url, $params, 'POST', $header, $expire); + } + + /** + * GET请求 + * @param string $url 请求的地址 + * @param mixed $params 传递的参数 + * @param array $header 传递的头部参数 + * @param int $expire 超时设置,默认30秒 + * @param mixed $options CURL的参数 + * @return array|string + * @author 橘子俊 <364666827@qq.com> + */ + public static function get($url, $params = '', $header = [], $expire = 30, $options = []) + { + return self::send($url, $params, 'GET', $header, $expire, $options); + } + + /** + * POST请求 + * @param string $url 请求的地址 + * @param mixed $params 传递的参数 + * @param array $header 传递的头部参数 + * @param int $expire 超时设置,默认30秒 + * @param mixed $options CURL的参数 + * @return array|string + * @author 橘子俊 <364666827@qq.com> + */ + public static function post($url, $params = '', $header = [], $expire = 30, $options = []) + { + return self::send($url, $params, 'POST', $header, $expire, $options); + } + + /** + * DELETE请求 + * @param string $url 请求的地址 + * @param mixed $params 传递的参数 + * @param array $header 传递的头部参数 + * @param int $expire 超时设置,默认30秒 + * @param mixed $options CURL的参数 + * @return array|string + * @author 橘子俊 <364666827@qq.com> + */ + public static function delete($url, $params = '', $header = [], $expire = 30, $options = []) + { + return self::send($url, $params, 'DELETE', $header, $expire, $options); + } + + /** + * PUT请求 + * @param string $url 请求的地址 + * @param mixed $params 传递的参数 + * @param array $header 传递的头部参数 + * @param int $expire 超时设置,默认30秒 + * @param mixed $options CURL的参数 + * @return array|string + * @author 橘子俊 <364666827@qq.com> + */ + public static function put($url, $params = '', $header = [], $expire = 30, $options = []) + { + return self::send($url, $params, 'PUT', $header, $expire, $options); + } + + /** + * CURL发送Request请求,支持GET、POST、PUT、DELETE + * @param string $url 请求的地址 + * @param mixed $params 传递的参数 + * @param string $method 请求的方法 + * @param array $header 传递的头部参数 + * @param int $expire 超时设置,默认30秒 + * @param mixed $options CURL的参数 + * @return array|string + * @author 橘子俊 <364666827@qq.com> + */ + private static function send($url, $params = '', $method = 'GET', $header = [], $expire = 30, $options = []) + { + $cookieFile = RUNTIME_PATH . 'temp/' . md5(config('authkey')) . '.txt'; + $userAgent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'; $ch = curl_init(); - //设置代理 - if (!is_null($proxy)) { - curl_setopt($ch, CURLOPT_PROXY, $proxy); - } - if (!$header) { - $header = ["content-type: application/x-www-form-urlencoded;charset=".$charset]; + $opt = []; + $opt[CURLOPT_COOKIEJAR] = $cookieFile; + $opt[CURLOPT_COOKIEFILE] = $cookieFile; + $opt[CURLOPT_USERAGENT] = $userAgent; + $opt[CURLOPT_CONNECTTIMEOUT] = $expire; + $opt[CURLOPT_TIMEOUT] = $expire; + $opt[CURLOPT_HTTPHEADER] = $header ? : ['Expect:']; + $opt[CURLOPT_FOLLOWLOCATION] = true; + $opt[CURLOPT_RETURNTRANSFER] = true; + + if (substr($url, 0, 8) == 'https://') { + $opt[CURLOPT_SSL_VERIFYPEER] = false; + $opt[CURLOPT_SSL_VERIFYHOST] = 2; } - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_HTTPHEADER, $header); - curl_setopt($ch, CURLOPT_COOKIEJAR, $cookieFile); - curl_setopt($ch, CURLOPT_COOKIEFILE, $cookieFile); - curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,FALSE); - curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,FALSE); - //设置浏览器 - curl_setopt($ch, CURLOPT_USERAGENT, isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Safari/537.36'); - curl_setopt($ch, CURLOPT_HEADER, 0); - //发送一个常规的Post请求 - curl_setopt($ch, CURLOPT_POST, true); - //Post提交data的数据包 - if (is_array($data) && $data) { - curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); - } else { - curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + if (is_array($params)) { + $params = http_build_query($params); } - //使用自动跳转 - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_TIMEOUT, $expire); + switch (strtoupper($method)) { + case 'GET': + $extStr = (strpos($url, '?') !== false) ? '&' : '?'; + $url = $url . (($params) ? $extStr . $params : ''); + $opt[CURLOPT_URL] = $url . '?' . $params; + break; + + case 'POST': + $opt[CURLOPT_POST] = true; + $opt[CURLOPT_POSTFIELDS] = $params; + $opt[CURLOPT_URL] = $url; + break; - $content = curl_exec($ch); + case 'PUT': + $opt[CURLOPT_CUSTOMREQUEST] = 'PUT'; + $opt[CURLOPT_POSTFIELDS] = $params; + $opt[CURLOPT_URL] = $url; + break; + + case 'DELETE': + $opt[CURLOPT_CUSTOMREQUEST] = 'DELETE'; + $opt[CURLOPT_POSTFIELDS] = $params; + $opt[CURLOPT_URL] = $url; + break; + + default: + return ['error' => 0, 'msg' => '请求的方法不存在', 'info' => []]; + break; + } + curl_setopt_array($ch, (array) $opt + $options); + $result = curl_exec($ch); + $error = curl_error($ch); + if ($result == false || !empty($error)) { + $errno = curl_errno($ch); + $info = curl_getinfo($ch); + curl_close($ch); + return [ + 'errno' => $errno, + 'msg' => $error, + 'info' => $info, + ]; + } curl_close($ch); - return $content; + return $result; } } diff --git a/app/common/validate/AdminMember.php b/app/common/validate/AdminMember.php index ac9bae0..f6aca14 100644 --- a/app/common/validate/AdminMember.php +++ b/app/common/validate/AdminMember.php @@ -25,6 +25,7 @@ class AdminMember extends Validate 'mobile|手机号' => 'requireWith:mobile|checkMobile:thinkphp|unique:admin_member', 'password|密码' => 'require|length:6,20', 'nick|昵称' => 'requireWith:nick|unique:admin_member', + 'status|状态' => 'require|in:0,1', ]; //定义验证提示 @@ -36,6 +37,13 @@ class AdminMember extends Validate //定义验证场景 protected $scene = [ + 'register' => [ + 'username', + 'email', + 'mobile', + 'password', + 'nick', + ], //无token验证登录 'login' => [ 'username|用户名' => 'requireWith:username|checkUsername:thinkphp', @@ -49,7 +57,25 @@ class AdminMember extends Validate 'email|邮箱' => 'requireWith:email|email|checkEmail:thinkphp', 'mobile|手机号' => 'requireWith:mobile|checkMobile:thinkphp', 'password|密码' => 'require|length:6,20|token', - ] + ], + // 后台创建会员 + 'admin_create' => [ + 'username', + 'email', + 'mobile', + 'password', + 'nick', + 'status', + ], + // 后台修改会员 + 'admin_update' => [ + 'username', + 'email', + 'mobile', + 'password', + 'nick', + 'status', + ], ]; /** diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..a0b3ba7 --- /dev/null +++ b/composer.json @@ -0,0 +1,39 @@ +{ + "name": "hisiphp/hisiphp", + "description": "the hisiphp admin framework", + "type": "project", + "keywords": [ + "hisiphp", + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://www.hisiphp.com/", + "license": "Apache-2.0", + "authors": [ + { + "name": "hisiphp", + "email": "364666827@qq.com", + "homepage": "http://www.hisiphp.com", + "role": "Creator" + } + ], + "require": { + "php": ">=5.6.0", + "topthink/framework": "5.0.*", + "topthink/think-captcha": "^1.0", + "topthink/think-image": "^1.0", + "topthink/think-helper": "^1.0" + }, + "autoload": { + "psr-4": { + "app\\": "application" + } + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist" + } +} \ No newline at end of file diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..84b8870 --- /dev/null +++ b/composer.lock @@ -0,0 +1,254 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", + "This file is @generated automatically" + ], + "content-hash": "636e0356883d7cc2c9fced132416db37", + "packages": [ + { + "name": "topthink/framework", + "version": "v5.0.22", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "b0514ee54c4703c1598d3caa3fad2b8a6a41e311" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/b0514ee54c4703c1598d3caa3fad2b8a6a41e311", + "reference": "b0514ee54c4703c1598d3caa3fad2b8a6a41e311", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", + "phpunit/phpunit": "4.8.*", + "sebastian/phpcpd": "2.*" + }, + "type": "think-framework", + "autoload": { + "psr-4": { + "think\\": "library/think" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "time": "2018-10-21T08:36:55+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/0c55455df26a1626a60d0dc35d2d89002b741d44", + "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "captcha package for thinkphp5", + "time": "2016-07-06T01:47:11+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v1.0.6", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\helper\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Helper Package", + "time": "2017-04-05T07:15:37+00:00" + }, + { + "name": "topthink/think-image", + "version": "v1.0.7", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP5 Image Package", + "time": "2016-09-29T06:05:43+00:00" + }, + { + "name": "topthink/think-installer", + "version": "v1.0.12", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-installer.git", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-installer/zipball/1be326e68f63de4e95977ed50f46ae75f017556d", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "composer-plugin-api": "^1.0" + }, + "require-dev": { + "composer/composer": "1.0.*@dev" + }, + "type": "composer-plugin", + "extra": { + "class": "think\\composer\\Plugin" + }, + "autoload": { + "psr-4": { + "think\\composer\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "time": "2017-05-27T06:58:09+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.6.0" + }, + "platform-dev": [] +} diff --git a/static/admin/css/style.css b/static/admin/css/style.css index e9e537d..0bd8d42 100644 --- a/static/admin/css/style.css +++ b/static/admin/css/style.css @@ -1,4 +1,4 @@ -@import url(//at.alicdn.com/t/font_247300_crrz83kvhh.css); +@import url(//at.alicdn.com/t/font_247300_r6e8bw2bb8.css); body {background-color:#fff} .fl {float:left} .fr {float:right} @@ -181,4 +181,11 @@ body {background-color:#fff} .lock-screen input:-moz-placeholder {color:#fff} .lock-screen input::-moz-placeholder {color:#fff} .lock-screen input:-ms-input-placeholder {color:#fff} -.lock-screen button {float:left;margin-left:20px} \ No newline at end of file +.lock-screen button {float:left;margin-left:20px} +/*应用themes*/ +.app-themes{} +.app-themes li{float:left;width:360px;overflow:hidden;margin:10px 20px 10px 10px;} +.app-themes li img{float:left;border-radius:5px;padding:2px;border:1px solid #ddd;} +.app-themes li dl{float:right;width:190px;overflow:hidden;} +.app-themes li dd{line-height:28px;} +.app-themes li dt{margin-top:10px;} \ No newline at end of file diff --git a/thinkphp/.gitignore b/thinkphp/.gitignore old mode 100755 new mode 100644 index e0f813d..7e31ef5 --- a/thinkphp/.gitignore +++ b/thinkphp/.gitignore @@ -1,4 +1,4 @@ -/composer.lock -/vendor -.idea -.DS_Store +/composer.lock +/vendor +.idea +.DS_Store diff --git a/thinkphp/.htaccess b/thinkphp/.htaccess old mode 100755 new mode 100644 diff --git a/thinkphp/.travis.yml b/thinkphp/.travis.yml old mode 100755 new mode 100644 index 5ca8cc2..f74ffca --- a/thinkphp/.travis.yml +++ b/thinkphp/.travis.yml @@ -1,47 +1,47 @@ -sudo: false - -language: php - -services: - - memcached - - mongodb - - mysql - - postgresql - - redis-server - -matrix: - fast_finish: true - include: - - php: 5.4 - - php: 5.5 - - php: 5.6 - - php: 7.0 - - php: hhvm - allow_failures: - - php: hhvm - -cache: - directories: - - $HOME/.composer/cache - -before_install: - - composer self-update - - mysql -e "create database IF NOT EXISTS test;" -uroot - - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres - - psql -c 'create database test;' -U postgres - -install: - - ./tests/script/install.sh - -script: - ## LINT - - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; - ## PHP Copy/Paste Detector - - vendor/bin/phpcpd --verbose --exclude vendor ./ || true - ## PHPLOC - - vendor/bin/phploc --exclude vendor ./ - ## PHPUNIT - - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml - -after_success: - - bash <(curl -s https://codecov.io/bash) +sudo: false + +language: php + +services: + - memcached + - mongodb + - mysql + - postgresql + - redis-server + +matrix: + fast_finish: true + include: + - php: 5.4 + - php: 5.5 + - php: 5.6 + - php: 7.0 + - php: hhvm + allow_failures: + - php: hhvm + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + - mysql -e "create database IF NOT EXISTS test;" -uroot + - psql -c 'DROP DATABASE IF EXISTS test;' -U postgres + - psql -c 'create database test;' -U postgres + +install: + - ./tests/script/install.sh + +script: + ## LINT + - find . -path ./vendor -prune -o -type f -name \*.php -exec php -l {} \; + ## PHP Copy/Paste Detector + - vendor/bin/phpcpd --verbose --exclude vendor ./ || true + ## PHPLOC + - vendor/bin/phploc --exclude vendor ./ + ## PHPUNIT + - vendor/bin/phpunit --coverage-clover=coverage.xml --configuration=phpunit.xml + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md old mode 100755 new mode 100644 index c53db29..dc8e91c --- a/thinkphp/CONTRIBUTING.md +++ b/thinkphp/CONTRIBUTING.md @@ -1,119 +1,119 @@ -如何贡献我的源代码 -=== - -此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 - -## 通过 Github 贡献代码 - -ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 - -参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 - -我们希望你贡献的代码符合: - -* ThinkPHP 的编码规范 -* 适当的注释,能让其他人读懂 -* 遵循 Apache2 开源协议 - -**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** - -### 注意事项 - -* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); -* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); -* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 -* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; -* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); - -## GitHub Issue - -GitHub 提供了 Issue 功能,该功能可以用于: - -* 提出 bug -* 提出功能改进 -* 反馈使用体验 - -该功能不应该用于: - - * 提出修改意见(涉及代码署名和修订追溯问题) - * 不友善的言论 - -## 快速修改 - -**GitHub 提供了快速编辑文件的功能** - -1. 登录 GitHub 帐号; -2. 浏览项目文件,找到要进行修改的文件; -3. 点击右上角铅笔图标进行修改; -4. 填写 `Commit changes` 相关内容(Title 必填); -5. 提交修改,等待 CI 验证和管理员合并。 - -**若您需要一次提交大量修改,请继续阅读下面的内容** - -## 完整流程 - -1. `fork`本项目; -2. 克隆(`clone`)你 `fork` 的项目到本地; -3. 新建分支(`branch`)并检出(`checkout`)新分支; -4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); -5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); -6. 变基(衍合 `rebase`)你的分支到上游 master 分支; -7. `push` 你的本地仓库到 GitHub; -8. 提交 `pull request`; -9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`); -10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 - -*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* - -*绝对不可以使用 `git push -f` 强行推送修改到上游* - -### 注意事项 - -* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); -* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); -* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) - -## 推荐资源 - -### 开发环境 - -* XAMPP for Windows 5.5.x -* WampServer (for Windows) -* upupw Apache PHP5.4 ( for Windows) - -或自行安装 - -- Apache / Nginx -- PHP 5.4 ~ 5.6 -- MySQL / MariaDB - -*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* - -*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* - -### 编辑器 - -Sublime Text 3 + phpfmt 插件 - -phpfmt 插件参数 - -```json -{ - "autocomplete": true, - "enable_auto_align": true, - "format_on_save": true, - "indent_with_space": true, - "psr1_naming": false, - "psr2": true, - "version": 4 -} -``` - -或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 - -### Git GUI - -* SourceTree -* GitHub Desktop - -或其他 Git 图形界面客户端 +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,不需要重新提交 `pull request`,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 5.4 ~ 5.6 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt old mode 100755 new mode 100644 index 79f08a6..2cb9a8a --- a/thinkphp/LICENSE.txt +++ b/thinkphp/LICENSE.txt @@ -1,32 +1,32 @@ - -ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 -版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) -All rights reserved。 -ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 - -Apache Licence是著名的非盈利开源组织Apache采用的协议。 -该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, -允许代码修改,再作为开源或商业软件发布。需要满足 -的条件: -1. 需要给代码的用户一份Apache Licence ; -2. 如果你修改了代码,需要在被修改的文件中说明; -3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 -带有原来代码中的协议,商标,专利声明和其他原来作者规 -定需要包含的说明; -4. 如果再发布的产品中包含一个Notice文件,则在Notice文 -件中需要带有本协议内容。你可以在Notice中增加自己的 -许可,但不可以表现为对Apache Licence构成更改。 -具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN -ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -POSSIBILITY OF SUCH DAMAGE. + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2017 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/thinkphp/README.md b/thinkphp/README.md old mode 100755 new mode 100644 index cc0bdbc..f01fd2b --- a/thinkphp/README.md +++ b/thinkphp/README.md @@ -1,114 +1,114 @@ -ThinkPHP 5.0 -=============== - -[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) -[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) -[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master) -[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) -[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) -[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) -[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) - -ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: - - + 基于命名空间和众多PHP新特性 - + 核心功能组件化 - + 强化路由功能 - + 更灵活的控制器 - + 重构的模型和数据库类 - + 配置文件可分离 - + 重写的自动验证和完成 - + 简化扩展机制 - + API支持完善 - + 改进的Log类 - + 命令行访问支持 - + REST支持 - + 引导文件支持 - + 方便的自动生成定义 - + 真正惰性加载 - + 分布式环境支持 - + 支持Composer - + 支持MongoDb - -> ThinkPHP5的运行环境要求PHP5.4以上。 - -详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) - -## 目录结构 - -初始的目录结构如下: - -~~~ -www WEB部署目录(或者子目录) -├─application 应用目录 -│ ├─common 公共模块目录(可以更改) -│ ├─module_name 模块目录 -│ │ ├─config.php 模块配置文件 -│ │ ├─common.php 模块函数文件 -│ │ ├─controller 控制器目录 -│ │ ├─model 模型目录 -│ │ ├─view 视图目录 -│ │ └─ ... 更多类库目录 -│ │ -│ ├─command.php 命令行工具配置文件 -│ ├─common.php 公共函数文件 -│ ├─config.php 公共配置文件 -│ ├─route.php 路由配置文件 -│ ├─tags.php 应用行为扩展定义文件 -│ └─database.php 数据库配置文件 -│ -├─public WEB目录(对外访问目录) -│ ├─index.php 入口文件 -│ ├─router.php 快速测试文件 -│ └─.htaccess 用于apache的重写 -│ -├─thinkphp 框架系统目录 -│ ├─lang 语言文件目录 -│ ├─library 框架类库目录 -│ │ ├─think Think类库包目录 -│ │ └─traits 系统Trait目录 -│ │ -│ ├─tpl 系统模板目录 -│ ├─base.php 基础定义文件 -│ ├─console.php 控制台入口文件 -│ ├─convention.php 框架惯例配置文件 -│ ├─helper.php 助手函数文件 -│ ├─phpunit.xml phpunit配置文件 -│ └─start.php 框架入口文件 -│ -├─extend 扩展类库目录 -├─runtime 应用的运行时目录(可写,可定制) -├─vendor 第三方类库目录(Composer依赖库) -├─build.php 自动生成定义文件(参考) -├─composer.json composer 定义文件 -├─LICENSE.txt 授权说明文件 -├─README.md README 文件 -├─think 命令行入口文件 -~~~ - -> router.php用于php自带webserver支持,可用于快速测试 -> 切换到public目录后,启动命令:php -S localhost:8888 router.php -> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 - -## 命名规范 - -ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。 - -## 参与开发 -注册并登录 Github 帐号, fork 本项目并进行改动。 - -更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) - -## 版权信息 - -ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 - -本项目包含的第三方源码和二进制文件之版权信息另行标注。 - -版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) - -All rights reserved。 - -ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 - -更多细节参阅 [LICENSE.txt](LICENSE.txt) +ThinkPHP 5.0 +=============== + +[![StyleCI](https://styleci.io/repos/48530411/shield?style=flat&branch=master)](https://styleci.io/repos/48530411) +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) +[![codecov.io](http://codecov.io/github/top-think/framework/coverage.svg?branch=master)](http://codecov.io/github/github/top-think/framework?branch=master) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![Latest Unstable Version](https://poser.pugx.org/topthink/framework/v/unstable)](https://packagist.org/packages/topthink/framework) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP5在保持快速开发和大道至简的核心理念不变的同时,PHP版本要求提升到5.4,优化核心,减少依赖,基于全新的架构思想和命名空间实现,是ThinkPHP突破原有框架思路的颠覆之作,其主要特性包括: + + + 基于命名空间和众多PHP新特性 + + 核心功能组件化 + + 强化路由功能 + + 更灵活的控制器 + + 重构的模型和数据库类 + + 配置文件可分离 + + 重写的自动验证和完成 + + 简化扩展机制 + + API支持完善 + + 改进的Log类 + + 命令行访问支持 + + REST支持 + + 引导文件支持 + + 方便的自动生成定义 + + 真正惰性加载 + + 分布式环境支持 + + 支持Composer + + 支持MongoDb + +> ThinkPHP5的运行环境要求PHP5.4以上。 + +详细开发文档参考 [ThinkPHP5完全开发手册](http://www.kancloud.cn/manual/thinkphp5) 以及[ThinkPHP5入门系列教程](http://www.kancloud.cn/special/thinkphp5_quickstart) + +## 目录结构 + +初始的目录结构如下: + +~~~ +www WEB部署目录(或者子目录) +├─application 应用目录 +│ ├─common 公共模块目录(可以更改) +│ ├─module_name 模块目录 +│ │ ├─config.php 模块配置文件 +│ │ ├─common.php 模块函数文件 +│ │ ├─controller 控制器目录 +│ │ ├─model 模型目录 +│ │ ├─view 视图目录 +│ │ └─ ... 更多类库目录 +│ │ +│ ├─command.php 命令行工具配置文件 +│ ├─common.php 公共函数文件 +│ ├─config.php 公共配置文件 +│ ├─route.php 路由配置文件 +│ ├─tags.php 应用行为扩展定义文件 +│ └─database.php 数据库配置文件 +│ +├─public WEB目录(对外访问目录) +│ ├─index.php 入口文件 +│ ├─router.php 快速测试文件 +│ └─.htaccess 用于apache的重写 +│ +├─thinkphp 框架系统目录 +│ ├─lang 语言文件目录 +│ ├─library 框架类库目录 +│ │ ├─think Think类库包目录 +│ │ └─traits 系统Trait目录 +│ │ +│ ├─tpl 系统模板目录 +│ ├─base.php 基础定义文件 +│ ├─console.php 控制台入口文件 +│ ├─convention.php 框架惯例配置文件 +│ ├─helper.php 助手函数文件 +│ ├─phpunit.xml phpunit配置文件 +│ └─start.php 框架入口文件 +│ +├─extend 扩展类库目录 +├─runtime 应用的运行时目录(可写,可定制) +├─vendor 第三方类库目录(Composer依赖库) +├─build.php 自动生成定义文件(参考) +├─composer.json composer 定义文件 +├─LICENSE.txt 授权说明文件 +├─README.md README 文件 +├─think 命令行入口文件 +~~~ + +> router.php用于php自带webserver支持,可用于快速测试 +> 切换到public目录后,启动命令:php -S localhost:8888 router.php +> 上面的目录结构和名称是可以改变的,这取决于你的入口文件和配置参数。 + +## 命名规范 + +ThinkPHP5的命名规范遵循`PSR-2`规范以及`PSR-4`自动加载规范。 + +## 参与开发 +注册并登录 Github 帐号, fork 本项目并进行改动。 + +更多细节参阅 [CONTRIBUTING.md](CONTRIBUTING.md) + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/thinkphp/base.php b/thinkphp/base.php old mode 100755 new mode 100644 index af4a09e..969447d --- a/thinkphp/base.php +++ b/thinkphp/base.php @@ -1,65 +1,65 @@ - -// +---------------------------------------------------------------------- - -define('THINK_VERSION', '5.0.20'); -define('THINK_START_TIME', microtime(true)); -define('THINK_START_MEM', memory_get_usage()); -define('EXT', '.php'); -define('DS', DIRECTORY_SEPARATOR); -defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); -define('LIB_PATH', THINK_PATH . 'library' . DS); -define('CORE_PATH', LIB_PATH . 'think' . DS); -define('TRAIT_PATH', LIB_PATH . 'traits' . DS); -defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); -defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); -defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); -defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); -defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); -defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); -defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); -defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); -defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 -defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 -defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 - -// 环境常量 -define('IS_CLI', PHP_SAPI == 'cli' ? true : false); -define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); - -// 载入Loader类 -require CORE_PATH . 'Loader.php'; - -// 加载环境变量配置文件 -if (is_file(ROOT_PATH . '.env')) { - $env = parse_ini_file(ROOT_PATH . '.env', true); - - foreach ($env as $key => $val) { - $name = ENV_PREFIX . strtoupper($key); - - if (is_array($val)) { - foreach ($val as $k => $v) { - $item = $name . '_' . strtoupper($k); - putenv("$item=$v"); - } - } else { - putenv("$name=$val"); - } - } -} - -// 注册自动加载 -\think\Loader::register(); - -// 注册错误和异常处理机制 -\think\Error::register(); - -// 加载惯例配置文件 -\think\Config::set(include THINK_PATH . 'convention' . EXT); + +// +---------------------------------------------------------------------- + +define('THINK_VERSION', '5.0.22'); +define('THINK_START_TIME', microtime(true)); +define('THINK_START_MEM', memory_get_usage()); +define('EXT', '.php'); +define('DS', DIRECTORY_SEPARATOR); +defined('THINK_PATH') or define('THINK_PATH', __DIR__ . DS); +define('LIB_PATH', THINK_PATH . 'library' . DS); +define('CORE_PATH', LIB_PATH . 'think' . DS); +define('TRAIT_PATH', LIB_PATH . 'traits' . DS); +defined('APP_PATH') or define('APP_PATH', dirname($_SERVER['SCRIPT_FILENAME']) . DS); +defined('ROOT_PATH') or define('ROOT_PATH', dirname(realpath(APP_PATH)) . DS); +defined('EXTEND_PATH') or define('EXTEND_PATH', ROOT_PATH . 'extend' . DS); +defined('VENDOR_PATH') or define('VENDOR_PATH', ROOT_PATH . 'vendor' . DS); +defined('RUNTIME_PATH') or define('RUNTIME_PATH', ROOT_PATH . 'runtime' . DS); +defined('LOG_PATH') or define('LOG_PATH', RUNTIME_PATH . 'log' . DS); +defined('CACHE_PATH') or define('CACHE_PATH', RUNTIME_PATH . 'cache' . DS); +defined('TEMP_PATH') or define('TEMP_PATH', RUNTIME_PATH . 'temp' . DS); +defined('CONF_PATH') or define('CONF_PATH', APP_PATH); // 配置文件目录 +defined('CONF_EXT') or define('CONF_EXT', EXT); // 配置文件后缀 +defined('ENV_PREFIX') or define('ENV_PREFIX', 'PHP_'); // 环境变量的配置前缀 + +// 环境常量 +define('IS_CLI', PHP_SAPI == 'cli' ? true : false); +define('IS_WIN', strpos(PHP_OS, 'WIN') !== false); + +// 载入Loader类 +require CORE_PATH . 'Loader.php'; + +// 加载环境变量配置文件 +if (is_file(ROOT_PATH . '.env')) { + $env = parse_ini_file(ROOT_PATH . '.env', true); + + foreach ($env as $key => $val) { + $name = ENV_PREFIX . strtoupper($key); + + if (is_array($val)) { + foreach ($val as $k => $v) { + $item = $name . '_' . strtoupper($k); + putenv("$item=$v"); + } + } else { + putenv("$name=$val"); + } + } +} + +// 注册自动加载 +\think\Loader::register(); + +// 注册错误和异常处理机制 +\think\Error::register(); + +// 加载惯例配置文件 +\think\Config::set(include THINK_PATH . 'convention' . EXT); diff --git a/thinkphp/codecov.yml b/thinkphp/codecov.yml old mode 100755 new mode 100644 index 89b0db5..bef9d64 --- a/thinkphp/codecov.yml +++ b/thinkphp/codecov.yml @@ -1,12 +1,12 @@ -comment: - layout: header, changes, diff -coverage: - ignore: - - base.php - - helper.php - - convention.php - - lang/zh-cn.php - - start.php - - console.php - status: - patch: false +comment: + layout: header, changes, diff +coverage: + ignore: + - base.php + - helper.php + - convention.php + - lang/zh-cn.php + - start.php + - console.php + status: + patch: false diff --git a/thinkphp/composer.json b/thinkphp/composer.json old mode 100755 new mode 100644 index 2cf2663..c546e11 --- a/thinkphp/composer.json +++ b/thinkphp/composer.json @@ -1,35 +1,35 @@ -{ - "name": "topthink/framework", - "description": "the new thinkphp framework", - "type": "think-framework", - "keywords": [ - "framework", - "thinkphp", - "ORM" - ], - "homepage": "http://thinkphp.cn/", - "license": "Apache-2.0", - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "require": { - "php": ">=5.4.0", - "topthink/think-installer": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "4.8.*", - "johnkary/phpunit-speedtrap": "^1.0", - "mikey179/vfsStream": "~1.6", - "phploc/phploc": "2.*", - "sebastian/phpcpd": "2.*", - "phpdocumentor/reflection-docblock": "^2.0" - }, - "autoload": { - "psr-4": { - "think\\": "library/think" - } - } -} +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" + }, + "require-dev": { + "phpunit/phpunit": "4.8.*", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsStream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\": "library/think" + } + } +} diff --git a/thinkphp/console.php b/thinkphp/console.php old mode 100755 new mode 100644 index d79d4ba..578e4a7 --- a/thinkphp/console.php +++ b/thinkphp/console.php @@ -1,20 +1,20 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -// ThinkPHP 引导文件 -// 加载基础文件 -require __DIR__ . '/base.php'; - -// 执行应用 -App::initCommon(); -Console::init(); + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 加载基础文件 +require __DIR__ . '/base.php'; + +// 执行应用 +App::initCommon(); +Console::init(); diff --git a/thinkphp/convention.php b/thinkphp/convention.php old mode 100755 new mode 100644 index 2aa93d0..31a0a0c --- a/thinkphp/convention.php +++ b/thinkphp/convention.php @@ -1,298 +1,298 @@ - '', - // 应用调试模式 - 'app_debug' => false, - // 应用Trace - 'app_trace' => false, - // 应用模式状态 - 'app_status' => '', - // 是否支持多模块 - 'app_multi_module' => true, - // 入口自动绑定模块 - 'auto_bind_module' => false, - // 注册的根命名空间 - 'root_namespace' => [], - // 扩展函数文件 - 'extra_file_list' => [THINK_PATH . 'helper' . EXT], - // 默认输出类型 - 'default_return_type' => 'html', - // 默认AJAX 数据返回格式,可选json xml ... - 'default_ajax_return' => 'json', - // 默认JSONP格式返回的处理方法 - 'default_jsonp_handler' => 'jsonpReturn', - // 默认JSONP处理方法 - 'var_jsonp_handler' => 'callback', - // 默认时区 - 'default_timezone' => 'PRC', - // 是否开启多语言 - 'lang_switch_on' => false, - // 默认全局过滤方法 用逗号分隔多个 - 'default_filter' => '', - // 默认语言 - 'default_lang' => 'zh-cn', - // 应用类库后缀 - 'class_suffix' => false, - // 控制器类后缀 - 'controller_suffix' => false, - - // +---------------------------------------------------------------------- - // | 模块设置 - // +---------------------------------------------------------------------- - - // 默认模块名 - 'default_module' => 'index', - // 禁止访问模块 - 'deny_module_list' => ['common'], - // 默认控制器名 - 'default_controller' => 'Index', - // 默认操作名 - 'default_action' => 'index', - // 默认验证器 - 'default_validate' => '', - // 默认的空控制器名 - 'empty_controller' => 'Error', - // 操作方法前缀 - 'use_action_prefix' => false, - // 操作方法后缀 - 'action_suffix' => '', - // 自动搜索控制器 - 'controller_auto_search' => false, - - // +---------------------------------------------------------------------- - // | URL设置 - // +---------------------------------------------------------------------- - - // PATHINFO变量名 用于兼容模式 - 'var_pathinfo' => 's', - // 兼容PATH_INFO获取 - 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], - // pathinfo分隔符 - 'pathinfo_depr' => '/', - // HTTPS代理标识 - 'https_agent_name' => '', - // URL伪静态后缀 - 'url_html_suffix' => 'html', - // URL普通方式参数 用于自动生成 - 'url_common_param' => false, - // URL参数方式 0 按名称成对解析 1 按顺序解析 - 'url_param_type' => 0, - // 是否开启路由 - 'url_route_on' => true, - // 路由配置文件(支持配置多个) - 'route_config_file' => ['route'], - // 路由使用完整匹配 - 'route_complete_match' => false, - // 是否强制使用路由 - 'url_route_must' => false, - // 域名部署 - 'url_domain_deploy' => false, - // 域名根,如thinkphp.cn - 'url_domain_root' => '', - // 是否自动转换URL中的控制器和操作名 - 'url_convert' => true, - // 默认的访问控制器层 - 'url_controller_layer' => 'controller', - // 表单请求类型伪装变量 - 'var_method' => '_method', - // 表单ajax伪装变量 - 'var_ajax' => '_ajax', - // 表单pjax伪装变量 - 'var_pjax' => '_pjax', - // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 - 'request_cache' => false, - // 请求缓存有效期 - 'request_cache_expire' => null, - // 全局请求缓存排除规则 - 'request_cache_except' => [], - - // +---------------------------------------------------------------------- - // | 模板设置 - // +---------------------------------------------------------------------- - - 'template' => [ - // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 - 'auto_rule' => 1, - // 模板引擎类型 支持 php think 支持扩展 - 'type' => 'Think', - // 视图基础目录,配置目录为所有模块的视图起始目录 - 'view_base' => '', - // 当前模板的视图目录 留空为自动获取 - 'view_path' => '', - // 模板后缀 - 'view_suffix' => 'html', - // 模板文件名分隔符 - 'view_depr' => DS, - // 模板引擎普通标签开始标记 - 'tpl_begin' => '{', - // 模板引擎普通标签结束标记 - 'tpl_end' => '}', - // 标签库标签开始标记 - 'taglib_begin' => '{', - // 标签库标签结束标记 - 'taglib_end' => '}', - ], - - // 视图输出字符串内容替换 - 'view_replace_str' => [], - // 默认跳转页面对应的模板文件 - 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', - - // +---------------------------------------------------------------------- - // | 异常及错误设置 - // +---------------------------------------------------------------------- - - // 异常页面的模板文件 - 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', - - // 错误显示信息,非调试模式有效 - 'error_message' => '页面错误!请稍后再试~', - // 显示错误信息 - 'show_error_msg' => false, - // 异常处理handle类 留空使用 \think\exception\Handle - 'exception_handle' => '', - // 是否记录trace信息到日志 - 'record_trace' => false, - - // +---------------------------------------------------------------------- - // | 日志设置 - // +---------------------------------------------------------------------- - - 'log' => [ - // 日志记录方式,内置 file socket 支持扩展 - 'type' => 'File', - // 日志保存目录 - 'path' => LOG_PATH, - // 日志记录级别 - 'level' => [], - ], - - // +---------------------------------------------------------------------- - // | Trace设置 开启 app_trace 后 有效 - // +---------------------------------------------------------------------- - 'trace' => [ - // 内置Html Console 支持扩展 - 'type' => 'Html', - ], - - // +---------------------------------------------------------------------- - // | 缓存设置 - // +---------------------------------------------------------------------- - - 'cache' => [ - // 驱动方式 - 'type' => 'File', - // 缓存保存目录 - 'path' => CACHE_PATH, - // 缓存前缀 - 'prefix' => '', - // 缓存有效期 0表示永久缓存 - 'expire' => 0, - ], - - // +---------------------------------------------------------------------- - // | 会话设置 - // +---------------------------------------------------------------------- - - 'session' => [ - 'id' => '', - // SESSION_ID的提交变量,解决flash上传跨域 - 'var_session_id' => '', - // SESSION 前缀 - 'prefix' => 'think', - // 驱动方式 支持redis memcache memcached - 'type' => '', - // 是否自动开启 SESSION - 'auto_start' => true, - 'httponly' => true, - 'secure' => false, - ], - - // +---------------------------------------------------------------------- - // | Cookie设置 - // +---------------------------------------------------------------------- - 'cookie' => [ - // cookie 名称前缀 - 'prefix' => '', - // cookie 保存时间 - 'expire' => 0, - // cookie 保存路径 - 'path' => '/', - // cookie 有效域名 - 'domain' => '', - // cookie 启用安全传输 - 'secure' => false, - // httponly设置 - 'httponly' => '', - // 是否使用 setcookie - 'setcookie' => true, - ], - - // +---------------------------------------------------------------------- - // | 数据库设置 - // +---------------------------------------------------------------------- - - 'database' => [ - // 数据库类型 - 'type' => 'mysql', - // 数据库连接DSN配置 - 'dsn' => '', - // 服务器地址 - 'hostname' => '127.0.0.1', - // 数据库名 - 'database' => '', - // 数据库用户名 - 'username' => 'root', - // 数据库密码 - 'password' => '', - // 数据库连接端口 - 'hostport' => '', - // 数据库连接参数 - 'params' => [], - // 数据库编码默认采用utf8 - 'charset' => 'utf8', - // 数据库表前缀 - 'prefix' => '', - // 数据库调试模式 - 'debug' => false, - // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, - // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, - // 读写分离后 主服务器数量 - 'master_num' => 1, - // 指定从服务器序号 - 'slave_no' => '', - // 是否严格检查字段是否存在 - 'fields_strict' => true, - // 数据集返回类型 - 'resultset_type' => 'array', - // 自动写入时间戳字段 - 'auto_timestamp' => false, - // 时间字段取出后的默认时间格式 - 'datetime_format' => 'Y-m-d H:i:s', - // 是否需要进行SQL性能分析 - 'sql_explain' => false, - ], - - //分页配置 - 'paginate' => [ - 'type' => 'bootstrap', - 'var_page' => 'page', - 'list_rows' => 15, - ], - - //控制台配置 - 'console' => [ - 'name' => 'Think Console', - 'version' => '0.1', - 'user' => null, - ], - -]; + '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 扩展函数文件 + 'extra_file_list' => [THINK_PATH . 'helper' . EXT], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'PRC', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由 + 'url_route_on' => true, + // 路由配置文件(支持配置多个) + 'route_config_file' => ['route'], + // 路由使用完整匹配 + 'route_complete_match' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 域名部署 + 'url_domain_deploy' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // 视图输出字符串内容替换 + 'view_replace_str' => [], + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + 'dispatch_error_tmpl' => THINK_PATH . 'tpl' . DS . 'dispatch_jump.tpl', + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 异常页面的模板文件 + 'exception_tmpl' => THINK_PATH . 'tpl' . DS . 'think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + // 是否记录trace信息到日志 + 'record_trace' => false, + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + 'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + ], + +]; diff --git a/thinkphp/helper.php b/thinkphp/helper.php old mode 100755 new mode 100644 index 2fb2b84..12683cf --- a/thinkphp/helper.php +++ b/thinkphp/helper.php @@ -1,589 +1,589 @@ - -// +---------------------------------------------------------------------- - -//------------------------ -// ThinkPHP 助手函数 -//------------------------- - -use think\Cache; -use think\Config; -use think\Cookie; -use think\Db; -use think\Debug; -use think\exception\HttpException; -use think\exception\HttpResponseException; -use think\Lang; -use think\Loader; -use think\Log; -use think\Model; -use think\Request; -use think\Response; -use think\Session; -use think\Url; -use think\View; - -if (!function_exists('load_trait')) { - /** - * 快速导入Traits PHP5.5以上无需调用 - * @param string $class trait库 - * @param string $ext 类库后缀 - * @return boolean - */ - function load_trait($class, $ext = EXT) - { - return Loader::import($class, TRAIT_PATH, $ext); - } -} - -if (!function_exists('exception')) { - /** - * 抛出异常处理 - * - * @param string $msg 异常消息 - * @param integer $code 异常代码 默认为0 - * @param string $exception 异常类 - * - * @throws Exception - */ - function exception($msg, $code = 0, $exception = '') - { - $e = $exception ?: '\think\Exception'; - throw new $e($msg, $code); - } -} - -if (!function_exists('debug')) { - /** - * 记录时间(微秒)和内存使用情况 - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer|string $dec 小数位 如果是m 表示统计内存占用 - * @return mixed - */ - function debug($start, $end = '', $dec = 6) - { - if ('' == $end) { - Debug::remark($start); - } else { - return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); - } - } -} - -if (!function_exists('lang')) { - /** - * 获取语言变量值 - * @param string $name 语言变量名 - * @param array $vars 动态变量值 - * @param string $lang 语言 - * @return mixed - */ - function lang($name, $vars = [], $lang = '') - { - return Lang::get($name, $vars, $lang); - } -} - -if (!function_exists('config')) { - /** - * 获取和设置配置参数 - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @param string $range 作用域 - * @return mixed - */ - function config($name = '', $value = null, $range = '') - { - if (is_null($value) && is_string($name)) { - return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); - } else { - return Config::set($name, $value, $range); - } - } -} - -if (!function_exists('input')) { - /** - * 获取输入数据 支持默认值和过滤 - * @param string $key 获取的变量名 - * @param mixed $default 默认值 - * @param string $filter 过滤方法 - * @return mixed - */ - function input($key = '', $default = null, $filter = '') - { - if (0 === strpos($key, '?')) { - $key = substr($key, 1); - $has = true; - } - if ($pos = strpos($key, '.')) { - // 指定参数来源 - list($method, $key) = explode('.', $key, 2); - if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { - $key = $method . '.' . $key; - $method = 'param'; - } - } else { - // 默认为自动判断 - $method = 'param'; - } - if (isset($has)) { - return request()->has($key, $method, $default); - } else { - return request()->$method($key, $default, $filter); - } - } -} - -if (!function_exists('widget')) { - /** - * 渲染输出Widget - * @param string $name Widget名称 - * @param array $data 传入的参数 - * @return mixed - */ - function widget($name, $data = []) - { - return Loader::action($name, $data, 'widget'); - } -} - -if (!function_exists('model')) { - /** - * 实例化Model - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Model - */ - function model($name = '', $layer = 'model', $appendSuffix = false) - { - return Loader::model($name, $layer, $appendSuffix); - } -} - -if (!function_exists('validate')) { - /** - * 实例化验证器 - * @param string $name 验证器名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Validate - */ - function validate($name = '', $layer = 'validate', $appendSuffix = false) - { - return Loader::validate($name, $layer, $appendSuffix); - } -} - -if (!function_exists('db')) { - /** - * 实例化数据库类 - * @param string $name 操作的数据表名称(不含前缀) - * @param array|string $config 数据库配置参数 - * @param bool $force 是否强制重新连接 - * @return \think\db\Query - */ - function db($name = '', $config = [], $force = false) - { - return Db::connect($config, $force)->name($name); - } -} - -if (!function_exists('controller')) { - /** - * 实例化控制器 格式:[模块/]控制器 - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return \think\Controller - */ - function controller($name, $layer = 'controller', $appendSuffix = false) - { - return Loader::controller($name, $layer, $appendSuffix); - } -} - -if (!function_exists('action')) { - /** - * 调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - */ - function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - return Loader::action($url, $vars, $layer, $appendSuffix); - } -} - -if (!function_exists('import')) { - /** - * 导入所需的类库 同java的Import 本函数有缓存功能 - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return boolean - */ - function import($class, $baseUrl = '', $ext = EXT) - { - return Loader::import($class, $baseUrl, $ext); - } -} - -if (!function_exists('vendor')) { - /** - * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 - * @param string $class 类库 - * @param string $ext 类库后缀 - * @return boolean - */ - function vendor($class, $ext = EXT) - { - return Loader::import($class, VENDOR_PATH, $ext); - } -} - -if (!function_exists('dump')) { - /** - * 浏览器友好的变量输出 - * @param mixed $var 变量 - * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 - * @param string $label 标签 默认为空 - * @return void|string - */ - function dump($var, $echo = true, $label = null) - { - return Debug::dump($var, $echo, $label); - } -} - -if (!function_exists('url')) { - /** - * Url生成 - * @param string $url 路由地址 - * @param string|array $vars 变量 - * @param bool|string $suffix 生成的URL后缀 - * @param bool|string $domain 域名 - * @return string - */ - function url($url = '', $vars = '', $suffix = true, $domain = false) - { - return Url::build($url, $vars, $suffix, $domain); - } -} - -if (!function_exists('session')) { - /** - * Session管理 - * @param string|array $name session名称,如果为数组表示进行session设置 - * @param mixed $value session值 - * @param string $prefix 前缀 - * @return mixed - */ - function session($name, $value = '', $prefix = null) - { - if (is_array($name)) { - // 初始化 - Session::init($name); - } elseif (is_null($name)) { - // 清除 - Session::clear('' === $value ? null : $value); - } elseif ('' === $value) { - // 判断或获取 - return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); - } elseif (is_null($value)) { - // 删除 - return Session::delete($name, $prefix); - } else { - // 设置 - return Session::set($name, $value, $prefix); - } - } -} - -if (!function_exists('cookie')) { - /** - * Cookie管理 - * @param string|array $name cookie名称,如果为数组表示进行cookie设置 - * @param mixed $value cookie值 - * @param mixed $option 参数 - * @return mixed - */ - function cookie($name, $value = '', $option = null) - { - if (is_array($name)) { - // 初始化 - Cookie::init($name); - } elseif (is_null($name)) { - // 清除 - Cookie::clear($value); - } elseif ('' === $value) { - // 获取 - return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); - } elseif (is_null($value)) { - // 删除 - return Cookie::delete($name); - } else { - // 设置 - return Cookie::set($name, $value, $option); - } - } -} - -if (!function_exists('cache')) { - /** - * 缓存管理 - * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 - * @param mixed $value 缓存值 - * @param mixed $options 缓存参数 - * @param string $tag 缓存标签 - * @return mixed - */ - function cache($name, $value = '', $options = null, $tag = null) - { - if (is_array($options)) { - // 缓存操作的同时初始化 - $cache = Cache::connect($options); - } elseif (is_array($name)) { - // 缓存初始化 - return Cache::connect($name); - } else { - $cache = Cache::init(); - } - - if (is_null($name)) { - return $cache->clear($value); - } elseif ('' === $value) { - // 获取缓存 - return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); - } elseif (is_null($value)) { - // 删除缓存 - return $cache->rm($name); - } elseif (0 === strpos($name, '?') && '' !== $value) { - $expire = is_numeric($options) ? $options : null; - return $cache->remember(substr($name, 1), $value, $expire); - } else { - // 缓存数据 - if (is_array($options)) { - $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 - } else { - $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 - } - if (is_null($tag)) { - return $cache->set($name, $value, $expire); - } else { - return $cache->tag($tag)->set($name, $value, $expire); - } - } - } -} - -if (!function_exists('trace')) { - /** - * 记录日志信息 - * @param mixed $log log信息 支持字符串和数组 - * @param string $level 日志级别 - * @return void|array - */ - function trace($log = '[think]', $level = 'log') - { - if ('[think]' === $log) { - return Log::getLog(); - } else { - Log::record($log, $level); - } - } -} - -if (!function_exists('request')) { - /** - * 获取当前Request对象实例 - * @return Request - */ - function request() - { - return Request::instance(); - } -} - -if (!function_exists('response')) { - /** - * 创建普通 Response 对象实例 - * @param mixed $data 输出数据 - * @param int|string $code 状态码 - * @param array $header 头信息 - * @param string $type - * @return Response - */ - function response($data = [], $code = 200, $header = [], $type = 'html') - { - return Response::create($data, $type, $code, $header); - } -} - -if (!function_exists('view')) { - /** - * 渲染模板输出 - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $replace 模板替换 - * @param integer $code 状态码 - * @return \think\response\View - */ - function view($template = '', $vars = [], $replace = [], $code = 200) - { - return Response::create($template, 'view', $code)->replace($replace)->assign($vars); - } -} - -if (!function_exists('json')) { - /** - * 获取\think\response\Json对象实例 - * @param mixed $data 返回的数据 - * @param integer $code 状态码 - * @param array $header 头部 - * @param array $options 参数 - * @return \think\response\Json - */ - function json($data = [], $code = 200, $header = [], $options = []) - { - return Response::create($data, 'json', $code, $header, $options); - } -} - -if (!function_exists('jsonp')) { - /** - * 获取\think\response\Jsonp对象实例 - * @param mixed $data 返回的数据 - * @param integer $code 状态码 - * @param array $header 头部 - * @param array $options 参数 - * @return \think\response\Jsonp - */ - function jsonp($data = [], $code = 200, $header = [], $options = []) - { - return Response::create($data, 'jsonp', $code, $header, $options); - } -} - -if (!function_exists('xml')) { - /** - * 获取\think\response\Xml对象实例 - * @param mixed $data 返回的数据 - * @param integer $code 状态码 - * @param array $header 头部 - * @param array $options 参数 - * @return \think\response\Xml - */ - function xml($data = [], $code = 200, $header = [], $options = []) - { - return Response::create($data, 'xml', $code, $header, $options); - } -} - -if (!function_exists('redirect')) { - /** - * 获取\think\response\Redirect对象实例 - * @param mixed $url 重定向地址 支持Url::build方法的地址 - * @param array|integer $params 额外参数 - * @param integer $code 状态码 - * @param array $with 隐式传参 - * @return \think\response\Redirect - */ - function redirect($url = [], $params = [], $code = 302, $with = []) - { - if (is_integer($params)) { - $code = $params; - $params = []; - } - return Response::create($url, 'redirect', $code)->params($params)->with($with); - } -} - -if (!function_exists('abort')) { - /** - * 抛出HTTP异常 - * @param integer|Response $code 状态码 或者 Response对象实例 - * @param string $message 错误信息 - * @param array $header 参数 - */ - function abort($code, $message = null, $header = []) - { - if ($code instanceof Response) { - throw new HttpResponseException($code); - } else { - throw new HttpException($code, $message, null, $header); - } - } -} - -if (!function_exists('halt')) { - /** - * 调试变量并且中断输出 - * @param mixed $var 调试变量或者信息 - */ - function halt($var) - { - dump($var); - throw new HttpResponseException(new Response); - } -} - -if (!function_exists('token')) { - /** - * 生成表单令牌 - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 - * @return string - */ - function token($name = '__token__', $type = 'md5') - { - $token = Request::instance()->token($name, $type); - return ''; - } -} - -if (!function_exists('load_relation')) { - /** - * 延迟预载入关联查询 - * @param mixed $resultSet 数据集 - * @param mixed $relation 关联 - * @return array - */ - function load_relation($resultSet, $relation) - { - $item = current($resultSet); - if ($item instanceof Model) { - $item->eagerlyResultSet($resultSet, $relation); - } - return $resultSet; - } -} - -if (!function_exists('collection')) { - /** - * 数组转换为数据集对象 - * @param array $resultSet 数据集数组 - * @return \think\model\Collection|\think\Collection - */ - function collection($resultSet) - { - $item = current($resultSet); - if ($item instanceof Model) { - return \think\model\Collection::make($resultSet); - } else { - return \think\Collection::make($resultSet); - } - } -} + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Cache; +use think\Config; +use think\Cookie; +use think\Db; +use think\Debug; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\Lang; +use think\Loader; +use think\Log; +use think\Model; +use think\Request; +use think\Response; +use think\Session; +use think\Url; +use think\View; + +if (!function_exists('load_trait')) { + /** + * 快速导入Traits PHP5.5以上无需调用 + * @param string $class trait库 + * @param string $ext 类库后缀 + * @return boolean + */ + function load_trait($class, $ext = EXT) + { + return Loader::import($class, TRAIT_PATH, $ext); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @param string $range 作用域 + * @return mixed + */ + function config($name = '', $value = null, $range = '') + { + if (is_null($value) && is_string($name)) { + return 0 === strpos($name, '?') ? Config::has(substr($name, 1), $range) : Config::get($name, $range); + } else { + return Config::set($name, $value, $range); + } + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + if ($pos = strpos($key, '.')) { + // 指定参数来源 + list($method, $key) = explode('.', $key, 2); + if (!in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = $method . '.' . $key; + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return Loader::action($name, $data, 'widget'); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return Loader::model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return Loader::validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = false) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return Loader::controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return Loader::action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('import')) { + /** + * 导入所需的类库 同java的Import 本函数有缓存功能 + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return boolean + */ + function import($class, $baseUrl = '', $ext = EXT) + { + return Loader::import($class, $baseUrl, $ext); + } +} + +if (!function_exists('vendor')) { + /** + * 快速导入第三方框架类库 所有第三方框架的类库文件统一放到 系统的Vendor目录下面 + * @param string $class 类库 + * @param string $ext 类库后缀 + * @return boolean + */ + function vendor($class, $ext = EXT) + { + return Loader::import($class, VENDOR_PATH, $ext); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear('' === $value ? null : $value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name, $option); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + $cache = Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } else { + $cache = Cache::init(); + } + + if (is_null($name)) { + return $cache->clear($value); + } elseif ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? $cache->has(substr($name, 1)) : $cache->get($name); + } elseif (is_null($value)) { + // 删除缓存 + return $cache->rm($name); + } elseif (0 === strpos($name, '?') && '' !== $value) { + $expire = is_numeric($options) ? $options : null; + return $cache->remember(substr($name, 1), $value, $expire); + } else { + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + if (is_null($tag)) { + return $cache->set($name, $value, $expire); + } else { + return $cache->tag($tag)->set($name, $value, $expire); + } + } + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return void|array + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return Request::instance(); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = [], $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $replace 模板替换 + * @param integer $code 状态码 + * @return \think\response\View + */ + function view($template = '', $vars = [], $replace = [], $code = 200) + { + return Response::create($template, 'view', $code)->replace($replace)->assign($vars); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @param array $with 隐式传参 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + return Response::create($url, 'redirect', $code)->params($params)->with($with); + } +} + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::instance()->token($name, $type); + return ''; + } +} + +if (!function_exists('load_relation')) { + /** + * 延迟预载入关联查询 + * @param mixed $resultSet 数据集 + * @param mixed $relation 关联 + * @return array + */ + function load_relation($resultSet, $relation) + { + $item = current($resultSet); + if ($item instanceof Model) { + $item->eagerlyResultSet($resultSet, $relation); + } + return $resultSet; + } +} + +if (!function_exists('collection')) { + /** + * 数组转换为数据集对象 + * @param array $resultSet 数据集数组 + * @return \think\model\Collection|\think\Collection + */ + function collection($resultSet) + { + $item = current($resultSet); + if ($item instanceof Model) { + return \think\model\Collection::make($resultSet); + } else { + return \think\Collection::make($resultSet); + } + } +} diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php old mode 100755 new mode 100644 index fb8fb83..eb7a914 --- a/thinkphp/lang/zh-cn.php +++ b/thinkphp/lang/zh-cn.php @@ -1,136 +1,136 @@ - -// +---------------------------------------------------------------------- - -// 核心中文语言包 -return [ - // 系统错误提示 - 'Undefined variable' => '未定义变量', - 'Undefined index' => '未定义数组索引', - 'Undefined offset' => '未定义数组下标', - 'Parse error' => '语法解析错误', - 'Type error' => '类型错误', - 'Fatal error' => '致命错误', - 'syntax error' => '语法错误', - - // 框架核心错误提示 - 'dispatch type not support' => '不支持的调度类型', - 'method param miss' => '方法参数错误', - 'method not exists' => '方法不存在', - 'module not exists' => '模块不存在', - 'controller not exists' => '控制器不存在', - 'class not exists' => '类不存在', - 'property not exists' => '类的属性不存在', - 'template not exists' => '模板文件不存在', - 'illegal controller name' => '非法的控制器名称', - 'illegal action name' => '非法的操作名称', - 'url suffix deny' => '禁止的URL后缀访问', - 'Route Not Found' => '当前访问路由未定义', - 'Undefined db type' => '未定义数据库类型', - 'variable type error' => '变量类型错误', - 'PSR-4 error' => 'PSR-4 规范错误', - 'not support total' => '简洁模式下不能获取数据总数', - 'not support last' => '简洁模式下不能获取最后一页', - 'error session handler' => '错误的SESSION处理器类', - 'not allow php tag' => '模板不允许使用PHP语法', - 'not support' => '不支持', - 'redisd master' => 'Redisd 主服务器错误', - 'redisd slave' => 'Redisd 从服务器错误', - 'must run at sae' => '必须在SAE运行', - 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', - 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', - 'fields not exists' => '数据表字段不存在', - 'where express error' => '查询表达式错误', - 'not support data' => '不支持的数据表达式', - 'no data to update' => '没有任何数据需要更新', - 'miss data to insert' => '缺少需要写入的数据', - 'miss complex primary data' => '缺少复合主键数据', - 'miss update condition' => '缺少更新条件', - 'model data Not Found' => '模型数据不存在', - 'table data not Found' => '表数据不存在', - 'delete without condition' => '没有条件不会执行删除操作', - 'miss relation data' => '缺少关联表数据', - 'tag attr must' => '模板标签属性必须', - 'tag error' => '模板标签错误', - 'cache write error' => '缓存写入失败', - 'sae mc write error' => 'SAE mc 写入错误', - 'route name not exists' => '路由标识不存在(或参数不够)', - 'invalid request' => '非法请求', - 'bind attr has exists' => '模型的属性已经存在', - 'relation data not exists' => '关联数据不存在', - 'relation not support' => '关联不支持', - 'chunk not support order' => 'Chunk不支持调用order方法', - 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', - - // 上传错误信息 - 'unknown upload error' => '未知上传错误!', - 'file write error' => '文件写入失败!', - 'upload temp dir not found' => '找不到临时文件夹!', - 'no file to uploaded' => '没有文件被上传!', - 'only the portion of file is uploaded' => '文件只有部分被上传!', - 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', - 'upload write error' => '文件上传保存错误!', - 'has the same filename: {:filename}' => '存在同名文件:{:filename}', - 'upload illegal files' => '非法上传文件', - 'illegal image files' => '非法图片文件', - 'extensions to upload is not allowed' => '上传文件后缀不允许', - 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', - 'filesize not match' => '上传文件大小不符!', - 'directory {:path} creation failed' => '目录 {:path} 创建失败!', - - // Validate Error Message - ':attribute require' => ':attribute不能为空', - ':attribute must be numeric' => ':attribute必须是数字', - ':attribute must be integer' => ':attribute必须是整数', - ':attribute must be float' => ':attribute必须是浮点数', - ':attribute must be bool' => ':attribute必须是布尔值', - ':attribute not a valid email address' => ':attribute格式不符', - ':attribute not a valid mobile' => ':attribute格式不符', - ':attribute must be a array' => ':attribute必须是数组', - ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', - ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', - ':attribute not a valid file' => ':attribute不是有效的上传文件', - ':attribute not a valid image' => ':attribute不是有效的图像文件', - ':attribute must be alpha' => ':attribute只能是字母', - ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', - ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', - ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', - ':attribute must be chinese' => ':attribute只能是汉字', - ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', - ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', - ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', - ':attribute not a valid url' => ':attribute不是有效的URL地址', - ':attribute not a valid ip' => ':attribute不是有效的IP地址', - ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', - ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', - ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', - ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', - ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', - 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', - 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', - 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', - ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', - ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', - ':attribute not within :rule' => '不在有效期内 :rule', - 'access IP is not allowed' => '不允许的IP访问', - 'access IP denied' => '禁止的IP访问', - ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', - ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', - ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', - ':attribute must greater than :rule' => ':attribute必须大于 :rule', - ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', - ':attribute must less than :rule' => ':attribute必须小于 :rule', - ':attribute must equal :rule' => ':attribute必须等于 :rule', - ':attribute has exists' => ':attribute已存在', - ':attribute not conform to the rules' => ':attribute不符合指定规则', - 'invalid Request method' => '无效的请求类型', - 'invalid token' => '令牌数据无效', - 'not conform to the rules' => '规则错误', -]; + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'not support data' => '不支持的数据表达式', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', +]; diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php old mode 100755 new mode 100644 index 0cf34a5..3c5f81f --- a/thinkphp/library/think/App.php +++ b/thinkphp/library/think/App.php @@ -1,672 +1,672 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; -use think\exception\HttpException; -use think\exception\HttpResponseException; -use think\exception\RouteNotFoundException; - -/** - * App 应用管理 - * @author liu21st - */ -class App -{ - /** - * @var bool 是否初始化过 - */ - protected static $init = false; - - /** - * @var string 当前模块路径 - */ - public static $modulePath; - - /** - * @var bool 应用调试模式 - */ - public static $debug = true; - - /** - * @var string 应用类库命名空间 - */ - public static $namespace = 'app'; - - /** - * @var bool 应用类库后缀 - */ - public static $suffix = false; - - /** - * @var bool 应用路由检测 - */ - protected static $routeCheck; - - /** - * @var bool 严格路由检测 - */ - protected static $routeMust; - - /** - * @var array 请求调度分发 - */ - protected static $dispatch; - - /** - * @var array 额外加载文件 - */ - protected static $file = []; - - /** - * 执行应用程序 - * @access public - * @param Request $request 请求对象 - * @return Response - * @throws Exception - */ - public static function run(Request $request = null) - { - $request = is_null($request) ? Request::instance() : $request; - - try { - $config = self::initCommon(); - - // 模块/控制器绑定 - if (defined('BIND_MODULE')) { - BIND_MODULE && Route::bind(BIND_MODULE); - } elseif ($config['auto_bind_module']) { - // 入口自动绑定 - $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); - if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { - Route::bind($name); - } - } - - $request->filter($config['default_filter']); - - // 默认语言 - Lang::range($config['default_lang']); - // 开启多语言机制 检测当前语言 - $config['lang_switch_on'] && Lang::detect(); - $request->langset(Lang::range()); - - // 加载系统语言包 - Lang::load([ - THINK_PATH . 'lang' . DS . $request->langset() . EXT, - APP_PATH . 'lang' . DS . $request->langset() . EXT, - ]); - - // 监听 app_dispatch - Hook::listen('app_dispatch', self::$dispatch); - // 获取应用调度信息 - $dispatch = self::$dispatch; - - // 未设置调度信息则进行 URL 路由检测 - if (empty($dispatch)) { - $dispatch = self::routeCheck($request, $config); - } - - // 记录当前调度信息 - $request->dispatch($dispatch); - - // 记录路由和请求信息 - if (self::$debug) { - Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); - Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); - Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); - } - - // 监听 app_begin - Hook::listen('app_begin', $dispatch); - - // 请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - - $data = self::exec($dispatch, $config); - } catch (HttpResponseException $exception) { - $data = $exception->getResponse(); - } - - // 清空类的实例化 - Loader::clearInstance(); - - // 输出数据到客户端 - if ($data instanceof Response) { - $response = $data; - } elseif (!is_null($data)) { - // 默认自动识别响应输出类型 - $type = $request->isAjax() ? - Config::get('default_ajax_return') : - Config::get('default_return_type'); - - $response = Response::create($data, $type); - } else { - $response = Response::create(); - } - - // 监听 app_end - Hook::listen('app_end', $response); - - return $response; - } - - /** - * 初始化应用,并返回配置信息 - * @access public - * @return array - */ - public static function initCommon() - { - if (empty(self::$init)) { - if (defined('APP_NAMESPACE')) { - self::$namespace = APP_NAMESPACE; - } - - Loader::addNamespace(self::$namespace, APP_PATH); - - // 初始化应用 - $config = self::init(); - self::$suffix = $config['class_suffix']; - - // 应用调试模式 - self::$debug = Env::get('app_debug', Config::get('app_debug')); - - if (!self::$debug) { - ini_set('display_errors', 'Off'); - } elseif (!IS_CLI) { - // 重新申请一块比较大的 buffer - if (ob_get_level() > 0) { - $output = ob_get_clean(); - } - - ob_start(); - - if (!empty($output)) { - echo $output; - } - - } - - if (!empty($config['root_namespace'])) { - Loader::addNamespace($config['root_namespace']); - } - - // 加载额外文件 - if (!empty($config['extra_file_list'])) { - foreach ($config['extra_file_list'] as $file) { - $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; - if (is_file($file) && !isset(self::$file[$file])) { - include $file; - self::$file[$file] = true; - } - } - } - - // 设置系统时区 - date_default_timezone_set($config['default_timezone']); - - // 监听 app_init - Hook::listen('app_init'); - - self::$init = true; - } - - return Config::get(); - } - - /** - * 初始化应用或模块 - * @access public - * @param string $module 模块名 - * @return array - */ - private static function init($module = '') - { - // 定位模块目录 - $module = $module ? $module . DS : ''; - - // 加载初始化文件 - if (is_file(APP_PATH . $module . 'init' . EXT)) { - include APP_PATH . $module . 'init' . EXT; - } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { - include RUNTIME_PATH . $module . 'init' . EXT; - } else { - // 加载模块配置 - $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); - - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - Config::load($filename, 'database'); - - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { - $filename = $dir . DS . $file; - Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - - // 加载应用状态配置 - if ($config['app_status']) { - Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - - // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - Hook::import(include CONF_PATH . $module . 'tags' . EXT); - } - - // 加载公共文件 - $path = APP_PATH . $module; - if (is_file($path . 'common' . EXT)) { - include $path . 'common' . EXT; - } - - // 加载当前模块语言包 - if ($module) { - Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); - } - } - - return Config::get(); - } - - /** - * 设置当前请求的调度信息 - * @access public - * @param array|string $dispatch 调度信息 - * @param string $type 调度类型 - * @return void - */ - public static function dispatch($dispatch, $type = 'module') - { - self::$dispatch = ['type' => $type, $type => $dispatch]; - } - - /** - * 执行函数或者闭包方法 支持参数调用 - * @access public - * @param string|array|\Closure $function 函数或者闭包 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeFunction($function, $vars = []) - { - $reflect = new \ReflectionFunction($function); - $args = self::bindParams($reflect, $vars); - - // 记录执行信息 - self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); - - return $reflect->invokeArgs($args); - } - - /** - * 调用反射执行类的方法 支持参数绑定 - * @access public - * @param string|array $method 方法 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeMethod($method, $vars = []) - { - if (is_array($method)) { - $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); - $reflect = new \ReflectionMethod($class, $method[1]); - } else { - // 静态方法 - $reflect = new \ReflectionMethod($method); - } - - $args = self::bindParams($reflect, $vars); - - self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); - - return $reflect->invokeArgs(isset($class) ? $class : null, $args); - } - - /** - * 调用反射执行类的实例化 支持依赖注入 - * @access public - * @param string $class 类名 - * @param array $vars 变量 - * @return mixed - */ - public static function invokeClass($class, $vars = []) - { - $reflect = new \ReflectionClass($class); - $constructor = $reflect->getConstructor(); - $args = $constructor ? self::bindParams($constructor, $vars) : []; - - return $reflect->newInstanceArgs($args); - } - - /** - * 绑定参数 - * @access private - * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 - * @param array $vars 变量 - * @return array - */ - private static function bindParams($reflect, $vars = []) - { - // 自动获取请求变量 - if (empty($vars)) { - $vars = Config::get('url_param_type') ? - Request::instance()->route() : - Request::instance()->param(); - } - - $args = []; - if ($reflect->getNumberOfParameters() > 0) { - // 判断数组类型 数字数组时按顺序绑定参数 - reset($vars); - $type = key($vars) === 0 ? 1 : 0; - - foreach ($reflect->getParameters() as $param) { - $args[] = self::getParamValue($param, $vars, $type); - } - } - - return $args; - } - - /** - * 获取参数值 - * @access private - * @param \ReflectionParameter $param 参数 - * @param array $vars 变量 - * @param string $type 类别 - * @return array - */ - private static function getParamValue($param, &$vars, $type) - { - $name = $param->getName(); - $class = $param->getClass(); - - if ($class) { - $className = $class->getName(); - $bind = Request::instance()->$name; - - if ($bind instanceof $className) { - $result = $bind; - } else { - if (method_exists($className, 'invoke')) { - $method = new \ReflectionMethod($className, 'invoke'); - - if ($method->isPublic() && $method->isStatic()) { - return $className::invoke(Request::instance()); - } - } - - $result = method_exists($className, 'instance') ? - $className::instance() : - new $className; - } - } elseif (1 == $type && !empty($vars)) { - $result = array_shift($vars); - } elseif (0 == $type && isset($vars[$name])) { - $result = $vars[$name]; - } elseif ($param->isDefaultValueAvailable()) { - $result = $param->getDefaultValue(); - } else { - throw new \InvalidArgumentException('method param miss:' . $name); - } - - return $result; - } - - /** - * 执行调用分发 - * @access protected - * @param array $dispatch 调用信息 - * @param array $config 配置信息 - * @return Response|mixed - * @throws \InvalidArgumentException - */ - protected static function exec($dispatch, $config) - { - switch ($dispatch['type']) { - case 'redirect': // 重定向跳转 - $data = Response::create($dispatch['url'], 'redirect') - ->code($dispatch['status']); - break; - case 'module': // 模块/控制器/操作 - $data = self::module( - $dispatch['module'], - $config, - isset($dispatch['convert']) ? $dispatch['convert'] : null - ); - break; - case 'controller': // 执行控制器操作 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = Loader::action( - $dispatch['controller'], - $vars, - $config['url_controller_layer'], - $config['controller_suffix'] - ); - break; - case 'method': // 回调方法 - $vars = array_merge(Request::instance()->param(), $dispatch['var']); - $data = self::invokeMethod($dispatch['method'], $vars); - break; - case 'function': // 闭包 - $data = self::invokeFunction($dispatch['function']); - break; - case 'response': // Response 实例 - $data = $dispatch['response']; - break; - default: - throw new \InvalidArgumentException('dispatch type not support'); - } - - return $data; - } - - /** - * 执行模块 - * @access public - * @param array $result 模块/控制器/操作 - * @param array $config 配置参数 - * @param bool $convert 是否自动转换控制器和操作名 - * @return mixed - * @throws HttpException - */ - public static function module($result, $config, $convert = null) - { - if (is_string($result)) { - $result = explode('/', $result); - } - - $request = Request::instance(); - - if ($config['app_multi_module']) { - // 多模块部署 - $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); - $bind = Route::getBind('module'); - $available = false; - - if ($bind) { - // 绑定模块 - list($bindModule) = explode('/', $bind); - - if (empty($result[0])) { - $module = $bindModule; - $available = true; - } elseif ($module == $bindModule) { - $available = true; - } - } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { - $available = true; - } - - // 模块初始化 - if ($module && $available) { - // 初始化模块 - $request->module($module); - $config = self::init($module); - - // 模块请求缓存检查 - $request->cache( - $config['request_cache'], - $config['request_cache_expire'], - $config['request_cache_except'] - ); - } else { - throw new HttpException(404, 'module not exists:' . $module); - } - } else { - // 单一模块部署 - $module = ''; - $request->module($module); - } - - // 设置默认过滤机制 - $request->filter($config['default_filter']); - - // 当前模块路径 - App::$modulePath = APP_PATH . ($module ? $module . DS : ''); - - // 是否自动转换控制器和操作名 - $convert = is_bool($convert) ? $convert : $config['url_convert']; - - // 获取控制器名 - $controller = strip_tags($result[1] ?: $config['default_controller']); - $controller = $convert ? strtolower($controller) : $controller; - - // 获取操作名 - $actionName = strip_tags($result[2] ?: $config['default_action']); - if (!empty($config['action_convert'])) { - $actionName = Loader::parseName($actionName, 1); - } else { - $actionName = $convert ? strtolower($actionName) : $actionName; - } - - // 设置当前请求的控制器、操作 - $request->controller(Loader::parseName($controller, 1))->action($actionName); - - // 监听module_init - Hook::listen('module_init', $request); - - try { - $instance = Loader::controller( - $controller, - $config['url_controller_layer'], - $config['controller_suffix'], - $config['empty_controller'] - ); - } catch (ClassNotFoundException $e) { - throw new HttpException(404, 'controller not exists:' . $e->getClass()); - } - - // 获取当前操作名 - $action = $actionName . $config['action_suffix']; - - $vars = []; - if (is_callable([$instance, $action])) { - // 执行操作方法 - $call = [$instance, $action]; - // 严格获取当前操作方法名 - $reflect = new \ReflectionMethod($instance, $action); - $methodName = $reflect->getName(); - $suffix = $config['action_suffix']; - $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; - $request->action($actionName); - - } elseif (is_callable([$instance, '_empty'])) { - // 空操作 - $call = [$instance, '_empty']; - $vars = [$actionName]; - } else { - // 操作不存在 - throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); - } - - Hook::listen('action_begin', $call); - - return self::invokeMethod($call, $vars); - } - - /** - * URL路由检测(根据PATH_INFO) - * @access public - * @param \think\Request $request 请求实例 - * @param array $config 配置信息 - * @return array - * @throws \think\Exception - */ - public static function routeCheck($request, array $config) - { - $path = $request->path(); - $depr = $config['pathinfo_depr']; - $result = false; - - // 路由检测 - $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; - if ($check) { - // 开启路由 - if (is_file(RUNTIME_PATH . 'route.php')) { - // 读取路由缓存 - $rules = include RUNTIME_PATH . 'route.php'; - is_array($rules) && Route::rules($rules); - } else { - $files = $config['route_config_file']; - foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - // 导入路由配置 - $rules = include CONF_PATH . $file . CONF_EXT; - is_array($rules) && Route::import($rules); - } - } - } - - // 路由检测(根据路由定义返回不同的URL调度) - $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); - $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; - - if ($must && false === $result) { - // 路由无效 - throw new RouteNotFoundException(); - } - } - - // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 - if (false === $result) { - $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); - } - - return $result; - } - - /** - * 设置应用的路由检测机制 - * @access public - * @param bool $route 是否需要检测路由 - * @param bool $must 是否强制检测路由 - * @return void - */ - public static function route($route, $must = false) - { - self::$routeCheck = $route; - self::$routeMust = $must; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\exception\RouteNotFoundException; + +/** + * App 应用管理 + * @author liu21st + */ +class App +{ + /** + * @var bool 是否初始化过 + */ + protected static $init = false; + + /** + * @var string 当前模块路径 + */ + public static $modulePath; + + /** + * @var bool 应用调试模式 + */ + public static $debug = true; + + /** + * @var string 应用类库命名空间 + */ + public static $namespace = 'app'; + + /** + * @var bool 应用类库后缀 + */ + public static $suffix = false; + + /** + * @var bool 应用路由检测 + */ + protected static $routeCheck; + + /** + * @var bool 严格路由检测 + */ + protected static $routeMust; + + /** + * @var array 请求调度分发 + */ + protected static $dispatch; + + /** + * @var array 额外加载文件 + */ + protected static $file = []; + + /** + * 执行应用程序 + * @access public + * @param Request $request 请求对象 + * @return Response + * @throws Exception + */ + public static function run(Request $request = null) + { + $request = is_null($request) ? Request::instance() : $request; + + try { + $config = self::initCommon(); + + // 模块/控制器绑定 + if (defined('BIND_MODULE')) { + BIND_MODULE && Route::bind(BIND_MODULE); + } elseif ($config['auto_bind_module']) { + // 入口自动绑定 + $name = pathinfo($request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir(APP_PATH . $name)) { + Route::bind($name); + } + } + + $request->filter($config['default_filter']); + + // 默认语言 + Lang::range($config['default_lang']); + // 开启多语言机制 检测当前语言 + $config['lang_switch_on'] && Lang::detect(); + $request->langset(Lang::range()); + + // 加载系统语言包 + Lang::load([ + THINK_PATH . 'lang' . DS . $request->langset() . EXT, + APP_PATH . 'lang' . DS . $request->langset() . EXT, + ]); + + // 监听 app_dispatch + Hook::listen('app_dispatch', self::$dispatch); + // 获取应用调度信息 + $dispatch = self::$dispatch; + + // 未设置调度信息则进行 URL 路由检测 + if (empty($dispatch)) { + $dispatch = self::routeCheck($request, $config); + } + + // 记录当前调度信息 + $request->dispatch($dispatch); + + // 记录路由和请求信息 + if (self::$debug) { + Log::record('[ ROUTE ] ' . var_export($dispatch, true), 'info'); + Log::record('[ HEADER ] ' . var_export($request->header(), true), 'info'); + Log::record('[ PARAM ] ' . var_export($request->param(), true), 'info'); + } + + // 监听 app_begin + Hook::listen('app_begin', $dispatch); + + // 请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + + $data = self::exec($dispatch, $config); + } catch (HttpResponseException $exception) { + $data = $exception->getResponse(); + } + + // 清空类的实例化 + Loader::clearInstance(); + + // 输出数据到客户端 + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $request->isAjax() ? + Config::get('default_ajax_return') : + Config::get('default_return_type'); + + $response = Response::create($data, $type); + } else { + $response = Response::create(); + } + + // 监听 app_end + Hook::listen('app_end', $response); + + return $response; + } + + /** + * 初始化应用,并返回配置信息 + * @access public + * @return array + */ + public static function initCommon() + { + if (empty(self::$init)) { + if (defined('APP_NAMESPACE')) { + self::$namespace = APP_NAMESPACE; + } + + Loader::addNamespace(self::$namespace, APP_PATH); + + // 初始化应用 + $config = self::init(); + self::$suffix = $config['class_suffix']; + + // 应用调试模式 + self::$debug = Env::get('app_debug', Config::get('app_debug')); + + if (!self::$debug) { + ini_set('display_errors', 'Off'); + } elseif (!IS_CLI) { + // 重新申请一块比较大的 buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + + ob_start(); + + if (!empty($output)) { + echo $output; + } + + } + + if (!empty($config['root_namespace'])) { + Loader::addNamespace($config['root_namespace']); + } + + // 加载额外文件 + if (!empty($config['extra_file_list'])) { + foreach ($config['extra_file_list'] as $file) { + $file = strpos($file, '.') ? $file : APP_PATH . $file . EXT; + if (is_file($file) && !isset(self::$file[$file])) { + include $file; + self::$file[$file] = true; + } + } + } + + // 设置系统时区 + date_default_timezone_set($config['default_timezone']); + + // 监听 app_init + Hook::listen('app_init'); + + self::$init = true; + } + + return Config::get(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return array + */ + private static function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DS : ''; + + // 加载初始化文件 + if (is_file(APP_PATH . $module . 'init' . EXT)) { + include APP_PATH . $module . 'init' . EXT; + } elseif (is_file(RUNTIME_PATH . $module . 'init' . EXT)) { + include RUNTIME_PATH . $module . 'init' . EXT; + } else { + // 加载模块配置 + $config = Config::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + Config::load($filename, 'database'); + + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === CONF_EXT) { + $filename = $dir . DS . $file; + Config::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + // 加载应用状态配置 + if ($config['app_status']) { + Config::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + Hook::import(include CONF_PATH . $module . 'tags' . EXT); + } + + // 加载公共文件 + $path = APP_PATH . $module; + if (is_file($path . 'common' . EXT)) { + include $path . 'common' . EXT; + } + + // 加载当前模块语言包 + if ($module) { + Lang::load($path . 'lang' . DS . Request::instance()->langset() . EXT); + } + } + + return Config::get(); + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param array|string $dispatch 调度信息 + * @param string $type 调度类型 + * @return void + */ + public static function dispatch($dispatch, $type = 'module') + { + self::$dispatch = ['type' => $type, $type => $dispatch]; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|array|\Closure $function 函数或者闭包 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeFunction($function, $vars = []) + { + $reflect = new \ReflectionFunction($function); + $args = self::bindParams($reflect, $vars); + + // 记录执行信息 + self::$debug && Log::record('[ RUN ] ' . $reflect->__toString(), 'info'); + + return $reflect->invokeArgs($args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param string|array $method 方法 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeMethod($method, $vars = []) + { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : self::invokeClass($method[0]); + $reflect = new \ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new \ReflectionMethod($method); + } + + $args = self::bindParams($reflect, $vars); + + self::$debug && Log::record('[ RUN ] ' . $reflect->class . '->' . $reflect->name . '[ ' . $reflect->getFileName() . ' ]', 'info'); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 变量 + * @return mixed + */ + public static function invokeClass($class, $vars = []) + { + $reflect = new \ReflectionClass($class); + $constructor = $reflect->getConstructor(); + $args = $constructor ? self::bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + } + + /** + * 绑定参数 + * @access private + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 变量 + * @return array + */ + private static function bindParams($reflect, $vars = []) + { + // 自动获取请求变量 + if (empty($vars)) { + $vars = Config::get('url_param_type') ? + Request::instance()->route() : + Request::instance()->param(); + } + + $args = []; + if ($reflect->getNumberOfParameters() > 0) { + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + + foreach ($reflect->getParameters() as $param) { + $args[] = self::getParamValue($param, $vars, $type); + } + } + + return $args; + } + + /** + * 获取参数值 + * @access private + * @param \ReflectionParameter $param 参数 + * @param array $vars 变量 + * @param string $type 类别 + * @return array + */ + private static function getParamValue($param, &$vars, $type) + { + $name = $param->getName(); + $class = $param->getClass(); + + if ($class) { + $className = $class->getName(); + $bind = Request::instance()->$name; + + if ($bind instanceof $className) { + $result = $bind; + } else { + if (method_exists($className, 'invoke')) { + $method = new \ReflectionMethod($className, 'invoke'); + + if ($method->isPublic() && $method->isStatic()) { + return $className::invoke(Request::instance()); + } + } + + $result = method_exists($className, 'instance') ? + $className::instance() : + new $className; + } + } elseif (1 == $type && !empty($vars)) { + $result = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $result = $vars[$name]; + } elseif ($param->isDefaultValueAvailable()) { + $result = $param->getDefaultValue(); + } else { + throw new \InvalidArgumentException('method param miss:' . $name); + } + + return $result; + } + + /** + * 执行调用分发 + * @access protected + * @param array $dispatch 调用信息 + * @param array $config 配置信息 + * @return Response|mixed + * @throws \InvalidArgumentException + */ + protected static function exec($dispatch, $config) + { + switch ($dispatch['type']) { + case 'redirect': // 重定向跳转 + $data = Response::create($dispatch['url'], 'redirect') + ->code($dispatch['status']); + break; + case 'module': // 模块/控制器/操作 + $data = self::module( + $dispatch['module'], + $config, + isset($dispatch['convert']) ? $dispatch['convert'] : null + ); + break; + case 'controller': // 执行控制器操作 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = Loader::action( + $dispatch['controller'], + $vars, + $config['url_controller_layer'], + $config['controller_suffix'] + ); + break; + case 'method': // 回调方法 + $vars = array_merge(Request::instance()->param(), $dispatch['var']); + $data = self::invokeMethod($dispatch['method'], $vars); + break; + case 'function': // 闭包 + $data = self::invokeFunction($dispatch['function']); + break; + case 'response': // Response 实例 + $data = $dispatch['response']; + break; + default: + throw new \InvalidArgumentException('dispatch type not support'); + } + + return $data; + } + + /** + * 执行模块 + * @access public + * @param array $result 模块/控制器/操作 + * @param array $config 配置参数 + * @param bool $convert 是否自动转换控制器和操作名 + * @return mixed + * @throws HttpException + */ + public static function module($result, $config, $convert = null) + { + if (is_string($result)) { + $result = explode('/', $result); + } + + $request = Request::instance(); + + if ($config['app_multi_module']) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $config['default_module'])); + $bind = Route::getBind('module'); + $available = false; + + if ($bind) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + + if (empty($result[0])) { + $module = $bindModule; + $available = true; + } elseif ($module == $bindModule) { + $available = true; + } + } elseif (!in_array($module, $config['deny_module_list']) && is_dir(APP_PATH . $module)) { + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $request->module($module); + $config = self::init($module); + + // 模块请求缓存检查 + $request->cache( + $config['request_cache'], + $config['request_cache_expire'], + $config['request_cache_except'] + ); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } else { + // 单一模块部署 + $module = ''; + $request->module($module); + } + + // 设置默认过滤机制 + $request->filter($config['default_filter']); + + // 当前模块路径 + App::$modulePath = APP_PATH . ($module ? $module . DS : ''); + + // 是否自动转换控制器和操作名 + $convert = is_bool($convert) ? $convert : $config['url_convert']; + + // 获取控制器名 + $controller = strip_tags($result[1] ?: $config['default_controller']); + $controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $actionName = strip_tags($result[2] ?: $config['default_action']); + if (!empty($config['action_convert'])) { + $actionName = Loader::parseName($actionName, 1); + } else { + $actionName = $convert ? strtolower($actionName) : $actionName; + } + + // 设置当前请求的控制器、操作 + $request->controller(Loader::parseName($controller, 1))->action($actionName); + + // 监听module_init + Hook::listen('module_init', $request); + + try { + $instance = Loader::controller( + $controller, + $config['url_controller_layer'], + $config['controller_suffix'], + $config['empty_controller'] + ); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 获取当前操作名 + $action = $actionName . $config['action_suffix']; + + $vars = []; + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + // 严格获取当前操作方法名 + $reflect = new \ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $config['action_suffix']; + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $request->action($actionName); + + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$actionName]; + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + Hook::listen('action_begin', $call); + + return self::invokeMethod($call, $vars); + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @param \think\Request $request 请求实例 + * @param array $config 配置信息 + * @return array + * @throws \think\Exception + */ + public static function routeCheck($request, array $config) + { + $path = $request->path(); + $depr = $config['pathinfo_depr']; + $result = false; + + // 路由检测 + $check = !is_null(self::$routeCheck) ? self::$routeCheck : $config['url_route_on']; + if ($check) { + // 开启路由 + if (is_file(RUNTIME_PATH . 'route.php')) { + // 读取路由缓存 + $rules = include RUNTIME_PATH . 'route.php'; + is_array($rules) && Route::rules($rules); + } else { + $files = $config['route_config_file']; + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + // 导入路由配置 + $rules = include CONF_PATH . $file . CONF_EXT; + is_array($rules) && Route::import($rules); + } + } + } + + // 路由检测(根据路由定义返回不同的URL调度) + $result = Route::check($request, $path, $depr, $config['url_domain_deploy']); + $must = !is_null(self::$routeMust) ? self::$routeMust : $config['url_route_must']; + + if ($must && false === $result) { + // 路由无效 + throw new RouteNotFoundException(); + } + } + + // 路由无效 解析模块/控制器/操作/参数... 支持控制器自动搜索 + if (false === $result) { + $result = Route::parseUrl($path, $depr, $config['controller_auto_search']); + } + + return $result; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $route 是否需要检测路由 + * @param bool $must 是否强制检测路由 + * @return void + */ + public static function route($route, $must = false) + { + self::$routeCheck = $route; + self::$routeMust = $must; + } +} diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php old mode 100755 new mode 100644 index 033b7ad..de7c327 --- a/thinkphp/library/think/Build.php +++ b/thinkphp/library/think/Build.php @@ -1,235 +1,235 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Build -{ - /** - * 根据传入的 build 资料创建目录和文件 - * @access public - * @param array $build build 列表 - * @param string $namespace 应用类库命名空间 - * @param bool $suffix 类库后缀 - * @return void - * @throws Exception - */ - public static function run(array $build = [], $namespace = 'app', $suffix = false) - { - // 锁定 - $lock = APP_PATH . 'build.lock'; - - // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 - if (!is_writable($lock)) { - if (!touch($lock)) { - throw new Exception( - '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
    请手动生成项目目录~', - 10006 - ); - } - - foreach ($build as $module => $list) { - if ('__dir__' == $module) { - // 创建目录列表 - self::buildDir($list); - } elseif ('__file__' == $module) { - // 创建文件列表 - self::buildFile($list); - } else { - // 创建模块 - self::module($module, $list, $namespace, $suffix); - } - } - - // 解除锁定 - unlink($lock); - } - } - - /** - * 创建目录 - * @access protected - * @param array $list 目录列表 - * @return void - */ - protected static function buildDir($list) - { - foreach ($list as $dir) { - // 目录不存在则创建目录 - !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); - } - } - - /** - * 创建文件 - * @access protected - * @param array $list 文件列表 - * @return void - */ - protected static function buildFile($list) - { - foreach ($list as $file) { - // 先创建目录 - if (!is_dir(APP_PATH . dirname($file))) { - mkdir(APP_PATH . dirname($file), 0755, true); - } - - // 再创建文件 - if (!is_file(APP_PATH . $file)) { - file_put_contents( - APP_PATH . $file, - 'php' == pathinfo($file, PATHINFO_EXTENSION) ? " ['config.php', 'common.php'], - '__dir__' => ['controller', 'model', 'view'], - ]; - } - - // 创建子目录和文件 - foreach ($list as $path => $file) { - $modulePath = APP_PATH . $module . DS; - - if ('__dir__' == $path) { - // 生成子目录 - foreach ($file as $dir) { - self::checkDirBuild($modulePath . $dir); - } - } elseif ('__file__' == $path) { - // 生成(空白)文件 - foreach ($file as $name) { - if (!is_file($modulePath . $name)) { - file_put_contents( - $modulePath . $name, - 'php' == pathinfo($name, PATHINFO_EXTENSION) ? " +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 根据传入的 build 资料创建目录和文件 + * @access public + * @param array $build build 列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + * @throws Exception + */ + public static function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lock = APP_PATH . 'build.lock'; + + // 如果锁定文件不可写(不存在)则进行处理,否则表示已经有程序在处理了 + if (!is_writable($lock)) { + if (!touch($lock)) { + throw new Exception( + '应用目录[' . APP_PATH . ']不可写,目录无法自动生成!
    请手动生成项目目录~', + 10006 + ); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + self::buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + self::buildFile($list); + } else { + // 创建模块 + self::module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lock); + } + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected static function buildDir($list) + { + foreach ($list as $dir) { + // 目录不存在则创建目录 + !is_dir(APP_PATH . $dir) && mkdir(APP_PATH . $dir, 0755, true); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected static function buildFile($list) + { + foreach ($list as $file) { + // 先创建目录 + if (!is_dir(APP_PATH . dirname($file))) { + mkdir(APP_PATH . dirname($file), 0755, true); + } + + // 再创建文件 + if (!is_file(APP_PATH . $file)) { + file_put_contents( + APP_PATH . $file, + 'php' == pathinfo($file, PATHINFO_EXTENSION) ? " ['config.php', 'common.php'], + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = APP_PATH . $module . DS; + + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + self::checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents( + $modulePath . $name, + 'php' == pathinfo($name, PATHINFO_EXTENSION) ? " -// +---------------------------------------------------------------------- - -namespace think; - -use think\cache\Driver; - -class Cache -{ - /** - * @var array 缓存的实例 - */ - public static $instance = []; - - /** - * @var int 缓存读取次数 - */ - public static $readTimes = 0; - - /** - * @var int 缓存写入次数 - */ - public static $writeTimes = 0; - - /** - * @var object 操作句柄 - */ - public static $handler; - - /** - * 连接缓存驱动 - * @access public - * @param array $options 配置数组 - * @param bool|string $name 缓存连接标识 true 强制重新连接 - * @return Driver - */ - public static function connect(array $options = [], $name = false) - { - $type = !empty($options['type']) ? $options['type'] : 'File'; - - if (false === $name) { - $name = md5(serialize($options)); - } - - if (true === $name || !isset(self::$instance[$name])) { - $class = false === strpos($type, '\\') ? - '\\think\\cache\\driver\\' . ucwords($type) : - $type; - - // 记录初始化信息 - App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); - - if (true === $name) { - return new $class($options); - } - - self::$instance[$name] = new $class($options); - } - - return self::$instance[$name]; - } - - /** - * 自动初始化缓存 - * @access public - * @param array $options 配置数组 - * @return Driver - */ - public static function init(array $options = []) - { - if (is_null(self::$handler)) { - if (empty($options) && 'complex' == Config::get('cache.type')) { - $default = Config::get('cache.default'); - // 获取默认缓存配置,并连接 - $options = Config::get('cache.' . $default['type']) ?: $default; - } elseif (empty($options)) { - $options = Config::get('cache'); - } - - self::$handler = self::connect($options); - } - - return self::$handler; - } - - /** - * 切换缓存类型 需要配置 cache.type 为 complex - * @access public - * @param string $name 缓存标识 - * @return Driver - */ - public static function store($name = '') - { - if ('' !== $name && 'complex' == Config::get('cache.type')) { - return self::connect(Config::get('cache.' . $name), strtolower($name)); - } - - return self::init(); - } - - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public static function has($name) - { - self::$readTimes++; - - return self::init()->has($name); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $default 默认值 - * @return mixed - */ - public static function get($name, $default = false) - { - self::$readTimes++; - - return self::init()->get($name, $default); - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存标识 - * @param mixed $value 存储数据 - * @param int|null $expire 有效时间 0为永久 - * @return boolean - */ - public static function set($name, $value, $expire = null) - { - self::$writeTimes++; - - return self::init()->set($name, $value, $expire); - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function inc($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->inc($name, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public static function dec($name, $step = 1) - { - self::$writeTimes++; - - return self::init()->dec($name, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存标识 - * @return boolean - */ - public static function rm($name) - { - self::$writeTimes++; - - return self::init()->rm($name); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public static function clear($tag = null) - { - self::$writeTimes++; - - return self::init()->clear($tag); - } - - /** - * 读取缓存并删除 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public static function pull($name) - { - self::$readTimes++; - self::$writeTimes++; - - return self::init()->pull($name); - } - - /** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ - public static function remember($name, $value, $expire = null) - { - self::$readTimes++; - - return self::init()->remember($name, $value, $expire); - } - - /** - * 缓存标签 - * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 - * @return Driver - */ - public static function tag($name, $keys = null, $overlay = false) - { - return self::init()->tag($name, $keys, $overlay); - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +class Cache +{ + /** + * @var array 缓存的实例 + */ + public static $instance = []; + + /** + * @var int 缓存读取次数 + */ + public static $readTimes = 0; + + /** + * @var int 缓存写入次数 + */ + public static $writeTimes = 0; + + /** + * @var object 操作句柄 + */ + public static $handler; + + /** + * 连接缓存驱动 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public static function connect(array $options = [], $name = false) + { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset(self::$instance[$name])) { + $class = false === strpos($type, '\\') ? + '\\think\\cache\\driver\\' . ucwords($type) : + $type; + + // 记录初始化信息 + App::$debug && Log::record('[ CACHE ] INIT ' . $type, 'info'); + + if (true === $name) { + return new $class($options); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @return Driver + */ + public static function init(array $options = []) + { + if (is_null(self::$handler)) { + if (empty($options) && 'complex' == Config::get('cache.type')) { + $default = Config::get('cache.default'); + // 获取默认缓存配置,并连接 + $options = Config::get('cache.' . $default['type']) ?: $default; + } elseif (empty($options)) { + $options = Config::get('cache'); + } + + self::$handler = self::connect($options); + } + + return self::$handler; + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public static function store($name = '') + { + if ('' !== $name && 'complex' == Config::get('cache.type')) { + return self::connect(Config::get('cache.' . $name), strtolower($name)); + } + + return self::init(); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public static function has($name) + { + self::$readTimes++; + + return self::init()->has($name); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $default 默认值 + * @return mixed + */ + public static function get($name, $default = false) + { + self::$readTimes++; + + return self::init()->get($name, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 存储数据 + * @param int|null $expire 有效时间 0为永久 + * @return boolean + */ + public static function set($name, $value, $expire = null) + { + self::$writeTimes++; + + return self::init()->set($name, $value, $expire); + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function inc($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->inc($name, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public static function dec($name, $step = 1) + { + self::$writeTimes++; + + return self::init()->dec($name, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存标识 + * @return boolean + */ + public static function rm($name) + { + self::$writeTimes++; + + return self::init()->rm($name); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public static function clear($tag = null) + { + self::$writeTimes++; + + return self::init()->clear($tag); + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public static function pull($name) + { + self::$readTimes++; + self::$writeTimes++; + + return self::init()->pull($name); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public static function remember($name, $value, $expire = null) + { + self::$readTimes++; + + return self::init()->remember($name, $value, $expire); + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return Driver + */ + public static function tag($name, $keys = null, $overlay = false) + { + return self::init()->tag($name, $keys, $overlay); + } + +} diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php old mode 100755 new mode 100644 index 519ea59..8e132b1 --- a/thinkphp/library/think/Collection.php +++ b/thinkphp/library/think/Collection.php @@ -1,457 +1,457 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use ArrayAccess; -use ArrayIterator; -use Countable; -use IteratorAggregate; -use JsonSerializable; - -class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable -{ - /** - * @var array 数据 - */ - protected $items = []; - - /** - * Collection constructor. - * @access public - * @param array $items 数据 - */ - public function __construct($items = []) - { - $this->items = $this->convertToArray($items); - } - - /** - * 创建 Collection 实例 - * @access public - * @param array $items 数据 - * @return static - */ - public static function make($items = []) - { - return new static($items); - } - - /** - * 判断数据是否为空 - * @access public - * @return bool - */ - public function isEmpty() - { - return empty($this->items); - } - - /** - * 将数据转成数组 - * @access public - * @return array - */ - public function toArray() - { - return array_map(function ($value) { - return ($value instanceof Model || $value instanceof self) ? - $value->toArray() : - $value; - }, $this->items); - } - - /** - * 获取全部的数据 - * @access public - * @return array - */ - public function all() - { - return $this->items; - } - - /** - * 交换数组中的键和值 - * @access public - * @return static - */ - public function flip() - { - return new static(array_flip($this->items)); - } - - /** - * 返回数组中所有的键名组成的新 Collection 实例 - * @access public - * @return static - */ - public function keys() - { - return new static(array_keys($this->items)); - } - - /** - * 合并数组并返回一个新的 Collection 实例 - * @access public - * @param mixed $items 新的数据 - * @return static - */ - public function merge($items) - { - return new static(array_merge($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回差集生成的新 Collection 实例 - * @access public - * @param mixed $items 做比较的数据 - * @return static - */ - public function diff($items) - { - return new static(array_diff($this->items, $this->convertToArray($items))); - } - - /** - * 比较数组,返回交集组成的 Collection 新实例 - * @access public - * @param mixed $items 比较数据 - * @return static - */ - public function intersect($items) - { - return new static(array_intersect($this->items, $this->convertToArray($items))); - } - - /** - * 返回并删除数据中的的最后一个元素(出栈) - * @access public - * @return mixed - */ - public function pop() - { - return array_pop($this->items); - } - - /** - * 返回并删除数据中首个元素 - * @access public - * @return mixed - */ - public function shift() - { - return array_shift($this->items); - } - - /** - * 在数组开头插入一个元素 - * @access public - * @param mixed $value 值 - * @param mixed $key 键名 - * @return void - */ - public function unshift($value, $key = null) - { - if (is_null($key)) { - array_unshift($this->items, $value); - } else { - $this->items = [$key => $value] + $this->items; - } - } - - /** - * 在数组结尾插入一个元素 - * @access public - * @param mixed $value 值 - * @param mixed $key 键名 - * @return void - */ - public function push($value, $key = null) - { - if (is_null($key)) { - $this->items[] = $value; - } else { - $this->items[$key] = $value; - } - } - - /** - * 通过使用用户自定义函数,以字符串返回数组 - * @access public - * @param callable $callback 回调函数 - * @param mixed $initial 初始值 - * @return mixed - */ - public function reduce(callable $callback, $initial = null) - { - return array_reduce($this->items, $callback, $initial); - } - - /** - * 以相反的顺序创建一个新的 Collection 实例 - * @access public - * @return static - */ - public function reverse() - { - return new static(array_reverse($this->items)); - } - - /** - * 把数据分割为新的数组块 - * @access public - * @param int $size 分隔长度 - * @param bool $preserveKeys 是否保持原数据索引 - * @return static - */ - public function chunk($size, $preserveKeys = false) - { - $chunks = []; - - foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { - $chunks[] = new static($chunk); - } - - return new static($chunks); - } - - /** - * 给数据中的每个元素执行回调 - * @access public - * @param callable $callback 回调函数 - * @return $this - */ - public function each(callable $callback) - { - foreach ($this->items as $key => $item) { - $result = $callback($item, $key); - - if (false === $result) { - break; - } - - if (!is_object($item)) { - $this->items[$key] = $result; - } - } - - return $this; - } - - /** - * 用回调函数过滤数据中的元素 - * @access public - * @param callable|null $callback 回调函数 - * @return static - */ - public function filter(callable $callback = null) - { - return new static(array_filter($this->items, $callback ?: null)); - } - - /** - * 返回数据中指定的一列 - * @access public - * @param mixed $columnKey 键名 - * @param null $indexKey 作为索引值的列 - * @return array - */ - public function column($columnKey, $indexKey = null) - { - if (function_exists('array_column')) { - return array_column($this->items, $columnKey, $indexKey); - } - - $result = []; - foreach ($this->items as $row) { - $key = $value = null; - $keySet = $valueSet = false; - - if (null !== $indexKey && array_key_exists($indexKey, $row)) { - $key = (string) $row[$indexKey]; - $keySet = true; - } - - if (null === $columnKey) { - $valueSet = true; - $value = $row; - } elseif (is_array($row) && array_key_exists($columnKey, $row)) { - $valueSet = true; - $value = $row[$columnKey]; - } - - if ($valueSet) { - if ($keySet) { - $result[$key] = $value; - } else { - $result[] = $value; - } - } - } - - return $result; - } - - /** - * 对数据排序,并返回排序后的数据组成的新 Collection 实例 - * @access public - * @param callable|null $callback 回调函数 - * @return static - */ - public function sort(callable $callback = null) - { - $items = $this->items; - $callback = $callback ?: function ($a, $b) { - return $a == $b ? 0 : (($a < $b) ? -1 : 1); - }; - - uasort($items, $callback); - return new static($items); - } - - /** - * 将数据打乱后组成新的 Collection 实例 - * @access public - * @return static - */ - public function shuffle() - { - $items = $this->items; - - shuffle($items); - return new static($items); - } - - /** - * 截取数据并返回新的 Collection 实例 - * @access public - * @param int $offset 起始位置 - * @param int $length 截取长度 - * @param bool $preserveKeys 是否保持原先的键名 - * @return static - */ - public function slice($offset, $length = null, $preserveKeys = false) - { - return new static(array_slice($this->items, $offset, $length, $preserveKeys)); - } - - /** - * 指定的键是否存在 - * @access public - * @param mixed $offset 键名 - * @return bool - */ - public function offsetExists($offset) - { - return array_key_exists($offset, $this->items); - } - - /** - * 获取指定键对应的值 - * @access public - * @param mixed $offset 键名 - * @return mixed - */ - public function offsetGet($offset) - { - return $this->items[$offset]; - } - - /** - * 设置键值 - * @access public - * @param mixed $offset 键名 - * @param mixed $value 值 - * @return void - */ - public function offsetSet($offset, $value) - { - if (is_null($offset)) { - $this->items[] = $value; - } else { - $this->items[$offset] = $value; - } - } - - /** - * 删除指定键值 - * @access public - * @param mixed $offset 键名 - * @return void - */ - public function offsetUnset($offset) - { - unset($this->items[$offset]); - } - - /** - * 统计数据的个数 - * @access public - * @return int - */ - public function count() - { - return count($this->items); - } - - /** - * 获取数据的迭代器 - * @access public - * @return ArrayIterator - */ - public function getIterator() - { - return new ArrayIterator($this->items); - } - - /** - * 将数据反序列化成数组 - * @access public - * @return array - */ - public function jsonSerialize() - { - return $this->toArray(); - } - - /** - * 转换当前数据集为 JSON 字符串 - * @access public - * @param integer $options json 参数 - * @return string - */ - public function toJson($options = JSON_UNESCAPED_UNICODE) - { - return json_encode($this->toArray(), $options); - } - - /** - * 将数据转换成字符串 - * @access public - * @return string - */ - public function __toString() - { - return $this->toJson(); - } - - /** - * 将数据转换成数组 - * @access protected - * @param mixed $items 数据 - * @return array - */ - protected function convertToArray($items) - { - return $items instanceof self ? $items->all() : (array) $items; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * @var array 数据 + */ + protected $items = []; + + /** + * Collection constructor. + * @access public + * @param array $items 数据 + */ + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + /** + * 创建 Collection 实例 + * @access public + * @param array $items 数据 + * @return static + */ + public static function make($items = []) + { + return new static($items); + } + + /** + * 判断数据是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + /** + * 将数据转成数组 + * @access public + * @return array + */ + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? + $value->toArray() : + $value; + }, $this->items); + } + + /** + * 获取全部的数据 + * @access public + * @return array + */ + public function all() + { + return $this->items; + } + + /** + * 交换数组中的键和值 + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名组成的新 Collection 实例 + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 合并数组并返回一个新的 Collection 实例 + * @access public + * @param mixed $items 新的数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回差集生成的新 Collection 实例 + * @access public + * @param mixed $items 做比较的数据 + * @return static + */ + public function diff($items) + { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + /** + * 比较数组,返回交集组成的 Collection 新实例 + * @access public + * @param mixed $items 比较数据 + * @return static + */ + public function intersect($items) + { + return new static(array_intersect($this->items, $this->convertToArray($items))); + } + + /** + * 返回并删除数据中的的最后一个元素(出栈) + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 返回并删除数据中首个元素 + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 值 + * @param mixed $key 键名 + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * @access public + * @param callable $callback 回调函数 + * @param mixed $initial 初始值 + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序创建一个新的 Collection 实例 + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 把数据分割为新的数组块 + * @access public + * @param int $size 分隔长度 + * @param bool $preserveKeys 是否保持原数据索引 + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 给数据中的每个元素执行回调 + * @access public + * @param callable $callback 回调函数 + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } + + if (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数过滤数据中的元素 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function filter(callable $callback = null) + { + return new static(array_filter($this->items, $callback ?: null)); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param null $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + if (function_exists('array_column')) { + return array_column($this->items, $columnKey, $indexKey); + } + + $result = []; + foreach ($this->items as $row) { + $key = $value = null; + $keySet = $valueSet = false; + + if (null !== $indexKey && array_key_exists($indexKey, $row)) { + $key = (string) $row[$indexKey]; + $keySet = true; + } + + if (null === $columnKey) { + $valueSet = true; + $value = $row; + } elseif (is_array($row) && array_key_exists($columnKey, $row)) { + $valueSet = true; + $value = $row[$columnKey]; + } + + if ($valueSet) { + if ($keySet) { + $result[$key] = $value; + } else { + $result[] = $value; + } + } + } + + return $result; + } + + /** + * 对数据排序,并返回排序后的数据组成的新 Collection 实例 + * @access public + * @param callable|null $callback 回调函数 + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + }; + + uasort($items, $callback); + return new static($items); + } + + /** + * 将数据打乱后组成新的 Collection 实例 + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + return new static($items); + } + + /** + * 截取数据并返回新的 Collection 实例 + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys 是否保持原先的键名 + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + /** + * 指定的键是否存在 + * @access public + * @param mixed $offset 键名 + * @return bool + */ + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + /** + * 获取指定键对应的值 + * @access public + * @param mixed $offset 键名 + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + /** + * 设置键值 + * @access public + * @param mixed $offset 键名 + * @param mixed $value 值 + * @return void + */ + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + /** + * 删除指定键值 + * @access public + * @param mixed $offset 键名 + * @return void + */ + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + /** + * 统计数据的个数 + * @access public + * @return int + */ + public function count() + { + return count($this->items); + } + + /** + * 获取数据的迭代器 + * @access public + * @return ArrayIterator + */ + public function getIterator() + { + return new ArrayIterator($this->items); + } + + /** + * 将数据反序列化成数组 + * @access public + * @return array + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为 JSON 字符串 + * @access public + * @param integer $options json 参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 将数据转换成字符串 + * @access public + * @return string + */ + public function __toString() + { + return $this->toJson(); + } + + /** + * 将数据转换成数组 + * @access protected + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items) + { + return $items instanceof self ? $items->all() : (array) $items; + } +} diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php old mode 100755 new mode 100644 index 572be01..8fa668d --- a/thinkphp/library/think/Config.php +++ b/thinkphp/library/think/Config.php @@ -1,214 +1,214 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Config -{ - /** - * @var array 配置参数 - */ - private static $config = []; - - /** - * @var string 参数作用域 - */ - private static $range = '_sys_'; - - /** - * 设定配置参数的作用域 - * @access public - * @param string $range 作用域 - * @return void - */ - public static function range($range) - { - self::$range = $range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - } - - /** - * 解析配置文件或内容 - * @access public - * @param string $config 配置文件路径或内容 - * @param string $type 配置解析类型 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 - * @return mixed - */ - public static function parse($config, $type = '', $name = '', $range = '') - { - $range = $range ?: self::$range; - - if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); - - $class = false !== strpos($type, '\\') ? - $type : - '\\think\\config\\driver\\' . ucwords($type); - - return self::set((new $class())->parse($config), $name, $range); - } - - /** - * 加载配置文件(PHP格式) - * @access public - * @param string $file 配置文件名 - * @param string $name 配置名(如设置即表示二级配置) - * @param string $range 作用域 - * @return mixed - */ - public static function load($file, $name = '', $range = '') - { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - - if (is_file($file)) { - $name = strtolower($name); - $type = pathinfo($file, PATHINFO_EXTENSION); - - if ('php' == $type) { - return self::set(include $file, $name, $range); - } - - if ('yaml' == $type && function_exists('yaml_parse_file')) { - return self::set(yaml_parse_file($file), $name, $range); - } - - return self::parse($file, $type, $name, $range); - } - - return self::$config[$range]; - } - - /** - * 检测配置是否存在 - * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 - * @return bool - */ - public static function has($name, $range = '') - { - $range = $range ?: self::$range; - - if (!strpos($name, '.')) { - return isset(self::$config[$range][strtolower($name)]); - } - - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); - return isset(self::$config[$range][strtolower($name[0])][$name[1]]); - } - - /** - * 获取配置参数 为空则获取所有配置 - * @access public - * @param string $name 配置参数名(支持二级配置 . 号分割) - * @param string $range 作用域 - * @return mixed - */ - public static function get($name = null, $range = '') - { - $range = $range ?: self::$range; - - // 无参数时获取所有 - if (empty($name) && isset(self::$config[$range])) { - return self::$config[$range]; - } - - // 非二级配置时直接返回 - if (!strpos($name, '.')) { - $name = strtolower($name); - return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; - } - - // 二维数组设置和获取支持 - $name = explode('.', $name, 2); - $name[0] = strtolower($name[0]); - - if (!isset(self::$config[$range][$name[0]])) { - // 动态载入额外配置 - $module = Request::instance()->module(); - $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; - - is_file($file) && self::load($file, $name[0]); - } - - return isset(self::$config[$range][$name[0]][$name[1]]) ? - self::$config[$range][$name[0]][$name[1]] : - null; - } - - /** - * 设置配置参数 name 为数组则为批量设置 - * @access public - * @param string|array $name 配置参数名(支持二级配置 . 号分割) - * @param mixed $value 配置值 - * @param string $range 作用域 - * @return mixed - */ - public static function set($name, $value = null, $range = '') - { - $range = $range ?: self::$range; - - if (!isset(self::$config[$range])) self::$config[$range] = []; - - // 字符串则表示单个配置设置 - if (is_string($name)) { - if (!strpos($name, '.')) { - self::$config[$range][strtolower($name)] = $value; - } else { - // 二维数组 - $name = explode('.', $name, 2); - self::$config[$range][strtolower($name[0])][$name[1]] = $value; - } - - return $value; - } - - // 数组则表示批量设置 - if (is_array($name)) { - if (!empty($value)) { - self::$config[$range][$value] = isset(self::$config[$range][$value]) ? - array_merge(self::$config[$range][$value], $name) : - $name; - - return self::$config[$range][$value]; - } - - return self::$config[$range] = array_merge( - self::$config[$range], array_change_key_case($name) - ); - } - - // 为空直接返回已有配置 - return self::$config[$range]; - } - - /** - * 重置配置参数 - * @access public - * @param string $range 作用域 - * @return void - */ - public static function reset($range = '') - { - $range = $range ?: self::$range; - - if (true === $range) { - self::$config = []; - } else { - self::$config[$range] = []; - } - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Config +{ + /** + * @var array 配置参数 + */ + private static $config = []; + + /** + * @var string 参数作用域 + */ + private static $range = '_sys_'; + + /** + * 设定配置参数的作用域 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function range($range) + { + self::$range = $range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function parse($config, $type = '', $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (empty($type)) $type = pathinfo($config, PATHINFO_EXTENSION); + + $class = false !== strpos($type, '\\') ? + $type : + '\\think\\config\\driver\\' . ucwords($type); + + return self::set((new $class())->parse($config), $name, $range); + } + + /** + * 加载配置文件(PHP格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 配置名(如设置即表示二级配置) + * @param string $range 作用域 + * @return mixed + */ + public static function load($file, $name = '', $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + if (is_file($file)) { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return self::set(include $file, $name, $range); + } + + if ('yaml' == $type && function_exists('yaml_parse_file')) { + return self::set(yaml_parse_file($file), $name, $range); + } + + return self::parse($file, $type, $name, $range); + } + + return self::$config[$range]; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return bool + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + if (!strpos($name, '.')) { + return isset(self::$config[$range][strtolower($name)]); + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + return isset(self::$config[$range][strtolower($name[0])][$name[1]]); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 . 号分割) + * @param string $range 作用域 + * @return mixed + */ + public static function get($name = null, $range = '') + { + $range = $range ?: self::$range; + + // 无参数时获取所有 + if (empty($name) && isset(self::$config[$range])) { + return self::$config[$range]; + } + + // 非二级配置时直接返回 + if (!strpos($name, '.')) { + $name = strtolower($name); + return isset(self::$config[$range][$name]) ? self::$config[$range][$name] : null; + } + + // 二维数组设置和获取支持 + $name = explode('.', $name, 2); + $name[0] = strtolower($name[0]); + + if (!isset(self::$config[$range][$name[0]])) { + // 动态载入额外配置 + $module = Request::instance()->module(); + $file = CONF_PATH . ($module ? $module . DS : '') . 'extra' . DS . $name[0] . CONF_EXT; + + is_file($file) && self::load($file, $name[0]); + } + + return isset(self::$config[$range][$name[0]][$name[1]]) ? + self::$config[$range][$name[0]][$name[1]] : + null; + } + + /** + * 设置配置参数 name 为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持二级配置 . 号分割) + * @param mixed $value 配置值 + * @param string $range 作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$config[$range])) self::$config[$range] = []; + + // 字符串则表示单个配置设置 + if (is_string($name)) { + if (!strpos($name, '.')) { + self::$config[$range][strtolower($name)] = $value; + } else { + // 二维数组 + $name = explode('.', $name, 2); + self::$config[$range][strtolower($name[0])][$name[1]] = $value; + } + + return $value; + } + + // 数组则表示批量设置 + if (is_array($name)) { + if (!empty($value)) { + self::$config[$range][$value] = isset(self::$config[$range][$value]) ? + array_merge(self::$config[$range][$value], $name) : + $name; + + return self::$config[$range][$value]; + } + + return self::$config[$range] = array_merge( + self::$config[$range], array_change_key_case($name) + ); + } + + // 为空直接返回已有配置 + return self::$config[$range]; + } + + /** + * 重置配置参数 + * @access public + * @param string $range 作用域 + * @return void + */ + public static function reset($range = '') + { + $range = $range ?: self::$range; + + if (true === $range) { + self::$config = []; + } else { + self::$config[$range] = []; + } + } +} diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php old mode 100755 new mode 100644 index df8cbba..32b2572 --- a/thinkphp/library/think/Console.php +++ b/thinkphp/library/think/Console.php @@ -1,863 +1,863 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\console\Command; -use think\console\command\Help as HelpCommand; -use think\console\Input; -use think\console\input\Argument as InputArgument; -use think\console\input\Definition as InputDefinition; -use think\console\input\Option as InputOption; -use think\console\Output; -use think\console\output\driver\Buffer; - -class Console -{ - /** - * @var string 命令名称 - */ - private $name; - - /** - * @var string 命令版本 - */ - private $version; - - /** - * @var Command[] 命令 - */ - private $commands = []; - - /** - * @var bool 是否需要帮助信息 - */ - private $wantHelps = false; - - /** - * @var bool 是否捕获异常 - */ - private $catchExceptions = true; - - /** - * @var bool 是否自动退出执行 - */ - private $autoExit = true; - - /** - * @var InputDefinition 输入定义 - */ - private $definition; - - /** - * @var string 默认执行的命令 - */ - private $defaultCommand; - - /** - * @var array 默认提供的命令 - */ - private static $defaultCommands = [ - "think\\console\\command\\Help", - "think\\console\\command\\Lists", - "think\\console\\command\\Build", - "think\\console\\command\\Clear", - "think\\console\\command\\make\\Controller", - "think\\console\\command\\make\\Model", - "think\\console\\command\\optimize\\Autoload", - "think\\console\\command\\optimize\\Config", - "think\\console\\command\\optimize\\Route", - "think\\console\\command\\optimize\\Schema", - ]; - - /** - * Console constructor. - * @access public - * @param string $name 名称 - * @param string $version 版本 - * @param null|string $user 执行用户 - */ - public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) - { - $this->name = $name; - $this->version = $version; - - if ($user) { - $this->setUser($user); - } - - $this->defaultCommand = 'list'; - $this->definition = $this->getDefaultInputDefinition(); - - foreach ($this->getDefaultCommands() as $command) { - $this->add($command); - } - } - - /** - * 设置执行用户 - * @param $user - */ - public function setUser($user) - { - $user = posix_getpwnam($user); - if ($user) { - posix_setuid($user['uid']); - posix_setgid($user['gid']); - } - } - - /** - * 初始化 Console - * @access public - * @param bool $run 是否运行 Console - * @return int|Console - */ - public static function init($run = true) - { - static $console; - - if (!$console) { - $config = Config::get('console'); - // 实例化 console - $console = new self($config['name'], $config['version'], $config['user']); - - // 读取指令集 - if (is_file(CONF_PATH . 'command' . EXT)) { - $commands = include CONF_PATH . 'command' . EXT; - - if (is_array($commands)) { - foreach ($commands as $command) { - class_exists($command) && - is_subclass_of($command, "\\think\\console\\Command") && - $console->add(new $command()); // 注册指令 - } - } - } - } - - return $run ? $console->run() : $console; - } - - /** - * 调用命令 - * @access public - * @param string $command - * @param array $parameters - * @param string $driver - * @return Output - */ - public static function call($command, array $parameters = [], $driver = 'buffer') - { - $console = self::init(false); - - array_unshift($parameters, $command); - - $input = new Input($parameters); - $output = new Output($driver); - - $console->setCatchExceptions(false); - $console->find($command)->run($input, $output); - - return $output; - } - - /** - * 执行当前的指令 - * @access public - * @return int - * @throws \Exception - */ - public function run() - { - $input = new Input(); - $output = new Output(); - - $this->configureIO($input, $output); - - try { - $exitCode = $this->doRun($input, $output); - } catch (\Exception $e) { - if (!$this->catchExceptions) throw $e; - - $output->renderException($e); - - $exitCode = $e->getCode(); - - if (is_numeric($exitCode)) { - $exitCode = ((int) $exitCode) ?: 1; - } else { - $exitCode = 1; - } - } - - if ($this->autoExit) { - if ($exitCode > 255) $exitCode = 255; - - exit($exitCode); - } - - return $exitCode; - } - - /** - * 执行指令 - * @access public - * @param Input $input 输入 - * @param Output $output 输出 - * @return int - */ - public function doRun(Input $input, Output $output) - { - // 获取版本信息 - if (true === $input->hasParameterOption(['--version', '-V'])) { - $output->writeln($this->getLongVersion()); - - return 0; - } - - $name = $this->getCommandName($input); - - // 获取帮助信息 - if (true === $input->hasParameterOption(['--help', '-h'])) { - if (!$name) { - $name = 'help'; - $input = new Input(['help']); - } else { - $this->wantHelps = true; - } - } - - if (!$name) { - $name = $this->defaultCommand; - $input = new Input([$this->defaultCommand]); - } - - return $this->doRunCommand($this->find($name), $input, $output); - } - - /** - * 设置输入参数定义 - * @access public - * @param InputDefinition $definition 输入定义 - * @return $this; - */ - public function setDefinition(InputDefinition $definition) - { - $this->definition = $definition; - - return $this; - } - - /** - * 获取输入参数定义 - * @access public - * @return InputDefinition - */ - public function getDefinition() - { - return $this->definition; - } - - /** - * 获取帮助信息 - * @access public - * @return string - */ - public function getHelp() - { - return $this->getLongVersion(); - } - - /** - * 设置是否捕获异常 - * @access public - * @param bool $boolean 是否捕获 - * @return $this - */ - public function setCatchExceptions($boolean) - { - $this->catchExceptions = (bool) $boolean; - - return $this; - } - - /** - * 设置是否自动退出 - * @access public - * @param bool $boolean 是否自动退出 - * @return $this - */ - public function setAutoExit($boolean) - { - $this->autoExit = (bool) $boolean; - - return $this; - } - - /** - * 获取名称 - * @access public - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * 设置名称 - * @access public - * @param string $name 名称 - * @return $this - */ - public function setName($name) - { - $this->name = $name; - - return $this; - } - - /** - * 获取版本 - * @access public - * @return string - */ - public function getVersion() - { - return $this->version; - } - - /** - * 设置版本 - * @access public - * @param string $version 版本信息 - * @return $this - */ - public function setVersion($version) - { - $this->version = $version; - - return $this; - } - - /** - * 获取完整的版本号 - * @access public - * @return string - */ - public function getLongVersion() - { - if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { - return sprintf( - '%s version %s', - $this->getName(), - $this->getVersion() - ); - } - - return 'Console Tool'; - } - - /** - * 注册一个指令 - * @access public - * @param string $name 指令名称 - * @return Command - */ - public function register($name) - { - return $this->add(new Command($name)); - } - - /** - * 批量添加指令 - * @access public - * @param Command[] $commands 指令实例 - * @return $this - */ - public function addCommands(array $commands) - { - foreach ($commands as $command) $this->add($command); - - return $this; - } - - /** - * 添加一个指令 - * @access public - * @param Command $command 命令实例 - * @return Command|bool - */ - public function add(Command $command) - { - if (!$command->isEnabled()) { - $command->setConsole(null); - return false; - } - - $command->setConsole($this); - - if (null === $command->getDefinition()) { - throw new \LogicException( - sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)) - ); - } - - $this->commands[$command->getName()] = $command; - - foreach ($command->getAliases() as $alias) { - $this->commands[$alias] = $command; - } - - return $command; - } - - /** - * 获取指令 - * @access public - * @param string $name 指令名称 - * @return Command - * @throws \InvalidArgumentException - */ - public function get($name) - { - if (!isset($this->commands[$name])) { - throw new \InvalidArgumentException( - sprintf('The command "%s" does not exist.', $name) - ); - } - - $command = $this->commands[$name]; - - if ($this->wantHelps) { - $this->wantHelps = false; - - /** @var HelpCommand $helpCommand */ - $helpCommand = $this->get('help'); - $helpCommand->setCommand($command); - - return $helpCommand; - } - - return $command; - } - - /** - * 某个指令是否存在 - * @access public - * @param string $name 指令名称 - * @return bool - */ - public function has($name) - { - return isset($this->commands[$name]); - } - - /** - * 获取所有的命名空间 - * @access public - * @return array - */ - public function getNamespaces() - { - $namespaces = []; - - foreach ($this->commands as $command) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($command->getName()) - ); - - foreach ($command->getAliases() as $alias) { - $namespaces = array_merge( - $namespaces, $this->extractAllNamespaces($alias) - ); - } - } - - return array_values(array_unique(array_filter($namespaces))); - } - - /** - * 查找注册命名空间中的名称或缩写 - * @access public - * @param string $namespace - * @return string - * @throws \InvalidArgumentException - */ - public function findNamespace($namespace) - { - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { - return preg_quote($matches[1]) . '[^:]*'; - }, $namespace); - - $allNamespaces = $this->getNamespaces(); - $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); - - if (empty($namespaces)) { - $message = sprintf( - 'There are no commands defined in the "%s" namespace.', $namespace - ); - - if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { - if (1 == count($alternatives)) { - $message .= "\n\nDid you mean this?\n "; - } else { - $message .= "\n\nDid you mean one of these?\n "; - } - - $message .= implode("\n ", $alternatives); - } - - throw new \InvalidArgumentException($message); - } - - $exact = in_array($namespace, $namespaces, true); - - if (count($namespaces) > 1 && !$exact) { - throw new \InvalidArgumentException( - sprintf( - 'The namespace "%s" is ambiguous (%s).', - $namespace, - $this->getAbbreviationSuggestions(array_values($namespaces))) - ); - } - - return $exact ? $namespace : reset($namespaces); - } - - /** - * 查找指令 - * @access public - * @param string $name 名称或者别名 - * @return Command - * @throws \InvalidArgumentException - */ - public function find($name) - { - $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { - return preg_quote($matches[1]) . '[^:]*'; - }, $name); - - $allCommands = array_keys($this->commands); - $commands = preg_grep('{^' . $expr . '}', $allCommands); - - if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { - if (false !== ($pos = strrpos($name, ':'))) { - $this->findNamespace(substr($name, 0, $pos)); - } - - $message = sprintf('Command "%s" is not defined.', $name); - - if ($alternatives = $this->findAlternatives($name, $allCommands)) { - if (1 == count($alternatives)) { - $message .= "\n\nDid you mean this?\n "; - } else { - $message .= "\n\nDid you mean one of these?\n "; - } - $message .= implode("\n ", $alternatives); - } - - throw new \InvalidArgumentException($message); - } - - if (count($commands) > 1) { - $commandList = $this->commands; - $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { - $commandName = $commandList[$nameOrAlias]->getName(); - - return $commandName === $nameOrAlias || !in_array($commandName, $commands); - }); - } - - $exact = in_array($name, $commands, true); - if (count($commands) > 1 && !$exact) { - $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); - - throw new \InvalidArgumentException( - sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions) - ); - } - - return $this->get($exact ? $name : reset($commands)); - } - - /** - * 获取所有的指令 - * @access public - * @param string $namespace 命名空间 - * @return Command[] - */ - public function all($namespace = null) - { - if (null === $namespace) return $this->commands; - - $commands = []; - - foreach ($this->commands as $name => $command) { - $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); - - if ($ext === $namespace) $commands[$name] = $command; - } - - return $commands; - } - - /** - * 获取可能的指令名 - * @access public - * @param array $names 指令名 - * @return array - */ - public static function getAbbreviations($names) - { - $abbrevs = []; - foreach ($names as $name) { - for ($len = strlen($name); $len > 0; --$len) { - $abbrev = substr($name, 0, $len); - $abbrevs[$abbrev][] = $name; - } - } - - return $abbrevs; - } - - /** - * 配置基于用户的参数和选项的输入和输出实例 - * @access protected - * @param Input $input 输入实例 - * @param Output $output 输出实例 - * @return void - */ - protected function configureIO(Input $input, Output $output) - { - if (true === $input->hasParameterOption(['--ansi'])) { - $output->setDecorated(true); - } elseif (true === $input->hasParameterOption(['--no-ansi'])) { - $output->setDecorated(false); - } - - if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { - $input->setInteractive(false); - } - - if (true === $input->hasParameterOption(['--quiet', '-q'])) { - $output->setVerbosity(Output::VERBOSITY_QUIET); - } else { - if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { - $output->setVerbosity(Output::VERBOSITY_DEBUG); - } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { - $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); - } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { - $output->setVerbosity(Output::VERBOSITY_VERBOSE); - } - } - } - - /** - * 执行指令 - * @access protected - * @param Command $command 指令实例 - * @param Input $input 输入实例 - * @param Output $output 输出实例 - * @return int - * @throws \Exception - */ - protected function doRunCommand(Command $command, Input $input, Output $output) - { - return $command->run($input, $output); - } - - /** - * 获取指令的名称 - * @access protected - * @param Input $input 输入实例 - * @return string - */ - protected function getCommandName(Input $input) - { - return $input->getFirstArgument(); - } - - /** - * 获取默认输入定义 - * @access protected - * @return InputDefinition - */ - protected function getDefaultInputDefinition() - { - return new InputDefinition([ - new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), - new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), - new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), - new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), - new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), - new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), - new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), - new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), - ]); - } - - /** - * 获取默认命令 - * @access protected - * @return Command[] - */ - protected function getDefaultCommands() - { - $defaultCommands = []; - - foreach (self::$defaultCommands as $class) { - if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { - $defaultCommands[] = new $class(); - } - } - - return $defaultCommands; - } - - /** - * 添加默认指令 - * @access public - * @param array $classes 指令 - * @return void - */ - public static function addDefaultCommands(array $classes) - { - self::$defaultCommands = array_merge(self::$defaultCommands, $classes); - } - - /** - * 获取可能的建议 - * @access private - * @param array $abbrevs - * @return string - */ - private function getAbbreviationSuggestions($abbrevs) - { - return sprintf( - '%s, %s%s', - $abbrevs[0], - $abbrevs[1], - count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '' - ); - } - - /** - * 返回指令的命名空间部分 - * @access public - * @param string $name 指令名称 - * @param string $limit 部分的命名空间的最大数量 - * @return string - */ - public function extractNamespace($name, $limit = null) - { - $parts = explode(':', $name); - array_pop($parts); - - return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); - } - - /** - * 查找可替代的建议 - * @access private - * @param string $name 指令名称 - * @param array|\Traversable $collection 建议集合 - * @return array - */ - private function findAlternatives($name, $collection) - { - $threshold = 1e3; - $alternatives = []; - $collectionParts = []; - - foreach ($collection as $item) { - $collectionParts[$item] = explode(':', $item); - } - - foreach (explode(':', $name) as $i => $subname) { - foreach ($collectionParts as $collectionName => $parts) { - $exists = isset($alternatives[$collectionName]); - - if (!isset($parts[$i]) && $exists) { - $alternatives[$collectionName] += $threshold; - continue; - } elseif (!isset($parts[$i])) { - continue; - } - - $lev = levenshtein($subname, $parts[$i]); - - if ($lev <= strlen($subname) / 3 || - '' !== $subname && - false !== strpos($parts[$i], $subname) - ) { - $alternatives[$collectionName] = $exists ? - $alternatives[$collectionName] + $lev : - $lev; - } elseif ($exists) { - $alternatives[$collectionName] += $threshold; - } - } - } - - foreach ($collection as $item) { - $lev = levenshtein($name, $item); - - if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { - $alternatives[$item] = isset($alternatives[$item]) ? - $alternatives[$item] - $lev : - $lev; - } - } - - $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { - return $lev < 2 * $threshold; - }); - - asort($alternatives); - - return array_keys($alternatives); - } - - /** - * 设置默认的指令 - * @access public - * @param string $commandName 指令名称 - * @return $this - */ - public function setDefaultCommand($commandName) - { - $this->defaultCommand = $commandName; - - return $this; - } - - /** - * 返回所有的命名空间 - * @access private - * @param string $name 指令名称 - * @return array - */ - private function extractAllNamespaces($name) - { - $namespaces = []; - - foreach (explode(':', $name, -1) as $part) { - if (count($namespaces)) { - $namespaces[] = end($namespaces) . ':' . $part; - } else { - $namespaces[] = $part; - } - } - - return $namespaces; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +class Console +{ + /** + * @var string 命令名称 + */ + private $name; + + /** + * @var string 命令版本 + */ + private $version; + + /** + * @var Command[] 命令 + */ + private $commands = []; + + /** + * @var bool 是否需要帮助信息 + */ + private $wantHelps = false; + + /** + * @var bool 是否捕获异常 + */ + private $catchExceptions = true; + + /** + * @var bool 是否自动退出执行 + */ + private $autoExit = true; + + /** + * @var InputDefinition 输入定义 + */ + private $definition; + + /** + * @var string 默认执行的命令 + */ + private $defaultCommand; + + /** + * @var array 默认提供的命令 + */ + private static $defaultCommands = [ + "think\\console\\command\\Help", + "think\\console\\command\\Lists", + "think\\console\\command\\Build", + "think\\console\\command\\Clear", + "think\\console\\command\\make\\Controller", + "think\\console\\command\\make\\Model", + "think\\console\\command\\optimize\\Autoload", + "think\\console\\command\\optimize\\Config", + "think\\console\\command\\optimize\\Route", + "think\\console\\command\\optimize\\Schema", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + + foreach ($this->getDefaultCommands() as $command) { + $this->add($command); + } + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Config::get('console'); + // 实例化 console + $console = new self($config['name'], $config['version'], $config['user']); + + // 读取指令集 + if (is_file(CONF_PATH . 'command' . EXT)) { + $commands = include CONF_PATH . 'command' . EXT; + + if (is_array($commands)) { + foreach ($commands as $command) { + class_exists($command) && + is_subclass_of($command, "\\think\\console\\Command") && + $console->add(new $command()); // 注册指令 + } + } + } + } + + return $run ? $console->run() : $console; + } + + /** + * 调用命令 + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) throw $e; + + $output->renderException($e); + + $exitCode = $e->getCode(); + + if (is_numeric($exitCode)) { + $exitCode = ((int) $exitCode) ?: 1; + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) $exitCode = 255; + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input 输入 + * @param Output $output 输出 + * @return int + */ + public function doRun(Input $input, Output $output) + { + // 获取版本信息 + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + // 获取帮助信息 + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + return $this->doRunCommand($this->find($name), $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition 输入定义 + * @return $this; + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + + return $this; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取帮助信息 + * @access public + * @return string + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 设置是否捕获异常 + * @access public + * @param bool $boolean 是否捕获 + * @return $this + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + + return $this; + } + + /** + * 设置是否自动退出 + * @access public + * @param bool $boolean 是否自动退出 + * @return $this + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + + return $this; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name 名称 + * @return $this + */ + public function setName($name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取版本 + * @access public + * @return string + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version 版本信息 + * @return $this + */ + public function setVersion($version) + { + $this->version = $version; + + return $this; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf( + '%s version %s', + $this->getName(), + $this->getVersion() + ); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 + * @access public + * @param string $name 指令名称 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 批量添加指令 + * @access public + * @param Command[] $commands 指令实例 + * @return $this + */ + public function addCommands(array $commands) + { + foreach ($commands as $command) $this->add($command); + + return $this; + } + + /** + * 添加一个指令 + * @access public + * @param Command $command 命令实例 + * @return Command|bool + */ + public function add(Command $command) + { + if (!$command->isEnabled()) { + $command->setConsole(null); + return false; + } + + $command->setConsole($this); + + if (null === $command->getDefinition()) { + throw new \LogicException( + sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command)) + ); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws \InvalidArgumentException + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException( + sprintf('The command "%s" does not exist.', $name) + ); + } + + $command = $this->commands[$name]; + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + + foreach ($this->commands as $command) { + $namespaces = array_merge( + $namespaces, $this->extractAllNamespaces($command->getName()) + ); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge( + $namespaces, $this->extractAllNamespaces($alias) + ); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写 + * @access public + * @param string $namespace + * @return string + * @throws \InvalidArgumentException + */ + public function findNamespace($namespace) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + + $allNamespaces = $this->getNamespaces(); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf( + 'There are no commands defined in the "%s" namespace.', $namespace + ); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException( + sprintf( + 'The namespace "%s" is ambiguous (%s).', + $namespace, + $this->getAbbreviationSuggestions(array_values($namespaces))) + ); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws \InvalidArgumentException + */ + public function find($name) + { + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $allCommands = array_keys($this->commands); + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== ($pos = strrpos($name, ':'))) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + if (count($commands) > 1) { + $commandList = $this->commands; + $commands = array_filter($commands, function ($nameOrAlias) use ($commandList, $commands) { + $commandName = $commandList[$nameOrAlias]->getName(); + + return $commandName === $nameOrAlias || !in_array($commandName, $commands); + }); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException( + sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions) + ); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + */ + public function all($namespace = null) + { + if (null === $namespace) return $this->commands; + + $commands = []; + + foreach ($this->commands as $name => $command) { + $ext = $this->extractNamespace($name, substr_count($namespace, ':') + 1); + + if ($ext === $namespace) $commands[$name] = $command; + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names 指令名 + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return void + */ + protected function configureIO(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的名称 + * @access protected + * @param Input $input 输入实例 + * @return string + */ + protected function getCommandName(Input $input) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + /** + * 获取默认命令 + * @access protected + * @return Command[] + */ + protected function getDefaultCommands() + { + $defaultCommands = []; + + foreach (self::$defaultCommands as $class) { + if (class_exists($class) && is_subclass_of($class, "think\\console\\Command")) { + $defaultCommands[] = new $class(); + } + } + + return $defaultCommands; + } + + /** + * 添加默认指令 + * @access public + * @param array $classes 指令 + * @return void + */ + public static function addDefaultCommands(array $classes) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classes); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf( + '%s, %s%s', + $abbrevs[0], + $abbrevs[1], + count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : '' + ); + } + + /** + * 返回指令的命名空间部分 + * @access public + * @param string $name 指令名称 + * @param string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name 指令名称 + * @param array|\Traversable $collection 建议集合 + * @return array + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = []; + $collectionParts = []; + + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + + if ($lev <= strlen($subname) / 3 || + '' !== $subname && + false !== strpos($parts[$i], $subname) + ) { + $alternatives[$collectionName] = $exists ? + $alternatives[$collectionName] + $lev : + $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? + $alternatives[$item] - $lev : + $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 设置默认的指令 + * @access public + * @param string $commandName 指令名称 + * @return $this + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + + return $this; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name 指令名称 + * @return array + */ + private function extractAllNamespaces($name) + { + $namespaces = []; + + foreach (explode(':', $name, -1) as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php old mode 100755 new mode 100644 index e0a5d0e..77225b7 --- a/thinkphp/library/think/Controller.php +++ b/thinkphp/library/think/Controller.php @@ -1,229 +1,229 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ValidateException; -use traits\controller\Jump; - -Loader::import('controller/Jump', TRAIT_PATH, EXT); - -class Controller -{ - use Jump; - - /** - * @var \think\View 视图类实例 - */ - protected $view; - - /** - * @var \think\Request Request 实例 - */ - protected $request; - - /** - * @var bool 验证失败是否抛出异常 - */ - protected $failException = false; - - /** - * @var bool 是否批量验证 - */ - protected $batchValidate = false; - - /** - * @var array 前置操作方法列表 - */ - protected $beforeActionList = []; - - /** - * 构造方法 - * @access public - * @param Request $request Request 对象 - */ - public function __construct(Request $request = null) - { - $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); - $this->request = is_null($request) ? Request::instance() : $request; - - // 控制器初始化 - $this->_initialize(); - - // 前置操作方法 - if ($this->beforeActionList) { - foreach ($this->beforeActionList as $method => $options) { - is_numeric($method) ? - $this->beforeAction($options) : - $this->beforeAction($method, $options); - } - } - } - - /** - * 初始化操作 - * @access protected - */ - protected function _initialize() - { - } - - /** - * 前置操作 - * @access protected - * @param string $method 前置操作方法名 - * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] - * @return void - */ - protected function beforeAction($method, $options = []) - { - if (isset($options['only'])) { - if (is_string($options['only'])) { - $options['only'] = explode(',', $options['only']); - } - - if (!in_array($this->request->action(), $options['only'])) { - return; - } - } elseif (isset($options['except'])) { - if (is_string($options['except'])) { - $options['except'] = explode(',', $options['except']); - } - - if (in_array($this->request->action(), $options['except'])) { - return; - } - } - - call_user_func([$this, $method]); - } - - /** - * 加载模板输出 - * @access protected - * @param string $template 模板文件名 - * @param array $vars 模板输出变量 - * @param array $replace 模板替换 - * @param array $config 模板参数 - * @return mixed - */ - protected function fetch($template = '', $vars = [], $replace = [], $config = []) - { - return $this->view->fetch($template, $vars, $replace, $config); - } - - /** - * 渲染内容输出 - * @access protected - * @param string $content 模板内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 - * @return mixed - */ - protected function display($content = '', $vars = [], $replace = [], $config = []) - { - return $this->view->display($content, $vars, $replace, $config); - } - - /** - * 模板变量赋值 - * @access protected - * @param mixed $name 要显示的模板变量 - * @param mixed $value 变量的值 - * @return $this - */ - protected function assign($name, $value = '') - { - $this->view->assign($name, $value); - - return $this; - } - - /** - * 初始化模板引擎 - * @access protected - * @param array|string $engine 引擎参数 - * @return $this - */ - protected function engine($engine) - { - $this->view->engine($engine); - - return $this; - } - - /** - * 设置验证失败后是否抛出异常 - * @access protected - * @param bool $fail 是否抛出异常 - * @return $this - */ - protected function validateFailException($fail = true) - { - $this->failException = $fail; - - return $this; - } - - /** - * 验证数据 - * @access protected - * @param array $data 数据 - * @param string|array $validate 验证器名或者验证规则数组 - * @param array $message 提示信息 - * @param bool $batch 是否批量验证 - * @param mixed $callback 回调方法(闭包) - * @return array|string|true - * @throws ValidateException - */ - protected function validate($data, $validate, $message = [], $batch = false, $callback = null) - { - if (is_array($validate)) { - $v = Loader::validate(); - $v->rule($validate); - } else { - // 支持场景 - if (strpos($validate, '.')) { - list($validate, $scene) = explode('.', $validate); - } - - $v = Loader::validate($validate); - - !empty($scene) && $v->scene($scene); - } - - // 批量验证 - if ($batch || $this->batchValidate) { - $v->batch(true); - } - - // 设置错误信息 - if (is_array($message)) { - $v->message($message); - } - - // 使用回调验证 - if ($callback && is_callable($callback)) { - call_user_func_array($callback, [$v, &$data]); - } - - if (!$v->check($data)) { - if ($this->failException) { - throw new ValidateException($v->getError()); - } - - return $v->getError(); - } - - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +Loader::import('controller/Jump', TRAIT_PATH, EXT); + +class Controller +{ + use Jump; + + /** + * @var \think\View 视图类实例 + */ + protected $view; + + /** + * @var \think\Request Request 实例 + */ + protected $request; + + /** + * @var bool 验证失败是否抛出异常 + */ + protected $failException = false; + + /** + * @var bool 是否批量验证 + */ + protected $batchValidate = false; + + /** + * @var array 前置操作方法列表 + */ + protected $beforeActionList = []; + + /** + * 构造方法 + * @access public + * @param Request $request Request 对象 + */ + public function __construct(Request $request = null) + { + $this->view = View::instance(Config::get('template'), Config::get('view_replace_str')); + $this->request = is_null($request) ? Request::instance() : $request; + + // 控制器初始化 + $this->_initialize(); + + // 前置操作方法 + if ($this->beforeActionList) { + foreach ($this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + } + + /** + * 初始化操作 + * @access protected + */ + protected function _initialize() + { + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者 ['except'=>[...]] + * @return void + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + if (!in_array($this->request->action(), $options['only'])) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + if (in_array($this->request->action(), $options['except'])) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $replace 模板替换 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $replace = [], $config = []) + { + return $this->view->fetch($template, $vars, $replace, $config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $replace = [], $config = []) + { + return $this->view->display($content, $vars, $replace, $config); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = Loader::validate(); + $v->rule($validate); + } else { + // 支持场景 + if (strpos($validate, '.')) { + list($validate, $scene) = explode('.', $validate); + } + + $v = Loader::validate($validate); + + !empty($scene) && $v->scene($scene); + } + + // 批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + // 设置错误信息 + if (is_array($message)) { + $v->message($message); + } + + // 使用回调验证 + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + + return $v->getError(); + } + + return true; + } +} diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php old mode 100755 new mode 100644 index 2c54563..61b47cc --- a/thinkphp/library/think/Cookie.php +++ b/thinkphp/library/think/Cookie.php @@ -1,268 +1,268 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Cookie -{ - /** - * @var array cookie 设置参数 - */ - protected static $config = [ - 'prefix' => '', // cookie 名称前缀 - 'expire' => 0, // cookie 保存时间 - 'path' => '/', // cookie 保存路径 - 'domain' => '', // cookie 有效域名 - 'secure' => false, // cookie 启用安全传输 - 'httponly' => false, // httponly 设置 - 'setcookie' => true, // 是否使用 setcookie - ]; - - /** - * @var bool 是否完成初始化了 - */ - protected static $init; - - /** - * Cookie初始化 - * @access public - * @param array $config 配置参数 - * @return void - */ - public static function init(array $config = []) - { - if (empty($config)) { - $config = Config::get('cookie'); - } - - self::$config = array_merge(self::$config, array_change_key_case($config)); - - if (!empty(self::$config['httponly'])) { - ini_set('session.cookie_httponly', 1); - } - - self::$init = true; - } - - /** - * 设置或者获取 cookie 作用域(前缀) - * @access public - * @param string $prefix 前缀 - * @return string| - */ - public static function prefix($prefix = '') - { - if (empty($prefix)) { - return self::$config['prefix']; - } - - return self::$config['prefix'] = $prefix; - } - - /** - * Cookie 设置、获取、删除 - * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 - * @param mixed $option 可选参数 可能会是 null|integer|string - * @return void - */ - public static function set($name, $value = '', $option = null) - { - !isset(self::$init) && self::init(); - - // 参数设置(会覆盖黙认设置) - if (!is_null($option)) { - if (is_numeric($option)) { - $option = ['expire' => $option]; - } elseif (is_string($option)) { - parse_str($option, $option); - } - - $config = array_merge(self::$config, array_change_key_case($option)); - } else { - $config = self::$config; - } - - $name = $config['prefix'] . $name; - - // 设置 cookie - if (is_array($value)) { - array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); - $value = 'think:' . json_encode($value); - } - - $expire = !empty($config['expire']) ? - $_SERVER['REQUEST_TIME'] + intval($config['expire']) : - 0; - - if ($config['setcookie']) { - setcookie( - $name, $value, $expire, $config['path'], $config['domain'], - $config['secure'], $config['httponly'] - ); - } - - $_COOKIE[$name] = $value; - } - - /** - * 永久保存 Cookie 数据 - * @access public - * @param string $name cookie 名称 - * @param mixed $value cookie 值 - * @param mixed $option 可选参数 可能会是 null|integer|string - * @return void - */ - public static function forever($name, $value = '', $option = null) - { - if (is_null($option) || is_numeric($option)) { - $option = []; - } - - $option['expire'] = 315360000; - - self::set($name, $value, $option); - } - - /** - * 判断是否有 Cookie 数据 - * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 - * @return bool - */ - public static function has($name, $prefix = null) - { - !isset(self::$init) && self::init(); - - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - - return isset($_COOKIE[$prefix . $name]); - } - - /** - * 获取 Cookie 的值 - * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 - * @return mixed - */ - public static function get($name = '', $prefix = null) - { - !isset(self::$init) && self::init(); - - $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; - $key = $prefix . $name; - - if ('' == $name) { - // 获取全部 - if ($prefix) { - $value = []; - - foreach ($_COOKIE as $k => $val) { - if (0 === strpos($k, $prefix)) { - $value[$k] = $val; - } - - } - } else { - $value = $_COOKIE; - } - } elseif (isset($_COOKIE[$key])) { - $value = $_COOKIE[$key]; - - if (0 === strpos($value, 'think:')) { - $value = json_decode(substr($value, 6), true); - array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); - } - } else { - $value = null; - } - - return $value; - } - - /** - * 删除 Cookie - * @access public - * @param string $name cookie 名称 - * @param string|null $prefix cookie 前缀 - * @return void - */ - public static function delete($name, $prefix = null) - { - !isset(self::$init) && self::init(); - - $config = self::$config; - $prefix = !is_null($prefix) ? $prefix : $config['prefix']; - $name = $prefix . $name; - - if ($config['setcookie']) { - setcookie( - $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); - } - - // 删除指定 cookie - unset($_COOKIE[$name]); - } - - /** - * 清除指定前缀的所有 cookie - * @access public - * @param string|null $prefix cookie 前缀 - * @return void - */ - public static function clear($prefix = null) - { - if (empty($_COOKIE)) { - return; - } - - !isset(self::$init) && self::init(); - - // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 - $config = self::$config; - $prefix = !is_null($prefix) ? $prefix : $config['prefix']; - - if ($prefix) { - foreach ($_COOKIE as $key => $val) { - if (0 === strpos($key, $prefix)) { - if ($config['setcookie']) { - setcookie( - $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], - $config['domain'], $config['secure'], $config['httponly'] - ); - } - - unset($_COOKIE[$key]); - } - } - } - } - - /** - * json 转换时的格式保护 - * @access protected - * @param mixed $val 要转换的值 - * @param string $key 键名 - * @param string $type 转换类别 - * @return void - */ - protected static function jsonFormatProtect(&$val, $key, $type = 'encode') - { - if (!empty($val) && true !== $val) { - $val = 'decode' == $type ? urldecode($val) : urlencode($val); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * @var array cookie 设置参数 + */ + protected static $config = [ + 'prefix' => '', // cookie 名称前缀 + 'expire' => 0, // cookie 保存时间 + 'path' => '/', // cookie 保存路径 + 'domain' => '', // cookie 有效域名 + 'secure' => false, // cookie 启用安全传输 + 'httponly' => false, // httponly 设置 + 'setcookie' => true, // 是否使用 setcookie + ]; + + /** + * @var bool 是否完成初始化了 + */ + protected static $init; + + /** + * Cookie初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('cookie'); + } + + self::$config = array_merge(self::$config, array_change_key_case($config)); + + if (!empty(self::$config['httponly'])) { + ini_set('session.cookie_httponly', 1); + } + + self::$init = true; + } + + /** + * 设置或者获取 cookie 作用域(前缀) + * @access public + * @param string $prefix 前缀 + * @return string| + */ + public static function prefix($prefix = '') + { + if (empty($prefix)) { + return self::$config['prefix']; + } + + return self::$config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function set($name, $value = '', $option = null) + { + !isset(self::$init) && self::init(); + + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge(self::$config, array_change_key_case($option)); + } else { + $config = self::$config; + } + + $name = $config['prefix'] . $name; + + // 设置 cookie + if (is_array($value)) { + array_walk_recursive($value, 'self::jsonFormatProtect', 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? + $_SERVER['REQUEST_TIME'] + intval($config['expire']) : + 0; + + if ($config['setcookie']) { + setcookie( + $name, $value, $expire, $config['path'], $config['domain'], + $config['secure'], $config['httponly'] + ); + } + + $_COOKIE[$name] = $value; + } + + /** + * 永久保存 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param mixed $value cookie 值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public static function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + self::set($name, $value, $option); + } + + /** + * 判断是否有 Cookie 数据 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return bool + */ + public static function has($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + + return isset($_COOKIE[$prefix . $name]); + } + + /** + * 获取 Cookie 的值 + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + !isset(self::$init) && self::init(); + + $prefix = !is_null($prefix) ? $prefix : self::$config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + // 获取全部 + if ($prefix) { + $value = []; + + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = json_decode(substr($value, 6), true); + array_walk_recursive($value, 'self::jsonFormatProtect', 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * 删除 Cookie + * @access public + * @param string $name cookie 名称 + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function delete($name, $prefix = null) + { + !isset(self::$init) && self::init(); + + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + setcookie( + $name, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + // 删除指定 cookie + unset($_COOKIE[$name]); + } + + /** + * 清除指定前缀的所有 cookie + * @access public + * @param string|null $prefix cookie 前缀 + * @return void + */ + public static function clear($prefix = null) + { + if (empty($_COOKIE)) { + return; + } + + !isset(self::$init) && self::init(); + + // 要删除的 cookie 前缀,不指定则删除 config 设置的指定前缀 + $config = self::$config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + setcookie( + $key, '', $_SERVER['REQUEST_TIME'] - 3600, $config['path'], + $config['domain'], $config['secure'], $config['httponly'] + ); + } + + unset($_COOKIE[$key]); + } + } + } + } + + /** + * json 转换时的格式保护 + * @access protected + * @param mixed $val 要转换的值 + * @param string $key 键名 + * @param string $type 转换类别 + * @return void + */ + protected static function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } +} diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php old mode 100755 new mode 100644 index 0e402f6..80f08d2 --- a/thinkphp/library/think/Db.php +++ b/thinkphp/library/think/Db.php @@ -1,180 +1,180 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\db\Connection; -use think\db\Query; - -/** - * Class Db - * @package think - * @method Query table(string $table) static 指定数据表(含前缀) - * @method Query name(string $name) static 指定数据表(不含前缀) - * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 - * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 - * @method Query union(mixed $union, boolean $all = false) static UNION查询 - * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT - * @method Query order(mixed $field, string $order = null) static 查询ORDER - * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 - * @method mixed value(string $field) static 获取某个字段的值 - * @method array column(string $field, string $key = '') static 获取某个列的值 - * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 - * @method mixed find(mixed $data = null) static 查询单个记录 - * @method mixed select(mixed $data = null) static 查询多个记录 - * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 - * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID - * @method integer insertAll(array $dataSet) static 插入多条记录 - * @method integer update(array $data) static 更新记录 - * @method integer delete(mixed $data = null) static 删除记录 - * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 - * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 - * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 - * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 - * @method mixed transaction(callable $callback) static 执行数据库事务 - * @method void startTrans() static 启动事务 - * @method void commit() static 用于非自动提交状态下面的查询提交 - * @method void rollback() static 事务回滚 - * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 - * @method string quote(string $str) static SQL指令安全过滤 - * @method string getLastInsID($sequence = null) static 获取最近插入的ID - */ -class Db -{ - /** - * @var Connection[] 数据库连接实例 - */ - private static $instance = []; - - /** - * @var int 查询次数 - */ - public static $queryTimes = 0; - - /** - * @var int 执行次数 - */ - public static $executeTimes = 0; - - /** - * 数据库初始化,并取得数据库类实例 - * @access public - * @param mixed $config 连接配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return Connection - * @throws Exception - */ - public static function connect($config = [], $name = false) - { - if (false === $name) { - $name = md5(serialize($config)); - } - - if (true === $name || !isset(self::$instance[$name])) { - // 解析连接参数 支持数组和字符串 - $options = self::parseConfig($config); - - if (empty($options['type'])) { - throw new \InvalidArgumentException('Undefined db type'); - } - - $class = false !== strpos($options['type'], '\\') ? - $options['type'] : - '\\think\\db\\connector\\' . ucwords($options['type']); - - // 记录初始化信息 - if (App::$debug) { - Log::record('[ DB ] INIT ' . $options['type'], 'info'); - } - - if (true === $name) { - $name = md5(serialize($config)); - } - - self::$instance[$name] = new $class($options); - } - - return self::$instance[$name]; - } - - /** - * 清除连接实例 - * @access public - * @return void - */ - public static function clear() - { - self::$instance = []; - } - - /** - * 数据库连接参数解析 - * @access private - * @param mixed $config 连接参数 - * @return array - */ - private static function parseConfig($config) - { - if (empty($config)) { - $config = Config::get('database'); - } elseif (is_string($config) && false === strpos($config, '/')) { - $config = Config::get($config); // 支持读取配置参数 - } - - return is_string($config) ? self::parseDsn($config) : $config; - } - - /** - * DSN 解析 - * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 - * @access private - * @param string $dsnStr 数据库 DSN 字符串解析 - * @return array - */ - private static function parseDsn($dsnStr) - { - $info = parse_url($dsnStr); - - if (!$info) { - return []; - } - - $dsn = [ - 'type' => $info['scheme'], - 'username' => isset($info['user']) ? $info['user'] : '', - 'password' => isset($info['pass']) ? $info['pass'] : '', - 'hostname' => isset($info['host']) ? $info['host'] : '', - 'hostport' => isset($info['port']) ? $info['port'] : '', - 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', - 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', - ]; - - if (isset($info['query'])) { - parse_str($info['query'], $dsn['params']); - } else { - $dsn['params'] = []; - } - - return $dsn; - } - - /** - * 调用驱动类的方法 - * @access public - * @param string $method 方法名 - * @param array $params 参数 - * @return mixed - */ - public static function __callStatic($method, $params) - { - return call_user_func_array([self::connect(), $method], $params); - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; +use think\db\Query; + +/** + * Class Db + * @package think + * @method Query table(string $table) static 指定数据表(含前缀) + * @method Query name(string $name) static 指定数据表(不含前缀) + * @method Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method Query union(mixed $union, boolean $all = false) static UNION查询 + * @method Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method Query order(mixed $field, string $order = null) static 查询ORDER + * @method Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 + * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID + * @method integer insertAll(array $dataSet) static 插入多条记录 + * @method integer update(array $data) static 更新记录 + * @method integer delete(mixed $data = null) static 删除记录 + * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 + * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 + * @method Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method mixed transaction(callable $callback) static 执行数据库事务 + * @method void startTrans() static 启动事务 + * @method void commit() static 用于非自动提交状态下面的查询提交 + * @method void rollback() static 事务回滚 + * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string quote(string $str) static SQL指令安全过滤 + * @method string getLastInsID($sequence = null) static 获取最近插入的ID + */ +class Db +{ + /** + * @var Connection[] 数据库连接实例 + */ + private static $instance = []; + + /** + * @var int 查询次数 + */ + public static $queryTimes = 0; + + /** + * @var int 执行次数 + */ + public static $executeTimes = 0; + + /** + * 数据库初始化,并取得数据库类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function connect($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + // 解析连接参数 支持数组和字符串 + $options = self::parseConfig($config); + + if (empty($options['type'])) { + throw new \InvalidArgumentException('Undefined db type'); + } + + $class = false !== strpos($options['type'], '\\') ? + $options['type'] : + '\\think\\db\\connector\\' . ucwords($options['type']); + + // 记录初始化信息 + if (App::$debug) { + Log::record('[ DB ] INIT ' . $options['type'], 'info'); + } + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = new $class($options); + } + + return self::$instance[$name]; + } + + /** + * 清除连接实例 + * @access public + * @return void + */ + public static function clear() + { + self::$instance = []; + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config 连接参数 + * @return array + */ + private static function parseConfig($config) + { + if (empty($config)) { + $config = Config::get('database'); + } elseif (is_string($config) && false === strpos($config, '/')) { + $config = Config::get($config); // 支持读取配置参数 + } + + return is_string($config) ? self::parseDsn($config) : $config; + } + + /** + * DSN 解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr 数据库 DSN 字符串解析 + * @return array + */ + private static function parseDsn($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + /** + * 调用驱动类的方法 + * @access public + * @param string $method 方法名 + * @param array $params 参数 + * @return mixed + */ + public static function __callStatic($method, $params) + { + return call_user_func_array([self::connect(), $method], $params); + } +} diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php old mode 100755 new mode 100644 index f6ef23c..df48748 --- a/thinkphp/library/think/Debug.php +++ b/thinkphp/library/think/Debug.php @@ -1,252 +1,252 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; -use think\response\Redirect; - -class Debug -{ - /** - * @var array 区间时间信息 - */ - protected static $info = []; - - /** - * @var array 区间内存信息 - */ - protected static $mem = []; - - /** - * 记录时间(微秒)和内存使用情况 - * @access public - * @param string $name 标记位置 - * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) - * @return void - */ - public static function remark($name, $value = '') - { - self::$info[$name] = is_float($value) ? $value : microtime(true); - - if ('time' != $value) { - self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); - self::$mem['peak'][$name] = memory_get_peak_usage(); - } - } - - /** - * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 - * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 - * @return string - */ - public static function getRangeTime($start, $end, $dec = 6) - { - if (!isset(self::$info[$end])) { - self::$info[$end] = microtime(true); - } - - return number_format((self::$info[$end] - self::$info[$start]), $dec); - } - - /** - * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 - * @access public - * @param integer $dec 小数位 - * @return string - */ - public static function getUseTime($dec = 6) - { - return number_format((microtime(true) - THINK_START_TIME), $dec); - } - - /** - * 获取当前访问的吞吐率情况 - * @access public - * @return string - */ - public static function getThroughputRate() - { - return number_format(1 / self::getUseTime(), 2) . 'req/s'; - } - - /** - * 记录区间的内存使用情况 - * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 - * @return string - */ - public static function getRangeMem($start, $end, $dec = 2) - { - if (!isset(self::$mem['mem'][$end])) { - self::$mem['mem'][$end] = memory_get_usage(); - } - - $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; - $a = ['B', 'KB', 'MB', 'GB', 'TB']; - $pos = 0; - - while ($size >= 1024) { - $size /= 1024; - $pos++; - } - - return round($size, $dec) . " " . $a[$pos]; - } - - /** - * 统计从开始到统计时的内存使用情况 - * @access public - * @param integer $dec 小数位 - * @return string - */ - public static function getUseMem($dec = 2) - { - $size = memory_get_usage() - THINK_START_MEM; - $a = ['B', 'KB', 'MB', 'GB', 'TB']; - $pos = 0; - - while ($size >= 1024) { - $size /= 1024; - $pos++; - } - - return round($size, $dec) . " " . $a[$pos]; - } - - /** - * 统计区间的内存峰值情况 - * @access public - * @param string $start 开始标签 - * @param string $end 结束标签 - * @param integer $dec 小数位 - * @return string - */ - public static function getMemPeak($start, $end, $dec = 2) - { - if (!isset(self::$mem['peak'][$end])) { - self::$mem['peak'][$end] = memory_get_peak_usage(); - } - - $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; - $a = ['B', 'KB', 'MB', 'GB', 'TB']; - $pos = 0; - - while ($size >= 1024) { - $size /= 1024; - $pos++; - } - - return round($size, $dec) . " " . $a[$pos]; - } - - /** - * 获取文件加载信息 - * @access public - * @param bool $detail 是否显示详细 - * @return integer|array - */ - public static function getFile($detail = false) - { - $files = get_included_files(); - - if ($detail) { - $info = []; - - foreach ($files as $file) { - $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; - } - - return $info; - } - - return count($files); - } - - /** - * 浏览器友好的变量输出 - * @access public - * @param mixed $var 变量 - * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) - * @param string|null $label 标签(默认为空) - * @param integer $flags htmlspecialchars 的标志 - * @return null|string - */ - public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) - { - $label = (null === $label) ? '' : rtrim($label) . ':'; - - ob_start(); - var_dump($var); - $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); - - if (IS_CLI) { - $output = PHP_EOL . $label . $output . PHP_EOL; - } else { - if (!extension_loaded('xdebug')) { - $output = htmlspecialchars($output, $flags); - } - - $output = '
    ' . $label . $output . '
    '; - } - - if ($echo) { - echo($output); - return; - } - - return $output; - } - - /** - * 调试信息注入到响应中 - * @access public - * @param Response $response 响应实例 - * @param string $content 返回的字符串 - * @return void - */ - public static function inject(Response $response, &$content) - { - $config = Config::get('trace'); - $type = isset($config['type']) ? $config['type'] : 'Html'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); - - unset($config['type']); - - if (!class_exists($class)) { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** @var \think\debug\Console|\think\debug\Html $trace */ - $trace = new $class($config); - - if ($response instanceof Redirect) { - // TODO 记录 - } else { - $output = $trace->output($response, Log::getLog()); - - if (is_string($output)) { - // trace 调试信息注入 - $pos = strripos($content, ''); - if (false !== $pos) { - $content = substr($content, 0, $pos) . $output . substr($content, $pos); - } else { - $content = $content . $output; - } - } - } - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\response\Redirect; + +class Debug +{ + /** + * @var array 区间时间信息 + */ + protected static $info = []; + + /** + * @var array 区间内存信息 + */ + protected static $mem = []; + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值(留空则取当前 time 表示仅记录时间 否则同时记录时间和内存) + * @return void + */ + public static function remark($name, $value = '') + { + self::$info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + self::$mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + self::$mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeTime($start, $end, $dec = 6) + { + if (!isset(self::$info[$end])) { + self::$info[$end] = microtime(true); + } + + return number_format((self::$info[$end] - self::$info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 返回值以秒为单位 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseTime($dec = 6) + { + return number_format((microtime(true) - THINK_START_TIME), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public static function getThroughputRate() + { + return number_format(1 / self::getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getRangeMem($start, $end, $dec = 2) + { + if (!isset(self::$mem['mem'][$end])) { + self::$mem['mem'][$end] = memory_get_usage(); + } + + $size = self::$mem['mem'][$end] - self::$mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer $dec 小数位 + * @return string + */ + public static function getUseMem($dec = 2) + { + $size = memory_get_usage() - THINK_START_MEM; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer $dec 小数位 + * @return string + */ + public static function getMemPeak($start, $end, $dec = 2) + { + if (!isset(self::$mem['peak'][$end])) { + self::$mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = self::$mem['peak'][$end] - self::$mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public static function getFile($detail = false) + { + $files = get_included_files(); + + if ($detail) { + $info = []; + + foreach ($files as $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count($files); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出(默认为 true,为 false 则返回输出字符串) + * @param string|null $label 标签(默认为空) + * @param integer $flags htmlspecialchars 的标志 + * @return null|string + */ + public static function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + + ob_start(); + var_dump($var); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', ob_get_clean()); + + if (IS_CLI) { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + + $output = '
    ' . $label . $output . '
    '; + } + + if ($echo) { + echo($output); + return; + } + + return $output; + } + + /** + * 调试信息注入到响应中 + * @access public + * @param Response $response 响应实例 + * @param string $content 返回的字符串 + * @return void + */ + public static function inject(Response $response, &$content) + { + $config = Config::get('trace'); + $type = isset($config['type']) ? $config['type'] : 'Html'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\debug\\' . ucwords($type); + + unset($config['type']); + + if (!class_exists($class)) { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** @var \think\debug\Console|\think\debug\Html $trace */ + $trace = new $class($config); + + if ($response instanceof Redirect) { + // TODO 记录 + } else { + $output = $trace->output($response, Log::getLog()); + + if (is_string($output)) { + // trace 调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } +} diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php old mode 100755 new mode 100644 index 47edbe2..0a8b250 --- a/thinkphp/library/think/Env.php +++ b/thinkphp/library/think/Env.php @@ -1,39 +1,39 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Env -{ - /** - * 获取环境变量值 - * @access public - * @param string $name 环境变量名(支持二级 . 号分割) - * @param string $default 默认值 - * @return mixed - */ - public static function get($name, $default = null) - { - $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); - - if (false !== $result) { - if ('false' === $result) { - $result = false; - } elseif ('true' === $result) { - $result = true; - } - - return $result; - } - - return $default; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 . 号分割) + * @param string $default 默认值 + * @return mixed + */ + public static function get($name, $default = null) + { + $result = getenv(ENV_PREFIX . strtoupper(str_replace('.', '_', $name))); + + if (false !== $result) { + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + return $result; + } + + return $default; + } +} diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php old mode 100755 new mode 100644 index 4a98e51..5f361d5 --- a/thinkphp/library/think/Error.php +++ b/thinkphp/library/think/Error.php @@ -1,136 +1,136 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\console\Output as ConsoleOutput; -use think\exception\ErrorException; -use think\exception\Handle; -use think\exception\ThrowableError; - -class Error -{ - /** - * 注册异常处理 - * @access public - * @return void - */ - public static function register() - { - error_reporting(E_ALL); - set_error_handler([__CLASS__, 'appError']); - set_exception_handler([__CLASS__, 'appException']); - register_shutdown_function([__CLASS__, 'appShutdown']); - } - - /** - * 异常处理 - * @access public - * @param \Exception|\Throwable $e 异常 - * @return void - */ - public static function appException($e) - { - if (!$e instanceof \Exception) { - $e = new ThrowableError($e); - } - - $handler = self::getExceptionHandler(); - $handler->report($e); - - if (IS_CLI) { - $handler->renderForConsole(new ConsoleOutput, $e); - } else { - $handler->render($e)->send(); - } - } - - /** - * 错误处理 - * @access public - * @param integer $errno 错误编号 - * @param integer $errstr 详细错误信息 - * @param string $errfile 出错的文件 - * @param integer $errline 出错行号 - * @return void - * @throws ErrorException - */ - public static function appError($errno, $errstr, $errfile = '', $errline = 0) - { - $exception = new ErrorException($errno, $errstr, $errfile, $errline); - - // 符合异常处理的则将错误信息托管至 think\exception\ErrorException - if (error_reporting() & $errno) { - throw $exception; - } - - self::getExceptionHandler()->report($exception); - } - - /** - * 异常中止处理 - * @access public - * @return void - */ - public static function appShutdown() - { - // 将错误信息托管至 think\ErrorException - if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { - self::appException(new ErrorException( - $error['type'], $error['message'], $error['file'], $error['line'] - )); - } - - // 写入日志 - Log::save(); - } - - /** - * 确定错误类型是否致命 - * @access protected - * @param int $type 错误类型 - * @return bool - */ - protected static function isFatal($type) - { - return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); - } - - /** - * 获取异常处理的实例 - * @access public - * @return Handle - */ - public static function getExceptionHandler() - { - static $handle; - - if (!$handle) { - // 异常处理 handle - $class = Config::get('exception_handle'); - - if ($class && is_string($class) && class_exists($class) && - is_subclass_of($class, "\\think\\exception\\Handle") - ) { - $handle = new $class; - } else { - $handle = new Handle; - - if ($class instanceof \Closure) { - $handle->setRender($class); - } - - } - } - - return $handle; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * 异常处理 + * @access public + * @param \Exception|\Throwable $e 异常 + * @return void + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + $handler = self::getExceptionHandler(); + $handler->report($e); + + if (IS_CLI) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($e)->send(); + } + } + + /** + * 错误处理 + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @return void + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + // 符合异常处理的则将错误信息托管至 think\exception\ErrorException + if (error_reporting() & $errno) { + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * 异常中止处理 + * @access public + * @return void + */ + public static function appShutdown() + { + // 将错误信息托管至 think\ErrorException + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + self::appException(new ErrorException( + $error['type'], $error['message'], $error['file'], $error['line'] + )); + } + + // 写入日志 + Log::save(); + } + + /** + * 确定错误类型是否致命 + * @access protected + * @param int $type 错误类型 + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 获取异常处理的实例 + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理 handle + $class = Config::get('exception_handle'); + + if ($class && is_string($class) && class_exists($class) && + is_subclass_of($class, "\\think\\exception\\Handle") + ) { + $handle = new $class; + } else { + $handle = new Handle; + + if ($class instanceof \Closure) { + $handle->setRender($class); + } + + } + } + + return $handle; + } +} diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php old mode 100755 new mode 100644 index 325f9bd..1ef06bd --- a/thinkphp/library/think/Exception.php +++ b/thinkphp/library/think/Exception.php @@ -1,55 +1,55 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Exception extends \Exception -{ - /** - * @var array 保存异常页面显示的额外 Debug 数据 - */ - protected $data = []; - - /** - * 设置异常额外的 Debug 数据 - * 数据将会显示为下面的格式 - * - * Exception Data - * -------------------------------------------------- - * Label 1 - * key1 value1 - * key2 value2 - * Label 2 - * key1 value1 - * key2 value2 - * - * @access protected - * @param string $label 数据分类,用于异常页面显示 - * @param array $data 需要显示的数据,必须为关联数组 - * @return void - */ - final protected function setData($label, array $data) - { - $this->data[$label] = $data; - } - - /** - * 获取异常额外 Debug 数据 - * 主要用于输出到异常页面便于调试 - * @access public - * @return array - */ - final public function getData() - { - return $this->data; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + /** + * @var array 保存异常页面显示的额外 Debug 数据 + */ + protected $data = []; + + /** + * 设置异常额外的 Debug 数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + * @return void + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外 Debug 数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php old mode 100755 new mode 100644 index 7757ca1..d2ed220 --- a/thinkphp/library/think/File.php +++ b/thinkphp/library/think/File.php @@ -1,478 +1,478 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use SplFileObject; - -class File extends SplFileObject -{ - /** - * @var string 错误信息 - */ - private $error = ''; - - /** - * @var string 当前完整文件名 - */ - protected $filename; - - /** - * @var string 上传文件名 - */ - protected $saveName; - - /** - * @var string 文件上传命名规则 - */ - protected $rule = 'date'; - - /** - * @var array 文件上传验证规则 - */ - protected $validate = []; - - /** - * @var bool 单元测试 - */ - protected $isTest; - - /** - * @var array 上传文件信息 - */ - protected $info; - - /** - * @var array 文件 hash 信息 - */ - protected $hash = []; - - /** - * File constructor. - * @access public - * @param string $filename 文件名称 - * @param string $mode 访问模式 - */ - public function __construct($filename, $mode = 'r') - { - parent::__construct($filename, $mode); - $this->filename = $this->getRealPath() ?: $this->getPathname(); - } - - /** - * 设置是否是单元测试 - * @access public - * @param bool $test 是否是测试 - * @return $this - */ - public function isTest($test = false) - { - $this->isTest = $test; - - return $this; - } - - /** - * 设置上传信息 - * @access public - * @param array $info 上传文件信息 - * @return $this - */ - public function setUploadInfo($info) - { - $this->info = $info; - - return $this; - } - - /** - * 获取上传文件的信息 - * @access public - * @param string $name 信息名称 - * @return array|string - */ - public function getInfo($name = '') - { - return isset($this->info[$name]) ? $this->info[$name] : $this->info; - } - - /** - * 获取上传文件的文件名 - * @access public - * @return string - */ - public function getSaveName() - { - return $this->saveName; - } - - /** - * 设置上传文件的保存文件名 - * @access public - * @param string $saveName 保存名称 - * @return $this - */ - public function setSaveName($saveName) - { - $this->saveName = $saveName; - - return $this; - } - - /** - * 获取文件的哈希散列值 - * @access public - * @param string $type 类型 - * @return string - */ - public function hash($type = 'sha1') - { - if (!isset($this->hash[$type])) { - $this->hash[$type] = hash_file($type, $this->filename); - } - - return $this->hash[$type]; - } - - /** - * 检查目录是否可写 - * @access protected - * @param string $path 目录 - * @return boolean - */ - protected function checkPath($path) - { - if (is_dir($path) || mkdir($path, 0755, true)) { - return true; - } - - $this->error = ['directory {:path} creation failed', ['path' => $path]]; - - return false; - } - - /** - * 获取文件类型信息 - * @access public - * @return string - */ - public function getMime() - { - $finfo = finfo_open(FILEINFO_MIME_TYPE); - - return finfo_file($finfo, $this->filename); - } - - /** - * 设置文件的命名规则 - * @access public - * @param string $rule 文件命名规则 - * @return $this - */ - public function rule($rule) - { - $this->rule = $rule; - - return $this; - } - - /** - * 设置上传文件的验证规则 - * @access public - * @param array $rule 验证规则 - * @return $this - */ - public function validate(array $rule = []) - { - $this->validate = $rule; - - return $this; - } - - /** - * 检测是否合法的上传文件 - * @access public - * @return bool - */ - public function isValid() - { - return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); - } - - /** - * 检测上传文件 - * @access public - * @param array $rule 验证规则 - * @return bool - */ - public function check($rule = []) - { - $rule = $rule ?: $this->validate; - - /* 检查文件大小 */ - if (isset($rule['size']) && !$this->checkSize($rule['size'])) { - $this->error = 'filesize not match'; - return false; - } - - /* 检查文件 Mime 类型 */ - if (isset($rule['type']) && !$this->checkMime($rule['type'])) { - $this->error = 'mimetype to upload is not allowed'; - return false; - } - - /* 检查文件后缀 */ - if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { - $this->error = 'extensions to upload is not allowed'; - return false; - } - - /* 检查图像文件 */ - if (!$this->checkImg()) { - $this->error = 'illegal image files'; - return false; - } - - return true; - } - - /** - * 检测上传文件后缀 - * @access public - * @param array|string $ext 允许后缀 - * @return bool - */ - public function checkExt($ext) - { - if (is_string($ext)) { - $ext = explode(',', $ext); - } - - $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - - return in_array($extension, $ext); - } - - /** - * 检测图像文件 - * @access public - * @return bool - */ - public function checkImg() - { - $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); - - // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true - return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); - } - - /** - * 判断图像类型 - * @access protected - * @param string $image 图片名称 - * @return bool|int - */ - protected function getImageType($image) - { - if (function_exists('exif_imagetype')) { - return exif_imagetype($image); - } - - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; - } - } - - /** - * 检测上传文件大小 - * @access public - * @param integer $size 最大大小 - * @return bool - */ - public function checkSize($size) - { - return $this->getSize() <= $size; - } - - /** - * 检测上传文件类型 - * @access public - * @param array|string $mime 允许类型 - * @return bool - */ - public function checkMime($mime) - { - $mime = is_string($mime) ? explode(',', $mime) : $mime; - - return in_array(strtolower($this->getMime()), $mime); - } - - /** - * 移动文件 - * @access public - * @param string $path 保存路径 - * @param string|bool $savename 保存的文件名 默认自动生成 - * @param boolean $replace 同名文件是否覆盖 - * @return false|File - */ - public function move($path, $savename = true, $replace = true) - { - // 文件上传失败,捕获错误代码 - if (!empty($this->info['error'])) { - $this->error($this->info['error']); - return false; - } - - // 检测合法性 - if (!$this->isValid()) { - $this->error = 'upload illegal files'; - return false; - } - - // 验证上传 - if (!$this->check()) { - return false; - } - - $path = rtrim($path, DS) . DS; - // 文件保存命名规则 - $saveName = $this->buildSaveName($savename); - $filename = $path . $saveName; - - // 检测目录 - if (false === $this->checkPath(dirname($filename))) { - return false; - } - - // 不覆盖同名文件 - if (!$replace && is_file($filename)) { - $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; - return false; - } - - /* 移动文件 */ - if ($this->isTest) { - rename($this->filename, $filename); - } elseif (!move_uploaded_file($this->filename, $filename)) { - $this->error = 'upload write error'; - return false; - } - - // 返回 File 对象实例 - $file = new self($filename); - $file->setSaveName($saveName)->setUploadInfo($this->info); - - return $file; - } - - /** - * 获取保存文件名 - * @access protected - * @param string|bool $savename 保存的文件名 默认自动生成 - * @return string - */ - protected function buildSaveName($savename) - { - // 自动生成文件名 - if (true === $savename) { - if ($this->rule instanceof \Closure) { - $savename = call_user_func_array($this->rule, [$this]); - } else { - switch ($this->rule) { - case 'date': - $savename = date('Ymd') . DS . md5(microtime(true)); - break; - default: - if (in_array($this->rule, hash_algos())) { - $hash = $this->hash($this->rule); - $savename = substr($hash, 0, 2) . DS . substr($hash, 2); - } elseif (is_callable($this->rule)) { - $savename = call_user_func($this->rule); - } else { - $savename = date('Ymd') . DS . md5(microtime(true)); - } - } - } - } elseif ('' === $savename || false === $savename) { - $savename = $this->getInfo('name'); - } - - if (!strpos($savename, '.')) { - $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); - } - - return $savename; - } - - /** - * 获取错误代码信息 - * @access private - * @param int $errorNo 错误号 - * @return $this - */ - private function error($errorNo) - { - switch ($errorNo) { - case 1: - case 2: - $this->error = 'upload File size exceeds the maximum value'; - break; - case 3: - $this->error = 'only the portion of file is uploaded'; - break; - case 4: - $this->error = 'no file to uploaded'; - break; - case 6: - $this->error = 'upload temp dir not found'; - break; - case 7: - $this->error = 'file write error'; - break; - default: - $this->error = 'unknown upload error'; - } - - return $this; - } - - /** - * 获取错误信息(支持多语言) - * @access public - * @return string - */ - public function getError() - { - if (is_array($this->error)) { - list($msg, $vars) = $this->error; - } else { - $msg = $this->error; - $vars = []; - } - - return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; - } - - /** - * 魔法方法,获取文件的 hash 值 - * @access public - * @param string $method 方法名 - * @param mixed $args 调用参数 - * @return string - */ - public function __call($method, $args) - { - return $this->hash($method); - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * @var string 错误信息 + */ + private $error = ''; + + /** + * @var string 当前完整文件名 + */ + protected $filename; + + /** + * @var string 上传文件名 + */ + protected $saveName; + + /** + * @var string 文件上传命名规则 + */ + protected $rule = 'date'; + + /** + * @var array 文件上传验证规则 + */ + protected $validate = []; + + /** + * @var bool 单元测试 + */ + protected $isTest; + + /** + * @var array 上传文件信息 + */ + protected $info; + + /** + * @var array 文件 hash 信息 + */ + protected $hash = []; + + /** + * File constructor. + * @access public + * @param string $filename 文件名称 + * @param string $mode 访问模式 + */ + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 设置是否是单元测试 + * @access public + * @param bool $test 是否是测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name 信息名称 + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName 保存名称 + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type 类型 + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path) || mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate(array $rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + return $this->isTest ? is_file($this->filename) : is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + /* 检查文件大小 */ + if (isset($rule['size']) && !$this->checkSize($rule['size'])) { + $this->error = 'filesize not match'; + return false; + } + + /* 检查文件 Mime 类型 */ + if (isset($rule['type']) && !$this->checkMime($rule['type'])) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + /* 检查文件后缀 */ + if (isset($rule['ext']) && !$this->checkExt($rule['ext'])) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + /* 检查图像文件 */ + if (!$this->checkImg()) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + return in_array($extension, $ext); + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + // 如果上传的不是图片,或者是图片而且后缀确实符合图片类型则返回 true + return !in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) || in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13]); + } + + /** + * 判断图像类型 + * @access protected + * @param string $image 图片名称 + * @return bool|int + */ + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 检测上传文件大小 + * @access public + * @param integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + return $this->getSize() <= $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + $mime = is_string($mime) ? explode(',', $mime) : $mime; + + return in_array(strtolower($this->getMime()), $mime); + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @return false|File + */ + public function move($path, $savename = true, $replace = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DS) . DS; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + // 不覆盖同名文件 + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File 对象实例 + $file = new self($filename); + $file->setSaveName($saveName)->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @return string + */ + protected function buildSaveName($savename) + { + // 自动生成文件名 + if (true === $savename) { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DS . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DS . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DS . md5(microtime(true)); + } + } + } + } elseif ('' === $savename || false === $savename) { + $savename = $this->getInfo('name'); + } + + if (!strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + * @return $this + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + + return $this; + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return Lang::has($msg) ? Lang::get($msg, $vars) : $msg; + } + + /** + * 魔法方法,获取文件的 hash 值 + * @access public + * @param string $method 方法名 + * @param mixed $args 调用参数 + * @return string + */ + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php old mode 100755 new mode 100644 index a3e7b8d..a69ce54 --- a/thinkphp/library/think/Hook.php +++ b/thinkphp/library/think/Hook.php @@ -1,148 +1,148 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Hook -{ - /** - * @var array 标签 - */ - private static $tags = []; - - /** - * 动态添加行为扩展到某个标签 - * @access public - * @param string $tag 标签名称 - * @param mixed $behavior 行为名称 - * @param bool $first 是否放到开头执行 - * @return void - */ - public static function add($tag, $behavior, $first = false) - { - isset(self::$tags[$tag]) || self::$tags[$tag] = []; - - if (is_array($behavior) && !is_callable($behavior)) { - if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { - unset($behavior['_overlay']); - self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); - } else { - unset($behavior['_overlay']); - self::$tags[$tag] = $behavior; - } - } elseif ($first) { - array_unshift(self::$tags[$tag], $behavior); - } else { - self::$tags[$tag][] = $behavior; - } - } - - /** - * 批量导入插件 - * @access public - * @param array $tags 插件信息 - * @param boolean $recursive 是否递归合并 - * @return void - */ - public static function import(array $tags, $recursive = true) - { - if ($recursive) { - foreach ($tags as $tag => $behavior) { - self::add($tag, $behavior); - } - } else { - self::$tags = $tags + self::$tags; - } - } - - /** - * 获取插件信息 - * @access public - * @param string $tag 插件位置(留空获取全部) - * @return array - */ - public static function get($tag = '') - { - if (empty($tag)) { - return self::$tags; - } - - return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; - } - - /** - * 监听标签的行为 - * @access public - * @param string $tag 标签名称 - * @param mixed $params 传入参数 - * @param mixed $extra 额外参数 - * @param bool $once 只获取一个有效返回值 - * @return mixed - */ - public static function listen($tag, &$params = null, $extra = null, $once = false) - { - $results = []; - - foreach (static::get($tag) as $key => $name) { - $results[$key] = self::exec($name, $tag, $params, $extra); - - // 如果返回 false,或者仅获取一个有效返回则中断行为执行 - if (false === $results[$key] || (!is_null($results[$key]) && $once)) { - break; - } - } - - return $once ? end($results) : $results; - } - - /** - * 执行某个行为 - * @access public - * @param mixed $class 要执行的行为 - * @param string $tag 方法名(标签名) - * @param mixed $params 传人的参数 - * @param mixed $extra 额外参数 - * @return mixed - */ - public static function exec($class, $tag = '', &$params = null, $extra = null) - { - App::$debug && Debug::remark('behavior_start', 'time'); - - $method = Loader::parseName($tag, 1, false); - - if ($class instanceof \Closure) { - $result = call_user_func_array($class, [ & $params, $extra]); - $class = 'Closure'; - } elseif (is_array($class)) { - list($class, $method) = $class; - - $result = (new $class())->$method($params, $extra); - $class = $class . '->' . $method; - } elseif (is_object($class)) { - $result = $class->$method($params, $extra); - $class = get_class($class); - } elseif (strpos($class, '::')) { - $result = call_user_func_array($class, [ & $params, $extra]); - } else { - $obj = new $class(); - $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; - $result = $obj->$method($params, $extra); - } - - if (App::$debug) { - Debug::remark('behavior_end', 'time'); - Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); - } - - return $result; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * @var array 标签 + */ + private static $tags = []; + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public static function add($tag, $behavior, $first = false) + { + isset(self::$tags[$tag]) || self::$tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior) || !$behavior['_overlay']) { + unset($behavior['_overlay']); + self::$tags[$tag] = array_merge(self::$tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + self::$tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift(self::$tags[$tag], $behavior); + } else { + self::$tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param boolean $recursive 是否递归合并 + * @return void + */ + public static function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + self::add($tag, $behavior); + } + } else { + self::$tags = $tags + self::$tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置(留空获取全部) + * @return array + */ + public static function get($tag = '') + { + if (empty($tag)) { + return self::$tags; + } + + return array_key_exists($tag, self::$tags) ? self::$tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param mixed $extra 额外参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public static function listen($tag, &$params = null, $extra = null, $once = false) + { + $results = []; + + foreach (static::get($tag) as $key => $name) { + $results[$key] = self::exec($name, $tag, $params, $extra); + + // 如果返回 false,或者仅获取一个有效返回则中断行为执行 + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行某个行为 + * @access public + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 传人的参数 + * @param mixed $extra 额外参数 + * @return mixed + */ + public static function exec($class, $tag = '', &$params = null, $extra = null) + { + App::$debug && Debug::remark('behavior_start', 'time'); + + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $result = call_user_func_array($class, [ & $params, $extra]); + $class = 'Closure'; + } elseif (is_array($class)) { + list($class, $method) = $class; + + $result = (new $class())->$method($params, $extra); + $class = $class . '->' . $method; + } elseif (is_object($class)) { + $result = $class->$method($params, $extra); + $class = get_class($class); + } elseif (strpos($class, '::')) { + $result = call_user_func_array($class, [ & $params, $extra]); + } else { + $obj = new $class(); + $method = ($tag && is_callable([$obj, $method])) ? $method : 'run'; + $result = $obj->$method($params, $extra); + } + + if (App::$debug) { + Debug::remark('behavior_end', 'time'); + Log::record('[ BEHAVIOR ] Run ' . $class . ' @' . $tag . ' [ RunTime:' . Debug::getRangeTime('behavior_start', 'behavior_end') . 's ]', 'info'); + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php old mode 100755 new mode 100644 index 171bc7b..a50d838 --- a/thinkphp/library/think/Lang.php +++ b/thinkphp/library/think/Lang.php @@ -1,265 +1,265 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Lang -{ - /** - * @var array 语言数据 - */ - private static $lang = []; - - /** - * @var string 语言作用域 - */ - private static $range = 'zh-cn'; - - /** - * @var string 语言自动侦测的变量 - */ - protected static $langDetectVar = 'lang'; - - /** - * @var string 语言 Cookie 变量 - */ - protected static $langCookieVar = 'think_var'; - - /** - * @var int 语言 Cookie 的过期时间 - */ - protected static $langCookieExpire = 3600; - - /** - * @var array 允许语言列表 - */ - protected static $allowLangList = []; - - /** - * @var array Accept-Language 转义为对应语言包名称 系统默认配置 - */ - protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; - - /** - * 设定当前的语言 - * @access public - * @param string $range 语言作用域 - * @return string - */ - public static function range($range = '') - { - if ($range) { - self::$range = $range; - } - - return self::$range; - } - - /** - * 设置语言定义(不区分大小写) - * @access public - * @param string|array $name 语言变量 - * @param string $value 语言值 - * @param string $range 语言作用域 - * @return mixed - */ - public static function set($name, $value = null, $range = '') - { - $range = $range ?: self::$range; - - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; - } - - if (is_array($name)) { - return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; - } - - return self::$lang[$range][strtolower($name)] = $value; - } - - /** - * 加载语言定义(不区分大小写) - * @access public - * @param array|string $file 语言文件 - * @param string $range 语言作用域 - * @return mixed - */ - public static function load($file, $range = '') - { - $range = $range ?: self::$range; - $file = is_string($file) ? [$file] : $file; - - if (!isset(self::$lang[$range])) { - self::$lang[$range] = []; - } - - $lang = []; - - foreach ($file as $_file) { - if (is_file($_file)) { - // 记录加载信息 - App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); - - $_lang = include $_file; - - if (is_array($_lang)) { - $lang = array_change_key_case($_lang) + $lang; - } - } - } - - if (!empty($lang)) { - self::$lang[$range] = $lang + self::$lang[$range]; - } - - return self::$lang[$range]; - } - - /** - * 获取语言定义(不区分大小写) - * @access public - * @param string|null $name 语言变量 - * @param string $range 语言作用域 - * @return mixed - */ - public static function has($name, $range = '') - { - $range = $range ?: self::$range; - - return isset(self::$lang[$range][strtolower($name)]); - } - - /** - * 获取语言定义(不区分大小写) - * @access public - * @param string|null $name 语言变量 - * @param array $vars 变量替换 - * @param string $range 语言作用域 - * @return mixed - */ - public static function get($name = null, $vars = [], $range = '') - { - $range = $range ?: self::$range; - - // 空参数返回所有定义 - if (empty($name)) { - return self::$lang[$range]; - } - - $key = strtolower($name); - $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; - - // 变量解析 - if (!empty($vars) && is_array($vars)) { - /** - * Notes: - * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 - * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 - */ - if (key($vars) === 0) { - // 数字索引解析 - array_unshift($vars, $value); - $value = call_user_func_array('sprintf', $vars); - } else { - // 关联索引解析 - $replace = array_keys($vars); - foreach ($replace as &$v) { - $v = "{:{$v}}"; - } - $value = str_replace($replace, $vars, $value); - } - - } - - return $value; - } - - /** - * 自动侦测设置获取语言选择 - * @access public - * @return string - */ - public static function detect() - { - $langSet = ''; - - if (isset($_GET[self::$langDetectVar])) { - // url 中设置了语言变量 - $langSet = strtolower($_GET[self::$langDetectVar]); - } elseif (isset($_COOKIE[self::$langCookieVar])) { - // Cookie 中设置了语言变量 - $langSet = strtolower($_COOKIE[self::$langCookieVar]); - } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - // 自动侦测浏览器语言 - preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); - $langSet = strtolower($matches[1]); - $acceptLangs = Config::get('header_accept_lang'); - - if (isset($acceptLangs[$langSet])) { - $langSet = $acceptLangs[$langSet]; - } elseif (isset(self::$acceptLanguage[$langSet])) { - $langSet = self::$acceptLanguage[$langSet]; - } - } - - // 合法的语言 - if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { - self::$range = $langSet ?: self::$range; - } - - return self::$range; - } - - /** - * 设置语言自动侦测的变量 - * @access public - * @param string $var 变量名称 - * @return void - */ - public static function setLangDetectVar($var) - { - self::$langDetectVar = $var; - } - - /** - * 设置语言的 cookie 保存变量 - * @access public - * @param string $var 变量名称 - * @return void - */ - public static function setLangCookieVar($var) - { - self::$langCookieVar = $var; - } - - /** - * 设置语言的 cookie 的过期时间 - * @access public - * @param string $expire 过期时间 - * @return void - */ - public static function setLangCookieExpire($expire) - { - self::$langCookieExpire = $expire; - } - - /** - * 设置允许的语言列表 - * @access public - * @param array $list 语言列表 - * @return void - */ - public static function setAllowLangList($list) - { - self::$allowLangList = $list; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * @var array 语言数据 + */ + private static $lang = []; + + /** + * @var string 语言作用域 + */ + private static $range = 'zh-cn'; + + /** + * @var string 语言自动侦测的变量 + */ + protected static $langDetectVar = 'lang'; + + /** + * @var string 语言 Cookie 变量 + */ + protected static $langCookieVar = 'think_var'; + + /** + * @var int 语言 Cookie 的过期时间 + */ + protected static $langCookieExpire = 3600; + + /** + * @var array 允许语言列表 + */ + protected static $allowLangList = []; + + /** + * @var array Accept-Language 转义为对应语言包名称 系统默认配置 + */ + protected static $acceptLanguage = ['zh-hans-cn' => 'zh-cn']; + + /** + * 设定当前的语言 + * @access public + * @param string $range 语言作用域 + * @return string + */ + public static function range($range = '') + { + if ($range) { + self::$range = $range; + } + + return self::$range; + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public static function set($name, $value = null, $range = '') + { + $range = $range ?: self::$range; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + if (is_array($name)) { + return self::$lang[$range] = array_change_key_case($name) + self::$lang[$range]; + } + + return self::$lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param array|string $file 语言文件 + * @param string $range 语言作用域 + * @return mixed + */ + public static function load($file, $range = '') + { + $range = $range ?: self::$range; + $file = is_string($file) ? [$file] : $file; + + if (!isset(self::$lang[$range])) { + self::$lang[$range] = []; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + App::$debug && Log::record('[ LANG ] ' . $_file, 'info'); + + $_lang = include $_file; + + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + self::$lang[$range] = $lang + self::$lang[$range]; + } + + return self::$lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return mixed + */ + public static function has($name, $range = '') + { + $range = $range ?: self::$range; + + return isset(self::$lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public static function get($name = null, $vars = [], $range = '') + { + $range = $range ?: self::$range; + + // 空参数返回所有定义 + if (empty($name)) { + return self::$lang[$range]; + } + + $key = strtolower($name); + $value = isset(self::$lang[$range][$key]) ? self::$lang[$range][$key] : $name; + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @return string + */ + public static function detect() + { + $langSet = ''; + + if (isset($_GET[self::$langDetectVar])) { + // url 中设置了语言变量 + $langSet = strtolower($_GET[self::$langDetectVar]); + } elseif (isset($_COOKIE[self::$langCookieVar])) { + // Cookie 中设置了语言变量 + $langSet = strtolower($_COOKIE[self::$langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + $acceptLangs = Config::get('header_accept_lang'); + + if (isset($acceptLangs[$langSet])) { + $langSet = $acceptLangs[$langSet]; + } elseif (isset(self::$acceptLanguage[$langSet])) { + $langSet = self::$acceptLanguage[$langSet]; + } + } + + // 合法的语言 + if (empty(self::$allowLangList) || in_array($langSet, self::$allowLangList)) { + self::$range = $langSet ?: self::$range; + } + + return self::$range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangDetectVar($var) + { + self::$langDetectVar = $var; + } + + /** + * 设置语言的 cookie 保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public static function setLangCookieVar($var) + { + self::$langCookieVar = $var; + } + + /** + * 设置语言的 cookie 的过期时间 + * @access public + * @param string $expire 过期时间 + * @return void + */ + public static function setLangCookieExpire($expire) + { + self::$langCookieExpire = $expire; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public static function setAllowLangList($list) + { + self::$allowLangList = $list; + } +} diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php old mode 100755 new mode 100644 index be89c7a..d813a5d --- a/thinkphp/library/think/Loader.php +++ b/thinkphp/library/think/Loader.php @@ -1,677 +1,677 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; - -class Loader -{ - /** - * @var array 实例数组 - */ - protected static $instance = []; - - /** - * @var array 类名映射 - */ - protected static $classMap = []; - - /** - * @var array 命名空间别名 - */ - protected static $namespaceAlias = []; - - /** - * @var array PSR-4 命名空间前缀长度映射 - */ - private static $prefixLengthsPsr4 = []; - - /** - * @var array PSR-4 的加载目录 - */ - private static $prefixDirsPsr4 = []; - - /** - * @var array PSR-4 加载失败的回退目录 - */ - private static $fallbackDirsPsr4 = []; - - /** - * @var array PSR-0 命名空间前缀映射 - */ - private static $prefixesPsr0 = []; - - /** - * @var array PSR-0 加载失败的回退目录 - */ - private static $fallbackDirsPsr0 = []; - - /** - * @var array 需要加载的文件 - */ - private static $files = []; - - /** - * 自动加载 - * @access public - * @param string $class 类名 - * @return bool - */ - public static function autoload($class) - { - // 检测命名空间别名 - if (!empty(self::$namespaceAlias)) { - $namespace = dirname($class); - if (isset(self::$namespaceAlias[$namespace])) { - $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); - if (class_exists($original)) { - return class_alias($original, $class, false); - } - } - } - - if ($file = self::findFile($class)) { - // 非 Win 环境不严格区分大小写 - if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { - __include_file($file); - return true; - } - } - - return false; - } - - /** - * 查找文件 - * @access private - * @param string $class 类名 - * @return bool|string - */ - private static function findFile($class) - { - // 类库映射 - if (!empty(self::$classMap[$class])) { - return self::$classMap[$class]; - } - - // 查找 PSR-4 - $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; - $first = $class[0]; - - if (isset(self::$prefixLengthsPsr4[$first])) { - foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { - if (0 === strpos($class, $prefix)) { - foreach (self::$prefixDirsPsr4[$prefix] as $dir) { - if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { - return $file; - } - } - } - } - } - - // 查找 PSR-4 fallback dirs - foreach (self::$fallbackDirsPsr4 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr4)) { - return $file; - } - } - - // 查找 PSR-0 - if (false !== $pos = strrpos($class, '\\')) { - // namespace class name - $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) - . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); - } else { - // PEAR-like class name - $logicalPathPsr0 = strtr($class, '_', DS) . EXT; - } - - if (isset(self::$prefixesPsr0[$first])) { - foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { - if (0 === strpos($class, $prefix)) { - foreach ($dirs as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { - return $file; - } - } - } - } - } - - // 查找 PSR-0 fallback dirs - foreach (self::$fallbackDirsPsr0 as $dir) { - if (is_file($file = $dir . DS . $logicalPathPsr0)) { - return $file; - } - } - - // 找不到则设置映射为 false 并返回 - return self::$classMap[$class] = false; - } - - /** - * 注册 classmap - * @access public - * @param string|array $class 类名 - * @param string $map 映射 - * @return void - */ - public static function addClassMap($class, $map = '') - { - if (is_array($class)) { - self::$classMap = array_merge(self::$classMap, $class); - } else { - self::$classMap[$class] = $map; - } - } - - /** - * 注册命名空间 - * @access public - * @param string|array $namespace 命名空间 - * @param string $path 路径 - * @return void - */ - public static function addNamespace($namespace, $path = '') - { - if (is_array($namespace)) { - foreach ($namespace as $prefix => $paths) { - self::addPsr4($prefix . '\\', rtrim($paths, DS), true); - } - } else { - self::addPsr4($namespace . '\\', rtrim($path, DS), true); - } - } - - /** - * 添加 PSR-0 命名空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param array $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ - private static function addPsr0($prefix, $paths, $prepend = false) - { - if (!$prefix) { - self::$fallbackDirsPsr0 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr0) : - array_merge(self::$fallbackDirsPsr0, (array) $paths); - } else { - $first = $prefix[0]; - - if (!isset(self::$prefixesPsr0[$first][$prefix])) { - self::$prefixesPsr0[$first][$prefix] = (array) $paths; - } else { - self::$prefixesPsr0[$first][$prefix] = $prepend ? - array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : - array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); - } - } - } - - /** - * 添加 PSR-4 空间 - * @access private - * @param array|string $prefix 空间前缀 - * @param string $paths 路径 - * @param bool $prepend 预先设置的优先级更高 - * @return void - */ - private static function addPsr4($prefix, $paths, $prepend = false) - { - if (!$prefix) { - // Register directories for the root namespace. - self::$fallbackDirsPsr4 = $prepend ? - array_merge((array) $paths, self::$fallbackDirsPsr4) : - array_merge(self::$fallbackDirsPsr4, (array) $paths); - - } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { - // Register directories for a new namespace. - $length = strlen($prefix); - if ('\\' !== $prefix[$length - 1]) { - throw new \InvalidArgumentException( - "A non-empty PSR-4 prefix must end with a namespace separator." - ); - } - - self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; - self::$prefixDirsPsr4[$prefix] = (array) $paths; - - } else { - self::$prefixDirsPsr4[$prefix] = $prepend ? - // Prepend directories for an already registered namespace. - array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : - // Append directories for an already registered namespace. - array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); - } - } - - /** - * 注册命名空间别名 - * @access public - * @param array|string $namespace 命名空间 - * @param string $original 源文件 - * @return void - */ - public static function addNamespaceAlias($namespace, $original = '') - { - if (is_array($namespace)) { - self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); - } else { - self::$namespaceAlias[$namespace] = $original; - } - } - - /** - * 注册自动加载机制 - * @access public - * @param callable $autoload 自动加载处理方法 - * @return void - */ - public static function register($autoload = null) - { - // 注册系统自动加载 - spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); - - // Composer 自动加载支持 - if (is_dir(VENDOR_PATH . 'composer')) { - if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { - require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; - - $declaredClass = get_declared_classes(); - $composerClass = array_pop($declaredClass); - - foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { - if (property_exists($composerClass, $attr)) { - self::${$attr} = $composerClass::${$attr}; - } - } - } else { - self::registerComposerLoader(); - } - } - - // 注册命名空间定义 - self::addNamespace([ - 'think' => LIB_PATH . 'think' . DS, - 'behavior' => LIB_PATH . 'behavior' . DS, - 'traits' => LIB_PATH . 'traits' . DS, - ]); - - // 加载类库映射文件 - if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { - self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); - } - - self::loadComposerAutoloadFiles(); - - // 自动加载 extend 目录 - self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); - } - - /** - * 注册 composer 自动加载 - * @access private - * @return void - */ - private static function registerComposerLoader() - { - if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { - $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; - foreach ($map as $namespace => $path) { - self::addPsr0($namespace, $path); - } - } - - if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { - $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; - foreach ($map as $namespace => $path) { - self::addPsr4($namespace, $path); - } - } - - if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { - $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; - if ($classMap) { - self::addClassMap($classMap); - } - } - - if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { - self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; - } - } - - // 加载composer autofile文件 - public static function loadComposerAutoloadFiles() - { - foreach (self::$files as $fileIdentifier => $file) { - if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - __require_file($file); - - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - } - } - } - - /** - * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 - * @access public - * @param string $class 类库命名空间字符串 - * @param string $baseUrl 起始路径 - * @param string $ext 导入的文件扩展名 - * @return bool - */ - public static function import($class, $baseUrl = '', $ext = EXT) - { - static $_file = []; - $key = $class . $baseUrl; - $class = str_replace(['.', '#'], [DS, '.'], $class); - - if (isset($_file[$key])) { - return true; - } - - if (empty($baseUrl)) { - list($name, $class) = explode(DS, $class, 2); - - if (isset(self::$prefixDirsPsr4[$name . '\\'])) { - // 注册的命名空间 - $baseUrl = self::$prefixDirsPsr4[$name . '\\']; - } elseif ('@' == $name) { - // 加载当前模块应用类库 - $baseUrl = App::$modulePath; - } elseif (is_dir(EXTEND_PATH . $name)) { - $baseUrl = EXTEND_PATH . $name . DS; - } else { - // 加载其它模块的类库 - $baseUrl = APP_PATH . $name . DS; - } - } elseif (substr($baseUrl, -1) != DS) { - $baseUrl .= DS; - } - - // 如果类存在则导入类库文件 - if (is_array($baseUrl)) { - foreach ($baseUrl as $path) { - if (is_file($filename = $path . DS . $class . $ext)) { - break; - } - } - } else { - $filename = $baseUrl . $class . $ext; - } - - if (!empty($filename) && - is_file($filename) && - (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) - ) { - __include_file($filename); - $_file[$key] = true; - - return true; - } - - return false; - } - - /** - * 实例化(分层)模型 - * @access public - * @param string $name Model名称 - * @param string $layer 业务层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object - * @throws ClassNotFoundException - */ - public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') - { - $uid = $name . $layer; - - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $model = new $class(); - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $model = new $class(); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $model; - } - - /** - * 实例化(分层)控制器 格式:[模块名/]控制器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $empty 空控制器名称 - * @return object - * @throws ClassNotFoundException - */ - public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') - { - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - return App::invokeClass($class); - } - - if ($empty) { - $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); - - if (class_exists($emptyClass)) { - return new $emptyClass(Request::instance()); - } - } - - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - /** - * 实例化验证类 格式:[模块名/]验证器名 - * @access public - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @param string $common 公共模块名 - * @return object|false - * @throws ClassNotFoundException - */ - public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') - { - $name = $name ?: Config::get('default_validate'); - - if (empty($name)) { - return new Validate; - } - - $uid = $name . $layer; - if (isset(self::$instance[$uid])) { - return self::$instance[$uid]; - } - - list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); - - if (class_exists($class)) { - $validate = new $class; - } else { - $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); - - if (class_exists($class)) { - $validate = new $class; - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - } - - return self::$instance[$uid] = $validate; - } - - /** - * 解析模块和类名 - * @access protected - * @param string $name 资源地址 - * @param string $layer 验证层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return array - */ - protected static function getModuleAndClass($name, $layer, $appendSuffix) - { - if (false !== strpos($name, '\\')) { - $module = Request::instance()->module(); - $class = $name; - } else { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = Request::instance()->module(); - } - - $class = self::parseClass($module, $layer, $name, $appendSuffix); - } - - return [$module, $class]; - } - - /** - * 数据库初始化 并取得数据库类实例 - * @access public - * @param mixed $config 数据库配置 - * @param bool|string $name 连接标识 true 强制重新连接 - * @return \think\db\Connection - */ - public static function db($config = [], $name = false) - { - return Db::connect($config, $name); - } - - /** - * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 - * @access public - * @param string $url 调用地址 - * @param string|array $vars 调用参数 支持字符串和数组 - * @param string $layer 要调用的控制层名称 - * @param bool $appendSuffix 是否添加类名后缀 - * @return mixed - */ - public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) - { - $info = pathinfo($url); - $action = $info['basename']; - $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); - $class = self::controller($module, $layer, $appendSuffix); - - if ($class) { - if (is_scalar($vars)) { - if (strpos($vars, '=')) { - parse_str($vars, $vars); - } else { - $vars = [$vars]; - } - } - - return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); - } - - return false; - } - - /** - * 字符串命名风格转换 - * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 - * @access public - * @param string $name 字符串 - * @param integer $type 转换类型 - * @param bool $ucfirst 首字母是否大写(驼峰规则) - * @return string - */ - public static function parseName($name, $type = 0, $ucfirst = true) - { - if ($type) { - $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { - return strtoupper($match[1]); - }, $name); - - return $ucfirst ? ucfirst($name) : lcfirst($name); - } - - return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); - } - - /** - * 解析应用类的类名 - * @access public - * @param string $module 模块名 - * @param string $layer 层名 controller model ... - * @param string $name 类名 - * @param bool $appendSuffix 是否添加类名后缀 - * @return string - */ - public static function parseClass($module, $layer, $name, $appendSuffix = false) - { - - $array = explode('\\', str_replace(['/', '.'], '\\', $name)); - $class = self::parseName(array_pop($array), 1); - $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); - $path = $array ? implode('\\', $array) . '\\' : ''; - - return App::$namespace . '\\' . - ($module ? $module . '\\' : '') . - $layer . '\\' . $path . $class; - } - - /** - * 初始化类的实例 - * @access public - * @return void - */ - public static function clearInstance() - { - self::$instance = []; - } -} - -// 作用范围隔离 - -/** - * include - * @param string $file 文件路径 - * @return mixed - */ -function __include_file($file) -{ - return include $file; -} - -/** - * require - * @param string $file 文件路径 - * @return mixed - */ -function __require_file($file) -{ - return require $file; -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * @var array 实例数组 + */ + protected static $instance = []; + + /** + * @var array 类名映射 + */ + protected static $classMap = []; + + /** + * @var array 命名空间别名 + */ + protected static $namespaceAlias = []; + + /** + * @var array PSR-4 命名空间前缀长度映射 + */ + private static $prefixLengthsPsr4 = []; + + /** + * @var array PSR-4 的加载目录 + */ + private static $prefixDirsPsr4 = []; + + /** + * @var array PSR-4 加载失败的回退目录 + */ + private static $fallbackDirsPsr4 = []; + + /** + * @var array PSR-0 命名空间前缀映射 + */ + private static $prefixesPsr0 = []; + + /** + * @var array PSR-0 加载失败的回退目录 + */ + private static $fallbackDirsPsr0 = []; + + /** + * @var array 需要加载的文件 + */ + private static $files = []; + + /** + * 自动加载 + * @access public + * @param string $class 类名 + * @return bool + */ + public static function autoload($class) + { + // 检测命名空间别名 + if (!empty(self::$namespaceAlias)) { + $namespace = dirname($class); + if (isset(self::$namespaceAlias[$namespace])) { + $original = self::$namespaceAlias[$namespace] . '\\' . basename($class); + if (class_exists($original)) { + return class_alias($original, $class, false); + } + } + } + + if ($file = self::findFile($class)) { + // 非 Win 环境不严格区分大小写 + if (!IS_WIN || pathinfo($file, PATHINFO_FILENAME) == pathinfo(realpath($file), PATHINFO_FILENAME)) { + __include_file($file); + return true; + } + } + + return false; + } + + /** + * 查找文件 + * @access private + * @param string $class 类名 + * @return bool|string + */ + private static function findFile($class) + { + // 类库映射 + if (!empty(self::$classMap[$class])) { + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DS) . EXT; + $first = $class[0]; + + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DS . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespace class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DS); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DS) . EXT; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DS . $logicalPathPsr0)) { + return $file; + } + } + + // 找不到则设置映射为 false 并返回 + return self::$classMap[$class] = false; + } + + /** + * 注册 classmap + * @access public + * @param string|array $class 类名 + * @param string $map 映射 + * @return void + */ + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + /** + * 注册命名空间 + * @access public + * @param string|array $namespace 命名空间 + * @param string $path 路径 + * @return void + */ + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DS), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DS), true); + } + } + + /** + * 添加 PSR-0 命名空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param array $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + self::$fallbackDirsPsr0 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr0) : + array_merge(self::$fallbackDirsPsr0, (array) $paths); + } else { + $first = $prefix[0]; + + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + } else { + self::$prefixesPsr0[$first][$prefix] = $prepend ? + array_merge((array) $paths, self::$prefixesPsr0[$first][$prefix]) : + array_merge(self::$prefixesPsr0[$first][$prefix], (array) $paths); + } + } + } + + /** + * 添加 PSR-4 空间 + * @access private + * @param array|string $prefix 空间前缀 + * @param string $paths 路径 + * @param bool $prepend 预先设置的优先级更高 + * @return void + */ + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + self::$fallbackDirsPsr4 = $prepend ? + array_merge((array) $paths, self::$fallbackDirsPsr4) : + array_merge(self::$fallbackDirsPsr4, (array) $paths); + + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException( + "A non-empty PSR-4 prefix must end with a namespace separator." + ); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + + } else { + self::$prefixDirsPsr4[$prefix] = $prepend ? + // Prepend directories for an already registered namespace. + array_merge((array) $paths, self::$prefixDirsPsr4[$prefix]) : + // Append directories for an already registered namespace. + array_merge(self::$prefixDirsPsr4[$prefix], (array) $paths); + } + } + + /** + * 注册命名空间别名 + * @access public + * @param array|string $namespace 命名空间 + * @param string $original 源文件 + * @return void + */ + public static function addNamespaceAlias($namespace, $original = '') + { + if (is_array($namespace)) { + self::$namespaceAlias = array_merge(self::$namespaceAlias, $namespace); + } else { + self::$namespaceAlias[$namespace] = $original; + } + } + + /** + * 注册自动加载机制 + * @access public + * @param callable $autoload 自动加载处理方法 + * @return void + */ + public static function register($autoload = null) + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + // Composer 自动加载支持 + if (is_dir(VENDOR_PATH . 'composer')) { + if (PHP_VERSION_ID >= 50600 && is_file(VENDOR_PATH . 'composer' . DS . 'autoload_static.php')) { + require VENDOR_PATH . 'composer' . DS . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => LIB_PATH . 'think' . DS, + 'behavior' => LIB_PATH . 'behavior' . DS, + 'traits' => LIB_PATH . 'traits' . DS, + ]); + + // 加载类库映射文件 + if (is_file(RUNTIME_PATH . 'classmap' . EXT)) { + self::addClassMap(__include_file(RUNTIME_PATH . 'classmap' . EXT)); + } + + self::loadComposerAutoloadFiles(); + + // 自动加载 extend 目录 + self::$fallbackDirsPsr4[] = rtrim(EXTEND_PATH, DS); + } + + /** + * 注册 composer 自动加载 + * @access private + * @return void + */ + private static function registerComposerLoader() + { + if (is_file(VENDOR_PATH . 'composer/autoload_namespaces.php')) { + $map = require VENDOR_PATH . 'composer/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_psr4.php')) { + $map = require VENDOR_PATH . 'composer/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_classmap.php')) { + $classMap = require VENDOR_PATH . 'composer/autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file(VENDOR_PATH . 'composer/autoload_files.php')) { + self::$files = require VENDOR_PATH . 'composer/autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 导入所需的类库 同 Java 的 Import 本函数有缓存功能 + * @access public + * @param string $class 类库命名空间字符串 + * @param string $baseUrl 起始路径 + * @param string $ext 导入的文件扩展名 + * @return bool + */ + public static function import($class, $baseUrl = '', $ext = EXT) + { + static $_file = []; + $key = $class . $baseUrl; + $class = str_replace(['.', '#'], [DS, '.'], $class); + + if (isset($_file[$key])) { + return true; + } + + if (empty($baseUrl)) { + list($name, $class) = explode(DS, $class, 2); + + if (isset(self::$prefixDirsPsr4[$name . '\\'])) { + // 注册的命名空间 + $baseUrl = self::$prefixDirsPsr4[$name . '\\']; + } elseif ('@' == $name) { + // 加载当前模块应用类库 + $baseUrl = App::$modulePath; + } elseif (is_dir(EXTEND_PATH . $name)) { + $baseUrl = EXTEND_PATH . $name . DS; + } else { + // 加载其它模块的类库 + $baseUrl = APP_PATH . $name . DS; + } + } elseif (substr($baseUrl, -1) != DS) { + $baseUrl .= DS; + } + + // 如果类存在则导入类库文件 + if (is_array($baseUrl)) { + foreach ($baseUrl as $path) { + if (is_file($filename = $path . DS . $class . $ext)) { + break; + } + } + } else { + $filename = $baseUrl . $class . $ext; + } + + if (!empty($filename) && + is_file($filename) && + (!IS_WIN || pathinfo($filename, PATHINFO_FILENAME) == pathinfo(realpath($filename), PATHINFO_FILENAME)) + ) { + __include_file($filename); + $_file[$key] = true; + + return true; + } + + return false; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public static function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + $uid = $name . $layer; + + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $model = new $class(); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $model = new $class(); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $model; + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public static function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return App::invokeClass($class); + } + + if ($empty) { + $emptyClass = self::parseClass($module, $layer, $empty, $appendSuffix); + + if (class_exists($emptyClass)) { + return new $emptyClass(Request::instance()); + } + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object|false + * @throws ClassNotFoundException + */ + public static function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: Config::get('default_validate'); + + if (empty($name)) { + return new Validate; + } + + $uid = $name . $layer; + if (isset(self::$instance[$uid])) { + return self::$instance[$uid]; + } + + list($module, $class) = self::getModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $validate = new $class; + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + + if (class_exists($class)) { + $validate = new $class; + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + return self::$instance[$uid] = $validate; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected static function getModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $module = Request::instance()->module(); + $class = $name; + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = Request::instance()->module(); + } + + $class = self::parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 数据库初始化 并取得数据库类实例 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Connection + */ + public static function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + public static function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : Request::instance()->controller(); + $class = self::controller($module, $layer, $appendSuffix); + + if ($class) { + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return App::invokeMethod([$class, $action . Config::get('action_suffix')], $vars); + } + + return false; + } + + /** + * 字符串命名风格转换 + * type 0 将 Java 风格转换为 C 的风格 1 将 C 风格转换为 Java 的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix 是否添加类名后缀 + * @return string + */ + public static function parseClass($module, $layer, $name, $appendSuffix = false) + { + + $array = explode('\\', str_replace(['/', '.'], '\\', $name)); + $class = self::parseName(array_pop($array), 1); + $class = $class . (App::$suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return App::$namespace . '\\' . + ($module ? $module . '\\' : '') . + $layer . '\\' . $path . $class; + } + + /** + * 初始化类的实例 + * @access public + * @return void + */ + public static function clearInstance() + { + self::$instance = []; + } +} + +// 作用范围隔离 + +/** + * include + * @param string $file 文件路径 + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +/** + * require + * @param string $file 文件路径 + * @return mixed + */ +function __require_file($file) +{ + return require $file; +} diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php old mode 100755 new mode 100644 index 54a8e48..c064306 --- a/thinkphp/library/think/Log.php +++ b/thinkphp/library/think/Log.php @@ -1,237 +1,237 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; - -/** - * Class Log - * @package think - * - * @method void log($msg) static 记录一般日志 - * @method void error($msg) static 记录错误日志 - * @method void info($msg) static 记录一般信息日志 - * @method void sql($msg) static 记录 SQL 查询日志 - * @method void notice($msg) static 记录提示日志 - * @method void alert($msg) static 记录报警日志 - */ -class Log -{ - const LOG = 'log'; - const ERROR = 'error'; - const INFO = 'info'; - const SQL = 'sql'; - const NOTICE = 'notice'; - const ALERT = 'alert'; - const DEBUG = 'debug'; - - /** - * @var array 日志信息 - */ - protected static $log = []; - - /** - * @var array 配置参数 - */ - protected static $config = []; - - /** - * @var array 日志类型 - */ - protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; - - /** - * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 - */ - protected static $driver; - - /** - * @var string 当前日志授权 key - */ - protected static $key; - - /** - * 日志初始化 - * @access public - * @param array $config 配置参数 - * @return void - */ - public static function init($config = []) - { - $type = isset($config['type']) ? $config['type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); - - self::$config = $config; - unset($config['type']); - - if (class_exists($class)) { - self::$driver = new $class($config); - } else { - throw new ClassNotFoundException('class not exists:' . $class, $class); - } - - // 记录初始化信息 - App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); - } - - /** - * 获取日志信息 - * @access public - * @param string $type 信息类型 - * @return array|string - */ - public static function getLog($type = '') - { - return $type ? self::$log[$type] : self::$log; - } - - /** - * 记录调试信息 - * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @return void - */ - public static function record($msg, $type = 'log') - { - self::$log[$type][] = $msg; - - // 命令行下面日志写入改进 - IS_CLI && self::save(); - } - - /** - * 清空日志信息 - * @access public - * @return void - */ - public static function clear() - { - self::$log = []; - } - - /** - * 设置当前日志记录的授权 key - * @access public - * @param string $key 授权 key - * @return void - */ - public static function key($key) - { - self::$key = $key; - } - - /** - * 检查日志写入权限 - * @access public - * @param array $config 当前日志配置参数 - * @return bool - */ - public static function check($config) - { - return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); - } - - /** - * 保存调试信息 - * @access public - * @return bool - */ - public static function save() - { - // 没有需要保存的记录则直接返回 - if (empty(self::$log)) { - return true; - } - - is_null(self::$driver) && self::init(Config::get('log')); - - // 检测日志写入权限 - if (!self::check(self::$config)) { - return false; - } - - if (empty(self::$config['level'])) { - // 获取全部日志 - $log = self::$log; - if (!App::$debug && isset($log['debug'])) { - unset($log['debug']); - } - } else { - // 记录允许级别 - $log = []; - foreach (self::$config['level'] as $level) { - if (isset(self::$log[$level])) { - $log[$level] = self::$log[$level]; - } - } - } - - if ($result = self::$driver->save($log)) { - self::$log = []; - } - - Hook::listen('log_write_done', $log); - - return $result; - } - - /** - * 实时写入日志信息 并支持行为 - * @access public - * @param mixed $msg 调试信息 - * @param string $type 信息类型 - * @param bool $force 是否强制写入 - * @return bool - */ - public static function write($msg, $type = 'log', $force = false) - { - $log = self::$log; - - // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 - if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { - return false; - } - - // 封装日志信息 - $log[$type][] = $msg; - - // 监听 log_write - Hook::listen('log_write', $log); - - is_null(self::$driver) && self::init(Config::get('log')); - - // 写入日志 - if ($result = self::$driver->save($log)) { - self::$log = []; - } - - return $result; - } - - /** - * 静态方法调用 - * @access public - * @param string $method 调用方法 - * @param mixed $args 参数 - * @return void - */ - public static function __callStatic($method, $args) - { - if (in_array($method, self::$type)) { - array_push($args, $method); - - call_user_func_array('\\think\\Log::record', $args); - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +/** + * Class Log + * @package think + * + * @method void log($msg) static 记录一般日志 + * @method void error($msg) static 记录错误日志 + * @method void info($msg) static 记录一般信息日志 + * @method void sql($msg) static 记录 SQL 查询日志 + * @method void notice($msg) static 记录提示日志 + * @method void alert($msg) static 记录报警日志 + */ +class Log +{ + const LOG = 'log'; + const ERROR = 'error'; + const INFO = 'info'; + const SQL = 'sql'; + const NOTICE = 'notice'; + const ALERT = 'alert'; + const DEBUG = 'debug'; + + /** + * @var array 日志信息 + */ + protected static $log = []; + + /** + * @var array 配置参数 + */ + protected static $config = []; + + /** + * @var array 日志类型 + */ + protected static $type = ['log', 'error', 'info', 'sql', 'notice', 'alert', 'debug']; + + /** + * @var log\driver\File|log\driver\Test|log\driver\Socket 日志写入驱动 + */ + protected static $driver; + + /** + * @var string 当前日志授权 key + */ + protected static $key; + + /** + * 日志初始化 + * @access public + * @param array $config 配置参数 + * @return void + */ + public static function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\log\\driver\\' . ucwords($type); + + self::$config = $config; + unset($config['type']); + + if (class_exists($class)) { + self::$driver = new $class($config); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + // 记录初始化信息 + App::$debug && Log::record('[ LOG ] INIT ' . $type, 'info'); + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array|string + */ + public static function getLog($type = '') + { + return $type ? self::$log[$type] : self::$log; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public static function record($msg, $type = 'log') + { + self::$log[$type][] = $msg; + + // 命令行下面日志写入改进 + IS_CLI && self::save(); + } + + /** + * 清空日志信息 + * @access public + * @return void + */ + public static function clear() + { + self::$log = []; + } + + /** + * 设置当前日志记录的授权 key + * @access public + * @param string $key 授权 key + * @return void + */ + public static function key($key) + { + self::$key = $key; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public static function check($config) + { + return !self::$key || empty($config['allow_key']) || in_array(self::$key, $config['allow_key']); + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public static function save() + { + // 没有需要保存的记录则直接返回 + if (empty(self::$log)) { + return true; + } + + is_null(self::$driver) && self::init(Config::get('log')); + + // 检测日志写入权限 + if (!self::check(self::$config)) { + return false; + } + + if (empty(self::$config['level'])) { + // 获取全部日志 + $log = self::$log; + if (!App::$debug && isset($log['debug'])) { + unset($log['debug']); + } + } else { + // 记录允许级别 + $log = []; + foreach (self::$config['level'] as $level) { + if (isset(self::$log[$level])) { + $log[$level] = self::$log[$level]; + } + } + } + + if ($result = self::$driver->save($log, true)) { + self::$log = []; + } + + Hook::listen('log_write_done', $log); + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @param bool $force 是否强制写入 + * @return bool + */ + public static function write($msg, $type = 'log', $force = false) + { + $log = self::$log; + + // 如果不是强制写入,而且信息类型不在可记录的类别中则直接返回 false 不做记录 + if (true !== $force && !empty(self::$config['level']) && !in_array($type, self::$config['level'])) { + return false; + } + + // 封装日志信息 + $log[$type][] = $msg; + + // 监听 log_write + Hook::listen('log_write', $log); + + is_null(self::$driver) && self::init(Config::get('log')); + + // 写入日志 + if ($result = self::$driver->save($log, false)) { + self::$log = []; + } + + return $result; + } + + /** + * 静态方法调用 + * @access public + * @param string $method 调用方法 + * @param mixed $args 参数 + * @return void + */ + public static function __callStatic($method, $args) + { + if (in_array($method, self::$type)) { + array_push($args, $method); + + call_user_func_array('\\think\\Log::record', $args); + } + } + +} diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php old mode 100755 new mode 100644 index e6805f4..386660c --- a/thinkphp/library/think/Model.php +++ b/thinkphp/library/think/Model.php @@ -1,2334 +1,2334 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use BadMethodCallException; -use InvalidArgumentException; -use think\db\Query; -use think\exception\ValidateException; -use think\model\Collection as ModelCollection; -use think\model\Relation; -use think\model\relation\BelongsTo; -use think\model\relation\BelongsToMany; -use think\model\relation\HasMany; -use think\model\relation\HasManyThrough; -use think\model\relation\HasOne; -use think\model\relation\MorphMany; -use think\model\relation\MorphOne; -use think\model\relation\MorphTo; - -/** - * Class Model - * @package think - * @mixin Query - */ -abstract class Model implements \JsonSerializable, \ArrayAccess -{ - // 数据库查询对象池 - protected static $links = []; - // 数据库配置 - protected $connection = []; - // 父关联模型对象 - protected $parent; - // 数据库查询对象 - protected $query; - // 当前模型名称 - protected $name; - // 数据表名称 - protected $table; - // 当前类名称 - protected $class; - // 回调事件 - private static $event = []; - // 错误信息 - protected $error; - // 字段验证规则 - protected $validate; - // 数据表主键 复合主键使用数组定义 不设置则自动获取 - protected $pk; - // 数据表字段信息 留空则自动获取 - protected $field = []; - // 数据排除字段 - protected $except = []; - // 数据废弃字段 - protected $disuse = []; - // 只读字段 - protected $readonly = []; - // 显示属性 - protected $visible = []; - // 隐藏属性 - protected $hidden = []; - // 追加属性 - protected $append = []; - // 数据信息 - protected $data = []; - // 原始数据 - protected $origin = []; - // 关联模型 - protected $relation = []; - - // 保存自动完成列表 - protected $auto = []; - // 新增自动完成列表 - protected $insert = []; - // 更新自动完成列表 - protected $update = []; - // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 - protected $autoWriteTimestamp; - // 创建时间字段 - protected $createTime = 'create_time'; - // 更新时间字段 - protected $updateTime = 'update_time'; - // 时间字段取出后的默认时间格式 - protected $dateFormat; - // 字段类型或者格式转换 - protected $type = []; - // 是否为更新数据 - protected $isUpdate = false; - // 是否强制更新所有数据 - protected $force = false; - // 更新条件 - protected $updateWhere; - // 验证失败是否抛出异常 - protected $failException = false; - // 全局查询范围 - protected $useGlobalScope = true; - // 是否采用批量验证 - protected $batchValidate = false; - // 查询数据集对象 - protected $resultSetType; - // 关联自动写入 - protected $relationWrite; - - /** - * 初始化过的模型. - * - * @var array - */ - protected static $initialized = []; - - /** - * 是否从主库读取(主从分布式有效) - * @var array - */ - protected static $readMaster; - - /** - * 构造方法 - * @access public - * @param array|object $data 数据 - */ - public function __construct($data = []) - { - if (is_object($data)) { - $this->data = get_object_vars($data); - } else { - $this->data = $data; - } - - if ($this->disuse) { - // 废弃字段 - foreach ((array) $this->disuse as $key) { - if (array_key_exists($key, $this->data)) { - unset($this->data[$key]); - } - } - } - - // 记录原始数据 - $this->origin = $this->data; - - // 当前类名 - $this->class = get_called_class(); - - if (empty($this->name)) { - // 当前模型名 - $name = str_replace('\\', '/', $this->class); - $this->name = basename($name); - if (Config::get('class_suffix')) { - $suffix = basename(dirname($name)); - $this->name = substr($this->name, 0, -strlen($suffix)); - } - } - - if (is_null($this->autoWriteTimestamp)) { - // 自动写入时间戳 - $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); - } - - if (is_null($this->dateFormat)) { - // 设置时间戳格式 - $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); - } - - if (is_null($this->resultSetType)) { - $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); - } - // 执行初始化操作 - $this->initialize(); - } - - /** - * 是否从主库读取数据(主从分布有效) - * @access public - * @param bool $all 是否所有模型生效 - * @return $this - */ - public function readMaster($all = false) - { - $model = $all ? '*' : $this->class; - - static::$readMaster[$model] = true; - return $this; - } - - /** - * 创建模型的查询对象 - * @access protected - * @return Query - */ - protected function buildQuery() - { - // 合并数据库配置 - if (!empty($this->connection)) { - if (is_array($this->connection)) { - $connection = array_merge(Config::get('database'), $this->connection); - } else { - $connection = $this->connection; - } - } else { - $connection = []; - } - - $con = Db::connect($connection); - // 设置当前模型 确保查询返回模型对象 - $queryClass = $this->query ?: $con->getConfig('query'); - $query = new $queryClass($con, $this); - - if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { - $query->master(true); - } - - // 设置当前数据表和模型名 - if (!empty($this->table)) { - $query->setTable($this->table); - } else { - $query->name($this->name); - } - - if (!empty($this->pk)) { - $query->pk($this->pk); - } - - return $query; - } - - /** - * 创建新的模型实例 - * @access public - * @param array|object $data 数据 - * @param bool $isUpdate 是否为更新 - * @param mixed $where 更新条件 - * @return Model - */ - public function newInstance($data = [], $isUpdate = false, $where = null) - { - return (new static($data))->isUpdate($isUpdate, $where); - } - - /** - * 获取当前模型的查询对象 - * @access public - * @param bool $buildNewQuery 创建新的查询对象 - * @return Query - */ - public function getQuery($buildNewQuery = false) - { - if ($buildNewQuery) { - return $this->buildQuery(); - } elseif (!isset(self::$links[$this->class])) { - // 创建模型查询对象 - self::$links[$this->class] = $this->buildQuery(); - } - - return self::$links[$this->class]; - } - - /** - * 获取当前模型的数据库查询对象 - * @access public - * @param bool $useBaseQuery 是否调用全局查询范围 - * @param bool $buildNewQuery 创建新的查询对象 - * @return Query - */ - public function db($useBaseQuery = true, $buildNewQuery = true) - { - $query = $this->getQuery($buildNewQuery); - - // 全局作用域 - if ($useBaseQuery && method_exists($this, 'base')) { - call_user_func_array([$this, 'base'], [ & $query]); - } - - // 返回当前模型的数据库查询对象 - return $query; - } - - /** - * 初始化模型 - * @access protected - * @return void - */ - protected function initialize() - { - $class = get_class($this); - if (!isset(static::$initialized[$class])) { - static::$initialized[$class] = true; - static::init(); - } - } - - /** - * 初始化处理 - * @access protected - * @return void - */ - protected static function init() - { - } - - /** - * 设置父关联对象 - * @access public - * @param Model $model 模型对象 - * @return $this - */ - public function setParent($model) - { - $this->parent = $model; - return $this; - } - - /** - * 获取父关联对象 - * @access public - * @return Model - */ - public function getParent() - { - return $this->parent; - } - - /** - * 设置数据对象值 - * @access public - * @param mixed $data 数据或者属性名 - * @param mixed $value 值 - * @return $this - */ - public function data($data, $value = null) - { - if (is_string($data)) { - $this->data[$data] = $value; - } else { - // 清空数据 - $this->data = []; - if (is_object($data)) { - $data = get_object_vars($data); - } - if (true === $value) { - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - } else { - $this->data = $data; - } - } - return $this; - } - - /** - * 获取对象原始数据 如果不存在指定字段返回false - * @access public - * @param string $name 字段名 留空获取全部 - * @return mixed - * @throws InvalidArgumentException - */ - public function getData($name = null) - { - if (is_null($name)) { - return $this->data; - } elseif (array_key_exists($name, $this->data)) { - return $this->data[$name]; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - - /** - * 是否需要自动写入时间字段 - * @access public - * @param bool $auto - * @return $this - */ - public function isAutoWriteTimestamp($auto) - { - $this->autoWriteTimestamp = $auto; - return $this; - } - - /** - * 更新是否强制写入数据 而不做比较 - * @access public - * @param bool $force - * @return $this - */ - public function force($force = true) - { - $this->force = $force; - return $this; - } - - /** - * 修改器 设置数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @param array $data 数据 - * @return $this - */ - public function setAttr($name, $value, $data = []) - { - if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { - // 自动写入的时间戳字段 - $value = $this->autoWriteTimestamp($name); - } else { - // 检测修改器 - $method = 'set' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, array_merge($this->data, $data), $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->writeTransform($value, $this->type[$name]); - } - } - - // 设置数据对象属性 - $this->data[$name] = $value; - return $this; - } - - /** - * 获取当前模型的关联模型数据 - * @access public - * @param string $name 关联方法名 - * @return mixed - */ - public function getRelation($name = null) - { - if (is_null($name)) { - return $this->relation; - } elseif (array_key_exists($name, $this->relation)) { - return $this->relation[$name]; - } else { - return; - } - } - - /** - * 设置关联数据对象值 - * @access public - * @param string $name 属性名 - * @param mixed $value 属性值 - * @return $this - */ - public function setRelation($name, $value) - { - $this->relation[$name] = $value; - return $this; - } - - /** - * 自动写入时间戳 - * @access public - * @param string $name 时间戳字段 - * @return mixed - */ - protected function autoWriteTimestamp($name) - { - if (isset($this->type[$name])) { - $type = $this->type[$name]; - if (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'datetime': - case 'date': - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(time(), $format); - break; - case 'timestamp': - case 'integer': - default: - $value = time(); - break; - } - } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(time(), $this->dateFormat); - } else { - $value = $this->formatDateTime(time(), $this->dateFormat, true); - } - return $value; - } - - /** - * 时间日期字段格式化处理 - * @access public - * @param mixed $time 时间日期表达式 - * @param mixed $format 日期格式 - * @param bool $timestamp 是否进行时间戳转换 - * @return mixed - */ - protected function formatDateTime($time, $format, $timestamp = false) - { - if (false !== strpos($format, '\\')) { - $time = new $format($time); - } elseif (!$timestamp && false !== $format) { - $time = date($format, $time); - } - return $time; - } - - /** - * 数据写入 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function writeTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_numeric($value)) { - $value = strtotime($value); - } - break; - case 'datetime': - $format = !empty($param) ? $param : $this->dateFormat; - $value = is_numeric($value) ? $value : strtotime($value); - $value = $this->formatDateTime($value, $format); - break; - case 'object': - if (is_object($value)) { - $value = json_encode($value, JSON_FORCE_OBJECT); - } - break; - case 'array': - $value = (array) $value; - case 'json': - $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; - $value = json_encode($value, $option); - break; - case 'serialize': - $value = serialize($value); - break; - - } - return $value; - } - - /** - * 获取器 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - * @throws InvalidArgumentException - */ - public function getAttr($name) - { - try { - $notFound = false; - $value = $this->getData($name); - } catch (InvalidArgumentException $e) { - $notFound = true; - $value = null; - } - - // 检测属性获取器 - $method = 'get' . Loader::parseName($name, 1) . 'Attr'; - if (method_exists($this, $method)) { - $value = $this->$method($value, $this->data, $this->relation); - } elseif (isset($this->type[$name])) { - // 类型转换 - $value = $this->readTransform($value, $this->type[$name]); - } elseif (in_array($name, [$this->createTime, $this->updateTime])) { - if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ - 'datetime', - 'date', - 'timestamp', - ]) - ) { - $value = $this->formatDateTime(strtotime($value), $this->dateFormat); - } else { - $value = $this->formatDateTime($value, $this->dateFormat); - } - } elseif ($notFound) { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - // 不存在该字段 获取关联数据 - $value = $this->getRelationData($modelRelation); - // 保存关联对象值 - $this->relation[$name] = $value; - } else { - throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); - } - } - return $value; - } - - /** - * 获取关联模型数据 - * @access public - * @param Relation $modelRelation 模型关联对象 - * @return mixed - * @throws BadMethodCallException - */ - protected function getRelationData(Relation $modelRelation) - { - if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { - $value = $this->parent; - } else { - // 首先获取关联数据 - if (method_exists($modelRelation, 'getRelation')) { - $value = $modelRelation->getRelation(); - } else { - throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); - } - } - return $value; - } - - /** - * 数据读取 类型转换 - * @access public - * @param mixed $value 值 - * @param string|array $type 要转换的类型 - * @return mixed - */ - protected function readTransform($value, $type) - { - if (is_null($value)) { - return; - } - - if (is_array($type)) { - list($type, $param) = $type; - } elseif (strpos($type, ':')) { - list($type, $param) = explode(':', $type, 2); - } - switch ($type) { - case 'integer': - $value = (int) $value; - break; - case 'float': - if (empty($param)) { - $value = (float) $value; - } else { - $value = (float) number_format($value, $param, '.', ''); - } - break; - case 'boolean': - $value = (bool) $value; - break; - case 'timestamp': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime($value, $format); - } - break; - case 'datetime': - if (!is_null($value)) { - $format = !empty($param) ? $param : $this->dateFormat; - $value = $this->formatDateTime(strtotime($value), $format); - } - break; - case 'json': - $value = json_decode($value, true); - break; - case 'array': - $value = empty($value) ? [] : json_decode($value, true); - break; - case 'object': - $value = empty($value) ? new \stdClass() : json_decode($value); - break; - case 'serialize': - try { - $value = unserialize($value); - } catch (\Exception $e) { - $value = null; - } - break; - default: - if (false !== strpos($type, '\\')) { - // 对象类型 - $value = new $type($value); - } - } - return $value; - } - - /** - * 设置需要追加的输出属性 - * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function append($append = [], $override = false) - { - $this->append = $override ? $append : array_merge($this->append, $append); - return $this; - } - - /** - * 设置附加关联对象的属性 - * @access public - * @param string $relation 关联方法 - * @param string|array $append 追加属性名 - * @return $this - * @throws Exception - */ - public function appendRelationAttr($relation, $append) - { - if (is_string($append)) { - $append = explode(',', $append); - } - - $relation = Loader::parseName($relation, 1, false); - - // 获取关联数据 - if (isset($this->relation[$relation])) { - $model = $this->relation[$relation]; - } else { - $model = $this->getRelationData($this->$relation()); - } - - if ($model instanceof Model) { - foreach ($append as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $this->data[$key] = $model->getAttr($attr); - } - } - } - return $this; - } - - /** - * 设置需要隐藏的输出属性 - * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function hidden($hidden = [], $override = false) - { - $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); - return $this; - } - - /** - * 设置需要输出的属性 - * @access public - * @param array $visible - * @param bool $override 是否覆盖 - * @return $this - */ - public function visible($visible = [], $override = false) - { - $this->visible = $override ? $visible : array_merge($this->visible, $visible); - return $this; - } - - /** - * 解析隐藏及显示属性 - * @access protected - * @param array $attrs 属性 - * @param array $result 结果集 - * @param bool $visible - * @return array - */ - protected function parseAttr($attrs, &$result, $visible = true) - { - $array = []; - foreach ($attrs as $key => $val) { - if (is_array($val)) { - if ($visible) { - $array[] = $key; - } - $result[$key] = $val; - } elseif (strpos($val, '.')) { - list($key, $name) = explode('.', $val); - if ($visible) { - $array[] = $key; - } - $result[$key][] = $name; - } else { - $array[] = $val; - } - } - return $array; - } - - /** - * 转换子模型对象 - * @access protected - * @param Model|ModelCollection $model - * @param $visible - * @param $hidden - * @param $key - * @return array - */ - protected function subToArray($model, $visible, $hidden, $key) - { - // 关联模型对象 - if (isset($visible[$key])) { - $model->visible($visible[$key]); - } elseif (isset($hidden[$key])) { - $model->hidden($hidden[$key]); - } - return $model->toArray(); - } - - /** - * 转换当前模型对象为数组 - * @access public - * @return array - */ - public function toArray() - { - $item = []; - $visible = []; - $hidden = []; - - $data = array_merge($this->data, $this->relation); - - // 过滤属性 - if (!empty($this->visible)) { - $array = $this->parseAttr($this->visible, $visible); - $data = array_intersect_key($data, array_flip($array)); - } elseif (!empty($this->hidden)) { - $array = $this->parseAttr($this->hidden, $hidden, false); - $data = array_diff_key($data, array_flip($array)); - } - - foreach ($data as $key => $val) { - if ($val instanceof Model || $val instanceof ModelCollection) { - // 关联模型对象 - $item[$key] = $this->subToArray($val, $visible, $hidden, $key); - } elseif (is_array($val) && reset($val) instanceof Model) { - // 关联模型数据集 - $arr = []; - foreach ($val as $k => $value) { - $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); - } - $item[$key] = $arr; - } else { - // 模型属性 - $item[$key] = $this->getAttr($key); - } - } - // 追加属性(必须定义获取器) - if (!empty($this->append)) { - foreach ($this->append as $key => $name) { - if (is_array($name)) { - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append($name)->toArray(); - } elseif (strpos($name, '.')) { - list($key, $attr) = explode('.', $name); - // 追加关联对象属性 - $relation = $this->getAttr($key); - $item[$key] = $relation->append([$attr])->toArray(); - } else { - $relation = Loader::parseName($name, 1, false); - if (method_exists($this, $relation)) { - $modelRelation = $this->$relation(); - $value = $this->getRelationData($modelRelation); - - if (method_exists($modelRelation, 'getBindAttr')) { - $bindAttr = $modelRelation->getBindAttr(); - if ($bindAttr) { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($this->data[$key])) { - throw new Exception('bind attr has exists:' . $key); - } else { - $item[$key] = $value ? $value->getAttr($attr) : null; - } - } - continue; - } - } - $item[$name] = $value; - } else { - $item[$name] = $this->getAttr($name); - } - } - } - } - return !empty($item) ? $item : []; - } - - /** - * 转换当前模型对象为JSON字符串 - * @access public - * @param integer $options json参数 - * @return string - */ - public function toJson($options = JSON_UNESCAPED_UNICODE) - { - return json_encode($this->toArray(), $options); - } - - /** - * 移除当前模型的关联属性 - * @access public - * @return $this - */ - public function removeRelation() - { - $this->relation = []; - return $this; - } - - /** - * 转换当前模型数据集为数据集对象 - * @access public - * @param array|\think\Collection $collection 数据集 - * @return \think\Collection - */ - public function toCollection($collection) - { - if ($this->resultSetType) { - if ('collection' == $this->resultSetType) { - $collection = new ModelCollection($collection); - } elseif (false !== strpos($this->resultSetType, '\\')) { - $class = $this->resultSetType; - $collection = new $class($collection); - } - } - return $collection; - } - - /** - * 关联数据一起更新 - * @access public - * @param mixed $relation 关联 - * @return $this - */ - public function together($relation) - { - if (is_string($relation)) { - $relation = explode(',', $relation); - } - $this->relationWrite = $relation; - return $this; - } - - /** - * 获取模型对象的主键 - * @access public - * @param string $name 模型名 - * @return mixed - */ - public function getPk($name = '') - { - if (!empty($name)) { - $table = $this->getQuery()->getTable($name); - return $this->getQuery()->getPk($table); - } elseif (empty($this->pk)) { - $this->pk = $this->getQuery()->getPk(); - } - return $this->pk; - } - - /** - * 判断一个字段名是否为主键字段 - * @access public - * @param string $key 名称 - * @return bool - */ - protected function isPk($key) - { - $pk = $this->getPk(); - if (is_string($pk) && $pk == $key) { - return true; - } elseif (is_array($pk) && in_array($key, $pk)) { - return true; - } - return false; - } - - /** - * 保存当前数据对象 - * @access public - * @param array $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return integer|false - */ - public function save($data = [], $where = [], $sequence = null) - { - if (is_string($data)) { - $sequence = $data; - $data = []; - } - - if (!empty($data)) { - // 数据自动验证 - if (!$this->validateData($data)) { - return false; - } - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - if (!empty($where)) { - $this->isUpdate = true; - $this->updateWhere = $where; - } - } - - // 自动关联写入 - if (!empty($this->relationWrite)) { - $relation = []; - foreach ($this->relationWrite as $key => $name) { - if (is_array($name)) { - if (key($name) === 0) { - $relation[$key] = []; - foreach ($name as $val) { - if (isset($this->data[$val])) { - $relation[$key][$val] = $this->data[$val]; - unset($this->data[$val]); - } - } - } else { - $relation[$key] = $name; - } - } elseif (isset($this->relation[$name])) { - $relation[$name] = $this->relation[$name]; - } elseif (isset($this->data[$name])) { - $relation[$name] = $this->data[$name]; - unset($this->data[$name]); - } - } - } - - // 数据自动完成 - $this->autoCompleteData($this->auto); - - // 事件回调 - if (false === $this->trigger('before_write', $this)) { - return false; - } - $pk = $this->getPk(); - if ($this->isUpdate) { - // 自动更新 - $this->autoCompleteData($this->update); - - // 事件回调 - if (false === $this->trigger('before_update', $this)) { - return false; - } - - // 获取有更新的数据 - $data = $this->getChangedData(); - - if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - return 0; - } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { - // 自动写入更新时间 - $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - $this->data[$this->updateTime] = $data[$this->updateTime]; - } - - if (empty($where) && !empty($this->updateWhere)) { - $where = $this->updateWhere; - } - - // 保留主键数据 - foreach ($this->data as $key => $val) { - if ($this->isPk($key)) { - $data[$key] = $val; - } - } - - $array = []; - - foreach ((array) $pk as $key) { - if (isset($data[$key])) { - $array[$key] = $data[$key]; - unset($data[$key]); - } - } - - if (!empty($array)) { - $where = $array; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); - - // 模型更新 - if (!empty($allowFields)) { - $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); - } else { - $result = $this->getQuery()->where($where)->update($data); - } - - // 关联更新 - if (isset($relation)) { - $this->autoRelationUpdate($relation); - } - - // 更新回调 - $this->trigger('after_update', $this); - - } else { - // 自动写入 - $this->autoCompleteData($this->insert); - - // 自动写入创建时间和更新时间 - if ($this->autoWriteTimestamp) { - if ($this->createTime && !isset($this->data[$this->createTime])) { - $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); - } - if ($this->updateTime && !isset($this->data[$this->updateTime])) { - $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); - } - } - - if (false === $this->trigger('before_insert', $this)) { - return false; - } - - // 检测字段 - $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); - if (!empty($allowFields)) { - $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); - } else { - $result = $this->getQuery()->insert($this->data, false, false, $sequence); - } - - // 获取自动增长主键 - if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { - foreach ((array) $pk as $key) { - if (!isset($this->data[$key]) || '' == $this->data[$key]) { - $this->data[$key] = $insertId; - } - } - } - - // 关联写入 - if (isset($relation)) { - foreach ($relation as $name => $val) { - $method = Loader::parseName($name, 1, false); - $this->$method()->save($val); - } - } - - // 标记为更新 - $this->isUpdate = true; - - // 新增回调 - $this->trigger('after_insert', $this); - } - // 写入回调 - $this->trigger('after_write', $this); - - // 重新记录原始数据 - $this->origin = $this->data; - - return $result; - } - - protected function checkAllowField($auto = []) - { - if (true === $this->field) { - $this->field = $this->getQuery()->getTableInfo('', 'fields'); - $field = $this->field; - } elseif (!empty($this->field)) { - $field = array_merge($this->field, $auto); - if ($this->autoWriteTimestamp) { - array_push($field, $this->createTime, $this->updateTime); - } - } elseif (!empty($this->except)) { - $fields = $this->getQuery()->getTableInfo('', 'fields'); - $field = array_diff($fields, (array) $this->except); - $this->field = $field; - } else { - $field = []; - } - - if ($this->disuse) { - // 废弃字段 - $field = array_diff($field, (array) $this->disuse); - } - return $field; - } - - protected function autoRelationUpdate($relation) - { - foreach ($relation as $name => $val) { - if ($val instanceof Model) { - $val->save(); - } else { - unset($this->data[$name]); - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->save($val); - } - } - } - } - - /** - * 获取变化的数据 并排除只读数据 - * @access public - * @return array - */ - public function getChangedData() - { - if ($this->force) { - $data = $this->data; - } else { - $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { - if ((empty($a) || empty($b)) && $a !== $b) { - return 1; - } - return is_object($a) || $a != $b ? 1 : 0; - }); - } - - if (!empty($this->readonly)) { - // 只读字段不允许更新 - foreach ($this->readonly as $key => $field) { - if (isset($data[$field])) { - unset($data[$field]); - } - } - } - - return $data; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setInc($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - - $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] += $step; - } - - return $result; - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setDec($field, $step = 1, $lazyTime = 0) - { - // 更新条件 - $where = $this->getWhere(); - $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); - if (true !== $result) { - $this->data[$field] -= $step; - } - - return $result; - } - - /** - * 获取更新条件 - * @access protected - * @return mixed - */ - protected function getWhere() - { - // 删除条件 - $pk = $this->getPk(); - - if (is_string($pk) && isset($this->data[$pk])) { - $where = [$pk => $this->data[$pk]]; - } elseif (!empty($this->updateWhere)) { - $where = $this->updateWhere; - } else { - $where = null; - } - return $where; - } - - /** - * 保存多个数据到当前数据对象 - * @access public - * @param array $dataSet 数据 - * @param boolean $replace 是否自动识别更新和写入 - * @return array|false - * @throws \Exception - */ - public function saveAll($dataSet, $replace = true) - { - if ($this->validate) { - // 数据批量验证 - $validate = $this->validate; - foreach ($dataSet as $data) { - if (!$this->validateData($data, $validate)) { - return false; - } - } - } - - $result = []; - $db = $this->getQuery(); - $db->startTrans(); - try { - $pk = $this->getPk(); - if (is_string($pk) && $replace) { - $auto = true; - } - foreach ($dataSet as $key => $data) { - if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { - $result[$key] = self::update($data, [], $this->field); - } else { - $result[$key] = self::create($data, $this->field); - } - } - $db->commit(); - return $this->toCollection($result); - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - - /** - * 设置允许写入的字段 - * @access public - * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 - * @return $this - */ - public function allowField($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->field = $field; - return $this; - } - - /** - * 设置排除写入的字段 - * @access public - * @param string|array $field 排除允许写入的字段 - * @return $this - */ - public function except($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->except = $field; - return $this; - } - - /** - * 设置只读字段 - * @access public - * @param mixed $field 只读字段 - * @return $this - */ - public function readonly($field) - { - if (is_string($field)) { - $field = explode(',', $field); - } - $this->readonly = $field; - return $this; - } - - /** - * 是否为更新数据 - * @access public - * @param bool $update - * @param mixed $where - * @return $this - */ - public function isUpdate($update = true, $where = null) - { - $this->isUpdate = $update; - if (!empty($where)) { - $this->updateWhere = $where; - } - return $this; - } - - /** - * 数据自动完成 - * @access public - * @param array $auto 要自动更新的字段列表 - * @return void - */ - protected function autoCompleteData($auto = []) - { - foreach ($auto as $field => $value) { - if (is_integer($field)) { - $field = $value; - $value = null; - } - - if (!isset($this->data[$field])) { - $default = null; - } else { - $default = $this->data[$field]; - } - - $this->setAttr($field, !is_null($value) ? $value : $default); - } - } - - /** - * 删除当前的记录 - * @access public - * @return integer - */ - public function delete() - { - if (false === $this->trigger('before_delete', $this)) { - return false; - } - - // 删除条件 - $where = $this->getWhere(); - - // 删除当前模型数据 - $result = $this->getQuery()->where($where)->delete(); - - // 关联删除 - if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $key => $name) { - $name = is_numeric($key) ? $name : $key; - $model = $this->getAttr($name); - if ($model instanceof Model) { - $model->delete(); - } - } - } - - $this->trigger('after_delete', $this); - // 清空原始数据 - $this->origin = []; - - return $result; - } - - /** - * 设置自动完成的字段( 规则通过修改器定义) - * @access public - * @param array $fields 需要自动完成的字段 - * @return $this - */ - public function auto($fields) - { - $this->auto = $fields; - return $this; - } - - /** - * 设置字段验证 - * @access public - * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 - * @param array $msg 提示信息 - * @param bool $batch 批量验证 - * @return $this - */ - public function validate($rule = true, $msg = [], $batch = false) - { - if (is_array($rule)) { - $this->validate = [ - 'rule' => $rule, - 'msg' => $msg, - ]; - } else { - $this->validate = true === $rule ? $this->name : $rule; - } - $this->batchValidate = $batch; - return $this; - } - - /** - * 设置验证失败后是否抛出异常 - * @access public - * @param bool $fail 是否抛出异常 - * @return $this - */ - public function validateFailException($fail = true) - { - $this->failException = $fail; - return $this; - } - - /** - * 自动验证数据 - * @access protected - * @param array $data 验证数据 - * @param mixed $rule 验证规则 - * @param bool $batch 批量验证 - * @return bool - */ - protected function validateData($data, $rule = null, $batch = null) - { - $info = is_null($rule) ? $this->validate : $rule; - - if (!empty($info)) { - if (is_array($info)) { - $validate = Loader::validate(); - $validate->rule($info['rule']); - $validate->message($info['msg']); - } else { - $name = is_string($info) ? $info : $this->name; - if (strpos($name, '.')) { - list($name, $scene) = explode('.', $name); - } - $validate = Loader::validate($name); - if (!empty($scene)) { - $validate->scene($scene); - } - } - $batch = is_null($batch) ? $this->batchValidate : $batch; - - if (!$validate->batch($batch)->check($data)) { - $this->error = $validate->getError(); - if ($this->failException) { - throw new ValidateException($this->error); - } else { - return false; - } - } - $this->validate = null; - } - return true; - } - - /** - * 返回模型的错误信息 - * @access public - * @return string|array - */ - public function getError() - { - return $this->error; - } - - /** - * 注册回调方法 - * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @param bool $override 是否覆盖 - * @return void - */ - public static function event($event, $callback, $override = false) - { - $class = get_called_class(); - if ($override) { - self::$event[$class][$event] = []; - } - self::$event[$class][$event][] = $callback; - } - - /** - * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 传入参数(引用) - * @return bool - */ - protected function trigger($event, &$params) - { - if (isset(self::$event[$this->class][$event])) { - foreach (self::$event[$this->class][$event] as $callback) { - if (is_callable($callback)) { - $result = call_user_func_array($callback, [ & $params]); - if (false === $result) { - return false; - } - } - } - } - return true; - } - - /** - * 写入数据 - * @access public - * @param array $data 数据数组 - * @param array|true $field 允许字段 - * @return $this - */ - public static function create($data = [], $field = null) - { - $model = new static(); - if (!empty($field)) { - $model->allowField($field); - } - $model->isUpdate(false)->save($data, []); - return $model; - } - - /** - * 更新数据 - * @access public - * @param array $data 数据数组 - * @param array $where 更新条件 - * @param array|true $field 允许字段 - * @return $this - */ - public static function update($data = [], $where = [], $field = null) - { - $model = new static(); - if (!empty($field)) { - $model->allowField($field); - } - $result = $model->isUpdate(true)->save($data, $where); - return $model; - } - - /** - * 查找单条记录 - * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return static|null - * @throws exception\DbException - */ - public static function get($data, $with = [], $cache = false) - { - if (is_null($data)) { - return; - } - - if (true === $with || is_int($with)) { - $cache = $with; - $with = []; - } - $query = static::parseQuery($data, $with, $cache); - return $query->find($data); - } - - /** - * 查找所有记录 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return static[]|false - * @throws exception\DbException - */ - public static function all($data = null, $with = [], $cache = false) - { - if (true === $with || is_int($with)) { - $cache = $with; - $with = []; - } - $query = static::parseQuery($data, $with, $cache); - return $query->select($data); - } - - /** - * 分析查询表达式 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param string $with 关联预查询 - * @param bool $cache 是否缓存 - * @return Query - */ - protected static function parseQuery(&$data, $with, $cache) - { - $result = self::with($with)->cache($cache); - if (is_array($data) && key($data) !== 0) { - $result = $result->where($data); - $data = null; - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $result]); - $data = null; - } elseif ($data instanceof Query) { - $result = $data->with($with)->cache($cache); - $data = null; - } - return $result; - } - - /** - * 删除记录 - * @access public - * @param mixed $data 主键列表 支持闭包查询条件 - * @return integer 成功删除的记录数 - */ - public static function destroy($data) - { - $model = new static(); - $query = $model->db(); - if (empty($data) && 0 !== $data) { - return 0; - } elseif (is_array($data) && key($data) !== 0) { - $query->where($data); - $data = null; - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $query]); - $data = null; - } - $resultSet = $query->select($data); - $count = 0; - if ($resultSet) { - foreach ($resultSet as $data) { - $result = $data->delete(); - $count += $result; - } - } - return $count; - } - - /** - * 命名范围 - * @access public - * @param string|array|\Closure $name 命名范围名称 逗号分隔 - * @internal mixed ...$params 参数调用 - * @return Query - */ - public static function scope($name) - { - $model = new static(); - $query = $model->db(); - $params = func_get_args(); - array_shift($params); - array_unshift($params, $query); - if ($name instanceof \Closure) { - call_user_func_array($name, $params); - } elseif (is_string($name)) { - $name = explode(',', $name); - } - if (is_array($name)) { - foreach ($name as $scope) { - $method = 'scope' . trim($scope); - if (method_exists($model, $method)) { - call_user_func_array([$model, $method], $params); - } - } - } - return $query; - } - - /** - * 设置是否使用全局查询范围 - * @param bool $use 是否启用全局查询范围 - * @access public - * @return Query - */ - public static function useGlobalScope($use) - { - $model = new static(); - return $model->db($use); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Relation|Query - */ - public static function has($relation, $operator = '>=', $count = 1, $id = '*') - { - $relation = (new static())->$relation(); - if (is_array($operator) || $operator instanceof \Closure) { - return $relation->hasWhere($operator); - } - return $relation->has($operator, $count, $id); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $relation 关联方法名 - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Relation|Query - */ - public static function hasWhere($relation, $where = [], $fields = null) - { - return (new static())->$relation()->hasWhere($where, $fields); - } - - /** - * 解析模型的完整命名空间 - * @access public - * @param string $model 模型名(或者完整类名) - * @return string - */ - protected function parseModel($model) - { - if (false === strpos($model, '\\')) { - $path = explode('\\', get_called_class()); - array_pop($path); - array_push($path, Loader::parseName($model, 1)); - $model = implode('\\', $path); - } - return $model; - } - - /** - * 查询当前模型的关联数据 - * @access public - * @param string|array $relations 关联名 - * @return $this - */ - public function relationQuery($relations) - { - if (is_string($relations)) { - $relations = explode(',', $relations); - } - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = null; - if ($relation instanceof \Closure) { - // 支持闭包查询过滤关联条件 - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $method = Loader::parseName($relation, 1, false); - $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); - } - return $this; - } - - /** - * 预载入关联查询 返回数据集 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 关联名 - * @return array - */ - public function eagerlyResultSet(&$resultSet, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); - } - } - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 关联名 - * @return Model - */ - public function eagerlyResult(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - if (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (strpos($relation, '.')) { - list($relation, $subRelation) = explode('.', $relation, 2); - } - $relation = Loader::parseName($relation, 1, false); - $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param string|array $relation 关联名 - * @return void - */ - public function relationCount(&$result, $relation) - { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - - foreach ($relations as $key => $relation) { - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } elseif (is_string($key)) { - $name = $relation; - $relation = $key; - } - $relation = Loader::parseName($relation, 1, false); - $count = $this->$relation()->relationCount($result, $closure); - if (!isset($name)) { - $name = Loader::parseName($relation) . '_count'; - } - $result->setAttr($name, $count); - } - } - - /** - * 获取模型的默认外键名 - * @access public - * @param string $name 模型名 - * @return string - */ - protected function getForeignKey($name) - { - if (strpos($name, '\\')) { - $name = basename(str_replace('\\', '/', $name)); - } - return Loader::parseName($name) . '_id'; - } - - /** - * HAS ONE 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return HasOne - */ - public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasOne($this, $model, $foreignKey, $localKey, $joinType); - } - - /** - * BELONGS TO 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param array $alias 别名定义(已经废弃) - * @param string $joinType JOIN类型 - * @return BelongsTo - */ - public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $foreignKey = $foreignKey ?: $this->getForeignKey($model); - $localKey = $localKey ?: (new $model)->getPk(); - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); - } - - /** - * HAS MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasMany - */ - public function hasMany($model, $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - return new HasMany($this, $model, $foreignKey, $localKey); - } - - /** - * HAS MANY 远程关联定义 - * @access public - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 当前模型主键 - * @return HasManyThrough - */ - public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $through = $this->parseModel($through); - $localKey = $localKey ?: $this->getPk(); - $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); - $throughKey = $throughKey ?: $this->getForeignKey($through); - return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); - } - - /** - * BELONGS TO MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型关联键 - * @return BelongsToMany - */ - public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - $name = Loader::parseName(basename(str_replace('\\', '/', $model))); - $table = $table ?: Loader::parseName($this->name) . '_' . $name; - $foreignKey = $foreignKey ?: $name . '_id'; - $localKey = $localKey ?: $this->getForeignKey($this->name); - return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); - } - - /** - * MORPH MANY 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphMany - */ - public function morphMany($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphMany($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH One 关联定义 - * @access public - * @param string $model 模型名 - * @param string|array $morph 多态字段信息 - * @param string $type 多态类型 - * @return MorphOne - */ - public function morphOne($model, $morph = null, $type = '') - { - // 记录当前关联信息 - $model = $this->parseModel($model); - if (is_null($morph)) { - $trace = debug_backtrace(false, 2); - $morph = Loader::parseName($trace[1]['function']); - } - $type = $type ?: get_class($this); - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphOne($this, $model, $foreignKey, $morphType, $type); - } - - /** - * MORPH TO 关联定义 - * @access public - * @param string|array $morph 多态字段信息 - * @param array $alias 多态别名定义 - * @return MorphTo - */ - public function morphTo($morph = null, $alias = []) - { - $trace = debug_backtrace(false, 2); - $relation = Loader::parseName($trace[1]['function']); - - if (is_null($morph)) { - $morph = $relation; - } - // 记录当前关联信息 - if (is_array($morph)) { - list($morphType, $foreignKey) = $morph; - } else { - $morphType = $morph . '_type'; - $foreignKey = $morph . '_id'; - } - return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); - } - - public function __call($method, $args) - { - $query = $this->db(true, false); - if (method_exists($this, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - call_user_func_array([$this, $method], $args); - return $this; - } else { - return call_user_func_array([$query, $method], $args); - } - } - - public static function __callStatic($method, $args) - { - $model = new static(); - $query = $model->db(); - if (method_exists($model, 'scope' . $method)) { - // 动态调用命名范围 - $method = 'scope' . $method; - array_unshift($args, $query); - - call_user_func_array([$model, $method], $args); - return $query; - } else { - return call_user_func_array([$query, $method], $args); - } - } - - /** - * 修改器 设置数据对象的值 - * @access public - * @param string $name 名称 - * @param mixed $value 值 - * @return void - */ - public function __set($name, $value) - { - $this->setAttr($name, $value); - } - - /** - * 获取器 获取数据对象的值 - * @access public - * @param string $name 名称 - * @return mixed - */ - public function __get($name) - { - return $this->getAttr($name); - } - - /** - * 检测数据对象的值 - * @access public - * @param string $name 名称 - * @return boolean - */ - public function __isset($name) - { - try { - if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { - return true; - } else { - $this->getAttr($name); - return true; - } - } catch (InvalidArgumentException $e) { - return false; - } - - } - - /** - * 销毁数据对象的值 - * @access public - * @param string $name 名称 - * @return void - */ - public function __unset($name) - { - unset($this->data[$name], $this->relation[$name]); - } - - public function __toString() - { - return $this->toJson(); - } - - // JsonSerializable - public function jsonSerialize() - { - return $this->toArray(); - } - - // ArrayAccess - public function offsetSet($name, $value) - { - $this->setAttr($name, $value); - } - - public function offsetExists($name) - { - return $this->__isset($name); - } - - public function offsetUnset($name) - { - $this->__unset($name); - } - - public function offsetGet($name) - { - return $this->getAttr($name); - } - - /** - * 解序列化后处理 - */ - public function __wakeup() - { - $this->initialize(); - } - - /** - * 模型事件快捷方法 - * @param $callback - * @param bool $override - */ - protected static function beforeInsert($callback, $override = false) - { - self::event('before_insert', $callback, $override); - } - - protected static function afterInsert($callback, $override = false) - { - self::event('after_insert', $callback, $override); - } - - protected static function beforeUpdate($callback, $override = false) - { - self::event('before_update', $callback, $override); - } - - protected static function afterUpdate($callback, $override = false) - { - self::event('after_update', $callback, $override); - } - - protected static function beforeWrite($callback, $override = false) - { - self::event('before_write', $callback, $override); - } - - protected static function afterWrite($callback, $override = false) - { - self::event('after_write', $callback, $override); - } - - protected static function beforeDelete($callback, $override = false) - { - self::event('before_delete', $callback, $override); - } - - protected static function afterDelete($callback, $override = false) - { - self::event('after_delete', $callback, $override); - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use BadMethodCallException; +use InvalidArgumentException; +use think\db\Query; +use think\exception\ValidateException; +use think\model\Collection as ModelCollection; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * Class Model + * @package think + * @mixin Query + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + // 数据库查询对象池 + protected static $links = []; + // 数据库配置 + protected $connection = []; + // 父关联模型对象 + protected $parent; + // 数据库查询对象 + protected $query; + // 当前模型名称 + protected $name; + // 数据表名称 + protected $table; + // 当前类名称 + protected $class; + // 回调事件 + private static $event = []; + // 错误信息 + protected $error; + // 字段验证规则 + protected $validate; + // 数据表主键 复合主键使用数组定义 不设置则自动获取 + protected $pk; + // 数据表字段信息 留空则自动获取 + protected $field = []; + // 数据排除字段 + protected $except = []; + // 数据废弃字段 + protected $disuse = []; + // 只读字段 + protected $readonly = []; + // 显示属性 + protected $visible = []; + // 隐藏属性 + protected $hidden = []; + // 追加属性 + protected $append = []; + // 数据信息 + protected $data = []; + // 原始数据 + protected $origin = []; + // 关联模型 + protected $relation = []; + + // 保存自动完成列表 + protected $auto = []; + // 新增自动完成列表 + protected $insert = []; + // 更新自动完成列表 + protected $update = []; + // 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + protected $autoWriteTimestamp; + // 创建时间字段 + protected $createTime = 'create_time'; + // 更新时间字段 + protected $updateTime = 'update_time'; + // 时间字段取出后的默认时间格式 + protected $dateFormat; + // 字段类型或者格式转换 + protected $type = []; + // 是否为更新数据 + protected $isUpdate = false; + // 是否强制更新所有数据 + protected $force = false; + // 更新条件 + protected $updateWhere; + // 验证失败是否抛出异常 + protected $failException = false; + // 全局查询范围 + protected $useGlobalScope = true; + // 是否采用批量验证 + protected $batchValidate = false; + // 查询数据集对象 + protected $resultSetType; + // 关联自动写入 + protected $relationWrite; + + /** + * 初始化过的模型. + * + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 构造方法 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + // 当前类名 + $this->class = get_called_class(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', $this->class); + $this->name = basename($name); + if (Config::get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $this->getQuery()->getConfig('auto_timestamp'); + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $this->getQuery()->getConfig('datetime_format'); + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $this->getQuery()->getConfig('resultset_type'); + } + // 执行初始化操作 + $this->initialize(); + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型生效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : $this->class; + + static::$readMaster[$model] = true; + return $this; + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 合并数据库配置 + if (!empty($this->connection)) { + if (is_array($this->connection)) { + $connection = array_merge(Config::get('database'), $this->connection); + } else { + $connection = $this->connection; + } + } else { + $connection = []; + } + + $con = Db::connect($connection); + // 设置当前模型 确保查询返回模型对象 + $queryClass = $this->query ?: $con->getConfig('query'); + $query = new $queryClass($con, $this); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[$this->class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->setTable($this->table); + } else { + $query->name($this->name); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 获取当前模型的查询对象 + * @access public + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function getQuery($buildNewQuery = false) + { + if ($buildNewQuery) { + return $this->buildQuery(); + } elseif (!isset(self::$links[$this->class])) { + // 创建模型查询对象 + self::$links[$this->class] = $this->buildQuery(); + } + + return self::$links[$this->class]; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool $useBaseQuery 是否调用全局查询范围 + * @param bool $buildNewQuery 创建新的查询对象 + * @return Query + */ + public function db($useBaseQuery = true, $buildNewQuery = true) + { + $query = $this->getQuery($buildNewQuery); + + // 全局作用域 + if ($useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + $class = get_class($this); + if (!isset(static::$initialized[$class])) { + static::$initialized[$class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + } else { + // 清空数据 + $this->data = []; + if (is_object($data)) { + $data = get_object_vars($data); + } + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + $this->data = $data; + } + } + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + return $this; + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setAttr($name, $value, $data = []) + { + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data), $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + return $this; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } else { + return; + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @return $this + */ + public function setRelation($name, $value) + { + $this->relation[$name] = $value; + return $this; + } + + /** + * 自动写入时间戳 + * @access public + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(time(), $format); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(time(), $this->dateFormat); + } else { + $value = $this->formatDateTime(time(), $this->dateFormat, true); + } + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access public + * @param mixed $time 时间日期表达式 + * @param mixed $format 日期格式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($time, $format, $timestamp = false) + { + if (false !== strpos($format, '\\')) { + $time = new $format($time); + } elseif (!$timestamp && false !== $format) { + $time = date($format, $time); + } + return $time; + } + + /** + * 数据写入 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($value, $format); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + + } + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + if (method_exists($this, $method)) { + $value = $this->$method($value, $this->data, $this->relation); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif (in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ]) + ) { + $value = $this->formatDateTime(strtotime($value), $this->dateFormat); + } else { + $value = $this->formatDateTime($value, $this->dateFormat); + } + } elseif ($notFound) { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + // 不存在该字段 获取关联数据 + $value = $this->getRelationData($modelRelation); + // 保存关联对象值 + $this->relation[$name] = $value; + } else { + throw new InvalidArgumentException('property not exists:' . $this->class . '->' . $name); + } + } + return $value; + } + + /** + * 获取关联模型数据 + * @access public + * @param Relation $modelRelation 模型关联对象 + * @return mixed + * @throws BadMethodCallException + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($modelRelation->getModel()) == get_class($this->parent)) { + $value = $this->parent; + } else { + // 首先获取关联数据 + if (method_exists($modelRelation, 'getRelation')) { + $value = $modelRelation->getRelation(); + } else { + throw new BadMethodCallException('method not exists:' . get_class($modelRelation) . '-> getRelation'); + } + } + return $value; + } + + /** + * 数据读取 类型转换 + * @access public + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($value, $format); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime(strtotime($value), $format); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + return $value; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $relation 关联方法 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($relation, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($relation, 1, false); + + // 获取关联数据 + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->data[$key] = $model->getAttr($attr); + } + } + } + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + return $this; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + if ($visible) { + $array[] = $key; + } + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + return $array; + } + + /** + * 转换子模型对象 + * @access protected + * @param Model|ModelCollection $model + * @param $visible + * @param $hidden + * @param $key + * @return array + */ + protected function subToArray($model, $visible, $hidden, $key) + { + // 关联模型对象 + if (isset($visible[$key])) { + $model->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $model->hidden($hidden[$key]); + } + return $model->toArray(); + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + $item[$key] = $this->subToArray($val, $visible, $hidden, $key); + } elseif (is_array($val) && reset($val) instanceof Model) { + // 关联模型数据集 + $arr = []; + foreach ($val as $k => $value) { + $arr[$k] = $this->subToArray($value, $visible, $hidden, $key); + } + $item[$key] = $arr; + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getAttr($key); + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $relation = Loader::parseName($name, 1, false); + if (method_exists($this, $relation)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + + if (method_exists($modelRelation, 'getBindAttr')) { + $bindAttr = $modelRelation->getBindAttr(); + if ($bindAttr) { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + continue; + } + } + $item[$name] = $value; + } else { + $item[$name] = $this->getAttr($name); + } + } + } + } + return !empty($item) ? $item : []; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + /** + * 转换当前模型数据集为数据集对象 + * @access public + * @param array|\think\Collection $collection 数据集 + * @return \think\Collection + */ + public function toCollection($collection) + { + if ($this->resultSetType) { + if ('collection' == $this->resultSetType) { + $collection = new ModelCollection($collection); + } elseif (false !== strpos($this->resultSetType, '\\')) { + $class = $this->resultSetType; + $collection = new $class($collection); + } + } + return $collection; + } + + /** + * 关联数据一起更新 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + $this->relationWrite = $relation; + return $this; + } + + /** + * 获取模型对象的主键 + * @access public + * @param string $name 模型名 + * @return mixed + */ + public function getPk($name = '') + { + if (!empty($name)) { + $table = $this->getQuery()->getTable($name); + return $this->getQuery()->getPk($table); + } elseif (empty($this->pk)) { + $this->pk = $this->getQuery()->getPk(); + } + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + return false; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return integer|false + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + $this->updateWhere = $where; + } + } + + // 自动关联写入 + if (!empty($this->relationWrite)) { + $relation = []; + foreach ($this->relationWrite as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $relation[$key] = []; + foreach ($name as $val) { + if (isset($this->data[$val])) { + $relation[$key][$val] = $this->data[$val]; + unset($this->data[$val]); + } + } + } else { + $relation[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $relation[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $relation[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + $pk = $this->getPk(); + if ($this->isUpdate) { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update', $this)) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data) || (count($data) == 1 && is_string($pk) && isset($data[$pk]))) { + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + return 0; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[$key] = $data[$key]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->update)); + + // 模型更新 + if (!empty($allowFields)) { + $result = $this->getQuery()->where($where)->strict(false)->field($allowFields)->update($data); + } else { + $result = $this->getQuery()->where($where)->update($data); + } + + // 关联更新 + if (isset($relation)) { + $this->autoRelationUpdate($relation); + } + + // 更新回调 + $this->trigger('after_update', $this); + + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 检测字段 + $allowFields = $this->checkAllowField(array_merge($this->auto, $this->insert)); + if (!empty($allowFields)) { + $result = $this->getQuery()->strict(false)->field($allowFields)->insert($this->data, false, false, $sequence); + } else { + $result = $this->getQuery()->insert($this->data, false, false, $sequence); + } + + // 获取自动增长主键 + if ($result && $insertId = $this->getQuery()->getLastInsID($sequence)) { + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (isset($relation)) { + foreach ($relation as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + // 标记为更新 + $this->isUpdate = true; + + // 新增回调 + $this->trigger('after_insert', $this); + } + // 写入回调 + $this->trigger('after_write', $this); + + // 重新记录原始数据 + $this->origin = $this->data; + + return $result; + } + + protected function checkAllowField($auto = []) + { + if (true === $this->field) { + $this->field = $this->getQuery()->getTableInfo('', 'fields'); + $field = $this->field; + } elseif (!empty($this->field)) { + $field = array_merge($this->field, $auto); + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } elseif (!empty($this->except)) { + $fields = $this->getQuery()->getTableInfo('', 'fields'); + $field = array_diff($fields, (array) $this->except); + $this->field = $field; + } else { + $field = []; + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + return $field; + } + + protected function autoRelationUpdate($relation) + { + foreach ($relation as $name => $val) { + if ($val instanceof Model) { + $val->save(); + } else { + unset($this->data[$name]); + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->save($val); + } + } + } + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + return is_object($a) || $a != $b ? 1 : 0; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + + $result = $this->getQuery()->where($where)->setInc($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] += $step; + } + + return $result; + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 更新条件 + $where = $this->getWhere(); + $result = $this->getQuery()->where($where)->setDec($field, $step, $lazyTime); + if (true !== $result) { + $this->data[$field] -= $step; + } + + return $result; + } + + /** + * 获取更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where = [$pk => $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + if ($this->validate) { + // 数据批量验证 + $validate = $this->validate; + foreach ($dataSet as $data) { + if (!$this->validateData($data, $validate)) { + return false; + } + } + } + + $result = []; + $db = $this->getQuery(); + $db->startTrans(); + try { + $pk = $this->getPk(); + if (is_string($pk) && $replace) { + $auto = true; + } + foreach ($dataSet as $key => $data) { + if ($this->isUpdate || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field); + } + } + $db->commit(); + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置允许写入的字段 + * @access public + * @param string|array $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->field = $field; + return $this; + } + + /** + * 设置排除写入的字段 + * @access public + * @param string|array $field 排除允许写入的字段 + * @return $this + */ + public function except($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->except = $field; + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param mixed $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + $this->readonly = $field; + return $this; + } + + /** + * 是否为更新数据 + * @access public + * @param bool $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + $this->isUpdate = $update; + if (!empty($where)) { + $this->updateWhere = $where; + } + return $this; + } + + /** + * 数据自动完成 + * @access public + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 删除当前的记录 + * @access public + * @return integer + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + // 删除条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->getQuery()->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $model = $this->getAttr($name); + if ($model instanceof Model) { + $model->delete(); + } + } + } + + $this->trigger('after_delete', $this); + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + return $this; + } + + /** + * 设置字段验证 + * @access public + * @param array|string|bool $rule 验证规则 true表示自动读取验证器类 + * @param array $msg 提示信息 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($rule = true, $msg = [], $batch = false) + { + if (is_array($rule)) { + $this->validate = [ + 'rule' => $rule, + 'msg' => $msg, + ]; + } else { + $this->validate = true === $rule ? $this->name : $rule; + } + $this->batchValidate = $batch; + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access public + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function validateFailException($fail = true) + { + $this->failException = $fail; + return $this; + } + + /** + * 自动验证数据 + * @access protected + * @param array $data 验证数据 + * @param mixed $rule 验证规则 + * @param bool $batch 批量验证 + * @return bool + */ + protected function validateData($data, $rule = null, $batch = null) + { + $info = is_null($rule) ? $this->validate : $rule; + + if (!empty($info)) { + if (is_array($info)) { + $validate = Loader::validate(); + $validate->rule($info['rule']); + $validate->message($info['msg']); + } else { + $name = is_string($info) ? $info : $this->name; + if (strpos($name, '.')) { + list($name, $scene) = explode('.', $name); + } + $validate = Loader::validate($name); + if (!empty($scene)) { + $validate->scene($scene); + } + } + $batch = is_null($batch) ? $this->batchValidate : $batch; + + if (!$validate->batch($batch)->check($data)) { + $this->error = $validate->getError(); + if ($this->failException) { + throw new ValidateException($this->error); + } else { + return false; + } + } + $this->validate = null; + } + return true; + } + + /** + * 返回模型的错误信息 + * @access public + * @return string|array + */ + public function getError() + { + return $this->error; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = get_called_class(); + if ($override) { + self::$event[$class][$event] = []; + } + self::$event[$class][$event][] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 传入参数(引用) + * @return bool + */ + protected function trigger($event, &$params) + { + if (isset(self::$event[$this->class][$event])) { + foreach (self::$event[$this->class][$event] as $callback) { + if (is_callable($callback)) { + $result = call_user_func_array($callback, [ & $params]); + if (false === $result) { + return false; + } + } + } + } + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @return $this + */ + public static function create($data = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $model->isUpdate(false)->save($data, []); + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return $this + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + if (!empty($field)) { + $model->allowField($field); + } + $result = $model->isUpdate(true)->save($data, $where); + return $model; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public static function get($data, $with = [], $cache = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->find($data); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public static function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + $query = static::parseQuery($data, $with, $cache); + return $query->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected static function parseQuery(&$data, $with, $cache) + { + $result = self::with($with)->cache($cache); + if (is_array($data) && key($data) !== 0) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $result]); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return integer 成功删除的记录数 + */ + public static function destroy($data) + { + $model = new static(); + $query = $model->db(); + if (empty($data) && 0 !== $data) { + return 0; + } elseif (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + $resultSet = $query->select($data); + $count = 0; + if ($resultSet) { + foreach ($resultSet as $data) { + $result = $data->delete(); + $count += $result; + } + } + return $count; + } + + /** + * 命名范围 + * @access public + * @param string|array|\Closure $name 命名范围名称 逗号分隔 + * @internal mixed ...$params 参数调用 + * @return Query + */ + public static function scope($name) + { + $model = new static(); + $query = $model->db(); + $params = func_get_args(); + array_shift($params); + array_unshift($params, $query); + if ($name instanceof \Closure) { + call_user_func_array($name, $params); + } elseif (is_string($name)) { + $name = explode(',', $name); + } + if (is_array($name)) { + foreach ($name as $scope) { + $method = 'scope' . trim($scope); + if (method_exists($model, $method)) { + call_user_func_array([$model, $method], $params); + } + } + } + return $query; + } + + /** + * 设置是否使用全局查询范围 + * @param bool $use 是否启用全局查询范围 + * @access public + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + return $model->db($use); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Relation|Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*') + { + $relation = (new static())->$relation(); + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + return $relation->has($operator, $count, $id); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Relation|Query + */ + public static function hasWhere($relation, $where = [], $fields = null) + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 解析模型的完整命名空间 + * @access public + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', get_called_class()); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @return $this + */ + public function relationQuery($relations) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $method = Loader::parseName($relation, 1, false); + $this->data[$relation] = $this->$method()->getRelation($subRelation, $closure); + } + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResultSet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @return Model + */ + public function eagerlyResult(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + $relation = Loader::parseName($relation, 1, false); + $this->$relation()->eagerlyResult($result, $relation, $subRelation, $closure); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param string|array $relation 关联名 + * @return void + */ + public function relationCount(&$result, $relation) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = $this->$relation()->relationCount($result, $closure); + if (!isset($name)) { + $name = Loader::parseName($relation) . '_count'; + } + $result->setAttr($name, $count); + } + } + + /** + * 获取模型的默认外键名 + * @access public + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + return Loader::parseName($name) . '_id'; + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasOne($this, $model, $foreignKey, $localKey, $joinType); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param array $alias 别名定义(已经废弃) + * @param string $joinType JOIN类型 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '', $alias = [], $joinType = 'INNER') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($model); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + return new BelongsTo($this, $model, $foreignKey, $localKey, $joinType, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前模型主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey($through); + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + $type = $type ?: get_class($this); + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + public function __call($method, $args) + { + $query = $this->db(true, false); + if (method_exists($this, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + call_user_func_array([$this, $method], $args); + return $this; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + public static function __callStatic($method, $args) + { + $model = new static(); + $query = $model->db(); + if (method_exists($model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $query); + + call_user_func_array([$model, $method], $args); + return $query; + } else { + return call_user_func_array([$query, $method], $args); + } + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + if (array_key_exists($name, $this->data) || array_key_exists($name, $this->relation)) { + return true; + } else { + $this->getAttr($name); + return true; + } + } catch (InvalidArgumentException $e) { + return false; + } + + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 模型事件快捷方法 + * @param $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + +} diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php old mode 100755 new mode 100644 index 47b9293..3655567 --- a/thinkphp/library/think/Paginator.php +++ b/thinkphp/library/think/Paginator.php @@ -1,409 +1,409 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use ArrayAccess; -use ArrayIterator; -use Countable; -use IteratorAggregate; -use JsonSerializable; -use Traversable; - -abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable -{ - /** @var bool 是否为简洁模式 */ - protected $simple = false; - - /** @var Collection 数据集 */ - protected $items; - - /** @var integer 当前页 */ - protected $currentPage; - - /** @var integer 最后一页 */ - protected $lastPage; - - /** @var integer|null 数据总数 */ - protected $total; - - /** @var integer 每页的数量 */ - protected $listRows; - - /** @var bool 是否有下一页 */ - protected $hasMore; - - /** @var array 一些配置 */ - protected $options = [ - 'var_page' => 'page', - 'path' => '/', - 'query' => [], - 'fragment' => '', - ]; - - /** @var mixed simple模式下的下个元素 */ - protected $nextItem; - - public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) - { - $this->options = array_merge($this->options, $options); - - $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; - - $this->simple = $simple; - $this->listRows = $listRows; - - if (!$items instanceof Collection) { - $items = Collection::make($items); - } - - if ($simple) { - $this->currentPage = $this->setCurrentPage($currentPage); - $this->hasMore = count($items) > ($this->listRows); - if ($this->hasMore) { - $this->nextItem = $items->slice($this->listRows, 1); - } - $items = $items->slice(0, $this->listRows); - } else { - $this->total = $total; - $this->lastPage = (int) ceil($total / $listRows); - $this->currentPage = $this->setCurrentPage($currentPage); - $this->hasMore = $this->currentPage < $this->lastPage; - } - $this->items = $items; - } - - /** - * @param $items - * @param $listRows - * @param null $currentPage - * @param bool $simple - * @param null $total - * @param array $options - * @return Paginator - */ - public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) - { - return new static($items, $listRows, $currentPage, $total, $simple, $options); - } - - protected function setCurrentPage($currentPage) - { - if (!$this->simple && $currentPage > $this->lastPage) { - return $this->lastPage > 0 ? $this->lastPage : 1; - } - - return $currentPage; - } - - /** - * 获取页码对应的链接 - * - * @param $page - * @return string - */ - protected function url($page) - { - if ($page <= 0) { - $page = 1; - } - - if (strpos($this->options['path'], '[PAGE]') === false) { - $parameters = [$this->options['var_page'] => $page]; - $path = $this->options['path']; - } else { - $parameters = []; - $path = str_replace('[PAGE]', $page, $this->options['path']); - } - if (count($this->options['query']) > 0) { - $parameters = array_merge($this->options['query'], $parameters); - } - $url = $path; - if (!empty($parameters)) { - $url .= '?' . http_build_query($parameters, null, '&'); - } - return $url . $this->buildFragment(); - } - - /** - * 自动获取当前页码 - * @param string $varPage - * @param int $default - * @return int - */ - public static function getCurrentPage($varPage = 'page', $default = 1) - { - $page = (int) Request::instance()->param($varPage); - - if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { - return $page; - } - - return $default; - } - - /** - * 自动获取当前的path - * @return string - */ - public static function getCurrentPath() - { - return Request::instance()->baseUrl(); - } - - public function total() - { - if ($this->simple) { - throw new \DomainException('not support total'); - } - return $this->total; - } - - public function listRows() - { - return $this->listRows; - } - - public function currentPage() - { - return $this->currentPage; - } - - public function lastPage() - { - if ($this->simple) { - throw new \DomainException('not support last'); - } - return $this->lastPage; - } - - /** - * 数据是否足够分页 - * @return boolean - */ - public function hasPages() - { - return !(1 == $this->currentPage && !$this->hasMore); - } - - /** - * 创建一组分页链接 - * - * @param int $start - * @param int $end - * @return array - */ - public function getUrlRange($start, $end) - { - $urls = []; - - for ($page = $start; $page <= $end; $page++) { - $urls[$page] = $this->url($page); - } - - return $urls; - } - - /** - * 设置URL锚点 - * - * @param string|null $fragment - * @return $this - */ - public function fragment($fragment) - { - $this->options['fragment'] = $fragment; - return $this; - } - - /** - * 添加URL参数 - * - * @param array|string $key - * @param string|null $value - * @return $this - */ - public function appends($key, $value = null) - { - if (!is_array($key)) { - $queries = [$key => $value]; - } else { - $queries = $key; - } - - foreach ($queries as $k => $v) { - if ($k !== $this->options['var_page']) { - $this->options['query'][$k] = $v; - } - } - - return $this; - } - - /** - * 构造锚点字符串 - * - * @return string - */ - protected function buildFragment() - { - return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; - } - - /** - * 渲染分页html - * @return mixed - */ - abstract public function render(); - - public function items() - { - return $this->items->all(); - } - - public function getCollection() - { - return $this->items; - } - - public function isEmpty() - { - return $this->items->isEmpty(); - } - - /** - * 给每个元素执行个回调 - * - * @param callable $callback - * @return $this - */ - public function each(callable $callback) - { - foreach ($this->items as $key => $item) { - $result = $callback($item, $key); - if (false === $result) { - break; - } elseif (!is_object($item)) { - $this->items[$key] = $result; - } - } - - return $this; - } - - /** - * Retrieve an external iterator - * @return Traversable An instance of an object implementing Iterator or - * Traversable - */ - public function getIterator() - { - return new ArrayIterator($this->items->all()); - } - - /** - * Whether a offset exists - * @param mixed $offset - * @return bool - */ - public function offsetExists($offset) - { - return $this->items->offsetExists($offset); - } - - /** - * Offset to retrieve - * @param mixed $offset - * @return mixed - */ - public function offsetGet($offset) - { - return $this->items->offsetGet($offset); - } - - /** - * Offset to set - * @param mixed $offset - * @param mixed $value - */ - public function offsetSet($offset, $value) - { - $this->items->offsetSet($offset, $value); - } - - /** - * Offset to unset - * @param mixed $offset - * @return void - * @since 5.0.0 - */ - public function offsetUnset($offset) - { - $this->items->offsetUnset($offset); - } - - /** - * Count elements of an object - */ - public function count() - { - return $this->items->count(); - } - - public function __toString() - { - return (string) $this->render(); - } - - public function toArray() - { - if ($this->simple) { - return [ - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'has_more' => $this->hasMore, - 'next_item' => $this->nextItem, - 'data' => $this->items->toArray(), - ]; - } else { - return [ - 'total' => $this->total, - 'per_page' => $this->listRows, - 'current_page' => $this->currentPage, - 'last_page' => $this->lastPage, - 'data' => $this->items->toArray(), - ]; - } - - } - - /** - * Specify data which should be serialized to JSON - */ - public function jsonSerialize() - { - return $this->toArray(); - } - - public function __call($name, $arguments) - { - $collection = $this->getCollection(); - - $result = call_user_func_array([$collection, $name], $arguments); - - if ($result === $collection) { - return $this; - } - - return $result; - } - -} + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** @var bool 是否为简洁模式 */ + protected $simple = false; + + /** @var Collection 数据集 */ + protected $items; + + /** @var integer 当前页 */ + protected $currentPage; + + /** @var integer 最后一页 */ + protected $lastPage; + + /** @var integer|null 数据总数 */ + protected $total; + + /** @var integer 每页的数量 */ + protected $listRows; + + /** @var bool 是否有下一页 */ + protected $hasMore; + + /** @var array 一些配置 */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** @var mixed simple模式下的下个元素 */ + protected $nextItem; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + if ($this->hasMore) { + $this->nextItem = $items->slice($this->listRows, 1); + } + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @param $items + * @param $listRows + * @param null $currentPage + * @param bool $simple + * @param null $total + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @param $page + * @return string + */ + protected function url($page) + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, null, '&'); + } + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = (int) Request::instance()->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @return string + */ + public static function getCurrentPath() + { + return Request::instance()->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + return $this; + } + + /** + * 添加URL参数 + * + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + if ($this->simple) { + return [ + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'has_more' => $this->hasMore, + 'next_item' => $this->nextItem, + 'data' => $this->items->toArray(), + ]; + } else { + return [ + 'total' => $this->total, + 'per_page' => $this->listRows, + 'current_page' => $this->currentPage, + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php old mode 100755 new mode 100644 index 37e9c98..6f3faa3 --- a/thinkphp/library/think/Process.php +++ b/thinkphp/library/think/Process.php @@ -1,1205 +1,1205 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\process\exception\Failed as ProcessFailedException; -use think\process\exception\Timeout as ProcessTimeoutException; -use think\process\pipes\Pipes; -use think\process\pipes\Unix as UnixPipes; -use think\process\pipes\Windows as WindowsPipes; -use think\process\Utils; - -class Process -{ - - const ERR = 'err'; - const OUT = 'out'; - - const STATUS_READY = 'ready'; - const STATUS_STARTED = 'started'; - const STATUS_TERMINATED = 'terminated'; - - const STDIN = 0; - const STDOUT = 1; - const STDERR = 2; - - const TIMEOUT_PRECISION = 0.2; - - private $callback; - private $commandline; - private $cwd; - private $env; - private $input; - private $starttime; - private $lastOutputTime; - private $timeout; - private $idleTimeout; - private $options; - private $exitcode; - private $fallbackExitcode; - private $processInformation; - private $outputDisabled = false; - private $stdout; - private $stderr; - private $enhanceWindowsCompatibility = true; - private $enhanceSigchildCompatibility; - private $process; - private $status = self::STATUS_READY; - private $incrementalOutputOffset = 0; - private $incrementalErrorOutputOffset = 0; - private $tty; - private $pty; - - private $useFileHandles = false; - - /** @var Pipes */ - private $processPipes; - - private $latestSignal; - - private static $sigchild; - - /** - * @var array - */ - public static $exitCodes = [ - 0 => 'OK', - 1 => 'General error', - 2 => 'Misuse of shell builtins', - 126 => 'Invoked command cannot execute', - 127 => 'Command not found', - 128 => 'Invalid exit argument', - // signals - 129 => 'Hangup', - 130 => 'Interrupt', - 131 => 'Quit and dump core', - 132 => 'Illegal instruction', - 133 => 'Trace/breakpoint trap', - 134 => 'Process aborted', - 135 => 'Bus error: "access to undefined portion of memory object"', - 136 => 'Floating point exception: "erroneous arithmetic operation"', - 137 => 'Kill (terminate immediately)', - 138 => 'User-defined 1', - 139 => 'Segmentation violation', - 140 => 'User-defined 2', - 141 => 'Write to pipe with no one reading', - 142 => 'Signal raised by alarm', - 143 => 'Termination (request to terminate)', - // 144 - not defined - 145 => 'Child process terminated, stopped (or continued*)', - 146 => 'Continue if stopped', - 147 => 'Stop executing temporarily', - 148 => 'Terminal stop signal', - 149 => 'Background process attempting to read from tty ("in")', - 150 => 'Background process attempting to write to tty ("out")', - 151 => 'Urgent data available on socket', - 152 => 'CPU time limit exceeded', - 153 => 'File size limit exceeded', - 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', - 155 => 'Profiling timer expired', - // 156 - not defined - 157 => 'Pollable event', - // 158 - not defined - 159 => 'Bad syscall', - ]; - - /** - * 构造方法 - * @param string $commandline 指令 - * @param string|null $cwd 工作目录 - * @param array|null $env 环境变量 - * @param string|null $input 输入 - * @param int|float|null $timeout 超时时间 - * @param array $options proc_open的选项 - * @throws \RuntimeException - * @api - */ - public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) - { - if (!function_exists('proc_open')) { - throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); - } - - $this->commandline = $commandline; - $this->cwd = $cwd; - - if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { - $this->cwd = getcwd(); - } - if (null !== $env) { - $this->setEnv($env); - } - - $this->input = $input; - $this->setTimeout($timeout); - $this->useFileHandles = '\\' === DS; - $this->pty = false; - $this->enhanceWindowsCompatibility = true; - $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); - $this->options = array_replace([ - 'suppress_errors' => true, - 'binary_pipes' => true, - ], $options); - } - - public function __destruct() - { - $this->stop(); - } - - public function __clone() - { - $this->resetProcessData(); - } - - /** - * 运行指令 - * @param callback|null $callback - * @return int - */ - public function run($callback = null) - { - $this->start($callback); - - return $this->wait(); - } - - /** - * 运行指令 - * @param callable|null $callback - * @return self - * @throws \RuntimeException - * @throws ProcessFailedException - */ - public function mustRun($callback = null) - { - if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); - } - - if (0 !== $this->run($callback)) { - throw new ProcessFailedException($this); - } - - return $this; - } - - /** - * 启动进程并写到 STDIN 输入后返回。 - * @param callable|null $callback - * @throws \RuntimeException - * @throws \RuntimeException - * @throws \LogicException - */ - public function start($callback = null) - { - if ($this->isRunning()) { - throw new \RuntimeException('Process is already running'); - } - if ($this->outputDisabled && null !== $callback) { - throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); - } - - $this->resetProcessData(); - $this->starttime = $this->lastOutputTime = microtime(true); - $this->callback = $this->buildCallback($callback); - $descriptors = $this->getDescriptors(); - - $commandline = $this->commandline; - - if ('\\' === DS && $this->enhanceWindowsCompatibility) { - $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; - foreach ($this->processPipes->getFiles() as $offset => $filename) { - $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); - } - $commandline .= '"'; - - if (!isset($this->options['bypass_shell'])) { - $this->options['bypass_shell'] = true; - } - } - - $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); - - if (!is_resource($this->process)) { - throw new \RuntimeException('Unable to launch a new process.'); - } - $this->status = self::STATUS_STARTED; - - if ($this->tty) { - return; - } - - $this->updateStatus(false); - $this->checkTimeout(); - } - - /** - * 重启进程 - * @param callable|null $callback - * @return Process - * @throws \RuntimeException - * @throws \RuntimeException - */ - public function restart($callback = null) - { - if ($this->isRunning()) { - throw new \RuntimeException('Process is already running'); - } - - $process = clone $this; - $process->start($callback); - - return $process; - } - - /** - * 等待要终止的进程 - * @param callable|null $callback - * @return int - */ - public function wait($callback = null) - { - $this->requireProcessIsStarted(__FUNCTION__); - - $this->updateStatus(false); - if (null !== $callback) { - $this->callback = $this->buildCallback($callback); - } - - do { - $this->checkTimeout(); - $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); - $close = '\\' !== DS || !$running; - $this->readPipes(true, $close); - } while ($running); - - while ($this->isRunning()) { - usleep(1000); - } - - if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { - throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); - } - - return $this->exitcode; - } - - /** - * 获取PID - * @return int|null - * @throws \RuntimeException - */ - public function getPid() - { - if ($this->isSigchildEnabled()) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); - } - - $this->updateStatus(false); - - return $this->isRunning() ? $this->processInformation['pid'] : null; - } - - /** - * 将一个 POSIX 信号发送到进程中 - * @param int $signal - * @return Process - */ - public function signal($signal) - { - $this->doSignal($signal, true); - - return $this; - } - - /** - * 禁用从底层过程获取输出和错误输出。 - * @return Process - */ - public function disableOutput() - { - if ($this->isRunning()) { - throw new \RuntimeException('Disabling output while the process is running is not possible.'); - } - if (null !== $this->idleTimeout) { - throw new \LogicException('Output can not be disabled while an idle timeout is set.'); - } - - $this->outputDisabled = true; - - return $this; - } - - /** - * 开启从底层过程获取输出和错误输出。 - * @return Process - * @throws \RuntimeException - */ - public function enableOutput() - { - if ($this->isRunning()) { - throw new \RuntimeException('Enabling output while the process is running is not possible.'); - } - - $this->outputDisabled = false; - - return $this; - } - - /** - * 输出是否禁用 - * @return bool - */ - public function isOutputDisabled() - { - return $this->outputDisabled; - } - - /** - * 获取当前的输出管道 - * @return string - * @throws \LogicException - * @throws \LogicException - * @api - */ - public function getOutput() - { - if ($this->outputDisabled) { - throw new \LogicException('Output has been disabled.'); - } - - $this->requireProcessIsStarted(__FUNCTION__); - - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); - - return $this->stdout; - } - - /** - * 以增量方式返回的输出结果。 - * @return string - */ - public function getIncrementalOutput() - { - $this->requireProcessIsStarted(__FUNCTION__); - - $data = $this->getOutput(); - - $latest = substr($data, $this->incrementalOutputOffset); - - if (false === $latest) { - return ''; - } - - $this->incrementalOutputOffset = strlen($data); - - return $latest; - } - - /** - * 清空输出 - * @return Process - */ - public function clearOutput() - { - $this->stdout = ''; - $this->incrementalOutputOffset = 0; - - return $this; - } - - /** - * 返回当前的错误输出的过程 (STDERR)。 - * @return string - */ - public function getErrorOutput() - { - if ($this->outputDisabled) { - throw new \LogicException('Output has been disabled.'); - } - - $this->requireProcessIsStarted(__FUNCTION__); - - $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); - - return $this->stderr; - } - - /** - * 以增量方式返回 errorOutput - * @return string - */ - public function getIncrementalErrorOutput() - { - $this->requireProcessIsStarted(__FUNCTION__); - - $data = $this->getErrorOutput(); - - $latest = substr($data, $this->incrementalErrorOutputOffset); - - if (false === $latest) { - return ''; - } - - $this->incrementalErrorOutputOffset = strlen($data); - - return $latest; - } - - /** - * 清空 errorOutput - * @return Process - */ - public function clearErrorOutput() - { - $this->stderr = ''; - $this->incrementalErrorOutputOffset = 0; - - return $this; - } - - /** - * 获取退出码 - * @return null|int - */ - public function getExitCode() - { - if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); - } - - $this->updateStatus(false); - - return $this->exitcode; - } - - /** - * 获取退出文本 - * @return null|string - */ - public function getExitCodeText() - { - if (null === $exitcode = $this->getExitCode()) { - return; - } - - return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; - } - - /** - * 检查是否成功 - * @return bool - */ - public function isSuccessful() - { - return 0 === $this->getExitCode(); - } - - /** - * 是否未捕获的信号已被终止子进程 - * @return bool - */ - public function hasBeenSignaled() - { - $this->requireProcessIsTerminated(__FUNCTION__); - - if ($this->isSigchildEnabled()) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); - } - - $this->updateStatus(false); - - return $this->processInformation['signaled']; - } - - /** - * 返回导致子进程终止其执行的数。 - * @return int - */ - public function getTermSignal() - { - $this->requireProcessIsTerminated(__FUNCTION__); - - if ($this->isSigchildEnabled()) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); - } - - $this->updateStatus(false); - - return $this->processInformation['termsig']; - } - - /** - * 检查子进程信号是否已停止 - * @return bool - */ - public function hasBeenStopped() - { - $this->requireProcessIsTerminated(__FUNCTION__); - - $this->updateStatus(false); - - return $this->processInformation['stopped']; - } - - /** - * 返回导致子进程停止其执行的数。 - * @return int - */ - public function getStopSignal() - { - $this->requireProcessIsTerminated(__FUNCTION__); - - $this->updateStatus(false); - - return $this->processInformation['stopsig']; - } - - /** - * 检查是否正在运行 - * @return bool - */ - public function isRunning() - { - if (self::STATUS_STARTED !== $this->status) { - return false; - } - - $this->updateStatus(false); - - return $this->processInformation['running']; - } - - /** - * 检查是否已开始 - * @return bool - */ - public function isStarted() - { - return self::STATUS_READY != $this->status; - } - - /** - * 检查是否已终止 - * @return bool - */ - public function isTerminated() - { - $this->updateStatus(false); - - return self::STATUS_TERMINATED == $this->status; - } - - /** - * 获取当前的状态 - * @return string - */ - public function getStatus() - { - $this->updateStatus(false); - - return $this->status; - } - - /** - * 终止进程 - */ - public function stop() - { - if ($this->isRunning()) { - if ('\\' === DS && !$this->isSigchildEnabled()) { - exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); - if ($exitCode > 0) { - throw new \RuntimeException('Unable to kill the process'); - } - } else { - $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); - foreach ($pids as $pid) { - if (is_numeric($pid)) { - posix_kill($pid, 9); - } - } - } - } - - $this->updateStatus(false); - if ($this->processInformation['running']) { - $this->close(); - } - - return $this->exitcode; - } - - /** - * 添加一行输出 - * @param string $line - */ - public function addOutput($line) -{ - $this->lastOutputTime = microtime(true); - $this->stdout .= $line; - } - - /** - * 添加一行错误输出 - * @param string $line - */ - public function addErrorOutput($line) -{ - $this->lastOutputTime = microtime(true); - $this->stderr .= $line; - } - - /** - * 获取被执行的指令 - * @return string - */ - public function getCommandLine() -{ - return $this->commandline; - } - - /** - * 设置指令 - * @param string $commandline - * @return self - */ - public function setCommandLine($commandline) -{ - $this->commandline = $commandline; - - return $this; - } - - /** - * 获取超时时间 - * @return float|null - */ - public function getTimeout() -{ - return $this->timeout; - } - - /** - * 获取idle超时时间 - * @return float|null - */ - public function getIdleTimeout() -{ - return $this->idleTimeout; - } - - /** - * 设置超时时间 - * @param int|float|null $timeout - * @return self - */ - public function setTimeout($timeout) -{ - $this->timeout = $this->validateTimeout($timeout); - - return $this; - } - - /** - * 设置idle超时时间 - * @param int|float|null $timeout - * @return self - */ - public function setIdleTimeout($timeout) -{ - if (null !== $timeout && $this->outputDisabled) { - throw new \LogicException('Idle timeout can not be set while the output is disabled.'); - } - - $this->idleTimeout = $this->validateTimeout($timeout); - - return $this; - } - - /** - * 设置TTY - * @param bool $tty - * @return self - */ - public function setTty($tty) -{ - if ('\\' === DS && $tty) { - throw new \RuntimeException('TTY mode is not supported on Windows platform.'); - } - if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { - throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); - } - - $this->tty = (bool) $tty; - - return $this; - } - - /** - * 检查是否是tty模式 - * @return bool - */ - public function isTty() -{ - return $this->tty; - } - - /** - * 设置pty模式 - * @param bool $bool - * @return self - */ - public function setPty($bool) -{ - $this->pty = (bool) $bool; - - return $this; - } - - /** - * 是否是pty模式 - * @return bool - */ - public function isPty() -{ - return $this->pty; - } - - /** - * 获取工作目录 - * @return string|null - */ - public function getWorkingDirectory() -{ - if (null === $this->cwd) { - return getcwd() ?: null; - } - - return $this->cwd; - } - - /** - * 设置工作目录 - * @param string $cwd - * @return self - */ - public function setWorkingDirectory($cwd) -{ - $this->cwd = $cwd; - - return $this; - } - - /** - * 获取环境变量 - * @return array - */ - public function getEnv() -{ - return $this->env; - } - - /** - * 设置环境变量 - * @param array $env - * @return self - */ - public function setEnv(array $env) -{ - $env = array_filter($env, function ($value) { - return !is_array($value); - }); - - $this->env = []; - foreach ($env as $key => $value) { - $this->env[(binary) $key] = (binary) $value; - } - - return $this; - } - - /** - * 获取输入 - * @return null|string - */ - public function getInput() -{ - return $this->input; - } - - /** - * 设置输入 - * @param mixed $input - * @return self - */ - public function setInput($input) -{ - if ($this->isRunning()) { - throw new \LogicException('Input can not be set while the process is running.'); - } - - $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); - - return $this; - } - - /** - * 获取proc_open的选项 - * @return array - */ - public function getOptions() -{ - return $this->options; - } - - /** - * 设置proc_open的选项 - * @param array $options - * @return self - */ - public function setOptions(array $options) -{ - $this->options = $options; - - return $this; - } - - /** - * 是否兼容windows - * @return bool - */ - public function getEnhanceWindowsCompatibility() -{ - return $this->enhanceWindowsCompatibility; - } - - /** - * 设置是否兼容windows - * @param bool $enhance - * @return self - */ - public function setEnhanceWindowsCompatibility($enhance) -{ - $this->enhanceWindowsCompatibility = (bool) $enhance; - - return $this; - } - - /** - * 返回是否 sigchild 兼容模式激活 - * @return bool - */ - public function getEnhanceSigchildCompatibility() -{ - return $this->enhanceSigchildCompatibility; - } - - /** - * 激活 sigchild 兼容性模式。 - * @param bool $enhance - * @return self - */ - public function setEnhanceSigchildCompatibility($enhance) -{ - $this->enhanceSigchildCompatibility = (bool) $enhance; - - return $this; - } - - /** - * 是否超时 - */ - public function checkTimeout() -{ - if (self::STATUS_STARTED !== $this->status) { - return; - } - - if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { - $this->stop(); - - throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); - } - - if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { - $this->stop(); - - throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); - } - } - - /** - * 是否支持pty - * @return bool - */ - public static function isPtySupported() -{ - static $result; - - if (null !== $result) { - return $result; - } - - if ('\\' === DS) { - return $result = false; - } - - $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); - if (is_resource($proc)) { - proc_close($proc); - - return $result = true; - } - - return $result = false; - } - - /** - * 创建所需的 proc_open 的描述符 - * @return array - */ - private function getDescriptors() -{ - if ('\\' === DS) { - $this->processPipes = WindowsPipes::create($this, $this->input); - } else { - $this->processPipes = UnixPipes::create($this, $this->input); - } - $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); - - if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { - - $descriptors = array_merge($descriptors, [['pipe', 'w']]); - - $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; - } - - return $descriptors; - } - - /** - * 建立 wait () 使用的回调。 - * @param callable|null $callback - * @return callable - */ - protected function buildCallback($callback) -{ - $out = self::OUT; - $callback = function ($type, $data) use ($callback, $out) { - if ($out == $type) { - $this->addOutput($data); - } else { - $this->addErrorOutput($data); - } - - if (null !== $callback) { - call_user_func($callback, $type, $data); - } - }; - - return $callback; - } - - /** - * 更新状态 - * @param bool $blocking - */ - protected function updateStatus($blocking) -{ - if (self::STATUS_STARTED !== $this->status) { - return; - } - - $this->processInformation = proc_get_status($this->process); - $this->captureExitCode(); - - $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); - - if (!$this->processInformation['running']) { - $this->close(); - } - } - - /** - * 是否开启 '--enable-sigchild' - * @return bool - */ - protected function isSigchildEnabled() -{ - if (null !== self::$sigchild) { - return self::$sigchild; - } - - if (!function_exists('phpinfo')) { - return self::$sigchild = false; - } - - ob_start(); - phpinfo(INFO_GENERAL); - - return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); - } - - /** - * 验证是否超时 - * @param int|float|null $timeout - * @return float|null - */ - private function validateTimeout($timeout) -{ - $timeout = (float) $timeout; - - if (0.0 === $timeout) { - $timeout = null; - } elseif ($timeout < 0) { - throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); - } - - return $timeout; - } - - /** - * 读取pipes - * @param bool $blocking - * @param bool $close - */ - private function readPipes($blocking, $close) -{ - $result = $this->processPipes->readAndWrite($blocking, $close); - - $callback = $this->callback; - foreach ($result as $type => $data) { - if (3 == $type) { - $this->fallbackExitcode = (int) $data; - } else { - $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); - } - } - } - - /** - * 捕获退出码 - */ - private function captureExitCode() -{ - if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { - $this->exitcode = $this->processInformation['exitcode']; - } - } - - /** - * 关闭资源 - * @return int 退出码 - */ - private function close() -{ - $this->processPipes->close(); - if (is_resource($this->process)) { - $exitcode = proc_close($this->process); - } else { - $exitcode = -1; - } - - $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); - $this->status = self::STATUS_TERMINATED; - - if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { - $this->exitcode = $this->fallbackExitcode; - } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] - && 0 < $this->processInformation['termsig'] - ) { - $this->exitcode = 128 + $this->processInformation['termsig']; - } - - return $this->exitcode; - } - - /** - * 重置数据 - */ - private function resetProcessData() -{ - $this->starttime = null; - $this->callback = null; - $this->exitcode = null; - $this->fallbackExitcode = null; - $this->processInformation = null; - $this->stdout = null; - $this->stderr = null; - $this->process = null; - $this->latestSignal = null; - $this->status = self::STATUS_READY; - $this->incrementalOutputOffset = 0; - $this->incrementalErrorOutputOffset = 0; - } - - /** - * 将一个 POSIX 信号发送到进程中。 - * @param int $signal - * @param bool $throwException - * @return bool - */ - private function doSignal($signal, $throwException) -{ - if (!$this->isRunning()) { - if ($throwException) { - throw new \LogicException('Can not send signal on a non running process.'); - } - - return false; - } - - if ($this->isSigchildEnabled()) { - if ($throwException) { - throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); - } - - return false; - } - - if (true !== @proc_terminate($this->process, $signal)) { - if ($throwException) { - throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); - } - - return false; - } - - $this->latestSignal = $signal; - - return true; - } - - /** - * 确保进程已经开启 - * @param string $functionName - */ - private function requireProcessIsStarted($functionName) -{ - if (!$this->isStarted()) { - throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); - } - } - - /** - * 确保进程已经终止 - * @param string $functionName - */ - private function requireProcessIsTerminated($functionName) -{ - if (!$this->isTerminated()) { - throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * 构造方法 + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DS)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DS; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DS && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DS && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DS ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DS || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @return Process + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new \LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @return string + * @throws \LogicException + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DS ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DS && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @param int|float|null $timeout + * @return self + */ + public function setIdleTimeout($timeout) +{ + if (null !== $timeout && $this->outputDisabled) { + throw new \LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置TTY + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DS && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DS) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DS) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DS ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @return bool + */ + protected function isSigchildEnabled() +{ + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * 验证是否超时 + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * 读取pipes + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php old mode 100755 new mode 100644 index b51b670..02e0573 --- a/thinkphp/library/think/Request.php +++ b/thinkphp/library/think/Request.php @@ -1,1657 +1,1679 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Request -{ - /** - * @var object 对象实例 - */ - protected static $instance; - - protected $method; - /** - * @var string 域名(含协议和端口) - */ - protected $domain; - - /** - * @var string URL地址 - */ - protected $url; - - /** - * @var string 基础URL - */ - protected $baseUrl; - - /** - * @var string 当前执行的文件 - */ - protected $baseFile; - - /** - * @var string 访问的ROOT地址 - */ - protected $root; - - /** - * @var string pathinfo - */ - protected $pathinfo; - - /** - * @var string pathinfo(不含后缀) - */ - protected $path; - - /** - * @var array 当前路由信息 - */ - protected $routeInfo = []; - - /** - * @var array 环境变量 - */ - protected $env; - - /** - * @var array 当前调度信息 - */ - protected $dispatch = []; - protected $module; - protected $controller; - protected $action; - // 当前语言集 - protected $langset; - - /** - * @var array 请求参数 - */ - protected $param = []; - protected $get = []; - protected $post = []; - protected $request = []; - protected $route = []; - protected $put; - protected $session = []; - protected $file = []; - protected $cookie = []; - protected $server = []; - protected $header = []; - - /** - * @var array 资源类型 - */ - protected $mimeType = [ - 'xml' => 'application/xml,text/xml,application/x-xml', - 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', - 'js' => 'text/javascript,application/javascript,application/x-javascript', - 'css' => 'text/css', - 'rss' => 'application/rss+xml', - 'yaml' => 'application/x-yaml,text/yaml', - 'atom' => 'application/atom+xml', - 'pdf' => 'application/pdf', - 'text' => 'text/plain', - 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', - 'csv' => 'text/csv', - 'html' => 'text/html,application/xhtml+xml,*/*', - ]; - - protected $content; - - // 全局过滤规则 - protected $filter; - // Hook扩展方法 - protected static $hook = []; - // 绑定的属性 - protected $bind = []; - // php://input - protected $input; - // 请求缓存 - protected $cache; - // 缓存是否检查 - protected $isCheckCache; - - /** - * 构造函数 - * @access protected - * @param array $options 参数 - */ - protected function __construct($options = []) - { - foreach ($options as $name => $item) { - if (property_exists($this, $name)) { - $this->$name = $item; - } - } - if (is_null($this->filter)) { - $this->filter = Config::get('default_filter'); - } - - // 保存 php://input - $this->input = file_get_contents('php://input'); - } - - public function __call($method, $args) - { - if (array_key_exists($method, self::$hook)) { - array_unshift($args, $this); - return call_user_func_array(self::$hook[$method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } - - /** - * Hook 方法注入 - * @access public - * @param string|array $method 方法名 - * @param mixed $callback callable - * @return void - */ - public static function hook($method, $callback = null) - { - if (is_array($method)) { - self::$hook = array_merge(self::$hook, $method); - } else { - self::$hook[$method] = $callback; - } - } - - /** - * 初始化 - * @access public - * @param array $options 参数 - * @return \think\Request - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) { - self::$instance = new static($options); - } - return self::$instance; - } - - /** - * 创建一个URL请求 - * @access public - * @param string $uri URL地址 - * @param string $method 请求类型 - * @param array $params 请求参数 - * @param array $cookie - * @param array $files - * @param array $server - * @param string $content - * @return \think\Request - */ - public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) - { - $server['PATH_INFO'] = ''; - $server['REQUEST_METHOD'] = strtoupper($method); - $info = parse_url($uri); - if (isset($info['host'])) { - $server['SERVER_NAME'] = $info['host']; - $server['HTTP_HOST'] = $info['host']; - } - if (isset($info['scheme'])) { - if ('https' === $info['scheme']) { - $server['HTTPS'] = 'on'; - $server['SERVER_PORT'] = 443; - } else { - unset($server['HTTPS']); - $server['SERVER_PORT'] = 80; - } - } - if (isset($info['port'])) { - $server['SERVER_PORT'] = $info['port']; - $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; - } - if (isset($info['user'])) { - $server['PHP_AUTH_USER'] = $info['user']; - } - if (isset($info['pass'])) { - $server['PHP_AUTH_PW'] = $info['pass']; - } - if (!isset($info['path'])) { - $info['path'] = '/'; - } - $options = []; - $options[strtolower($method)] = $params; - $queryString = ''; - if (isset($info['query'])) { - parse_str(html_entity_decode($info['query']), $query); - if (!empty($params)) { - $params = array_replace($query, $params); - $queryString = http_build_query($params, '', '&'); - } else { - $params = $query; - $queryString = $info['query']; - } - } elseif (!empty($params)) { - $queryString = http_build_query($params, '', '&'); - } - if ($queryString) { - parse_str($queryString, $get); - $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; - } - - $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); - $server['QUERY_STRING'] = $queryString; - $options['cookie'] = $cookie; - $options['param'] = $params; - $options['file'] = $files; - $options['server'] = $server; - $options['url'] = $server['REQUEST_URI']; - $options['baseUrl'] = $info['path']; - $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); - $options['method'] = $server['REQUEST_METHOD']; - $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; - $options['content'] = $content; - self::$instance = new self($options); - return self::$instance; - } - - /** - * 设置或获取当前包含协议的域名 - * @access public - * @param string $domain 域名 - * @return string - */ - public function domain($domain = null) - { - if (!is_null($domain)) { - $this->domain = $domain; - return $this; - } elseif (!$this->domain) { - $this->domain = $this->scheme() . '://' . $this->host(); - } - return $this->domain; - } - - /** - * 设置或获取当前完整URL 包括QUERY_STRING - * @access public - * @param string|true $url URL地址 true 带域名获取 - * @return string - */ - public function url($url = null) - { - if (!is_null($url) && true !== $url) { - $this->url = $url; - return $this; - } elseif (!$this->url) { - if (IS_CLI) { - $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { - $this->url = $_SERVER['HTTP_X_REWRITE_URL']; - } elseif (isset($_SERVER['REQUEST_URI'])) { - $this->url = $_SERVER['REQUEST_URI']; - } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { - $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); - } else { - $this->url = ''; - } - } - return true === $url ? $this->domain() . $this->url : $this->url; - } - - /** - * 设置或获取当前URL 不含QUERY_STRING - * @access public - * @param string $url URL地址 - * @return string - */ - public function baseUrl($url = null) - { - if (!is_null($url) && true !== $url) { - $this->baseUrl = $url; - return $this; - } elseif (!$this->baseUrl) { - $str = $this->url(); - $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; - } - return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; - } - - /** - * 设置或获取当前执行的文件 SCRIPT_NAME - * @access public - * @param string $file 当前执行的文件 - * @return string - */ - public function baseFile($file = null) - { - if (!is_null($file) && true !== $file) { - $this->baseFile = $file; - return $this; - } elseif (!$this->baseFile) { - $url = ''; - if (!IS_CLI) { - $script_name = basename($_SERVER['SCRIPT_FILENAME']); - if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['SCRIPT_NAME']; - } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { - $url = $_SERVER['PHP_SELF']; - } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { - $url = $_SERVER['ORIG_SCRIPT_NAME']; - } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { - $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; - } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { - $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); - } - } - $this->baseFile = $url; - } - return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; - } - - /** - * 设置或获取URL访问根地址 - * @access public - * @param string $url URL地址 - * @return string - */ - public function root($url = null) - { - if (!is_null($url) && true !== $url) { - $this->root = $url; - return $this; - } elseif (!$this->root) { - $file = $this->baseFile(); - if ($file && 0 !== strpos($this->url(), $file)) { - $file = str_replace('\\', '/', dirname($file)); - } - $this->root = rtrim($file, '/'); - } - return true === $url ? $this->domain() . $this->root : $this->root; - } - - /** - * 获取当前请求URL的pathinfo信息(含URL后缀) - * @access public - * @return string - */ - public function pathinfo() - { - if (is_null($this->pathinfo)) { - if (isset($_GET[Config::get('var_pathinfo')])) { - // 判断URL里面是否有兼容模式参数 - $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; - unset($_GET[Config::get('var_pathinfo')]); - } elseif (IS_CLI) { - // CLI模式下 index.php module/controller/action/params/... - $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; - } - - // 分析PATHINFO信息 - if (!isset($_SERVER['PATH_INFO'])) { - foreach (Config::get('pathinfo_fetch') as $type) { - if (!empty($_SERVER[$type])) { - $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? - substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; - break; - } - } - } - $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); - } - return $this->pathinfo; - } - - /** - * 获取当前请求URL的pathinfo信息(不含URL后缀) - * @access public - * @return string - */ - public function path() - { - if (is_null($this->path)) { - $suffix = Config::get('url_html_suffix'); - $pathinfo = $this->pathinfo(); - if (false === $suffix) { - // 禁止伪静态访问 - $this->path = $pathinfo; - } elseif ($suffix) { - // 去除正常的URL后缀 - $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); - } else { - // 允许任何后缀访问 - $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); - } - } - return $this->path; - } - - /** - * 当前URL的访问后缀 - * @access public - * @return string - */ - public function ext() - { - return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); - } - - /** - * 获取当前请求的时间 - * @access public - * @param bool $float 是否使用浮点类型 - * @return integer|float - */ - public function time($float = false) - { - return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; - } - - /** - * 当前请求的资源类型 - * @access public - * @return false|string - */ - public function type() - { - $accept = $this->server('HTTP_ACCEPT'); - if (empty($accept)) { - return false; - } - - foreach ($this->mimeType as $key => $val) { - $array = explode(',', $val); - foreach ($array as $k => $v) { - if (stristr($accept, $v)) { - return $key; - } - } - } - return false; - } - - /** - * 设置资源类型 - * @access public - * @param string|array $type 资源类型名 - * @param string $val 资源类型 - * @return void - */ - public function mimeType($type, $val = '') - { - if (is_array($type)) { - $this->mimeType = array_merge($this->mimeType, $type); - } else { - $this->mimeType[$type] = $val; - } - } - - /** - * 当前的请求类型 - * @access public - * @param bool $method true 获取原始请求类型 - * @return string - */ - public function method($method = false) - { - if (true === $method) { - // 获取原始请求类型 - return IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); - } elseif (!$this->method) { - if (isset($_POST[Config::get('var_method')])) { - $this->method = strtoupper($_POST[Config::get('var_method')]); - $this->{$this->method}($_POST); - } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { - $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); - } else { - $this->method = IS_CLI ? 'GET' : (isset($this->server['REQUEST_METHOD']) ? $this->server['REQUEST_METHOD'] : $_SERVER['REQUEST_METHOD']); - } - } - return $this->method; - } - - /** - * 是否为GET请求 - * @access public - * @return bool - */ - public function isGet() - { - return $this->method() == 'GET'; - } - - /** - * 是否为POST请求 - * @access public - * @return bool - */ - public function isPost() - { - return $this->method() == 'POST'; - } - - /** - * 是否为PUT请求 - * @access public - * @return bool - */ - public function isPut() - { - return $this->method() == 'PUT'; - } - - /** - * 是否为DELTE请求 - * @access public - * @return bool - */ - public function isDelete() - { - return $this->method() == 'DELETE'; - } - - /** - * 是否为HEAD请求 - * @access public - * @return bool - */ - public function isHead() - { - return $this->method() == 'HEAD'; - } - - /** - * 是否为PATCH请求 - * @access public - * @return bool - */ - public function isPatch() - { - return $this->method() == 'PATCH'; - } - - /** - * 是否为OPTIONS请求 - * @access public - * @return bool - */ - public function isOptions() - { - return $this->method() == 'OPTIONS'; - } - - /** - * 是否为cli - * @access public - * @return bool - */ - public function isCli() - { - return PHP_SAPI == 'cli'; - } - - /** - * 是否为cgi - * @access public - * @return bool - */ - public function isCgi() - { - return strpos(PHP_SAPI, 'cgi') === 0; - } - - /** - * 获取当前请求的参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function param($name = '', $default = null, $filter = '') - { - if (empty($this->param)) { - $method = $this->method(true); - // 自动获取请求变量 - switch ($method) { - case 'POST': - $vars = $this->post(false); - break; - case 'PUT': - case 'DELETE': - case 'PATCH': - $vars = $this->put(false); - break; - default: - $vars = []; - } - // 当前请求参数和URL地址中的参数合并 - $this->param = array_merge($this->get(false), $vars, $this->route(false)); - } - if (true === $name) { - // 获取包含文件上传信息的数组 - $file = $this->file(); - $data = is_array($file) ? array_merge($this->param, $file) : $this->param; - return $this->input($data, '', $default, $filter); - } - return $this->input($this->param, $name, $default, $filter); - } - - /** - * 设置获取路由参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function route($name = '', $default = null, $filter = '') - { - if (is_array($name)) { - $this->param = []; - return $this->route = array_merge($this->route, $name); - } - return $this->input($this->route, $name, $default, $filter); - } - - /** - * 设置获取GET参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function get($name = '', $default = null, $filter = '') - { - if (empty($this->get)) { - $this->get = $_GET; - } - if (is_array($name)) { - $this->param = []; - return $this->get = array_merge($this->get, $name); - } - return $this->input($this->get, $name, $default, $filter); - } - - /** - * 设置获取POST参数 - * @access public - * @param string $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function post($name = '', $default = null, $filter = '') - { - if (empty($this->post)) { - $content = $this->input; - if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { - $this->post = (array) json_decode($content, true); - } else { - $this->post = $_POST; - } - } - if (is_array($name)) { - $this->param = []; - return $this->post = array_merge($this->post, $name); - } - return $this->input($this->post, $name, $default, $filter); - } - - /** - * 设置获取PUT参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function put($name = '', $default = null, $filter = '') - { - if (is_null($this->put)) { - $content = $this->input; - if (false !== strpos($this->contentType(), 'application/json')) { - $this->put = (array) json_decode($content, true); - } else { - parse_str($content, $this->put); - } - } - if (is_array($name)) { - $this->param = []; - return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); - } - - return $this->input($this->put, $name, $default, $filter); - } - - /** - * 设置获取DELETE参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function delete($name = '', $default = null, $filter = '') - { - return $this->put($name, $default, $filter); - } - - /** - * 设置获取PATCH参数 - * @access public - * @param string|array $name 变量名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function patch($name = '', $default = null, $filter = '') - { - return $this->put($name, $default, $filter); - } - - /** - * 获取request变量 - * @param string $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function request($name = '', $default = null, $filter = '') - { - if (empty($this->request)) { - $this->request = $_REQUEST; - } - if (is_array($name)) { - $this->param = []; - return $this->request = array_merge($this->request, $name); - } - return $this->input($this->request, $name, $default, $filter); - } - - /** - * 获取session数据 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function session($name = '', $default = null, $filter = '') - { - if (empty($this->session)) { - $this->session = Session::get(); - } - if (is_array($name)) { - return $this->session = array_merge($this->session, $name); - } - return $this->input($this->session, $name, $default, $filter); - } - - /** - * 获取cookie参数 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function cookie($name = '', $default = null, $filter = '') - { - if (empty($this->cookie)) { - $this->cookie = Cookie::get(); - } - if (is_array($name)) { - return $this->cookie = array_merge($this->cookie, $name); - } elseif (!empty($name)) { - $data = Cookie::has($name) ? Cookie::get($name) : $default; - } else { - $data = $this->cookie; - } - - // 解析过滤器 - $filter = $this->getFilter($filter, $default); - - if (is_array($data)) { - array_walk_recursive($data, [$this, 'filterValue'], $filter); - reset($data); - } else { - $this->filterValue($data, $name, $filter); - } - return $data; - } - - /** - * 获取server参数 - * @access public - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function server($name = '', $default = null, $filter = '') - { - if (empty($this->server)) { - $this->server = $_SERVER; - } - if (is_array($name)) { - return $this->server = array_merge($this->server, $name); - } - return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); - } - - /** - * 获取上传的文件信息 - * @access public - * @param string|array $name 名称 - * @return null|array|\think\File - */ - public function file($name = '') - { - if (empty($this->file)) { - $this->file = isset($_FILES) ? $_FILES : []; - } - if (is_array($name)) { - return $this->file = array_merge($this->file, $name); - } - $files = $this->file; - if (!empty($files)) { - // 处理上传文件 - $array = []; - foreach ($files as $key => $file) { - if (is_array($file['name'])) { - $item = []; - $keys = array_keys($file); - $count = count($file['name']); - for ($i = 0; $i < $count; $i++) { - if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { - continue; - } - $temp['key'] = $key; - foreach ($keys as $_key) { - $temp[$_key] = $file[$_key][$i]; - } - $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); - } - $array[$key] = $item; - } else { - if ($file instanceof File) { - $array[$key] = $file; - } else { - if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { - continue; - } - $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); - } - } - } - if (strpos($name, '.')) { - list($name, $sub) = explode('.', $name); - } - if ('' === $name) { - // 获取全部文件 - return $array; - } elseif (isset($sub) && isset($array[$name][$sub])) { - return $array[$name][$sub]; - } elseif (isset($array[$name])) { - return $array[$name]; - } - } - return; - } - - /** - * 获取环境变量 - * @param string|array $name 数据名称 - * @param string $default 默认值 - * @param string|array $filter 过滤方法 - * @return mixed - */ - public function env($name = '', $default = null, $filter = '') - { - if (empty($this->env)) { - $this->env = $_ENV; - } - if (is_array($name)) { - return $this->env = array_merge($this->env, $name); - } - return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); - } - - /** - * 设置或者获取当前的Header - * @access public - * @param string|array $name header名称 - * @param string $default 默认值 - * @return string - */ - public function header($name = '', $default = null) - { - if (empty($this->header)) { - $header = []; - if (function_exists('apache_request_headers') && $result = apache_request_headers()) { - $header = $result; - } else { - $server = $this->server ?: $_SERVER; - foreach ($server as $key => $val) { - if (0 === strpos($key, 'HTTP_')) { - $key = str_replace('_', '-', strtolower(substr($key, 5))); - $header[$key] = $val; - } - } - if (isset($server['CONTENT_TYPE'])) { - $header['content-type'] = $server['CONTENT_TYPE']; - } - if (isset($server['CONTENT_LENGTH'])) { - $header['content-length'] = $server['CONTENT_LENGTH']; - } - } - $this->header = array_change_key_case($header); - } - if (is_array($name)) { - return $this->header = array_merge($this->header, $name); - } - if ('' === $name) { - return $this->header; - } - $name = str_replace('_', '-', strtolower($name)); - return isset($this->header[$name]) ? $this->header[$name] : $default; - } - - /** - * 获取变量 支持过滤和默认值 - * @param array $data 数据源 - * @param string|false $name 字段名 - * @param mixed $default 默认值 - * @param string|array $filter 过滤函数 - * @return mixed - */ - public function input($data = [], $name = '', $default = null, $filter = '') - { - if (false === $name) { - // 获取原始数据 - return $data; - } - $name = (string) $name; - if ('' != $name) { - // 解析name - if (strpos($name, '/')) { - list($name, $type) = explode('/', $name); - } else { - $type = 's'; - } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - // 无输入数据,返回默认值 - return $default; - } - } - if (is_object($data)) { - return $data; - } - } - - // 解析过滤器 - $filter = $this->getFilter($filter, $default); - - if (is_array($data)) { - array_walk_recursive($data, [$this, 'filterValue'], $filter); - reset($data); - } else { - $this->filterValue($data, $name, $filter); - } - - if (isset($type) && $data !== $default) { - // 强制类型转换 - $this->typeCast($data, $type); - } - return $data; - } - - /** - * 设置或获取当前的过滤规则 - * @param mixed $filter 过滤规则 - * @return mixed - */ - public function filter($filter = null) - { - if (is_null($filter)) { - return $this->filter; - } else { - $this->filter = $filter; - } - } - - protected function getFilter($filter, $default) - { - if (is_null($filter)) { - $filter = []; - } else { - $filter = $filter ?: $this->filter; - if (is_string($filter) && false === strpos($filter, '/')) { - $filter = explode(',', $filter); - } else { - $filter = (array) $filter; - } - } - - $filter[] = $default; - return $filter; - } - - /** - * 递归过滤给定的值 - * @param mixed $value 键值 - * @param mixed $key 键名 - * @param array $filters 过滤方法+默认值 - * @return mixed - */ - private function filterValue(&$value, $key, $filters) - { - $default = array_pop($filters); - foreach ($filters as $filter) { - if (is_callable($filter)) { - // 调用函数或者方法过滤 - $value = call_user_func($filter, $value); - } elseif (is_scalar($value)) { - if (false !== strpos($filter, '/')) { - // 正则过滤 - if (!preg_match($filter, $value)) { - // 匹配不成功返回默认值 - $value = $default; - break; - } - } elseif (!empty($filter)) { - // filter函数不存在时, 则使用filter_var进行过滤 - // filter为非整形值时, 调用filter_id取得过滤id - $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); - if (false === $value) { - $value = $default; - break; - } - } - } - } - return $this->filterExp($value); - } - - /** - * 过滤表单中的表达式 - * @param string $value - * @return void - */ - public function filterExp(&$value) - { - // 过滤查询特殊字符 - if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) { - $value .= ' '; - } - // TODO 其他安全过滤 - } - - /** - * 强制类型转换 - * @param string $data - * @param string $type - * @return mixed - */ - private function typeCast(&$data, $type) - { - switch (strtolower($type)) { - // 数组 - case 'a': - $data = (array) $data; - break; - // 数字 - case 'd': - $data = (int) $data; - break; - // 浮点 - case 'f': - $data = (float) $data; - break; - // 布尔 - case 'b': - $data = (boolean) $data; - break; - // 字符串 - case 's': - default: - if (is_scalar($data)) { - $data = (string) $data; - } else { - throw new \InvalidArgumentException('variable type error:' . gettype($data)); - } - } - } - - /** - * 是否存在某个请求参数 - * @access public - * @param string $name 变量名 - * @param string $type 变量类型 - * @param bool $checkEmpty 是否检测空值 - * @return mixed - */ - public function has($name, $type = 'param', $checkEmpty = false) - { - if (empty($this->$type)) { - $param = $this->$type(); - } else { - $param = $this->$type; - } - // 按.拆分成多维数组进行判断 - foreach (explode('.', $name) as $val) { - if (isset($param[$val])) { - $param = $param[$val]; - } else { - return false; - } - } - return ($checkEmpty && '' === $param) ? false : true; - } - - /** - * 获取指定的参数 - * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 - * @return mixed - */ - public function only($name, $type = 'param') - { - $param = $this->$type(); - if (is_string($name)) { - $name = explode(',', $name); - } - $item = []; - foreach ($name as $key) { - if (isset($param[$key])) { - $item[$key] = $param[$key]; - } - } - return $item; - } - - /** - * 排除指定参数获取 - * @access public - * @param string|array $name 变量名 - * @param string $type 变量类型 - * @return mixed - */ - public function except($name, $type = 'param') - { - $param = $this->$type(); - if (is_string($name)) { - $name = explode(',', $name); - } - foreach ($name as $key) { - if (isset($param[$key])) { - unset($param[$key]); - } - } - return $param; - } - - /** - * 当前是否ssl - * @access public - * @return bool - */ - public function isSsl() - { - $server = array_merge($_SERVER, $this->server); - if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { - return true; - } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { - return true; - } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { - return true; - } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { - return true; - } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { - return true; - } - return false; - } - - /** - * 当前是否Ajax请求 - * @access public - * @param bool $ajax true 获取原始ajax请求 - * @return bool - */ - public function isAjax($ajax = false) - { - $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); - $result = ('xmlhttprequest' == $value) ? true : false; - if (true === $ajax) { - return $result; - } else { - return $this->param(Config::get('var_ajax')) ? true : $result; - } - } - - /** - * 当前是否Pjax请求 - * @access public - * @param bool $pjax true 获取原始pjax请求 - * @return bool - */ - public function isPjax($pjax = false) - { - $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; - if (true === $pjax) { - return $result; - } else { - return $this->param(Config::get('var_pjax')) ? true : $result; - } - } - - /** - * 获取客户端IP地址 - * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 - * @param boolean $adv 是否进行高级模式获取(有可能被伪装) - * @return mixed - */ - public function ip($type = 0, $adv = true) - { - $type = $type ? 1 : 0; - static $ip = null; - if (null !== $ip) { - return $ip[$type]; - } - - $httpAgentIp = Config::get('http_agent_ip'); - - if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { - $ip = $_SERVER[$httpAgentIp]; - } elseif ($adv) { - if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { - $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); - $pos = array_search('unknown', $arr); - if (false !== $pos) { - unset($arr[$pos]); - } - $ip = trim(current($arr)); - } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { - $ip = $_SERVER['HTTP_CLIENT_IP']; - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; - } - } elseif (isset($_SERVER['REMOTE_ADDR'])) { - $ip = $_SERVER['REMOTE_ADDR']; - } - // IP地址合法验证 - $long = sprintf("%u", ip2long($ip)); - $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; - return $ip[$type]; - } - - /** - * 检测是否使用手机访问 - * @access public - * @return bool - */ - public function isMobile() - { - if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { - return true; - } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { - return true; - } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { - return true; - } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { - return true; - } else { - return false; - } - } - - /** - * 当前URL地址中的scheme参数 - * @access public - * @return string - */ - public function scheme() - { - return $this->isSsl() ? 'https' : 'http'; - } - - /** - * 当前请求URL地址中的query参数 - * @access public - * @return string - */ - public function query() - { - return $this->server('QUERY_STRING'); - } - - /** - * 当前请求的host - * @access public - * @param bool $strict true 仅仅获取HOST - * @return string - */ - public function host($strict = false) - { - if (isset($_SERVER['HTTP_X_REAL_HOST'])) { - $host = $_SERVER['HTTP_X_REAL_HOST']; - } else { - $host = $this->server('HTTP_HOST'); - } - - return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; - } - - /** - * 当前请求URL地址中的port参数 - * @access public - * @return integer - */ - public function port() - { - return $this->server('SERVER_PORT'); - } - - /** - * 当前请求 SERVER_PROTOCOL - * @access public - * @return integer - */ - public function protocol() - { - return $this->server('SERVER_PROTOCOL'); - } - - /** - * 当前请求 REMOTE_PORT - * @access public - * @return integer - */ - public function remotePort() - { - return $this->server('REMOTE_PORT'); - } - - /** - * 当前请求 HTTP_CONTENT_TYPE - * @access public - * @return string - */ - public function contentType() - { - $contentType = $this->server('CONTENT_TYPE'); - if ($contentType) { - if (strpos($contentType, ';')) { - list($type) = explode(';', $contentType); - } else { - $type = $contentType; - } - return trim($type); - } - return ''; - } - - /** - * 获取当前请求的路由信息 - * @access public - * @param array $route 路由名称 - * @return array - */ - public function routeInfo($route = []) - { - if (!empty($route)) { - $this->routeInfo = $route; - } else { - return $this->routeInfo; - } - } - - /** - * 设置或者获取当前请求的调度信息 - * @access public - * @param array $dispatch 调度信息 - * @return array - */ - public function dispatch($dispatch = null) - { - if (!is_null($dispatch)) { - $this->dispatch = $dispatch; - } - return $this->dispatch; - } - - /** - * 设置或者获取当前的模块名 - * @access public - * @param string $module 模块名 - * @return string|Request - */ - public function module($module = null) - { - if (!is_null($module)) { - $this->module = $module; - return $this; - } else { - return $this->module ?: ''; - } - } - - /** - * 设置或者获取当前的控制器名 - * @access public - * @param string $controller 控制器名 - * @return string|Request - */ - public function controller($controller = null) - { - if (!is_null($controller)) { - $this->controller = $controller; - return $this; - } else { - return $this->controller ?: ''; - } - } - - /** - * 设置或者获取当前的操作名 - * @access public - * @param string $action 操作名 - * @return string|Request - */ - public function action($action = null) - { - if (!is_null($action) && !is_bool($action)) { - $this->action = $action; - return $this; - } else { - $name = $this->action ?: ''; - return true === $action ? $name : strtolower($name); - } - } - - /** - * 设置或者获取当前的语言 - * @access public - * @param string $lang 语言名 - * @return string|Request - */ - public function langset($lang = null) - { - if (!is_null($lang)) { - $this->langset = $lang; - return $this; - } else { - return $this->langset ?: ''; - } - } - - /** - * 设置或者获取当前请求的content - * @access public - * @return string - */ - public function getContent() - { - if (is_null($this->content)) { - $this->content = $this->input; - } - return $this->content; - } - - /** - * 获取当前请求的php://input - * @access public - * @return string - */ - public function getInput() - { - return $this->input; - } - - /** - * 生成请求令牌 - * @access public - * @param string $name 令牌名称 - * @param mixed $type 令牌生成方法 - * @return string - */ - public function token($name = '__token__', $type = 'md5') - { - $type = is_callable($type) ? $type : 'md5'; - $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); - if ($this->isAjax()) { - header($name . ': ' . $token); - } - Session::set($name, $token); - return $token; - } - - /** - * 设置当前地址的请求缓存 - * @access public - * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id - * @param mixed $expire 缓存有效期 - * @param array $except 缓存排除 - * @param string $tag 缓存标签 - * @return void - */ - public function cache($key, $expire = null, $except = [], $tag = null) - { - if (!is_array($except)) { - $tag = $except; - $except = []; - } - - if (false !== $key && $this->isGet() && !$this->isCheckCache) { - // 标记请求缓存检查 - $this->isCheckCache = true; - if (false === $expire) { - // 关闭当前缓存 - return; - } - if ($key instanceof \Closure) { - $key = call_user_func_array($key, [$this]); - } elseif (true === $key) { - foreach ($except as $rule) { - if (0 === stripos($this->url(), $rule)) { - return; - } - } - // 自动缓存功能 - $key = '__URL__'; - } elseif (strpos($key, '|')) { - list($key, $fun) = explode('|', $key); - } - // 特殊规则替换 - if (false !== strpos($key, '__')) { - $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); - } - - if (false !== strpos($key, ':')) { - $param = $this->param(); - foreach ($param as $item => $val) { - if (is_string($val) && false !== strpos($key, ':' . $item)) { - $key = str_replace(':' . $item, $val, $key); - } - } - } elseif (strpos($key, ']')) { - if ('[' . $this->ext() . ']' == $key) { - // 缓存某个后缀的请求 - $key = md5($this->url()); - } else { - return; - } - } - if (isset($fun)) { - $key = $fun($key); - } - - if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { - // 读取缓存 - $response = Response::create()->code(304); - throw new \think\exception\HttpResponseException($response); - } elseif (Cache::has($key)) { - list($content, $header) = Cache::get($key); - $response = Response::create($content)->header($header); - throw new \think\exception\HttpResponseException($response); - } else { - $this->cache = [$key, $expire, $tag]; - } - } - } - - /** - * 读取请求缓存设置 - * @access public - * @return array - */ - public function getCache() - { - return $this->cache; - } - - /** - * 设置当前请求绑定的对象实例 - * @access public - * @param string|array $name 绑定的对象标识 - * @param mixed $obj 绑定的对象实例 - * @return mixed - */ - public function bind($name, $obj = null) - { - if (is_array($name)) { - $this->bind = array_merge($this->bind, $name); - } else { - $this->bind[$name] = $obj; - } - } - - public function __set($name, $value) - { - $this->bind[$name] = $value; - } - - public function __get($name) - { - return isset($this->bind[$name]) ? $this->bind[$name] : null; - } - - public function __isset($name) - { - return isset($this->bind[$name]); - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Request +{ + /** + * @var object 对象实例 + */ + protected static $instance; + + protected $method; + /** + * @var string 域名(含协议和端口) + */ + protected $domain; + + /** + * @var string URL地址 + */ + protected $url; + + /** + * @var string 基础URL + */ + protected $baseUrl; + + /** + * @var string 当前执行的文件 + */ + protected $baseFile; + + /** + * @var string 访问的ROOT地址 + */ + protected $root; + + /** + * @var string pathinfo + */ + protected $pathinfo; + + /** + * @var string pathinfo(不含后缀) + */ + protected $path; + + /** + * @var array 当前路由信息 + */ + protected $routeInfo = []; + + /** + * @var array 环境变量 + */ + protected $env; + + /** + * @var array 当前调度信息 + */ + protected $dispatch = []; + protected $module; + protected $controller; + protected $action; + // 当前语言集 + protected $langset; + + /** + * @var array 请求参数 + */ + protected $param = []; + protected $get = []; + protected $post = []; + protected $request = []; + protected $route = []; + protected $put; + protected $session = []; + protected $file = []; + protected $cookie = []; + protected $server = []; + protected $header = []; + + /** + * @var array 资源类型 + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + protected $content; + + // 全局过滤规则 + protected $filter; + // Hook扩展方法 + protected static $hook = []; + // 绑定的属性 + protected $bind = []; + // php://input + protected $input; + // 请求缓存 + protected $cache; + // 缓存是否检查 + protected $isCheckCache; + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 构造函数 + * @access protected + * @param array $options 参数 + */ + protected function __construct($options = []) + { + foreach ($options as $name => $item) { + if (property_exists($this, $name)) { + $this->$name = $item; + } + } + if (is_null($this->filter)) { + $this->filter = Config::get('default_filter'); + } + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function __call($method, $args) + { + if (array_key_exists($method, self::$hook)) { + array_unshift($args, $this); + return call_user_func_array(self::$hook[$method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public static function hook($method, $callback = null) + { + if (is_array($method)) { + self::$hook = array_merge(self::$hook, $method); + } else { + self::$hook[$method] = $callback; + } + } + + /** + * 初始化 + * @access public + * @param array $options 参数 + * @return \think\Request + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) { + self::$instance = new static($options); + } + return self::$instance; + } + + /** + * 销毁当前请求对象 + * @access public + * @return void + */ + public static function destroy() + { + if (!is_null(self::$instance)) { + self::$instance = null; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public static function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + if (!isset($info['path'])) { + $info['path'] = '/'; + } + $options = []; + $options[strtolower($method)] = $params; + $queryString = ''; + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + self::$instance = new self($options); + return self::$instance; + } + + /** + * 设置或获取当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return string + */ + public function domain($domain = null) + { + if (!is_null($domain)) { + $this->domain = $domain; + return $this; + } elseif (!$this->domain) { + $this->domain = $this->scheme() . '://' . $this->host(); + } + return $this->domain; + } + + /** + * 设置或获取当前完整URL 包括QUERY_STRING + * @access public + * @param string|true $url URL地址 true 带域名获取 + * @return string + */ + public function url($url = null) + { + if (!is_null($url) && true !== $url) { + $this->url = $url; + return $this; + } elseif (!$this->url) { + if (IS_CLI) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif (isset($_SERVER['HTTP_X_REWRITE_URL'])) { + $this->url = $_SERVER['HTTP_X_REWRITE_URL']; + } elseif (isset($_SERVER['REQUEST_URI'])) { + $this->url = $_SERVER['REQUEST_URI']; + } elseif (isset($_SERVER['ORIG_PATH_INFO'])) { + $this->url = $_SERVER['ORIG_PATH_INFO'] . (!empty($_SERVER['QUERY_STRING']) ? '?' . $_SERVER['QUERY_STRING'] : ''); + } else { + $this->url = ''; + } + } + return true === $url ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置或获取当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return string + */ + public function baseUrl($url = null) + { + if (!is_null($url) && true !== $url) { + $this->baseUrl = $url; + return $this; + } elseif (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + return true === $url ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param string $file 当前执行的文件 + * @return string + */ + public function baseFile($file = null) + { + if (!is_null($file) && true !== $file) { + $this->baseFile = $file; + return $this; + } elseif (!$this->baseFile) { + $url = ''; + if (!IS_CLI) { + $script_name = basename($_SERVER['SCRIPT_FILENAME']); + if (basename($_SERVER['SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['SCRIPT_NAME']; + } elseif (basename($_SERVER['PHP_SELF']) === $script_name) { + $url = $_SERVER['PHP_SELF']; + } elseif (isset($_SERVER['ORIG_SCRIPT_NAME']) && basename($_SERVER['ORIG_SCRIPT_NAME']) === $script_name) { + $url = $_SERVER['ORIG_SCRIPT_NAME']; + } elseif (($pos = strpos($_SERVER['PHP_SELF'], '/' . $script_name)) !== false) { + $url = substr($_SERVER['SCRIPT_NAME'], 0, $pos) . '/' . $script_name; + } elseif (isset($_SERVER['DOCUMENT_ROOT']) && strpos($_SERVER['SCRIPT_FILENAME'], $_SERVER['DOCUMENT_ROOT']) === 0) { + $url = str_replace('\\', '/', str_replace($_SERVER['DOCUMENT_ROOT'], '', $_SERVER['SCRIPT_FILENAME'])); + } + } + $this->baseFile = $url; + } + return true === $file ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置或获取URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string + */ + public function root($url = null) + { + if (!is_null($url) && true !== $url) { + $this->root = $url; + return $this; + } elseif (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + return true === $url ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[Config::get('var_pathinfo')])) { + // 判断URL里面是否有兼容模式参数 + $_SERVER['PATH_INFO'] = $_GET[Config::get('var_pathinfo')]; + unset($_GET[Config::get('var_pathinfo')]); + } elseif (IS_CLI) { + // CLI模式下 index.php module/controller/action/params/... + $_SERVER['PATH_INFO'] = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } + + // 分析PATHINFO信息 + if (!isset($_SERVER['PATH_INFO'])) { + foreach (Config::get('pathinfo_fetch') as $type) { + if (!empty($_SERVER[$type])) { + $_SERVER['PATH_INFO'] = (0 === strpos($_SERVER[$type], $_SERVER['SCRIPT_NAME'])) ? + substr($_SERVER[$type], strlen($_SERVER['SCRIPT_NAME'])) : $_SERVER[$type]; + break; + } + } + } + $this->pathinfo = empty($_SERVER['PATH_INFO']) ? '/' : ltrim($_SERVER['PATH_INFO'], '/'); + } + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = Config::get('url_html_suffix'); + $pathinfo = $this->pathinfo(); + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $_SERVER['REQUEST_TIME_FLOAT'] : $_SERVER['REQUEST_TIME']; + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $method true 获取原始请求类型 + * @return string + */ + public function method($method = false) + { + if (true === $method) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[Config::get('var_method')])) { + $this->method = strtoupper($_POST[Config::get('var_method')]); + $this->{$this->method}($_POST); + } elseif (isset($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'])) { + $this->method = strtoupper($_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE']); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (empty($this->mergeParam)) { + $method = $this->method(true); + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + $this->mergeParam = true; + } + if (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + return $this->input($data, '', $default, $filter); + } + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置获取路由参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + $this->param = []; + return $this->route = array_merge($this->route, $name); + } + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 设置获取GET参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (empty($this->get)) { + $this->get = $_GET; + } + if (is_array($name)) { + $this->param = []; + return $this->get = array_merge($this->get, $name); + } + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 设置获取POST参数 + * @access public + * @param string $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $content = $this->input; + if (empty($_POST) && false !== strpos($this->contentType(), 'application/json')) { + $this->post = (array) json_decode($content, true); + } else { + $this->post = $_POST; + } + } + if (is_array($name)) { + $this->param = []; + return $this->post = array_merge($this->post, $name); + } + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 设置获取PUT参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_null($this->put)) { + $content = $this->input; + if (false !== strpos($this->contentType(), 'application/json')) { + $this->put = (array) json_decode($content, true); + } else { + parse_str($content, $this->put); + } + } + if (is_array($name)) { + $this->param = []; + return $this->put = is_null($this->put) ? $name : array_merge($this->put, $name); + } + + return $this->input($this->put, $name, $default, $filter); + } + + /** + * 设置获取DELETE参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 设置获取PATCH参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @param string $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + if (is_array($name)) { + $this->param = []; + return $this->request = array_merge($this->request, $name); + } + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function session($name = '', $default = null, $filter = '') + { + if (empty($this->session)) { + $this->session = Session::get(); + } + if (is_array($name)) { + return $this->session = array_merge($this->session, $name); + } + return $this->input($this->session, $name, $default, $filter); + } + + /** + * 获取cookie参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + if (is_array($name)) { + return $this->cookie = array_merge($this->cookie, $name); + } elseif (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function server($name = '', $default = null, $filter = '') + { + if (empty($this->server)) { + $this->server = $_SERVER; + } + if (is_array($name)) { + return $this->server = array_merge($this->server, $name); + } + return $this->input($this->server, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 获取上传的文件信息 + * @access public + * @param string|array $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + if (is_array($name)) { + return $this->file = array_merge($this->file, $name); + } + $files = $this->file; + if (!empty($files)) { + // 处理上传文件 + $array = []; + foreach ($files as $key => $file) { + if (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + for ($i = 0; $i < $count; $i++) { + if (empty($file['tmp_name'][$i]) || !is_file($file['tmp_name'][$i])) { + continue; + } + $temp['key'] = $key; + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if (empty($file['tmp_name']) || !is_file($file['tmp_name'])) { + continue; + } + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + } + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + return; + } + + /** + * 获取环境变量 + * @param string|array $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function env($name = '', $default = null, $filter = '') + { + if (empty($this->env)) { + $this->env = $_ENV; + } + if (is_array($name)) { + return $this->env = array_merge($this->env, $name); + } + return $this->input($this->env, false === $name ? false : strtoupper($name), $default, $filter); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string|array $name header名称 + * @param string $default 默认值 + * @return string + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server ?: $_SERVER; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + $this->header = array_change_key_case($header); + } + if (is_array($name)) { + return $this->header = array_merge($this->header, $name); + } + if ('' === $name) { + return $this->header; + } + $name = str_replace('_', '-', strtolower($name)); + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } else { + $type = 's'; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + // 无输入数据,返回默认值 + return $default; + } + } + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } else { + $this->filter = $filter; + } + } + + protected function getFilter($filter, $default) + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + return $filter; + } + + /** + * 递归过滤给定的值 + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + return $this->filterExp($value); + } + + /** + * 过滤表单中的表达式 + * @param string $value + * @return void + */ + public function filterExp(&$value) + { + // 过滤查询特殊字符 + if (is_string($value) && preg_match('/^(EXP|NEQ|GT|EGT|LT|ELT|OR|XOR|LIKE|NOTLIKE|NOT LIKE|NOT BETWEEN|NOTBETWEEN|BETWEEN|NOT EXISTS|NOTEXISTS|EXISTS|NOT NULL|NOTNULL|NULL|BETWEEN TIME|NOT BETWEEN TIME|NOTBETWEEN TIME|NOTIN|NOT IN|IN)$/i', $value)) { + $value .= ' '; + } + // TODO 其他安全过滤 + } + + /** + * 强制类型转换 + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + default: + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + $item = []; + foreach ($name as $key) { + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } + } + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + $server = array_merge($_SERVER, $this->server); + if (isset($server['HTTPS']) && ('1' == $server['HTTPS'] || 'on' == strtolower($server['HTTPS']))) { + return true; + } elseif (isset($server['REQUEST_SCHEME']) && 'https' == $server['REQUEST_SCHEME']) { + return true; + } elseif (isset($server['SERVER_PORT']) && ('443' == $server['SERVER_PORT'])) { + return true; + } elseif (isset($server['HTTP_X_FORWARDED_PROTO']) && 'https' == $server['HTTP_X_FORWARDED_PROTO']) { + return true; + } elseif (Config::get('https_agent_name') && isset($server[Config::get('https_agent_name')])) { + return true; + } + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH', '', 'strtolower'); + $result = ('xmlhttprequest' == $value) ? true : false; + if (true === $ajax) { + return $result; + } else { + $result = $this->param(Config::get('var_ajax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + if (true === $pjax) { + return $result; + } else { + $result = $this->param(Config::get('var_pjax')) ? true : $result; + $this->mergeParam = false; + return $result; + } + } + + /** + * 获取客户端IP地址 + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = Config::get('http_agent_ip'); + + if ($httpAgentIp && isset($_SERVER[$httpAgentIp])) { + $ip = $_SERVER[$httpAgentIp]; + } elseif ($adv) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $arr = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $ip = $_SERVER['HTTP_CLIENT_IP']; + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + } elseif (isset($_SERVER['REMOTE_ADDR'])) { + $ip = $_SERVER['REMOTE_ADDR']; + } + // IP地址合法验证 + $long = sprintf("%u", ip2long($ip)); + $ip = $long ? [$ip, $long] : ['0.0.0.0', 0]; + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if (isset($_SERVER['HTTP_VIA']) && stristr($_SERVER['HTTP_VIA'], "wap")) { + return true; + } elseif (isset($_SERVER['HTTP_ACCEPT']) && strpos(strtoupper($_SERVER['HTTP_ACCEPT']), "VND.WAP.WML")) { + return true; + } elseif (isset($_SERVER['HTTP_X_WAP_PROFILE']) || isset($_SERVER['HTTP_PROFILE'])) { + return true; + } elseif (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $_SERVER['HTTP_USER_AGENT'])) { + return true; + } else { + return false; + } + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (isset($_SERVER['HTTP_X_REAL_HOST'])) { + $host = $_SERVER['HTTP_X_REAL_HOST']; + } else { + $host = $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return integer + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo($route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } else { + return $this->routeInfo; + } + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param array $dispatch 调度信息 + * @return array + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + return $this->dispatch; + } + + /** + * 设置或者获取当前的模块名 + * @access public + * @param string $module 模块名 + * @return string|Request + */ + public function module($module = null) + { + if (!is_null($module)) { + $this->module = $module; + return $this; + } else { + return $this->module ?: ''; + } + } + + /** + * 设置或者获取当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return string|Request + */ + public function controller($controller = null) + { + if (!is_null($controller)) { + $this->controller = $controller; + return $this; + } else { + return $this->controller ?: ''; + } + } + + /** + * 设置或者获取当前的操作名 + * @access public + * @param string $action 操作名 + * @return string|Request + */ + public function action($action = null) + { + if (!is_null($action) && !is_bool($action)) { + $this->action = $action; + return $this; + } else { + $name = $this->action ?: ''; + return true === $action ? $name : strtolower($name); + } + } + + /** + * 设置或者获取当前的语言 + * @access public + * @param string $lang 语言名 + * @return string|Request + */ + public function langset($lang = null) + { + if (!is_null($lang)) { + $this->langset = $lang; + return $this; + } else { + return $this->langset ?: ''; + } + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = 'md5') + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $_SERVER['REQUEST_TIME_FLOAT']); + if ($this->isAjax()) { + header($name . ': ' . $token); + } + Session::set($name, $token); + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false !== $key && $this->isGet() && !$this->isCheckCache) { + // 标记请求缓存检查 + $this->isCheckCache = true; + if (false === $expire) { + // 关闭当前缓存 + return; + } + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__', ''], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + if (isset($fun)) { + $key = $fun($key); + } + + if (strtotime($this->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $_SERVER['REQUEST_TIME']) { + // 读取缓存 + $response = Response::create()->code(304); + throw new \think\exception\HttpResponseException($response); + } elseif (Cache::has($key)) { + list($content, $header) = Cache::get($key); + $response = Response::create($content)->header($header); + throw new \think\exception\HttpResponseException($response); + } else { + $this->cache = [$key, $expire, $tag]; + } + } + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置当前请求绑定的对象实例 + * @access public + * @param string|array $name 绑定的对象标识 + * @param mixed $obj 绑定的对象实例 + * @return mixed + */ + public function bind($name, $obj = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $obj; + } + } + + public function __set($name, $value) + { + $this->bind[$name] = $value; + } + + public function __get($name) + { + return isset($this->bind[$name]) ? $this->bind[$name] : null; + } + + public function __isset($name) + { + return isset($this->bind[$name]); + } +} diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php old mode 100755 new mode 100644 index 7296866..c5c1520 --- a/thinkphp/library/think/Response.php +++ b/thinkphp/library/think/Response.php @@ -1,332 +1,332 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\response\Json as JsonResponse; -use think\response\Jsonp as JsonpResponse; -use think\response\Redirect as RedirectResponse; -use think\response\View as ViewResponse; -use think\response\Xml as XmlResponse; - -class Response -{ - // 原始数据 - protected $data; - - // 当前的contentType - protected $contentType = 'text/html'; - - // 字符集 - protected $charset = 'utf-8'; - - //状态 - protected $code = 200; - - // 输出参数 - protected $options = []; - // header参数 - protected $header = []; - - protected $content = null; - - /** - * 构造函数 - * @access public - * @param mixed $data 输出数据 - * @param int $code - * @param array $header - * @param array $options 输出参数 - */ - public function __construct($data = '', $code = 200, array $header = [], $options = []) - { - $this->data($data); - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $this->contentType($this->contentType, $this->charset); - $this->header = array_merge($this->header, $header); - $this->code = $code; - } - - /** - * 创建Response对象 - * @access public - * @param mixed $data 输出数据 - * @param string $type 输出类型 - * @param int $code - * @param array $header - * @param array $options 输出参数 - * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse - */ - public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) - { - $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); - if (class_exists($class)) { - $response = new $class($data, $code, $header, $options); - } else { - $response = new static($data, $code, $header, $options); - } - - return $response; - } - - /** - * 发送数据到客户端 - * @access public - * @return mixed - * @throws \InvalidArgumentException - */ - public function send() - { - // 监听response_send - Hook::listen('response_send', $this); - - // 处理输出数据 - $data = $this->getContent(); - - // Trace调试注入 - if (Env::get('app_trace', Config::get('app_trace'))) { - Debug::inject($this, $data); - } - - if (200 == $this->code) { - $cache = Request::instance()->getCache(); - if ($cache) { - $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; - $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; - $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; - Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); - } - } - - if (!headers_sent() && !empty($this->header)) { - // 发送状态码 - http_response_code($this->code); - // 发送头部信息 - foreach ($this->header as $name => $val) { - if (is_null($val)) { - header($name); - } else { - header($name . ':' . $val); - } - } - } - - echo $data; - - if (function_exists('fastcgi_finish_request')) { - // 提高页面响应 - fastcgi_finish_request(); - } - - // 监听response_end - Hook::listen('response_end', $this); - - // 清空当次请求有效的数据 - if (!($this instanceof RedirectResponse)) { - Session::flush(); - } - } - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - */ - protected function output($data) - { - return $data; - } - - /** - * 输出的参数 - * @access public - * @param mixed $options 输出参数 - * @return $this - */ - public function options($options = []) - { - $this->options = array_merge($this->options, $options); - return $this; - } - - /** - * 输出数据设置 - * @access public - * @param mixed $data 输出数据 - * @return $this - */ - public function data($data) - { - $this->data = $data; - return $this; - } - - /** - * 设置响应头 - * @access public - * @param string|array $name 参数名 - * @param string $value 参数值 - * @return $this - */ - public function header($name, $value = null) - { - if (is_array($name)) { - $this->header = array_merge($this->header, $name); - } else { - $this->header[$name] = $value; - } - return $this; - } - - /** - * 设置页面输出内容 - * @param $content - * @return $this - */ - public function content($content) - { - if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ - $content, - '__toString', - ]) - ) { - throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); - } - - $this->content = (string) $content; - - return $this; - } - - /** - * 发送HTTP状态 - * @param integer $code 状态码 - * @return $this - */ - public function code($code) - { - $this->code = $code; - return $this; - } - - /** - * LastModified - * @param string $time - * @return $this - */ - public function lastModified($time) - { - $this->header['Last-Modified'] = $time; - return $this; - } - - /** - * Expires - * @param string $time - * @return $this - */ - public function expires($time) - { - $this->header['Expires'] = $time; - return $this; - } - - /** - * ETag - * @param string $eTag - * @return $this - */ - public function eTag($eTag) - { - $this->header['ETag'] = $eTag; - return $this; - } - - /** - * 页面缓存控制 - * @param string $cache 状态码 - * @return $this - */ - public function cacheControl($cache) - { - $this->header['Cache-control'] = $cache; - return $this; - } - - /** - * 页面输出类型 - * @param string $contentType 输出类型 - * @param string $charset 输出编码 - * @return $this - */ - public function contentType($contentType, $charset = 'utf-8') - { - $this->header['Content-Type'] = $contentType . '; charset=' . $charset; - return $this; - } - - /** - * 获取头部信息 - * @param string $name 头部名称 - * @return mixed - */ - public function getHeader($name = '') - { - if (!empty($name)) { - return isset($this->header[$name]) ? $this->header[$name] : null; - } else { - return $this->header; - } - } - - /** - * 获取原始数据 - * @return mixed - */ - public function getData() - { - return $this->data; - } - - /** - * 获取输出数据 - * @return mixed - */ - public function getContent() - { - if (null == $this->content) { - $content = $this->output($this->data); - - if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ - $content, - '__toString', - ]) - ) { - throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); - } - - $this->content = (string) $content; - } - return $this->content; - } - - /** - * 获取状态码 - * @return integer - */ - public function getCode() - { - return $this->code; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Json as JsonResponse; +use think\response\Jsonp as JsonpResponse; +use think\response\Redirect as RedirectResponse; +use think\response\View as ViewResponse; +use think\response\Xml as XmlResponse; + +class Response +{ + // 原始数据 + protected $data; + + // 当前的contentType + protected $contentType = 'text/html'; + + // 字符集 + protected $charset = 'utf-8'; + + //状态 + protected $code = 200; + + // 输出参数 + protected $options = []; + // header参数 + protected $header = []; + + protected $content = null; + + /** + * 构造函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->contentType($this->contentType, $this->charset); + $this->header = array_merge($this->header, $header); + $this->code = $code; + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response|JsonResponse|ViewResponse|XmlResponse|RedirectResponse|JsonpResponse + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + if (class_exists($class)) { + $response = new $class($data, $code, $header, $options); + } else { + $response = new static($data, $code, $header, $options); + } + + return $response; + } + + /** + * 发送数据到客户端 + * @access public + * @return mixed + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + Hook::listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if (Env::get('app_trace', Config::get('app_trace'))) { + Debug::inject($this, $data); + } + + if (200 == $this->code) { + $cache = Request::instance()->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + Cache::tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + if (is_null($val)) { + header($name); + } else { + header($name . ':' . $val); + } + } + } + + echo $data; + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + Hook::listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + Session::flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + return $this; + } + + /** + * 设置响应头 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + return $this; + } + + /** + * 设置页面输出内容 + * @param $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + return $this; + } + + /** + * LastModified + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + return $this; + } + + /** + * Expires + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + return $this; + } + + /** + * ETag + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + return $this; + } + + /** + * 页面缓存控制 + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + return $this; + } + + /** + * 页面输出类型 + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + return $this; + } + + /** + * 获取头部信息 + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } else { + return $this->header; + } + } + + /** + * 获取原始数据 + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @return mixed + */ + public function getContent() + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + return $this->content; + } + + /** + * 获取状态码 + * @return integer + */ + public function getCode() + { + return $this->code; + } +} diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php old mode 100755 new mode 100644 index 76192e0..ab53aa2 --- a/thinkphp/library/think/Route.php +++ b/thinkphp/library/think/Route.php @@ -1,1604 +1,1645 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\HttpException; - -class Route -{ - // 路由规则 - private static $rules = [ - 'get' => [], - 'post' => [], - 'put' => [], - 'delete' => [], - 'patch' => [], - 'head' => [], - 'options' => [], - '*' => [], - 'alias' => [], - 'domain' => [], - 'pattern' => [], - 'name' => [], - ]; - - // REST路由操作方法定义 - private static $rest = [ - 'index' => ['get', '', 'index'], - 'create' => ['get', '/create', 'create'], - 'edit' => ['get', '/:id/edit', 'edit'], - 'read' => ['get', '/:id', 'read'], - 'save' => ['post', '', 'save'], - 'update' => ['put', '/:id', 'update'], - 'delete' => ['delete', '/:id', 'delete'], - ]; - - // 不同请求类型的方法前缀 - private static $methodPrefix = [ - 'get' => 'get', - 'post' => 'post', - 'put' => 'put', - 'delete' => 'delete', - 'patch' => 'patch', - ]; - - // 子域名 - private static $subDomain = ''; - // 域名绑定 - private static $bind = []; - // 当前分组信息 - private static $group = []; - // 当前子域名绑定 - private static $domainBind; - private static $domainRule; - // 当前域名 - private static $domain; - // 当前路由执行过程中的参数 - private static $option = []; - - /** - * 注册变量规则 - * @access public - * @param string|array $name 变量名 - * @param string $rule 变量规则 - * @return void - */ - public static function pattern($name = null, $rule = '') - { - if (is_array($name)) { - self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); - } else { - self::$rules['pattern'][$name] = $rule; - } - } - - /** - * 注册子域名部署规则 - * @access public - * @param string|array $domain 子域名 - * @param mixed $rule 路由规则 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function domain($domain, $rule = '', $option = [], $pattern = []) - { - if (is_array($domain)) { - foreach ($domain as $key => $item) { - self::domain($key, $item, $option, $pattern); - } - } elseif ($rule instanceof \Closure) { - // 执行闭包 - self::setDomain($domain); - call_user_func_array($rule, []); - self::setDomain(null); - } elseif (is_array($rule)) { - self::setDomain($domain); - self::group('', function () use ($rule) { - // 动态注册域名的路由规则 - self::registerRules($rule); - }, $option, $pattern); - self::setDomain(null); - } else { - self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; - } - } - - private static function setDomain($domain) - { - self::$domain = $domain; - } - - /** - * 设置路由绑定 - * @access public - * @param mixed $bind 绑定信息 - * @param string $type 绑定类型 默认为module 支持 namespace class controller - * @return mixed - */ - public static function bind($bind, $type = 'module') - { - self::$bind = ['type' => $type, $type => $bind]; - } - - /** - * 设置或者获取路由标识 - * @access public - * @param string|array $name 路由命名标识 数组表示批量设置 - * @param array $value 路由地址及变量信息 - * @return array - */ - public static function name($name = '', $value = null) - { - if (is_array($name)) { - return self::$rules['name'] = $name; - } elseif ('' === $name) { - return self::$rules['name']; - } elseif (!is_null($value)) { - self::$rules['name'][strtolower($name)][] = $value; - } else { - $name = strtolower($name); - return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; - } - } - - /** - * 读取路由绑定 - * @access public - * @param string $type 绑定类型 - * @return mixed - */ - public static function getBind($type) - { - return isset(self::$bind[$type]) ? self::$bind[$type] : null; - } - - /** - * 导入配置文件的路由规则 - * @access public - * @param array $rule 路由规则 - * @param string $type 请求类型 - * @return void - */ - public static function import(array $rule, $type = '*') - { - // 检查域名部署 - if (isset($rule['__domain__'])) { - self::domain($rule['__domain__']); - unset($rule['__domain__']); - } - - // 检查变量规则 - if (isset($rule['__pattern__'])) { - self::pattern($rule['__pattern__']); - unset($rule['__pattern__']); - } - - // 检查路由别名 - if (isset($rule['__alias__'])) { - self::alias($rule['__alias__']); - unset($rule['__alias__']); - } - - // 检查资源路由 - if (isset($rule['__rest__'])) { - self::resource($rule['__rest__']); - unset($rule['__rest__']); - } - - self::registerRules($rule, strtolower($type)); - } - - // 批量注册路由 - protected static function registerRules($rules, $type = '*') - { - foreach ($rules as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (empty($val)) { - continue; - } - if (is_string($key) && 0 === strpos($key, '[')) { - $key = substr($key, 1, -1); - self::group($key, $val); - } elseif (is_array($val)) { - self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); - } else { - self::setRule($key, $val, $type); - } - } - } - - /** - * 注册路由规则 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) - { - $group = self::getGroup('name'); - - if (!is_null($group)) { - // 路由分组 - $option = array_merge(self::getGroup('option'), $option); - $pattern = array_merge(self::getGroup('pattern'), $pattern); - } - - $type = strtolower($type); - - if (strpos($type, '|')) { - $option['method'] = $type; - $type = '*'; - } - if (is_array($rule) && empty($route)) { - foreach ($rule as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, $val[1]); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $option1 = null; - $pattern1 = null; - $route = $val; - } - self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); - } - } else { - self::setRule($rule, $route, $type, $option, $pattern, $group); - } - - } - - /** - * 设置路由规则 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param string $type 请求类型 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @param string $group 所属分组 - * @return void - */ - protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') - { - if (is_array($rule)) { - $name = $rule[0]; - $rule = $rule[1]; - } elseif (is_string($route)) { - $name = $route; - } - if (!isset($option['complete_match'])) { - if (Config::get('route_complete_match')) { - $option['complete_match'] = true; - } elseif ('$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { - // 是否完整匹配 - $option['complete_match'] = true; - } - - if ('$' == substr($rule, -1, 1)) { - $rule = substr($rule, 0, -1); - } - - if ('/' != $rule || $group) { - $rule = trim($rule, '/'); - } - $vars = self::parseVar($rule); - if (isset($name)) { - $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; - $suffix = isset($option['ext']) ? $option['ext'] : null; - self::name($name, [$key, $vars, self::$domain, $suffix]); - } - if (isset($option['modular'])) { - $route = $option['modular'] . '/' . $route; - } - if ($group) { - if ('*' != $type) { - $option['method'] = $type; - } - if (self::$domain) { - self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - } else { - if ('*' != $type && isset(self::$rules['*'][$rule])) { - unset(self::$rules['*'][$rule]); - } - if (self::$domain) { - self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } else { - self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; - } - if ('*' == $type) { - // 注册路由快捷方式 - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { - self::$rules['domain'][self::$domain][$method][$rule] = true; - } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { - self::$rules[$method][$rule] = true; - } - } - } - } - } - - /** - * 设置当前执行的参数信息 - * @access public - * @param array $options 参数信息 - * @return mixed - */ - protected static function setOption($options = []) - { - self::$option[] = $options; - } - - /** - * 获取当前执行的所有参数信息 - * @access public - * @return array - */ - public static function getOption() - { - return self::$option; - } - - /** - * 获取当前的分组信息 - * @access public - * @param string $type 分组信息名称 name option pattern - * @return mixed - */ - public static function getGroup($type) - { - if (isset(self::$group[$type])) { - return self::$group[$type]; - } else { - return 'name' == $type ? null : []; - } - } - - /** - * 设置当前的路由分组 - * @access public - * @param string $name 分组名称 - * @param array $option 分组路由参数 - * @param array $pattern 分组变量规则 - * @return void - */ - public static function setGroup($name, $option = [], $pattern = []) - { - self::$group['name'] = $name; - self::$group['option'] = $option ?: []; - self::$group['pattern'] = $pattern ?: []; - } - - /** - * 注册路由分组 - * @access public - * @param string|array $name 分组名称或者参数 - * @param array|\Closure $routes 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function group($name, $routes, $option = [], $pattern = []) - { - if (is_array($name)) { - $option = $name; - $name = isset($option['name']) ? $option['name'] : ''; - } - // 分组 - $currentGroup = self::getGroup('name'); - if ($currentGroup) { - $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); - } - if (!empty($name)) { - if ($routes instanceof \Closure) { - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - if ($currentGroup != $name) { - self::$rules['*'][$name]['route'] = ''; - self::$rules['*'][$name]['var'] = self::parseVar($name); - self::$rules['*'][$name]['option'] = $option; - self::$rules['*'][$name]['pattern'] = $pattern; - } - } else { - $item = []; - $completeMatch = Config::get('route_complete_match'); - foreach ($routes as $key => $val) { - if (is_numeric($key)) { - $key = array_shift($val); - } - if (is_array($val)) { - $route = $val[0]; - $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); - $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); - } else { - $route = $val; - } - - $options = isset($option1) ? $option1 : $option; - $patterns = isset($pattern1) ? $pattern1 : $pattern; - if ('$' == substr($key, -1, 1)) { - // 是否完整匹配 - $options['complete_match'] = true; - $key = substr($key, 0, -1); - } elseif ($completeMatch) { - $options['complete_match'] = true; - } - $key = trim($key, '/'); - $vars = self::parseVar($key); - $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; - // 设置路由标识 - $suffix = isset($options['ext']) ? $options['ext'] : null; - self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); - } - self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; - } - - foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { - if (!isset(self::$rules[$method][$name])) { - self::$rules[$method][$name] = true; - } elseif (is_array(self::$rules[$method][$name])) { - self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); - } - } - - } elseif ($routes instanceof \Closure) { - // 闭包注册 - $currentOption = self::getGroup('option'); - $currentPattern = self::getGroup('pattern'); - self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); - call_user_func_array($routes, []); - self::setGroup($currentGroup, $currentOption, $currentPattern); - } else { - // 批量注册路由 - self::rule($routes, '', '*', $option, $pattern); - } - } - - /** - * 注册路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function any($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, '*', $option, $pattern); - } - - /** - * 注册GET路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function get($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'GET', $option, $pattern); - } - - /** - * 注册POST路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function post($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'POST', $option, $pattern); - } - - /** - * 注册PUT路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function put($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'PUT', $option, $pattern); - } - - /** - * 注册DELETE路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function delete($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'DELETE', $option, $pattern); - } - - /** - * 注册PATCH路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function patch($rule, $route = '', $option = [], $pattern = []) - { - self::rule($rule, $route, 'PATCH', $option, $pattern); - } - - /** - * 注册资源路由 - * @access public - * @param string|array $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function resource($rule, $route = '', $option = [], $pattern = []) - { - if (is_array($rule)) { - foreach ($rule as $key => $val) { - if (is_array($val)) { - list($val, $option, $pattern) = array_pad($val, 3, []); - } - self::resource($key, $val, $option, $pattern); - } - } else { - if (strpos($rule, '.')) { - // 注册嵌套资源路由 - $array = explode('.', $rule); - $last = array_pop($array); - $item = []; - foreach ($array as $val) { - $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); - } - $rule = implode('/', $item) . '/' . $last; - } - // 注册资源路由 - foreach (self::$rest as $key => $val) { - if ((isset($option['only']) && !in_array($key, $option['only'])) - || (isset($option['except']) && in_array($key, $option['except']))) { - continue; - } - if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { - $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); - } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { - $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); - } - $item = ltrim($rule . $val[1], '/'); - $option['rest'] = $key; - self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); - } - } - } - - /** - * 注册控制器路由 操作方法对应不同的请求后缀 - * @access public - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @param array $pattern 变量规则 - * @return void - */ - public static function controller($rule, $route = '', $option = [], $pattern = []) - { - foreach (self::$methodPrefix as $type => $val) { - self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); - } - } - - /** - * 注册别名路由 - * @access public - * @param string|array $rule 路由别名 - * @param string $route 路由地址 - * @param array $option 路由参数 - * @return void - */ - public static function alias($rule = null, $route = '', $option = []) - { - if (is_array($rule)) { - self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); - } else { - self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; - } - } - - /** - * 设置不同请求类型下面的方法前缀 - * @access public - * @param string $method 请求类型 - * @param string $prefix 类型前缀 - * @return void - */ - public static function setMethodPrefix($method, $prefix = '') - { - if (is_array($method)) { - self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); - } else { - self::$methodPrefix[strtolower($method)] = $prefix; - } - } - - /** - * rest方法定义和修改 - * @access public - * @param string|array $name 方法名称 - * @param array|bool $resource 资源 - * @return void - */ - public static function rest($name, $resource = []) - { - if (is_array($name)) { - self::$rest = $resource ? $name : array_merge(self::$rest, $name); - } else { - self::$rest[$name] = $resource; - } - } - - /** - * 注册未匹配路由规则后的处理 - * @access public - * @param string $route 路由地址 - * @param string $method 请求类型 - * @param array $option 路由参数 - * @return void - */ - public static function miss($route, $method = '*', $option = []) - { - self::rule('__miss__', $route, $method, $option, []); - } - - /** - * 注册一个自动解析的URL路由 - * @access public - * @param string $route 路由地址 - * @return void - */ - public static function auto($route) - { - self::rule('__auto__', $route, '*', [], []); - } - - /** - * 获取或者批量设置路由定义 - * @access public - * @param mixed $rules 请求类型或者路由定义数组 - * @return array - */ - public static function rules($rules = '') - { - if (is_array($rules)) { - self::$rules = $rules; - } elseif ($rules) { - return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; - } else { - $rules = self::$rules; - unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); - return $rules; - } - } - - /** - * 检测子域名部署 - * @access public - * @param Request $request Request请求对象 - * @param array $currentRules 当前路由规则 - * @param string $method 请求类型 - * @return void - */ - public static function checkDomain($request, &$currentRules, $method = 'get') - { - // 域名规则 - $rules = self::$rules['domain']; - // 开启子域名部署 支持二级和三级域名 - if (!empty($rules)) { - $host = $request->host(true); - if (isset($rules[$host])) { - // 完整域名或者IP配置 - $item = $rules[$host]; - } else { - $rootDomain = Config::get('url_domain_root'); - if ($rootDomain) { - // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 - $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); - } else { - $domain = explode('.', $host, -2); - } - // 子域名配置 - if (!empty($domain)) { - // 当前子域名 - $subDomain = implode('.', $domain); - self::$subDomain = $subDomain; - $domain2 = array_pop($domain); - if ($domain) { - // 存在三级域名 - $domain3 = array_pop($domain); - } - if ($subDomain && isset($rules[$subDomain])) { - // 子域名配置 - $item = $rules[$subDomain]; - } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { - // 泛三级域名 - $item = $rules['*.' . $domain2]; - $panDomain = $domain3; - } elseif (isset($rules['*']) && !empty($domain2)) { - // 泛二级域名 - if ('www' != $domain2) { - $item = $rules['*']; - $panDomain = $domain2; - } - } - } - } - if (!empty($item)) { - if (isset($panDomain)) { - // 保存当前泛域名 - $request->route(['__domain__' => $panDomain]); - } - if (isset($item['[bind]'])) { - // 解析子域名部署规则 - list($rule, $option, $pattern) = $item['[bind]']; - if (!empty($option['https']) && !$request->isSsl()) { - // https检测 - throw new HttpException(404, 'must use https request:' . $host); - } - - if (strpos($rule, '?')) { - // 传入其它参数 - $array = parse_url($rule); - $result = $array['path']; - parse_str($array['query'], $params); - if (isset($panDomain)) { - $pos = array_search('*', $params); - if (false !== $pos) { - // 泛域名作为参数 - $params[$pos] = $panDomain; - } - } - $_GET = array_merge($_GET, $params); - } else { - $result = $rule; - } - - if (0 === strpos($result, '\\')) { - // 绑定到命名空间 例如 \app\index\behavior - self::$bind = ['type' => 'namespace', 'namespace' => $result]; - } elseif (0 === strpos($result, '@')) { - // 绑定到类 例如 @app\index\controller\User - self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; - } else { - // 绑定到模块/控制器 例如 index/user - self::$bind = ['type' => 'module', 'module' => $result]; - } - self::$domainBind = true; - } else { - self::$domainRule = $item; - $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; - } - } - } - } - - /** - * 检测URL路由 - * @access public - * @param Request $request Request请求对象 - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $checkDomain 是否检测域名规则 - * @return false|array - */ - public static function check($request, $url, $depr = '/', $checkDomain = false) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace($depr, '|', $url); - - if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { - // 检测路由别名 - $result = self::checkRouteAlias($request, $url, $depr); - if (false !== $result) { - return $result; - } - } - $method = strtolower($request->method()); - // 获取当前请求类型的路由规则 - $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; - // 检测域名部署 - if ($checkDomain) { - self::checkDomain($request, $rules, $method); - } - // 检测URL绑定 - $return = self::checkUrlBind($url, $rules, $depr); - if (false !== $return) { - return $return; - } - if ('|' != $url) { - $url = rtrim($url, '|'); - } - $item = str_replace('|', '/', $url); - if (isset($rules[$item])) { - // 静态路由规则检测 - $rule = $rules[$item]; - if (true === $rule) { - $rule = self::getRouteExpress($item); - } - if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { - self::setOption($rule['option']); - return self::parseRule($item, $rule['route'], $url, $rule['option']); - } - } - - // 路由规则检测 - if (!empty($rules)) { - return self::checkRoute($request, $rules, $url, $depr); - } - return false; - } - - private static function getRouteExpress($key) - { - return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; - } - - /** - * 检测路由规则 - * @access private - * @param Request $request - * @param array $rules 路由规则 - * @param string $url URL地址 - * @param string $depr URL分割符 - * @param string $group 路由分组名 - * @param array $options 路由参数(分组) - * @return mixed - */ - private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) - { - foreach ($rules as $key => $item) { - if (true === $item) { - $item = self::getRouteExpress($key); - } - if (!isset($item['rule'])) { - continue; - } - $rule = $item['rule']; - $route = $item['route']; - $vars = $item['var']; - $option = $item['option']; - $pattern = $item['pattern']; - - // 检查参数有效性 - if (!self::checkOption($option, $request)) { - continue; - } - - if (isset($option['ext'])) { - // 路由ext参数 优先于系统配置的URL伪静态后缀参数 - $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); - } - - if (is_array($rule)) { - // 分组路由 - $pos = strpos(str_replace('<', ':', $key), ':'); - if (false !== $pos) { - $str = substr($key, 0, $pos); - } else { - $str = $key; - } - if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { - continue; - } - self::setOption($option); - $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); - if (false !== $result) { - return $result; - } - } elseif ($route) { - if ('__miss__' == $rule || '__auto__' == $rule) { - // 指定特殊路由 - $var = trim($rule, '__'); - ${$var} = $item; - continue; - } - if ($group) { - $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); - } - - self::setOption($option); - if (isset($options['bind_model']) && isset($option['bind_model'])) { - $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); - } - $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); - if (false !== $result) { - return $result; - } - } - } - if (isset($auto)) { - // 自动解析URL地址 - return self::parseUrl($auto['route'] . '/' . $url, $depr); - } elseif (isset($miss)) { - // 未匹配所有路由的路由规则处理 - return self::parseRule('', $miss['route'], $url, $miss['option']); - } - return false; - } - - /** - * 检测路由别名 - * @access private - * @param Request $request - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkRouteAlias($request, $url, $depr) - { - $array = explode('|', $url); - $alias = array_shift($array); - $item = self::$rules['alias'][$alias]; - - if (is_array($item)) { - list($rule, $option) = $item; - $action = $array[0]; - if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { - // 允许操作 - return false; - } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { - // 排除操作 - return false; - } - if (isset($option['method'][$action])) { - $option['method'] = $option['method'][$action]; - } - } else { - $rule = $item; - } - $bind = implode('|', $array); - // 参数有效性检查 - if (isset($option) && !self::checkOption($option, $request)) { - // 路由不匹配 - return false; - } elseif (0 === strpos($rule, '\\')) { - // 路由到类 - return self::bindToClass($bind, substr($rule, 1), $depr); - } elseif (0 === strpos($rule, '@')) { - // 路由到控制器类 - return self::bindToController($bind, substr($rule, 1), $depr); - } else { - // 路由到模块/控制器 - return self::bindToModule($bind, $rule, $depr); - } - } - - /** - * 检测URL绑定 - * @access private - * @param string $url URL地址 - * @param array $rules 路由规则 - * @param string $depr URL分隔符 - * @return mixed - */ - private static function checkUrlBind(&$url, &$rules, $depr = '/') - { - if (!empty(self::$bind)) { - $type = self::$bind['type']; - $bind = self::$bind[$type]; - // 记录绑定信息 - App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); - // 如果有URL绑定 则进行绑定检测 - switch ($type) { - case 'class': - // 绑定到类 - return self::bindToClass($url, $bind, $depr); - case 'controller': - // 绑定到控制器类 - return self::bindToController($url, $bind, $depr); - case 'namespace': - // 绑定到命名空间 - return self::bindToNamespace($url, $bind, $depr); - } - } - return false; - } - - /** - * 绑定到类 - * @access public - * @param string $url URL地址 - * @param string $class 类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToClass($url, $class, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; - } - - /** - * 绑定到命名空间 - * @access public - * @param string $url URL地址 - * @param string $namespace 命名空间 - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToNamespace($url, $namespace, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 3); - $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); - $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); - if (!empty($array[2])) { - self::parseUrlParams($array[2]); - } - return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; - } - - /** - * 绑定到控制器类 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器名 (支持带模块名 index/user ) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToController($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; - } - - /** - * 绑定到模块/控制器 - * @access public - * @param string $url URL地址 - * @param string $controller 控制器类名(带命名空间) - * @param string $depr URL分隔符 - * @return array - */ - public static function bindToModule($url, $controller, $depr = '/') - { - $url = str_replace($depr, '|', $url); - $array = explode('|', $url, 2); - $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); - if (!empty($array[1])) { - self::parseUrlParams($array[1]); - } - return ['type' => 'module', 'module' => $controller . '/' . $action]; - } - - /** - * 路由参数有效性检查 - * @access private - * @param array $option 路由参数 - * @param Request $request Request对象 - * @return bool - */ - private static function checkOption($option, $request) - { - if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) - || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 - || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 - || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 - || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 - || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 - || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) - || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 - || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 - || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 - || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 - || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 - ) { - return false; - } - return true; - } - - /** - * 检测路由规则 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $url URL地址 - * @param array $pattern 变量规则 - * @param array $option 路由参数 - * @param string $depr URL分隔符(全局) - * @return array|false - */ - private static function checkRule($rule, $route, $url, $pattern, $option, $depr) - { - // 检查完整规则定义 - if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { - return false; - } - // 检查路由的参数分隔符 - if (isset($option['param_depr'])) { - $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); - } - - $len1 = substr_count($url, '|'); - $len2 = substr_count($rule, '/'); - // 多余参数是否合并 - $merge = !empty($option['merge_extra_vars']); - if ($merge && $len1 > $len2) { - $url = str_replace('|', $depr, $url); - $url = implode('|', explode($depr, $url, $len2 + 1)); - } - - if ($len1 >= $len2 || strpos($rule, '[')) { - if (!empty($option['complete_match'])) { - // 完整匹配 - if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { - return false; - } - } - $pattern = array_merge(self::$rules['pattern'], $pattern); - if (false !== $match = self::match($url, $rule, $pattern)) { - // 匹配到路由规则 - return self::parseRule($rule, $route, $url, $option, $match); - } - } - return false; - } - - /** - * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... - * @access public - * @param string $url URL地址 - * @param string $depr URL分隔符 - * @param bool $autoSearch 是否自动深度搜索控制器 - * @return array - */ - public static function parseUrl($url, $depr = '/', $autoSearch = false) - { - - if (isset(self::$bind['module'])) { - $bind = str_replace('/', $depr, self::$bind['module']); - // 如果有模块/控制器绑定 - $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); - } - $url = str_replace($depr, '|', $url); - list($path, $var) = self::parseUrlPath($url); - $route = [null, null, null]; - if (isset($path)) { - // 解析模块 - $module = Config::get('app_multi_module') ? array_shift($path) : null; - if ($autoSearch) { - // 自动搜索控制器 - $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); - $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; - $item = []; - $find = false; - foreach ($path as $val) { - $item[] = $val; - $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; - $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; - if (is_file($file)) { - $find = true; - break; - } else { - $dir .= DS . Loader::parseName($val); - } - } - if ($find) { - $controller = implode('.', $item); - $path = array_slice($path, count($item)); - } else { - $controller = array_shift($path); - } - } else { - // 解析控制器 - $controller = !empty($path) ? array_shift($path) : null; - } - // 解析操作 - $action = !empty($path) ? array_shift($path) : null; - // 解析额外参数 - self::parseUrlParams(empty($path) ? '' : implode('|', $path)); - // 封装路由 - $route = [$module, $controller, $action]; - // 检查地址是否被定义过路由 - $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); - $name2 = ''; - if (empty($module) || isset($bind) && $module == $bind) { - $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); - } - - if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { - throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); - } - } - return ['type' => 'module', 'module' => $route]; - } - - /** - * 解析URL的pathinfo参数和变量 - * @access private - * @param string $url URL地址 - * @return array - */ - private static function parseUrlPath($url) - { - // 分隔符替换 确保路由定义使用统一的分隔符 - $url = str_replace('|', '/', $url); - $url = trim($url, '/'); - $var = []; - if (false !== strpos($url, '?')) { - // [模块/控制器/操作?]参数1=值1&参数2=值2... - $info = parse_url($url); - $path = explode('/', $info['path']); - parse_str($info['query'], $var); - } elseif (strpos($url, '/')) { - // [模块/控制器/操作] - $path = explode('/', $url); - } else { - $path = [$url]; - } - return [$path, $var]; - } - - /** - * 检测URL和规则路由是否匹配 - * @access private - * @param string $url URL地址 - * @param string $rule 路由规则 - * @param array $pattern 变量规则 - * @return array|false - */ - private static function match($url, $rule, $pattern) - { - $m2 = explode('/', $rule); - $m1 = explode('|', $url); - - $var = []; - foreach ($m2 as $key => $val) { - // val中定义了多个变量 - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - $value = []; - $replace = []; - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; - } else { - $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; - } - $value[] = $name; - } - $val = str_replace($matches[0], $replace, $val); - if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { - array_shift($match); - foreach ($value as $k => $name) { - if (isset($match[$k])) { - $var[$name] = $match[$k]; - } - } - continue; - } else { - return false; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $val = substr($val, 1, -1); - $optional = true; - } else { - $optional = false; - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - if (!$optional && !isset($m1[$key])) { - return false; - } - if (isset($m1[$key]) && isset($pattern[$name])) { - // 检查变量规则 - if ($pattern[$name] instanceof \Closure) { - $result = call_user_func_array($pattern[$name], [$m1[$key]]); - if (false === $result) { - return false; - } - } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { - return false; - } - } - $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; - } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { - return false; - } - } - // 成功匹配后返回URL中的动态变量数组 - return $var; - } - - /** - * 解析规则路由 - * @access private - * @param string $rule 路由规则 - * @param string $route 路由地址 - * @param string $pathinfo URL地址 - * @param array $option 路由参数 - * @param array $matches 匹配的变量 - * @return array - */ - private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = []) - { - $request = Request::instance(); - // 解析路由规则 - if ($rule) { - $rule = explode('/', $rule); - // 获取URL地址中的参数 - $paths = explode('|', $pathinfo); - foreach ($rule as $item) { - $fun = ''; - if (0 === strpos($item, '[:')) { - $item = substr($item, 1, -1); - } - if (0 === strpos($item, ':')) { - $var = substr($item, 1); - $matches[$var] = array_shift($paths); - } else { - // 过滤URL中的静态变量 - array_shift($paths); - } - } - } else { - $paths = explode('|', $pathinfo); - } - - // 获取路由地址规则 - if (is_string($route) && isset($option['prefix'])) { - // 路由地址前缀 - $route = $option['prefix'] . $route; - } - // 替换路由地址中的变量 - if (is_string($route) && !empty($matches)) { - foreach ($matches as $key => $val) { - if (false !== strpos($route, ':' . $key)) { - $route = str_replace(':' . $key, $val, $route); - } - } - } - - // 绑定模型数据 - if (isset($option['bind_model'])) { - $bind = []; - foreach ($option['bind_model'] as $key => $val) { - if ($val instanceof \Closure) { - $result = call_user_func_array($val, [$matches]); - } else { - if (is_array($val)) { - $fields = explode('&', $val[1]); - $model = $val[0]; - $exception = isset($val[2]) ? $val[2] : true; - } else { - $fields = ['id']; - $model = $val; - $exception = true; - } - $where = []; - $match = true; - foreach ($fields as $field) { - if (!isset($matches[$field])) { - $match = false; - break; - } else { - $where[$field] = $matches[$field]; - } - } - if ($match) { - $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); - $result = $query->failException($exception)->find(); - } - } - if (!empty($result)) { - $bind[$key] = $result; - } - } - $request->bind($bind); - } - - if (!empty($option['response'])) { - Hook::add('response_send', $option['response']); - } - - // 解析额外参数 - self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); - // 记录匹配的路由信息 - $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); - - // 检测路由after行为 - if (!empty($option['after_behavior'])) { - if ($option['after_behavior'] instanceof \Closure) { - $result = call_user_func_array($option['after_behavior'], []); - } else { - foreach ((array) $option['after_behavior'] as $behavior) { - $result = Hook::exec($behavior, ''); - if (!is_null($result)) { - break; - } - } - } - // 路由规则重定向 - if ($result instanceof Response) { - return ['type' => 'response', 'response' => $result]; - } elseif (is_array($result)) { - return $result; - } - } - - if ($route instanceof \Closure) { - // 执行闭包 - $result = ['type' => 'function', 'function' => $route]; - } elseif (0 === strpos($route, '/') || strpos($route, '://')) { - // 路由到重定向地址 - $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; - } elseif (false !== strpos($route, '\\')) { - // 路由到方法 - list($path, $var) = self::parseUrlPath($route); - $route = str_replace('/', '@', implode('/', $path)); - $method = strpos($route, '@') ? explode('@', $route) : $route; - $result = ['type' => 'method', 'method' => $method, 'var' => $var]; - } elseif (0 === strpos($route, '@')) { - // 路由到控制器 - $route = substr($route, 1); - list($route, $var) = self::parseUrlPath($route); - $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; - $request->action(array_pop($route)); - $request->controller($route ? array_pop($route) : Config::get('default_controller')); - $request->module($route ? array_pop($route) : Config::get('default_module')); - App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); - } else { - // 路由到模块/控制器/操作 - $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); - } - // 开启请求缓存 - if ($request->isGet() && isset($option['cache'])) { - $cache = $option['cache']; - if (is_array($cache)) { - list($key, $expire, $tag) = array_pad($cache, 3, null); - } else { - $key = str_replace('|', '/', $pathinfo); - $expire = $cache; - $tag = null; - } - $request->cache($key, $expire, $tag); - } - return $result; - } - - /** - * 解析URL地址为 模块/控制器/操作 - * @access private - * @param string $url URL地址 - * @param bool $convert 是否自动转换URL地址 - * @return array - */ - private static function parseModule($url, $convert = false) - { - list($path, $var) = self::parseUrlPath($url); - $action = array_pop($path); - $controller = !empty($path) ? array_pop($path) : null; - $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; - $method = Request::instance()->method(); - if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { - // 操作方法前缀支持 - $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; - } - // 设置当前请求的路由变量 - Request::instance()->route($var); - // 路由到模块/控制器/操作 - return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; - } - - /** - * 解析URL地址中的参数Request对象 - * @access private - * @param string $url 路由规则 - * @param array $var 变量 - * @return void - */ - private static function parseUrlParams($url, &$var = []) - { - if ($url) { - if (Config::get('url_param_type')) { - $var += explode('|', $url); - } else { - preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { - $var[$match[1]] = strip_tags($match[2]); - }, $url); - } - } - // 设置当前请求的参数 - Request::instance()->route($var); - } - - // 分析路由规则中的变量 - private static function parseVar($rule) - { - // 提取路由规则中的变量 - $var = []; - foreach (explode('/', $rule) as $val) { - $optional = false; - if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { - foreach ($matches[1] as $name) { - if (strpos($name, '?')) { - $name = substr($name, 0, -1); - $optional = true; - } else { - $optional = false; - } - $var[$name] = $optional ? 2 : 1; - } - } - - if (0 === strpos($val, '[:')) { - // 可选参数 - $optional = true; - $val = substr($val, 1, -1); - } - if (0 === strpos($val, ':')) { - // URL变量 - $name = substr($val, 1); - $var[$name] = $optional ? 2 : 1; - } - } - return $var; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\HttpException; + +class Route +{ + // 路由规则 + private static $rules = [ + 'get' => [], + 'post' => [], + 'put' => [], + 'delete' => [], + 'patch' => [], + 'head' => [], + 'options' => [], + '*' => [], + 'alias' => [], + 'domain' => [], + 'pattern' => [], + 'name' => [], + ]; + + // REST路由操作方法定义 + private static $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '/:id/edit', 'edit'], + 'read' => ['get', '/:id', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/:id', 'update'], + 'delete' => ['delete', '/:id', 'delete'], + ]; + + // 不同请求类型的方法前缀 + private static $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + // 子域名 + private static $subDomain = ''; + // 域名绑定 + private static $bind = []; + // 当前分组信息 + private static $group = []; + // 当前子域名绑定 + private static $domainBind; + private static $domainRule; + // 当前域名 + private static $domain; + // 当前路由执行过程中的参数 + private static $option = []; + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return void + */ + public static function pattern($name = null, $rule = '') + { + if (is_array($name)) { + self::$rules['pattern'] = array_merge(self::$rules['pattern'], $name); + } else { + self::$rules['pattern'][$name] = $rule; + } + } + + /** + * 注册子域名部署规则 + * @access public + * @param string|array $domain 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function domain($domain, $rule = '', $option = [], $pattern = []) + { + if (is_array($domain)) { + foreach ($domain as $key => $item) { + self::domain($key, $item, $option, $pattern); + } + } elseif ($rule instanceof \Closure) { + // 执行闭包 + self::setDomain($domain); + call_user_func_array($rule, []); + self::setDomain(null); + } elseif (is_array($rule)) { + self::setDomain($domain); + self::group('', function () use ($rule) { + // 动态注册域名的路由规则 + self::registerRules($rule); + }, $option, $pattern); + self::setDomain(null); + } else { + self::$rules['domain'][$domain]['[bind]'] = [$rule, $option, $pattern]; + } + } + + private static function setDomain($domain) + { + self::$domain = $domain; + } + + /** + * 设置路由绑定 + * @access public + * @param mixed $bind 绑定信息 + * @param string $type 绑定类型 默认为module 支持 namespace class controller + * @return mixed + */ + public static function bind($bind, $type = 'module') + { + self::$bind = ['type' => $type, $type => $bind]; + } + + /** + * 设置或者获取路由标识 + * @access public + * @param string|array $name 路由命名标识 数组表示批量设置 + * @param array $value 路由地址及变量信息 + * @return array + */ + public static function name($name = '', $value = null) + { + if (is_array($name)) { + return self::$rules['name'] = $name; + } elseif ('' === $name) { + return self::$rules['name']; + } elseif (!is_null($value)) { + self::$rules['name'][strtolower($name)][] = $value; + } else { + $name = strtolower($name); + return isset(self::$rules['name'][$name]) ? self::$rules['name'][$name] : null; + } + } + + /** + * 读取路由绑定 + * @access public + * @param string $type 绑定类型 + * @return mixed + */ + public static function getBind($type) + { + return isset(self::$bind[$type]) ? self::$bind[$type] : null; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rule 路由规则 + * @param string $type 请求类型 + * @return void + */ + public static function import(array $rule, $type = '*') + { + // 检查域名部署 + if (isset($rule['__domain__'])) { + self::domain($rule['__domain__']); + unset($rule['__domain__']); + } + + // 检查变量规则 + if (isset($rule['__pattern__'])) { + self::pattern($rule['__pattern__']); + unset($rule['__pattern__']); + } + + // 检查路由别名 + if (isset($rule['__alias__'])) { + self::alias($rule['__alias__']); + unset($rule['__alias__']); + } + + // 检查资源路由 + if (isset($rule['__rest__'])) { + self::resource($rule['__rest__']); + unset($rule['__rest__']); + } + + self::registerRules($rule, strtolower($type)); + } + + // 批量注册路由 + protected static function registerRules($rules, $type = '*') + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (empty($val)) { + continue; + } + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + self::group($key, $val); + } elseif (is_array($val)) { + self::setRule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + self::setRule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function rule($rule, $route = '', $type = '*', $option = [], $pattern = []) + { + $group = self::getGroup('name'); + + if (!is_null($group)) { + // 路由分组 + $option = array_merge(self::getGroup('option'), $option); + $pattern = array_merge(self::getGroup('pattern'), $pattern); + } + + $type = strtolower($type); + + if (strpos($type, '|')) { + $option['method'] = $type; + $type = '*'; + } + if (is_array($rule) && empty($route)) { + foreach ($rule as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, $val[1]); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $option1 = null; + $pattern1 = null; + $route = $val; + } + self::setRule($key, $route, $type, !is_null($option1) ? $option1 : $option, !is_null($pattern1) ? $pattern1 : $pattern, $group); + } + } else { + self::setRule($rule, $route, $type, $option, $pattern, $group); + } + + } + + /** + * 设置路由规则 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param string $type 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param string $group 所属分组 + * @return void + */ + protected static function setRule($rule, $route, $type = '*', $option = [], $pattern = [], $group = '') + { + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } + if (!isset($option['complete_match'])) { + if (Config::get('route_complete_match')) { + $option['complete_match'] = true; + } elseif ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + } elseif (empty($option['complete_match']) && '$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $option['complete_match'] = true; + } + + if ('$' == substr($rule, -1, 1)) { + $rule = substr($rule, 0, -1); + } + + if ('/' != $rule || $group) { + $rule = trim($rule, '/'); + } + $vars = self::parseVar($rule); + if (isset($name)) { + $key = $group ? $group . ($rule ? '/' . $rule : '') : $rule; + $suffix = isset($option['ext']) ? $option['ext'] : null; + self::name($name, [$key, $vars, self::$domain, $suffix]); + } + if (isset($option['modular'])) { + $route = $option['modular'] . '/' . $route; + } + if ($group) { + if ('*' != $type) { + $option['method'] = $type; + } + if (self::$domain) { + self::$rules['domain'][self::$domain]['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules['*'][$group]['rule'][] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + } else { + if ('*' != $type && isset(self::$rules['*'][$rule])) { + unset(self::$rules['*'][$rule]); + } + if (self::$domain) { + self::$rules['domain'][self::$domain][$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } else { + self::$rules[$type][$rule] = ['rule' => $rule, 'route' => $route, 'var' => $vars, 'option' => $option, 'pattern' => $pattern]; + } + if ('*' == $type) { + // 注册路由快捷方式 + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (self::$domain && !isset(self::$rules['domain'][self::$domain][$method][$rule])) { + self::$rules['domain'][self::$domain][$method][$rule] = true; + } elseif (!self::$domain && !isset(self::$rules[$method][$rule])) { + self::$rules[$method][$rule] = true; + } + } + } + } + } + + /** + * 设置当前执行的参数信息 + * @access public + * @param array $options 参数信息 + * @return mixed + */ + protected static function setOption($options = []) + { + self::$option[] = $options; + } + + /** + * 获取当前执行的所有参数信息 + * @access public + * @return array + */ + public static function getOption() + { + return self::$option; + } + + /** + * 获取当前的分组信息 + * @access public + * @param string $type 分组信息名称 name option pattern + * @return mixed + */ + public static function getGroup($type) + { + if (isset(self::$group[$type])) { + return self::$group[$type]; + } else { + return 'name' == $type ? null : []; + } + } + + /** + * 设置当前的路由分组 + * @access public + * @param string $name 分组名称 + * @param array $option 分组路由参数 + * @param array $pattern 分组变量规则 + * @return void + */ + public static function setGroup($name, $option = [], $pattern = []) + { + self::$group['name'] = $name; + self::$group['option'] = $option ?: []; + self::$group['pattern'] = $pattern ?: []; + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $routes 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function group($name, $routes, $option = [], $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + // 分组 + $currentGroup = self::getGroup('name'); + if ($currentGroup) { + $name = $currentGroup . ($name ? '/' . ltrim($name, '/') : ''); + } + if (!empty($name)) { + if ($routes instanceof \Closure) { + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup($name, array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + if ($currentGroup != $name) { + self::$rules['*'][$name]['route'] = ''; + self::$rules['*'][$name]['var'] = self::parseVar($name); + self::$rules['*'][$name]['option'] = $option; + self::$rules['*'][$name]['pattern'] = $pattern; + } + } else { + $item = []; + $completeMatch = Config::get('route_complete_match'); + foreach ($routes as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + if (is_array($val)) { + $route = $val[0]; + $option1 = array_merge($option, isset($val[1]) ? $val[1] : []); + $pattern1 = array_merge($pattern, isset($val[2]) ? $val[2] : []); + } else { + $route = $val; + } + + $options = isset($option1) ? $option1 : $option; + $patterns = isset($pattern1) ? $pattern1 : $pattern; + if ('$' == substr($key, -1, 1)) { + // 是否完整匹配 + $options['complete_match'] = true; + $key = substr($key, 0, -1); + } elseif ($completeMatch) { + $options['complete_match'] = true; + } + $key = trim($key, '/'); + $vars = self::parseVar($key); + $item[] = ['rule' => $key, 'route' => $route, 'var' => $vars, 'option' => $options, 'pattern' => $patterns]; + // 设置路由标识 + $suffix = isset($options['ext']) ? $options['ext'] : null; + self::name($route, [$name . ($key ? '/' . $key : ''), $vars, self::$domain, $suffix]); + } + self::$rules['*'][$name] = ['rule' => $item, 'route' => '', 'var' => [], 'option' => $option, 'pattern' => $pattern]; + } + + foreach (['get', 'post', 'put', 'delete', 'patch', 'head', 'options'] as $method) { + if (!isset(self::$rules[$method][$name])) { + self::$rules[$method][$name] = true; + } elseif (is_array(self::$rules[$method][$name])) { + self::$rules[$method][$name] = array_merge(self::$rules['*'][$name], self::$rules[$method][$name]); + } + } + + } elseif ($routes instanceof \Closure) { + // 闭包注册 + $currentOption = self::getGroup('option'); + $currentPattern = self::getGroup('pattern'); + self::setGroup('', array_merge($currentOption, $option), array_merge($currentPattern, $pattern)); + call_user_func_array($routes, []); + self::setGroup($currentGroup, $currentOption, $currentPattern); + } else { + // 批量注册路由 + self::rule($routes, '', '*', $option, $pattern); + } + } + + /** + * 注册路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function any($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function get($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function post($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function put($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function delete($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function patch($rule, $route = '', $option = [], $pattern = []) + { + self::rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function resource($rule, $route = '', $option = [], $pattern = []) + { + if (is_array($rule)) { + foreach ($rule as $key => $val) { + if (is_array($val)) { + list($val, $option, $pattern) = array_pad($val, 3, []); + } + self::resource($key, $val, $option, $pattern); + } + } else { + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + foreach ($array as $val) { + $item[] = $val . '/:' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id'); + } + $rule = implode('/', $item) . '/' . $last; + } + // 注册资源路由 + foreach (self::$rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + if (isset($last) && strpos($val[1], ':id') && isset($option['var'][$last])) { + $val[1] = str_replace(':id', ':' . $option['var'][$last], $val[1]); + } elseif (strpos($val[1], ':id') && isset($option['var'][$rule])) { + $val[1] = str_replace(':id', ':' . $option['var'][$rule], $val[1]); + } + $item = ltrim($rule . $val[1], '/'); + $option['rest'] = $key; + self::rule($item . '$', $route . '/' . $val[2], $val[0], $option, $pattern); + } + } + } + + /** + * 注册控制器路由 操作方法对应不同的请求后缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public static function controller($rule, $route = '', $option = [], $pattern = []) + { + foreach (self::$methodPrefix as $type => $val) { + self::$type($rule . '/:action', $route . '/' . $val . ':action', $option, $pattern); + } + } + + /** + * 注册别名路由 + * @access public + * @param string|array $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return void + */ + public static function alias($rule = null, $route = '', $option = []) + { + if (is_array($rule)) { + self::$rules['alias'] = array_merge(self::$rules['alias'], $rule); + } else { + self::$rules['alias'][$rule] = $option ? [$route, $option] : $route; + } + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return void + */ + public static function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + self::$methodPrefix = array_merge(self::$methodPrefix, array_change_key_case($method)); + } else { + self::$methodPrefix[strtolower($method)] = $prefix; + } + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return void + */ + public static function rest($name, $resource = []) + { + if (is_array($name)) { + self::$rest = $resource ? $name : array_merge(self::$rest, $name); + } else { + self::$rest[$name] = $resource; + } + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return void + */ + public static function miss($route, $method = '*', $option = []) + { + self::rule('__miss__', $route, $method, $option, []); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return void + */ + public static function auto($route) + { + self::rule('__auto__', $route, '*', [], []); + } + + /** + * 获取或者批量设置路由定义 + * @access public + * @param mixed $rules 请求类型或者路由定义数组 + * @return array + */ + public static function rules($rules = '') + { + if (is_array($rules)) { + self::$rules = $rules; + } elseif ($rules) { + return true === $rules ? self::$rules : self::$rules[strtolower($rules)]; + } else { + $rules = self::$rules; + unset($rules['pattern'], $rules['alias'], $rules['domain'], $rules['name']); + return $rules; + } + } + + /** + * 检测子域名部署 + * @access public + * @param Request $request Request请求对象 + * @param array $currentRules 当前路由规则 + * @param string $method 请求类型 + * @return void + */ + public static function checkDomain($request, &$currentRules, $method = 'get') + { + // 域名规则 + $rules = self::$rules['domain']; + // 开启子域名部署 支持二级和三级域名 + if (!empty($rules)) { + $host = $request->host(true); + if (isset($rules[$host])) { + // 完整域名或者IP配置 + $item = $rules[$host]; + } else { + $rootDomain = Config::get('url_domain_root'); + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($host, $rootDomain, true), '.')); + } else { + $domain = explode('.', $host, -2); + } + // 子域名配置 + if (!empty($domain)) { + // 当前子域名 + $subDomain = implode('.', $domain); + self::$subDomain = $subDomain; + $domain2 = array_pop($domain); + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + if ($subDomain && isset($rules[$subDomain])) { + // 子域名配置 + $item = $rules[$subDomain]; + } elseif (isset($rules['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $rules['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($rules['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $rules['*']; + $panDomain = $domain2; + } + } + } + } + if (!empty($item)) { + if (isset($panDomain)) { + // 保存当前泛域名 + $request->route(['__domain__' => $panDomain]); + } + if (isset($item['[bind]'])) { + // 解析子域名部署规则 + list($rule, $option, $pattern) = $item['[bind]']; + if (!empty($option['https']) && !$request->isSsl()) { + // https检测 + throw new HttpException(404, 'must use https request:' . $host); + } + + if (strpos($rule, '?')) { + // 传入其它参数 + $array = parse_url($rule); + $result = $array['path']; + parse_str($array['query'], $params); + if (isset($panDomain)) { + $pos = array_search('*', $params); + if (false !== $pos) { + // 泛域名作为参数 + $params[$pos] = $panDomain; + } + } + $_GET = array_merge($_GET, $params); + } else { + $result = $rule; + } + + if (0 === strpos($result, '\\')) { + // 绑定到命名空间 例如 \app\index\behavior + self::$bind = ['type' => 'namespace', 'namespace' => $result]; + } elseif (0 === strpos($result, '@')) { + // 绑定到类 例如 @app\index\controller\User + self::$bind = ['type' => 'class', 'class' => substr($result, 1)]; + } else { + // 绑定到模块/控制器 例如 index/user + self::$bind = ['type' => 'module', 'module' => $result]; + } + self::$domainBind = true; + } else { + self::$domainRule = $item; + $currentRules = isset($item[$method]) ? $item[$method] : $item['*']; + } + } + } + } + + /** + * 检测URL路由 + * @access public + * @param Request $request Request请求对象 + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $checkDomain 是否检测域名规则 + * @return false|array + */ + public static function check($request, $url, $depr = '/', $checkDomain = false) + { + //检查解析缓存 + if (!App::$debug && Config::get('route_check_cache')) { + $key = self::getCheckCacheKey($request); + if (Cache::has($key)) { + list($rule, $route, $pathinfo, $option, $matches) = Cache::get($key); + return self::parseRule($rule, $route, $pathinfo, $option, $matches, true); + } + } + + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace($depr, '|', $url); + + if (isset(self::$rules['alias'][$url]) || isset(self::$rules['alias'][strstr($url, '|', true)])) { + // 检测路由别名 + $result = self::checkRouteAlias($request, $url, $depr); + if (false !== $result) { + return $result; + } + } + $method = strtolower($request->method()); + // 获取当前请求类型的路由规则 + $rules = isset(self::$rules[$method]) ? self::$rules[$method] : []; + // 检测域名部署 + if ($checkDomain) { + self::checkDomain($request, $rules, $method); + } + // 检测URL绑定 + $return = self::checkUrlBind($url, $rules, $depr); + if (false !== $return) { + return $return; + } + if ('|' != $url) { + $url = rtrim($url, '|'); + } + $item = str_replace('|', '/', $url); + if (isset($rules[$item])) { + // 静态路由规则检测 + $rule = $rules[$item]; + if (true === $rule) { + $rule = self::getRouteExpress($item); + } + if (!empty($rule['route']) && self::checkOption($rule['option'], $request)) { + self::setOption($rule['option']); + return self::parseRule($item, $rule['route'], $url, $rule['option']); + } + } + + // 路由规则检测 + if (!empty($rules)) { + return self::checkRoute($request, $rules, $url, $depr); + } + return false; + } + + private static function getRouteExpress($key) + { + return self::$domainRule ? self::$domainRule['*'][$key] : self::$rules['*'][$key]; + } + + /** + * 检测路由规则 + * @access private + * @param Request $request + * @param array $rules 路由规则 + * @param string $url URL地址 + * @param string $depr URL分割符 + * @param string $group 路由分组名 + * @param array $options 路由参数(分组) + * @return mixed + */ + private static function checkRoute($request, $rules, $url, $depr = '/', $group = '', $options = []) + { + foreach ($rules as $key => $item) { + if (true === $item) { + $item = self::getRouteExpress($key); + } + if (!isset($item['rule'])) { + continue; + } + $rule = $item['rule']; + $route = $item['route']; + $vars = $item['var']; + $option = $item['option']; + $pattern = $item['pattern']; + + // 检查参数有效性 + if (!self::checkOption($option, $request)) { + continue; + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.' . $request->ext() . '$/i', '', $url); + } + + if (is_array($rule)) { + // 分组路由 + $pos = strpos(str_replace('<', ':', $key), ':'); + if (false !== $pos) { + $str = substr($key, 0, $pos); + } else { + $str = $key; + } + if (is_string($str) && $str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + continue; + } + self::setOption($option); + $result = self::checkRoute($request, $rule, $url, $depr, $key, $option); + if (false !== $result) { + return $result; + } + } elseif ($route) { + if ('__miss__' == $rule || '__auto__' == $rule) { + // 指定特殊路由 + $var = trim($rule, '__'); + ${$var} = $item; + continue; + } + if ($group) { + $rule = $group . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + self::setOption($option); + if (isset($options['bind_model']) && isset($option['bind_model'])) { + $option['bind_model'] = array_merge($options['bind_model'], $option['bind_model']); + } + $result = self::checkRule($rule, $route, $url, $pattern, $option, $depr); + if (false !== $result) { + return $result; + } + } + } + if (isset($auto)) { + // 自动解析URL地址 + return self::parseUrl($auto['route'] . '/' . $url, $depr); + } elseif (isset($miss)) { + // 未匹配所有路由的路由规则处理 + return self::parseRule('', $miss['route'], $url, $miss['option']); + } + return false; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkRouteAlias($request, $url, $depr) + { + $array = explode('|', $url); + $alias = array_shift($array); + $item = self::$rules['alias'][$alias]; + + if (is_array($item)) { + list($rule, $option) = $item; + $action = $array[0]; + if (isset($option['allow']) && !in_array($action, explode(',', $option['allow']))) { + // 允许操作 + return false; + } elseif (isset($option['except']) && in_array($action, explode(',', $option['except']))) { + // 排除操作 + return false; + } + if (isset($option['method'][$action])) { + $option['method'] = $option['method'][$action]; + } + } else { + $rule = $item; + } + $bind = implode('|', $array); + // 参数有效性检查 + if (isset($option) && !self::checkOption($option, $request)) { + // 路由不匹配 + return false; + } elseif (0 === strpos($rule, '\\')) { + // 路由到类 + return self::bindToClass($bind, substr($rule, 1), $depr); + } elseif (0 === strpos($rule, '@')) { + // 路由到控制器类 + return self::bindToController($bind, substr($rule, 1), $depr); + } else { + // 路由到模块/控制器 + return self::bindToModule($bind, $rule, $depr); + } + } + + /** + * 检测URL绑定 + * @access private + * @param string $url URL地址 + * @param array $rules 路由规则 + * @param string $depr URL分隔符 + * @return mixed + */ + private static function checkUrlBind(&$url, &$rules, $depr = '/') + { + if (!empty(self::$bind)) { + $type = self::$bind['type']; + $bind = self::$bind[$type]; + // 记录绑定信息 + App::$debug && Log::record('[ BIND ] ' . var_export($bind, true), 'info'); + // 如果有URL绑定 则进行绑定检测 + switch ($type) { + case 'class': + // 绑定到类 + return self::bindToClass($url, $bind, $depr); + case 'controller': + // 绑定到控制器类 + return self::bindToController($url, $bind, $depr); + case 'namespace': + // 绑定到命名空间 + return self::bindToNamespace($url, $bind, $depr); + } + } + return false; + } + + /** + * 绑定到类 + * @access public + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToClass($url, $class, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'method', 'method' => [$class, $action], 'var' => []]; + } + + /** + * 绑定到命名空间 + * @access public + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToNamespace($url, $namespace, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : Config::get('default_controller'); + $method = !empty($array[1]) ? $array[1] : Config::get('default_action'); + if (!empty($array[2])) { + self::parseUrlParams($array[2]); + } + return ['type' => 'method', 'method' => [$namespace . '\\' . Loader::parseName($class, 1), $method], 'var' => []]; + } + + /** + * 绑定到控制器类 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToController($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'controller', 'controller' => $controller . '/' . $action, 'var' => []]; + } + + /** + * 绑定到模块/控制器 + * @access public + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @param string $depr URL分隔符 + * @return array + */ + public static function bindToModule($url, $controller, $depr = '/') + { + $url = str_replace($depr, '|', $url); + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : Config::get('default_action'); + if (!empty($array[1])) { + self::parseUrlParams($array[1]); + } + return ['type' => 'module', 'module' => $controller . '/' . $action]; + } + + /** + * 路由参数有效性检查 + * @access private + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + private static function checkOption($option, $request) + { + if ((isset($option['method']) && is_string($option['method']) && false === stripos($option['method'], $request->method())) + || (isset($option['ajax']) && $option['ajax'] && !$request->isAjax()) // Ajax检测 + || (isset($option['ajax']) && !$option['ajax'] && $request->isAjax()) // 非Ajax检测 + || (isset($option['pjax']) && $option['pjax'] && !$request->isPjax()) // Pjax检测 + || (isset($option['pjax']) && !$option['pjax'] && $request->isPjax()) // 非Pjax检测 + || (isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) // 伪静态后缀检测 + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['domain']) && !in_array($option['domain'], [$_SERVER['HTTP_HOST'], self::$subDomain])) // 域名检测 + || (isset($option['https']) && $option['https'] && !$request->isSsl()) // https检测 + || (isset($option['https']) && !$option['https'] && $request->isSsl()) // https检测 + || (!empty($option['before_behavior']) && false === Hook::exec($option['before_behavior'])) // 行为检测 + || (!empty($option['callback']) && is_callable($option['callback']) && false === call_user_func($option['callback'])) // 自定义检测 + ) { + return false; + } + return true; + } + + /** + * 检测路由规则 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $pattern 变量规则 + * @param array $option 路由参数 + * @param string $depr URL分隔符(全局) + * @return array|false + */ + private static function checkRule($rule, $route, $url, $pattern, $option, $depr) + { + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + // 检查路由的参数分隔符 + if (isset($option['param_depr'])) { + $url = str_replace(['|', $option['param_depr']], [$depr, '|'], $url); + } + + $len1 = substr_count($url, '|'); + $len2 = substr_count($rule, '/'); + // 多余参数是否合并 + $merge = !empty($option['merge_extra_vars']); + if ($merge && $len1 > $len2) { + $url = str_replace('|', $depr, $url); + $url = implode('|', explode($depr, $url, $len2 + 1)); + } + + if ($len1 >= $len2 || strpos($rule, '[')) { + if (!empty($option['complete_match'])) { + // 完整匹配 + if (!$merge && $len1 != $len2 && (false === strpos($rule, '[') || $len1 > $len2 || $len1 < $len2 - substr_count($rule, '['))) { + return false; + } + } + $pattern = array_merge(self::$rules['pattern'], $pattern); + if (false !== $match = self::match($url, $rule, $pattern)) { + // 匹配到路由规则 + return self::parseRule($rule, $route, $url, $option, $match); + } + } + return false; + } + + /** + * 解析模块的URL地址 [模块/控制器/操作?]参数1=值1&参数2=值2... + * @access public + * @param string $url URL地址 + * @param string $depr URL分隔符 + * @param bool $autoSearch 是否自动深度搜索控制器 + * @return array + */ + public static function parseUrl($url, $depr = '/', $autoSearch = false) + { + + if (isset(self::$bind['module'])) { + $bind = str_replace('/', $depr, self::$bind['module']); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + $url = str_replace($depr, '|', $url); + list($path, $var) = self::parseUrlPath($url); + $route = [null, null, null]; + if (isset($path)) { + // 解析模块 + $module = Config::get('app_multi_module') ? array_shift($path) : null; + if ($autoSearch) { + // 自动搜索控制器 + $dir = APP_PATH . ($module ? $module . DS : '') . Config::get('url_controller_layer'); + $suffix = App::$suffix || Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''; + $item = []; + $find = false; + foreach ($path as $val) { + $item[] = $val; + $file = $dir . DS . str_replace('.', DS, $val) . $suffix . EXT; + $file = pathinfo($file, PATHINFO_DIRNAME) . DS . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . EXT; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= DS . Loader::parseName($val); + } + } + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + // 解析额外参数 + self::parseUrlParams(empty($path) ? '' : implode('|', $path)); + // 封装路由 + $route = [$module, $controller, $action]; + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + $name2 = ''; + if (empty($module) || isset($bind) && $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + if (isset(self::$rules['name'][$name]) || isset(self::$rules['name'][$name2])) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + } + return ['type' => 'module', 'module' => $route]; + } + + /** + * 解析URL的pathinfo参数和变量 + * @access private + * @param string $url URL地址 + * @return array + */ + private static function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + return [$path, $var]; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param string $rule 路由规则 + * @param array $pattern 变量规则 + * @return array|false + */ + private static function match($url, $rule, $pattern) + { + $m2 = explode('/', $rule); + $m1 = explode('|', $url); + + $var = []; + foreach ($m2 as $key => $val) { + // val中定义了多个变量 + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + $value = []; + $replace = []; + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')?'; + } else { + $replace[] = '(' . (isset($pattern[$name]) ? $pattern[$name] : '\w+') . ')'; + } + $value[] = $name; + } + $val = str_replace($matches[0], $replace, $val); + if (preg_match('/^' . $val . '$/', isset($m1[$key]) ? $m1[$key] : '', $match)) { + array_shift($match); + foreach ($value as $k => $name) { + if (isset($match[$k])) { + $var[$name] = $match[$k]; + } + } + continue; + } else { + return false; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $val = substr($val, 1, -1); + $optional = true; + } else { + $optional = false; + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + if (!$optional && !isset($m1[$key])) { + return false; + } + if (isset($m1[$key]) && isset($pattern[$name])) { + // 检查变量规则 + if ($pattern[$name] instanceof \Closure) { + $result = call_user_func_array($pattern[$name], [$m1[$key]]); + if (false === $result) { + return false; + } + } elseif (!preg_match(0 === strpos($pattern[$name], '/') ? $pattern[$name] : '/^' . $pattern[$name] . '$/', $m1[$key])) { + return false; + } + } + $var[$name] = isset($m1[$key]) ? $m1[$key] : ''; + } elseif (!isset($m1[$key]) || 0 !== strcasecmp($val, $m1[$key])) { + return false; + } + } + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 解析规则路由 + * @access private + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $pathinfo URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @param bool $fromCache 通过缓存解析 + * @return array + */ + private static function parseRule($rule, $route, $pathinfo, $option = [], $matches = [], $fromCache = false) + { + $request = Request::instance(); + + //保存解析缓存 + if (Config::get('route_check_cache') && !$fromCache) { + try { + $key = self::getCheckCacheKey($request); + Cache::tag('route_check')->set($key, [$rule, $route, $pathinfo, $option, $matches]); + } catch (\Exception $e) { + + } + } + + // 解析路由规则 + if ($rule) { + $rule = explode('/', $rule); + // 获取URL地址中的参数 + $paths = explode('|', $pathinfo); + foreach ($rule as $item) { + $fun = ''; + if (0 === strpos($item, '[:')) { + $item = substr($item, 1, -1); + } + if (0 === strpos($item, ':')) { + $var = substr($item, 1); + $matches[$var] = array_shift($paths); + } else { + // 过滤URL中的静态变量 + array_shift($paths); + } + } + } else { + $paths = explode('|', $pathinfo); + } + + // 获取路由地址规则 + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 绑定模型数据 + if (isset($option['bind_model'])) { + $bind = []; + foreach ($option['bind_model'] as $key => $val) { + if ($val instanceof \Closure) { + $result = call_user_func_array($val, [$matches]); + } else { + if (is_array($val)) { + $fields = explode('&', $val[1]); + $model = $val[0]; + $exception = isset($val[2]) ? $val[2] : true; + } else { + $fields = ['id']; + $model = $val; + $exception = true; + } + $where = []; + $match = true; + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[$field] = $matches[$field]; + } + } + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : Loader::model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + if (!empty($result)) { + $bind[$key] = $result; + } + } + $request->bind($bind); + } + + if (!empty($option['response'])) { + Hook::add('response_send', $option['response']); + } + + // 解析额外参数 + self::parseUrlParams(empty($paths) ? '' : implode('|', $paths), $matches); + // 记录匹配的路由信息 + $request->routeInfo(['rule' => $rule, 'route' => $route, 'option' => $option, 'var' => $matches]); + + // 检测路由after行为 + if (!empty($option['after_behavior'])) { + if ($option['after_behavior'] instanceof \Closure) { + $result = call_user_func_array($option['after_behavior'], []); + } else { + foreach ((array) $option['after_behavior'] as $behavior) { + $result = Hook::exec($behavior, ''); + if (!is_null($result)) { + break; + } + } + } + // 路由规则重定向 + if ($result instanceof Response) { + return ['type' => 'response', 'response' => $result]; + } elseif (is_array($result)) { + return $result; + } + } + + if ($route instanceof \Closure) { + // 执行闭包 + $result = ['type' => 'function', 'function' => $route]; + } elseif (0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = ['type' => 'redirect', 'url' => $route, 'status' => isset($option['status']) ? $option['status'] : 301]; + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + list($path, $var) = self::parseUrlPath($route); + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + $result = ['type' => 'method', 'method' => $method, 'var' => $var]; + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $route = substr($route, 1); + list($route, $var) = self::parseUrlPath($route); + $result = ['type' => 'controller', 'controller' => implode('/', $route), 'var' => $var]; + $request->action(array_pop($route)); + $request->controller($route ? array_pop($route) : Config::get('default_controller')); + $request->module($route ? array_pop($route) : Config::get('default_module')); + App::$modulePath = APP_PATH . (Config::get('app_multi_module') ? $request->module() . DS : ''); + } else { + // 路由到模块/控制器/操作 + $result = self::parseModule($route, isset($option['convert']) ? $option['convert'] : false); + } + // 开启请求缓存 + if ($request->isGet() && isset($option['cache'])) { + $cache = $option['cache']; + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $pathinfo); + $expire = $cache; + $tag = null; + } + $request->cache($key, $expire, $tag); + } + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access private + * @param string $url URL地址 + * @param bool $convert 是否自动转换URL地址 + * @return array + */ + private static function parseModule($url, $convert = false) + { + list($path, $var) = self::parseUrlPath($url); + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = Config::get('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = Request::instance()->method(); + if (Config::get('use_action_prefix') && !empty(self::$methodPrefix[$method])) { + // 操作方法前缀支持 + $action = 0 !== strpos($action, self::$methodPrefix[$method]) ? self::$methodPrefix[$method] . $action : $action; + } + // 设置当前请求的路由变量 + Request::instance()->route($var); + // 路由到模块/控制器/操作 + return ['type' => 'module', 'module' => [$module, $controller, $action], 'convert' => $convert]; + } + + /** + * 解析URL地址中的参数Request对象 + * @access private + * @param string $url 路由规则 + * @param array $var 变量 + * @return void + */ + private static function parseUrlParams($url, &$var = []) + { + if ($url) { + if (Config::get('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + // 设置当前请求的参数 + Request::instance()->route($var); + } + + // 分析路由规则中的变量 + private static function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + foreach (explode('/', $rule) as $val) { + $optional = false; + if (false !== strpos($val, '<') && preg_match_all('/<(\w+(\??))>/', $val, $matches)) { + foreach ($matches[1] as $name) { + if (strpos($name, '?')) { + $name = substr($name, 0, -1); + $optional = true; + } else { + $optional = false; + } + $var[$name] = $optional ? 2 : 1; + } + } + + if (0 === strpos($val, '[:')) { + // 可选参数 + $optional = true; + $val = substr($val, 1, -1); + } + if (0 === strpos($val, ':')) { + // URL变量 + $name = substr($val, 1); + $var[$name] = $optional ? 2 : 1; + } + } + return $var; + } + + /** + * 获取路由解析缓存的key + * @param Request $request + * @return string + */ + private static function getCheckCacheKey(Request $request) + { + static $key; + + if (empty($key)) { + if ($callback = Config::get('route_check_cache_key')) { + $key = call_user_func($callback, $request); + } else { + $key = "{$request->host(true)}|{$request->method()}|{$request->path()}"; + } + } + + return $key; + } +} diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php old mode 100755 new mode 100644 index cc45b03..61150bc --- a/thinkphp/library/think/Session.php +++ b/thinkphp/library/think/Session.php @@ -1,366 +1,366 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; - -class Session -{ - protected static $prefix = ''; - protected static $init = null; - - /** - * 设置或者获取session作用域(前缀) - * @param string $prefix - * @return string|void - */ - public static function prefix($prefix = '') - { - empty(self::$init) && self::boot(); - if (empty($prefix) && null !== $prefix) { - return self::$prefix; - } else { - self::$prefix = $prefix; - } - } - - /** - * session初始化 - * @param array $config - * @return void - * @throws \think\Exception - */ - public static function init(array $config = []) - { - if (empty($config)) { - $config = Config::get('session'); - } - // 记录初始化信息 - App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); - $isDoStart = false; - if (isset($config['use_trans_sid'])) { - ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); - } - - // 启动session - if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { - ini_set('session.auto_start', 0); - $isDoStart = true; - } - - if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { - self::$prefix = $config['prefix']; - } - if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { - session_id($_REQUEST[$config['var_session_id']]); - } elseif (isset($config['id']) && !empty($config['id'])) { - session_id($config['id']); - } - if (isset($config['name'])) { - session_name($config['name']); - } - if (isset($config['path'])) { - session_save_path($config['path']); - } - if (isset($config['domain'])) { - ini_set('session.cookie_domain', $config['domain']); - } - if (isset($config['expire'])) { - ini_set('session.gc_maxlifetime', $config['expire']); - ini_set('session.cookie_lifetime', $config['expire']); - } - if (isset($config['secure'])) { - ini_set('session.cookie_secure', $config['secure']); - } - if (isset($config['httponly'])) { - ini_set('session.cookie_httponly', $config['httponly']); - } - if (isset($config['use_cookies'])) { - ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); - } - if (isset($config['cache_limiter'])) { - session_cache_limiter($config['cache_limiter']); - } - if (isset($config['cache_expire'])) { - session_cache_expire($config['cache_expire']); - } - if (!empty($config['type'])) { - // 读取session驱动 - $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); - - // 检查驱动类 - if (!class_exists($class) || !session_set_save_handler(new $class($config))) { - throw new ClassNotFoundException('error session handler:' . $class, $class); - } - } - if ($isDoStart) { - session_start(); - self::$init = true; - } else { - self::$init = false; - } - } - - /** - * session自动启动或者初始化 - * @return void - */ - public static function boot() - { - if (is_null(self::$init)) { - self::init(); - } elseif (false === self::$init) { - if (PHP_SESSION_ACTIVE != session_status()) { - session_start(); - } - self::$init = true; - } - } - - /** - * session设置 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function set($name, $value = '', $prefix = null) - { - empty(self::$init) && self::boot(); - - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { - // 二维数组赋值 - list($name1, $name2) = explode('.', $name); - if ($prefix) { - $_SESSION[$prefix][$name1][$name2] = $value; - } else { - $_SESSION[$name1][$name2] = $value; - } - } elseif ($prefix) { - $_SESSION[$prefix][$name] = $value; - } else { - $_SESSION[$name] = $value; - } - } - - /** - * session获取 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return mixed - */ - public static function get($name = '', $prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if ('' == $name) { - // 获取全部的session - $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; - } elseif ($prefix) { - // 获取session - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; - } else { - $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; - } - } else { - if (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; - } else { - $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; - } - } - return $value; - } - - /** - * session获取并删除 - * @param string $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return mixed - */ - public static function pull($name, $prefix = null) - { - $result = self::get($name, $prefix); - if ($result) { - self::delete($name, $prefix); - return $result; - } else { - return; - } - } - - /** - * session设置 下一次请求有效 - * @param string $name session名称 - * @param mixed $value session值 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function flash($name, $value) - { - self::set($name, $value); - if (!self::has('__flash__.__time__')) { - self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); - } - self::push('__flash__', $name); - } - - /** - * 清空当前请求的session数据 - * @return void - */ - public static function flush() - { - if (self::$init) { - $item = self::get('__flash__'); - - if (!empty($item)) { - $time = $item['__time__']; - if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { - unset($item['__time__']); - self::delete($item); - self::set('__flash__', []); - } - } - } - } - - /** - * 删除session数据 - * @param string|array $name session名称 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function delete($name, $prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (is_array($name)) { - foreach ($name as $key) { - self::delete($key, $prefix); - } - } elseif (strpos($name, '.')) { - list($name1, $name2) = explode('.', $name); - if ($prefix) { - unset($_SESSION[$prefix][$name1][$name2]); - } else { - unset($_SESSION[$name1][$name2]); - } - } else { - if ($prefix) { - unset($_SESSION[$prefix][$name]); - } else { - unset($_SESSION[$name]); - } - } - } - - /** - * 清空session数据 - * @param string|null $prefix 作用域(前缀) - * @return void - */ - public static function clear($prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if ($prefix) { - unset($_SESSION[$prefix]); - } else { - $_SESSION = []; - } - } - - /** - * 判断session数据 - * @param string $name session名称 - * @param string|null $prefix - * @return bool - */ - public static function has($name, $prefix = null) - { - empty(self::$init) && self::boot(); - $prefix = !is_null($prefix) ? $prefix : self::$prefix; - if (strpos($name, '.')) { - // 支持数组 - list($name1, $name2) = explode('.', $name); - return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); - } else { - return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); - } - } - - /** - * 添加数据到一个session数组 - * @param string $key - * @param mixed $value - * @return void - */ - public static function push($key, $value) - { - $array = self::get($key); - if (is_null($array)) { - $array = []; - } - $array[] = $value; - self::set($key, $array); - } - - /** - * 启动session - * @return void - */ - public static function start() - { - session_start(); - self::$init = true; - } - - /** - * 销毁session - * @return void - */ - public static function destroy() - { - if (!empty($_SESSION)) { - $_SESSION = []; - } - session_unset(); - session_destroy(); - self::$init = null; - } - - /** - * 重新生成session_id - * @param bool $delete 是否删除关联会话文件 - * @return void - */ - public static function regenerate($delete = false) - { - session_regenerate_id($delete); - } - - /** - * 暂停session - * @return void - */ - public static function pause() - { - // 暂停session - session_write_close(); - self::$init = false; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + protected static $prefix = ''; + protected static $init = null; + + /** + * 设置或者获取session作用域(前缀) + * @param string $prefix + * @return string|void + */ + public static function prefix($prefix = '') + { + empty(self::$init) && self::boot(); + if (empty($prefix) && null !== $prefix) { + return self::$prefix; + } else { + self::$prefix = $prefix; + } + } + + /** + * session初始化 + * @param array $config + * @return void + * @throws \think\Exception + */ + public static function init(array $config = []) + { + if (empty($config)) { + $config = Config::get('session'); + } + // 记录初始化信息 + App::$debug && Log::record('[ SESSION ] INIT ' . var_export($config, true), 'info'); + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix']) && ('' === self::$prefix || null === self::$prefix)) { + self::$prefix = $config['prefix']; + } + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + if (isset($config['name'])) { + session_name($config['name']); + } + if (isset($config['path'])) { + session_save_path($config['path']); + } + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + if ($isDoStart) { + session_start(); + self::$init = true; + } else { + self::$init = false; + } + } + + /** + * session自动启动或者初始化 + * @return void + */ + public static function boot() + { + if (is_null(self::$init)) { + self::init(); + } elseif (false === self::$init) { + if (PHP_SESSION_ACTIVE != session_status()) { + session_start(); + } + self::$init = true; + } + } + + /** + * session设置 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function set($name, $value = '', $prefix = null) + { + empty(self::$init) && self::boot(); + + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + } + + /** + * session获取 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function get($name = '', $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ('' == $name) { + // 获取全部的session + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + } elseif ($prefix) { + // 获取session + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$prefix][$name1][$name2]) ? $_SESSION[$prefix][$name1][$name2] : null; + } else { + $value = isset($_SESSION[$prefix][$name]) ? $_SESSION[$prefix][$name] : null; + } + } else { + if (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + $value = isset($_SESSION[$name1][$name2]) ? $_SESSION[$name1][$name2] : null; + } else { + $value = isset($_SESSION[$name]) ? $_SESSION[$name] : null; + } + } + return $value; + } + + /** + * session获取并删除 + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public static function pull($name, $prefix = null) + { + $result = self::get($name, $prefix); + if ($result) { + self::delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function flash($name, $value) + { + self::set($name, $value); + if (!self::has('__flash__.__time__')) { + self::set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + self::push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @return void + */ + public static function flush() + { + if (self::$init) { + $item = self::get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + self::delete($item); + self::set('__flash__', []); + } + } + } + } + + /** + * 删除session数据 + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function delete($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (is_array($name)) { + foreach ($name as $key) { + self::delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public static function clear($prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public static function has($name, $prefix = null) + { + empty(self::$init) && self::boot(); + $prefix = !is_null($prefix) ? $prefix : self::$prefix; + if (strpos($name, '.')) { + // 支持数组 + list($name1, $name2) = explode('.', $name); + return $prefix ? isset($_SESSION[$prefix][$name1][$name2]) : isset($_SESSION[$name1][$name2]); + } else { + return $prefix ? isset($_SESSION[$prefix][$name]) : isset($_SESSION[$name]); + } + } + + /** + * 添加数据到一个session数组 + * @param string $key + * @param mixed $value + * @return void + */ + public static function push($key, $value) + { + $array = self::get($key); + if (is_null($array)) { + $array = []; + } + $array[] = $value; + self::set($key, $array); + } + + /** + * 启动session + * @return void + */ + public static function start() + { + session_start(); + self::$init = true; + } + + /** + * 销毁session + * @return void + */ + public static function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + session_unset(); + session_destroy(); + self::$init = null; + } + + /** + * 重新生成session_id + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public static function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @return void + */ + public static function pause() + { + // 暂停session + session_write_close(); + self::$init = false; + } +} diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php old mode 100755 new mode 100644 index 1df4508..9ba0ff3 --- a/thinkphp/library/think/Template.php +++ b/thinkphp/library/think/Template.php @@ -1,1139 +1,1139 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\TemplateNotFoundException; -use think\template\TagLib; - -/** - * ThinkPHP分离出来的模板引擎 - * 支持XML标签和普通标签的模板解析 - * 编译型模板引擎 支持动态缓存 - */ -class Template -{ - // 模板变量 - protected $data = []; - // 引擎配置 - protected $config = [ - 'view_path' => '', // 模板路径 - 'view_base' => '', - 'view_suffix' => 'html', // 默认模板文件后缀 - 'view_depr' => DS, - 'cache_suffix' => 'php', // 默认模板缓存后缀 - 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 - 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 - 'tpl_begin' => '{', // 模板引擎普通标签开始标记 - 'tpl_end' => '}', // 模板引擎普通标签结束标记 - 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 - 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 - 'compile_type' => 'file', // 模板编译类型 - 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 - 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) - 'layout_on' => false, // 布局模板开关 - 'layout_name' => 'layout', // 布局模板入口文件 - 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 - 'taglib_begin' => '{', // 标签库标签开始标记 - 'taglib_end' => '}', // 标签库标签结束标记 - 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 - 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 - 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 - 'display_cache' => false, // 模板渲染缓存 - 'cache_id' => '', // 模板缓存ID - 'tpl_replace_string' => [], - 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 - ]; - - private $literal = []; - private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 - protected $storage; - - /** - * 构造函数 - * @access public - * @param array $config - */ - public function __construct(array $config = []) - { - $this->config['cache_path'] = TEMP_PATH; - $this->config = array_merge($this->config, $config); - - $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; - $this->config['taglib_end_origin'] = $this->config['taglib_end']; - - $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); - $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); - $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); - $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); - - // 初始化模板编译存储器 - $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; - $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); - $this->storage = new $class(); - } - - /** - * 模板变量赋值 - * @access public - * @param mixed $name - * @param mixed $value - * @return void - */ - public function assign($name, $value = '') - { - if (is_array($name)) { - $this->data = array_merge($this->data, $name); - } else { - $this->data[$name] = $value; - } - } - - /** - * 模板引擎参数赋值 - * @access public - * @param mixed $name - * @param mixed $value - */ - public function __set($name, $value) - { - $this->config[$name] = $value; - } - - /** - * 模板引擎配置项 - * @access public - * @param array|string $config - * @return string|void|array - */ - public function config($config) - { - if (is_array($config)) { - $this->config = array_merge($this->config, $config); - } elseif (isset($this->config[$config])) { - return $this->config[$config]; - } else { - return; - } - } - - /** - * 模板变量获取 - * @access public - * @param string $name 变量名 - * @return mixed - */ - public function get($name = '') - { - if ('' == $name) { - return $this->data; - } else { - $data = $this->data; - foreach (explode('.', $name) as $key => $val) { - if (isset($data[$val])) { - $data = $data[$val]; - } else { - $data = null; - break; - } - } - return $data; - } - } - - /** - * 渲染模板文件 - * @access public - * @param string $template 模板文件 - * @param array $vars 模板变量 - * @param array $config 模板参数 - * @return void - */ - public function fetch($template, $vars = [], $config = []) - { - if ($vars) { - $this->data = $vars; - } - if ($config) { - $this->config($config); - } - if (!empty($this->config['cache_id']) && $this->config['display_cache']) { - // 读取渲染缓存 - $cacheContent = Cache::get($this->config['cache_id']); - if (false !== $cacheContent) { - echo $cacheContent; - return; - } - } - $template = $this->parseTemplateFile($template); - if ($template) { - $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); - if (!$this->checkCache($cacheFile)) { - // 缓存无效 重新模板编译 - $content = file_get_contents($template); - $this->compiler($content, $cacheFile); - } - // 页面缓存 - ob_start(); - ob_implicit_flush(0); - // 读取编译存储 - $this->storage->read($cacheFile, $this->data); - // 获取并清空缓存 - $content = ob_get_clean(); - if (!empty($this->config['cache_id']) && $this->config['display_cache']) { - // 缓存页面输出 - Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); - } - echo $content; - } - } - - /** - * 渲染模板内容 - * @access public - * @param string $content 模板内容 - * @param array $vars 模板变量 - * @param array $config 模板参数 - * @return void - */ - public function display($content, $vars = [], $config = []) - { - if ($vars) { - $this->data = $vars; - } - if ($config) { - $this->config($config); - } - $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); - if (!$this->checkCache($cacheFile)) { - // 缓存无效 模板编译 - $this->compiler($content, $cacheFile); - } - // 读取编译存储 - $this->storage->read($cacheFile, $this->data); - } - - /** - * 设置布局 - * @access public - * @param mixed $name 布局模板名称 false 则关闭布局 - * @param string $replace 布局模板内容替换标识 - * @return Template - */ - public function layout($name, $replace = '') - { - if (false === $name) { - // 关闭布局 - $this->config['layout_on'] = false; - } else { - // 开启布局 - $this->config['layout_on'] = true; - // 名称必须为字符串 - if (is_string($name)) { - $this->config['layout_name'] = $name; - } - if (!empty($replace)) { - $this->config['layout_item'] = $replace; - } - } - return $this; - } - - /** - * 检查编译缓存是否有效 - * 如果无效则需要重新编译 - * @access private - * @param string $cacheFile 缓存文件名 - * @return boolean - */ - private function checkCache($cacheFile) - { - // 未开启缓存功能 - if (!$this->config['tpl_cache']) { - return false; - } - // 缓存文件不存在 - if (!is_file($cacheFile)) { - return false; - } - // 读取缓存文件失败 - if (!$handle = @fopen($cacheFile, "r")) { - return false; - } - // 读取第一行 - preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); - if (!isset($matches[1])) { - return false; - } - $includeFile = unserialize($matches[1]); - if (!is_array($includeFile)) { - return false; - } - // 检查模板文件是否有更新 - foreach ($includeFile as $path => $time) { - if (is_file($path) && filemtime($path) > $time) { - // 模板文件如果有更新则缓存需要更新 - return false; - } - } - // 检查编译存储是否有效 - return $this->storage->check($cacheFile, $this->config['cache_time']); - } - - /** - * 检查编译缓存是否存在 - * @access public - * @param string $cacheId 缓存的id - * @return boolean - */ - public function isCache($cacheId) - { - if ($cacheId && $this->config['display_cache']) { - // 缓存页面输出 - return Cache::has($cacheId); - } - return false; - } - - /** - * 编译模板文件内容 - * @access private - * @param string $content 模板内容 - * @param string $cacheFile 缓存文件名 - * @return void - */ - private function compiler(&$content, $cacheFile) - { - // 判断是否启用布局 - if ($this->config['layout_on']) { - if (false !== strpos($content, '{__NOLAYOUT__}')) { - // 可以单独定义不使用布局 - $content = str_replace('{__NOLAYOUT__}', '', $content); - } else { - // 读取布局模板 - $layoutFile = $this->parseTemplateFile($this->config['layout_name']); - if ($layoutFile) { - // 替换布局的主体内容 - $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); - } - } - } else { - $content = str_replace('{__NOLAYOUT__}', '', $content); - } - - // 模板解析 - $this->parse($content); - if ($this->config['strip_space']) { - /* 去除html空格与换行 */ - $find = ['~>\s+<~', '~>(\s+\n|\r)~']; - $replace = ['><', '>']; - $content = preg_replace($find, $replace, $content); - } - // 优化生成的php代码 - $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content); - // 模板过滤输出 - $replace = $this->config['tpl_replace_string']; - $content = str_replace(array_keys($replace), array_values($replace), $content); - // 添加安全代码及模板引用记录 - $content = 'includeFile) . '*/ ?>' . "\n" . $content; - // 编译存储 - $this->storage->write($cacheFile, $content); - $this->includeFile = []; - return; - } - - /** - * 模板解析入口 - * 支持普通标签和TagLib解析 支持自定义标签库 - * @access public - * @param string $content 要解析的模板内容 - * @return void - */ - public function parse(&$content) - { - // 内容为空不解析 - if (empty($content)) { - return; - } - // 替换literal标签内容 - $this->parseLiteral($content); - // 解析继承 - $this->parseExtend($content); - // 解析布局 - $this->parseLayout($content); - // 检查include语法 - $this->parseInclude($content); - // 替换包含文件中literal标签内容 - $this->parseLiteral($content); - // 检查PHP语法 - $this->parsePhp($content); - - // 获取需要引入的标签库列表 - // 标签库只需要定义一次,允许引入多个一次 - // 一般放在文件的最前面 - // 格式: - // 当TAGLIB_LOAD配置为true时才会进行检测 - if ($this->config['taglib_load']) { - $tagLibs = $this->getIncludeTagLib($content); - if (!empty($tagLibs)) { - // 对导入的TagLib进行解析 - foreach ($tagLibs as $tagLibName) { - $this->parseTagLib($tagLibName, $content); - } - } - } - // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 - if ($this->config['taglib_pre_load']) { - $tagLibs = explode(',', $this->config['taglib_pre_load']); - foreach ($tagLibs as $tag) { - $this->parseTagLib($tag, $content); - } - } - // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 - $tagLibs = explode(',', $this->config['taglib_build_in']); - foreach ($tagLibs as $tag) { - $this->parseTagLib($tag, $content, true); - } - // 解析普通模板标签 {$tagName} - $this->parseTag($content); - - // 还原被替换的Literal标签 - $this->parseLiteral($content, true); - return; - } - - /** - * 检查PHP语法 - * @access private - * @param string $content 要解析的模板内容 - * @return void - * @throws \think\Exception - */ - private function parsePhp(&$content) - { - // 短标签的情况要将' . "\n", $content); - // PHP语法检查 - if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { - // 替换Layout标签 - $content = str_replace($matches[0], '', $content); - // 解析Layout标签 - $array = $this->parseAttr($matches[0]); - if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { - // 读取布局模板 - $layoutFile = $this->parseTemplateFile($array['name']); - if ($layoutFile) { - $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; - // 替换布局的主体内容 - $content = str_replace($replace, $content, file_get_contents($layoutFile)); - } - } - } else { - $content = str_replace('{__NOLAYOUT__}', '', $content); - } - return; - } - - /** - * 解析模板中的include标签 - * @access private - * @param string $content 要解析的模板内容 - * @return void - */ - private function parseInclude(&$content) - { - $regex = $this->getRegex('include'); - $func = function ($template) use (&$func, &$regex, &$content) { - if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $array = $this->parseAttr($match[0]); - $file = $array['file']; - unset($array['file']); - // 分析模板文件名并读取内容 - $parseStr = $this->parseTemplateName($file); - foreach ($array as $k => $v) { - // 以$开头字符串转换成模板变量 - if (0 === strpos($v, '$')) { - $v = $this->get(substr($v, 1)); - } - $parseStr = str_replace('[' . $k . ']', $v, $parseStr); - } - $content = str_replace($match[0], $parseStr, $content); - // 再次对包含文件进行模板分析 - $func($parseStr); - } - unset($matches); - } - }; - // 替换模板中的include标签 - $func($content); - return; - } - - /** - * 解析模板中的extend标签 - * @access private - * @param string $content 要解析的模板内容 - * @return void - */ - private function parseExtend(&$content) - { - $regex = $this->getRegex('extend'); - $array = $blocks = $baseBlocks = []; - $extend = ''; - $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { - if (preg_match($regex, $template, $matches)) { - if (!isset($array[$matches['name']])) { - $array[$matches['name']] = 1; - // 读取继承模板 - $extend = $this->parseTemplateName($matches['name']); - // 递归检查继承 - $func($extend); - // 取得block标签内容 - $blocks = array_merge($blocks, $this->parseBlock($template)); - return; - } - } else { - // 取得顶层模板block标签内容 - $baseBlocks = $this->parseBlock($template, true); - if (empty($extend)) { - // 无extend标签但有block标签的情况 - $extend = $template; - } - } - }; - - $func($content); - if (!empty($extend)) { - if ($baseBlocks) { - $children = []; - foreach ($baseBlocks as $name => $val) { - $replace = $val['content']; - if (!empty($children[$name])) { - // 如果包含有子block标签 - foreach ($children[$name] as $key) { - $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); - } - } - if (isset($blocks[$name])) { - // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 - $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); - if (!empty($val['parent'])) { - // 如果不是最顶层的block标签 - $parent = $val['parent']; - if (isset($blocks[$parent])) { - $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); - } - $blocks[$name]['content'] = $replace; - $children[$parent][] = $name; - continue; - } - } elseif (!empty($val['parent'])) { - // 如果子标签没有被继承则用原值 - $children[$val['parent']][] = $name; - $blocks[$name] = $val; - } - if (!$val['parent']) { - // 替换模板中的顶级block标签 - $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); - } - } - } - $content = $extend; - unset($blocks, $baseBlocks); - } - return; - } - - /** - * 替换页面中的literal标签 - * @access private - * @param string $content 模板内容 - * @param boolean $restore 是否为还原 - * @return void - */ - private function parseLiteral(&$content, $restore = false) - { - $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); - if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { - if (!$restore) { - $count = count($this->literal); - // 替换literal标签 - foreach ($matches as $match) { - $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); - $content = str_replace($match[0], "", $content); - $count++; - } - } else { - // 还原literal标签 - foreach ($matches as $match) { - $content = str_replace($match[0], $this->literal[$match[1]], $content); - } - // 清空literal记录 - $this->literal = []; - } - unset($matches); - } - return; - } - - /** - * 获取模板中的block标签 - * @access private - * @param string $content 模板内容 - * @param boolean $sort 是否排序 - * @return array - */ - private function parseBlock(&$content, $sort = false) - { - $regex = $this->getRegex('block'); - $result = []; - if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { - $right = $keys = []; - foreach ($matches as $match) { - if (empty($match['name'][0])) { - if (count($right) > 0) { - $tag = array_pop($right); - $start = $tag['offset'] + strlen($tag['tag']); - $length = $match[0][1] - $start; - $result[$tag['name']] = [ - 'begin' => $tag['tag'], - 'content' => substr($content, $start, $length), - 'end' => $match[0][0], - 'parent' => count($right) ? end($right)['name'] : '', - ]; - $keys[$tag['name']] = $match[0][1]; - } - } else { - // 标签头压入栈 - $right[] = [ - 'name' => $match[2][0], - 'offset' => $match[0][1], - 'tag' => $match[0][0], - ]; - } - } - unset($right, $matches); - if ($sort) { - // 按block标签结束符在模板中的位置排序 - array_multisort($keys, $result); - } - } - return $result; - } - - /** - * 搜索模板页面中包含的TagLib库 - * 并返回列表 - * @access private - * @param string $content 模板内容 - * @return array|null - */ - private function getIncludeTagLib(&$content) - { - // 搜索是否有TagLib标签 - if (preg_match($this->getRegex('taglib'), $content, $matches)) { - // 替换TagLib标签 - $content = str_replace($matches[0], '', $content); - return explode(',', $matches['name']); - } - return; - } - - /** - * TagLib库解析 - * @access public - * @param string $tagLib 要解析的标签库 - * @param string $content 要解析的模板内容 - * @param boolean $hide 是否隐藏标签库前缀 - * @return void - */ - public function parseTagLib($tagLib, &$content, $hide = false) - { - if (false !== strpos($tagLib, '\\')) { - // 支持指定标签库的命名空间 - $className = $tagLib; - $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); - } else { - $className = '\\think\\template\\taglib\\' . ucwords($tagLib); - } - /** @var Taglib $tLib */ - $tLib = new $className($this); - $tLib->parseTag($content, $hide ? '' : $tagLib); - return; - } - - /** - * 分析标签属性 - * @access public - * @param string $str 属性字符串 - * @param string $name 不为空时返回指定的属性名 - * @return array - */ - public function parseAttr($str, $name = null) - { - $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; - $array = []; - if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $array[$match['name']] = $match['value']; - } - unset($matches); - } - if (!empty($name) && isset($array[$name])) { - return $array[$name]; - } else { - return $array; - } - } - - /** - * 模板标签解析 - * 格式: {TagName:args [|content] } - * @access private - * @param string $content 要解析的模板内容 - * @return void - */ - private function parseTag(&$content) - { - $regex = $this->getRegex('tag'); - if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { - foreach ($matches as $match) { - $str = stripslashes($match[1]); - $flag = substr($str, 0, 1); - switch ($flag) { - case '$': - // 解析模板变量 格式 {$varName} - // 是否带有?号 - if (false !== $pos = strpos($str, '?')) { - $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); - $name = $array[0]; - $this->parseVar($name); - $this->parseVarFunction($name); - - $str = trim(substr($str, $pos + 1)); - $this->parseVar($str); - $first = substr($str, 0, 1); - if (strpos($name, ')')) { - // $name为对象或是自动识别,或者含有函数 - if (isset($array[1])) { - $this->parseVar($array[2]); - $name .= $array[1] . $array[2]; - } - switch ($first) { - case '?': - $str = ''; - break; - case '=': - $str = ''; - break; - default: - $str = ''; - } - } else { - if (isset($array[1])) { - $this->parseVar($array[2]); - $express = $name . $array[1] . $array[2]; - } else { - $express = false; - } - // $name为数组 - switch ($first) { - case '?': - // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx - $str = ''; - break; - case '=': - // {$varname?='xxx'} $varname为真时才输出xxx - $str = ''; - break; - case ':': - // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx - $str = ''; - break; - default: - $str = ''; - } - } - } else { - $this->parseVar($str); - $this->parseVarFunction($str); - $str = ''; - } - break; - case ':': - // 输出某个函数的结果 - $str = substr($str, 1); - $this->parseVar($str); - $str = ''; - break; - case '~': - // 执行某个函数 - $str = substr($str, 1); - $this->parseVar($str); - $str = ''; - break; - case '-': - case '+': - // 输出计算 - $this->parseVar($str); - $str = ''; - break; - case '/': - // 注释标签 - $flag2 = substr($str, 1, 1); - if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { - $str = ''; - } - break; - default: - // 未识别的标签直接返回 - $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; - break; - } - $content = str_replace($match[0], $str, $content); - } - unset($matches); - } - return; - } - - /** - * 模板变量解析,支持使用函数 - * 格式: {$varname|function1|function2=arg1,arg2} - * @access public - * @param string $varStr 变量数据 - * @return void - */ - public function parseVar(&$varStr) - { - $varStr = trim($varStr); - if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { - static $_varParseList = []; - while ($matches[0]) { - $match = array_pop($matches[0]); - //如果已经解析过该变量字串,则直接返回变量值 - if (isset($_varParseList[$match[0]])) { - $parseStr = $_varParseList[$match[0]]; - } else { - if (strpos($match[0], '.')) { - $vars = explode('.', $match[0]); - $first = array_shift($vars); - if ('$Think' == $first) { - // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 - $parseStr = $this->parseThinkVar($vars); - } elseif ('$Request' == $first) { - // 获取Request请求对象参数 - $method = array_shift($vars); - if (!empty($vars)) { - $params = implode('.', $vars); - if ('true' != $params) { - $params = '\'' . $params . '\''; - } - } else { - $params = ''; - } - $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; - } else { - switch ($this->config['tpl_var_identify']) { - case 'array': // 识别为数组 - $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; - break; - case 'obj': // 识别为对象 - $parseStr = $first . '->' . implode('->', $vars); - break; - default: // 自动判断数组或对象 - $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; - } - } - } else { - $parseStr = str_replace(':', '->', $match[0]); - } - $_varParseList[$match[0]] = $parseStr; - } - $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); - } - unset($matches); - } - return; - } - - /** - * 对模板中使用了函数的变量进行解析 - * 格式 {$varname|function1|function2=arg1,arg2} - * @access public - * @param string $varStr 变量字符串 - * @return void - */ - public function parseVarFunction(&$varStr) - { - if (false == strpos($varStr, '|')) { - return; - } - static $_varFunctionList = []; - $_key = md5($varStr); - //如果已经解析过该变量字串,则直接返回变量值 - if (isset($_varFunctionList[$_key])) { - $varStr = $_varFunctionList[$_key]; - } else { - $varArray = explode('|', $varStr); - // 取得变量名称 - $name = array_shift($varArray); - // 对变量使用函数 - $length = count($varArray); - // 取得模板禁止使用函数列表 - $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); - for ($i = 0; $i < $length; $i++) { - $args = explode('=', $varArray[$i], 2); - // 模板函数过滤 - $fun = trim($args[0]); - switch ($fun) { - case 'default': // 特殊模板函数 - if (false === strpos($name, '(')) { - $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; - } else { - $name = '(' . $name . ' ?: ' . $args[1] . ')'; - } - break; - default: // 通用模板函数 - if (!in_array($fun, $template_deny_funs)) { - if (isset($args[1])) { - if (strstr($args[1], '###')) { - $args[1] = str_replace('###', $name, $args[1]); - $name = "$fun($args[1])"; - } else { - $name = "$fun($name,$args[1])"; - } - } else { - if (!empty($args[0])) { - $name = "$fun($name)"; - } - } - } - } - } - $_varFunctionList[$_key] = $name; - $varStr = $name; - } - return; - } - - /** - * 特殊模板变量解析 - * 格式 以 $Think. 打头的变量属于特殊模板变量 - * @access public - * @param array $vars 变量数组 - * @return string - */ - public function parseThinkVar($vars) - { - $type = strtoupper(trim(array_shift($vars))); - $param = implode('.', $vars); - if ($vars) { - switch ($type) { - case 'SERVER': - $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; - break; - case 'GET': - $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; - break; - case 'POST': - $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; - break; - case 'COOKIE': - $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; - break; - case 'SESSION': - $parseStr = '\\think\\Session::get(\'' . $param . '\')'; - break; - case 'ENV': - $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; - break; - case 'REQUEST': - $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; - break; - case 'CONST': - $parseStr = strtoupper($param); - break; - case 'LANG': - $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; - break; - case 'CONFIG': - $parseStr = '\\think\\Config::get(\'' . $param . '\')'; - break; - default: - $parseStr = '\'\''; - break; - } - } else { - switch ($type) { - case 'NOW': - $parseStr = "date('Y-m-d g:i a',time())"; - break; - case 'VERSION': - $parseStr = 'THINK_VERSION'; - break; - case 'LDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; - break; - case 'RDELIM': - $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; - break; - default: - if (defined($type)) { - $parseStr = $type; - } else { - $parseStr = ''; - } - } - } - return $parseStr; - } - - /** - * 分析加载的模板文件并读取内容 支持多个模板文件读取 - * @access private - * @param string $templateName 模板文件名 - * @return string - */ - private function parseTemplateName($templateName) - { - $array = explode(',', $templateName); - $parseStr = ''; - foreach ($array as $templateName) { - if (empty($templateName)) { - continue; - } - if (0 === strpos($templateName, '$')) { - //支持加载变量文件名 - $templateName = $this->get(substr($templateName, 1)); - } - $template = $this->parseTemplateFile($templateName); - if ($template) { - // 获取模板文件内容 - $parseStr .= file_get_contents($template); - } - } - return $parseStr; - } - - /** - * 解析模板文件名 - * @access private - * @param string $template 文件名 - * @return string|false - */ - private function parseTemplateFile($template) - { - if ('' == pathinfo($template, PATHINFO_EXTENSION)) { - if (strpos($template, '@')) { - list($module, $template) = explode('@', $template); - } - if (0 !== strpos($template, '/')) { - $template = str_replace(['/', ':'], $this->config['view_depr'], $template); - } else { - $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); - } - if ($this->config['view_base']) { - $module = isset($module) ? $module : Request::instance()->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); - } else { - $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; - } - $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); - } - - if (is_file($template)) { - // 记录模板文件的更新时间 - $this->includeFile[$template] = filemtime($template); - return $template; - } else { - throw new TemplateNotFoundException('template not exists:' . $template, $template); - } - } - - /** - * 按标签生成正则 - * @access private - * @param string $tagName 标签名 - * @return string - */ - private function getRegex($tagName) - { - $regex = ''; - if ('tag' == $tagName) { - $begin = $this->config['tpl_begin']; - $end = $this->config['tpl_end']; - if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { - $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; - } else { - $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; - } - } else { - $begin = $this->config['taglib_begin']; - $end = $this->config['taglib_end']; - $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; - switch ($tagName) { - case 'block': - if ($single) { - $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; - } else { - $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; - } - break; - case 'literal': - if ($single) { - $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; - $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; - $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; - } else { - $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; - $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; - $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; - } - break; - case 'restoreliteral': - $regex = ''; - break; - case 'include': - $name = 'file'; - case 'taglib': - case 'layout': - case 'extend': - if (empty($name)) { - $name = 'name'; - } - if ($single) { - $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; - } else { - $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; - } - break; - } - } - return '/' . $regex . '/is'; - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; +use think\template\TagLib; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + // 模板变量 + protected $data = []; + // 引擎配置 + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DS, + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + ]; + + private $literal = []; + private $includeFile = []; // 记录所有模板包含的文件路径及更新时间 + protected $storage; + + /** + * 构造函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config['cache_path'] = TEMP_PATH; + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + $class = false !== strpos($type, '\\') ? $type : '\\think\\template\\driver\\' . ucwords($type); + $this->storage = new $class(); + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return string|void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } else { + return; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } else { + $data = $this->data; + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + return $data; + } + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = Cache::get($this->config['cache_id']); + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + $template = $this->parseTemplateFile($template); + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + // 获取并清空缓存 + $content = ob_get_clean(); + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 缓存页面输出 + Cache::set($this->config['cache_id'], $content, $this->config['cache_time']); + } + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + if ($config) { + $this->config($config); + } + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return Template + */ + public function layout($name, $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return boolean + */ + private function checkCache($cacheFile) + { + // 未开启缓存功能 + if (!$this->config['tpl_cache']) { + return false; + } + // 缓存文件不存在 + if (!is_file($cacheFile)) { + return false; + } + // 读取缓存文件失败 + if (!$handle = @fopen($cacheFile, "r")) { + return false; + } + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + if (!isset($matches[1])) { + return false; + } + $includeFile = unserialize($matches[1]); + if (!is_array($includeFile)) { + return false; + } + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return Cache::has($cacheId); + } + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b)/s', '', $content); + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + $this->includeFile = []; + return; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + if (empty($content)) { + return; + } + // 替换literal标签内容 + $this->parseLiteral($content); + // 解析继承 + $this->parseExtend($content); + // 解析布局 + $this->parseLayout($content); + // 检查include语法 + $this->parseInclude($content); + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + return; + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\n", $content); + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($array['name']); + if ($layoutFile) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + return; + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + // 替换模板中的include标签 + $func($content); + return; + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + // 递归检查继承 + $func($extend); + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + $content = $extend; + unset($blocks, $baseBlocks); + } + return; + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + // 清空literal记录 + $this->literal = []; + } + unset($matches); + } + return; + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $regex = $this->getRegex('block'); + $result = []; + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + unset($right, $matches); + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + return explode(',', $matches['name']); + } + return; + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + /** @var Taglib $tLib */ + $tLib = new $className($this); + $tLib->parseTag($content, $hide ? '' : $tagLib); + return; + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $array = []; + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } else { + return $array; + } + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $regex = $this->getRegex('tag'); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + $this->parseVar($name); + $this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + switch ($first) { + case '?': + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = ''; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = ''; + break; + default: + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $str = ''; + break; + case '/': + // 注释标签 + $flag2 = substr($str, 1, 1); + if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + $content = str_replace($match[0], $str, $content); + } + unset($matches); + } + return; + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $varStr = trim($varStr); + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + while ($matches[0]) { + $match = array_pop($matches[0]); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + if ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + $parseStr = '\think\Request::instance()->' . $method . '(' . $params . ')'; + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + $_varParseList[$match[0]] = $parseStr; + } + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + return; + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @return void + */ + public function parseVarFunction(&$varStr) + { + if (false == strpos($varStr, '|')) { + return; + } + static $_varFunctionList = []; + $_key = md5($varStr); + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + // 取得变量名称 + $name = array_shift($varArray); + // 对变量使用函数 + $length = count($varArray); + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + // 模板函数过滤 + $fun = trim($args[0]); + switch ($fun) { + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (!in_array($fun, $template_deny_funs)) { + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + } + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = '\\think\\Request::instance()->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = '\\think\\Request::instance()->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = '\\think\\Request::instance()->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = '\\think\\Cookie::get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = '\\think\\Session::get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = '\\think\\Request::instance()->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = '\\think\\Request::instance()->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = '\\think\\Lang::get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = '\\think\\Config::get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'THINK_VERSION'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $array = explode(',', $templateName); + $parseStr = ''; + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + $template = $this->parseTemplateFile($templateName); + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + if ($this->config['view_base']) { + $module = isset($module) ? $module : Request::instance()->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . basename($this->config['view_path']) . DS : $this->config['view_path']; + } + $template = realpath($path . $template . '.' . ltrim($this->config['view_suffix'], '.')); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + return $template; + } else { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + return '/' . $regex . '/is'; + } +} diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php old mode 100755 new mode 100644 index 071007e..53a545f --- a/thinkphp/library/think/Url.php +++ b/thinkphp/library/think/Url.php @@ -1,333 +1,333 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class Url -{ - // 生成URL地址的root - protected static $root; - protected static $bindCheck; - - /** - * URL生成 支持路由反射 - * @param string $url 路由地址 - * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] - * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 - * @param boolean|string $domain 是否显示域名 或者直接传入域名 - * @return string - */ - public static function build($url = '', $vars = '', $suffix = true, $domain = false) - { - if (false === $domain && Route::rules('domain')) { - $domain = true; - } - // 解析URL - if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { - // [name] 表示使用路由命名标识生成URL - $name = substr($url, 1, $pos - 1); - $url = 'name' . substr($url, $pos + 1); - } - if (false === strpos($url, '://') && 0 !== strpos($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 (strpos($url, '@') && false === strpos($url, '\\')) { - // 解析域名 - list($url, $domain) = explode('@', $url, 2); - } - } - - // 解析参数 - if (is_string($vars)) { - // aaa=1&bbb=2 转换成数组 - parse_str($vars, $vars); - } - - if ($url) { - $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); - if (is_null($rule) && isset($info['query'])) { - $rule = Route::name($url); - // 解析地址里面参数 合并到vars - parse_str($info['query'], $params); - $vars = array_merge($params, $vars); - unset($info['query']); - } - } - if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { - // 匹配路由命名标识 - $url = $match[0]; - // 替换可选分隔符 - $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); - if (!empty($match[1])) { - $domain = $match[1]; - } - if (!is_null($match[2])) { - $suffix = $match[2]; - } - } elseif (!empty($rule) && isset($name)) { - throw new \InvalidArgumentException('route name not exists:' . $name); - } else { - // 检查别名路由 - $alias = Route::rules('alias'); - $matchAlias = false; - if ($alias) { - // 别名路由解析 - foreach ($alias as $key => $val) { - if (is_array($val)) { - $val = $val[0]; - } - if (0 === strpos($url, $val)) { - $url = $key . substr($url, strlen($val)); - $matchAlias = true; - break; - } - } - } - if (!$matchAlias) { - // 路由标识不存在 直接解析 - $url = self::parseUrl($url, $domain); - } - if (isset($info['query'])) { - // 解析地址里面参数 合并到vars - parse_str($info['query'], $params); - $vars = array_merge($params, $vars); - } - } - - // 检测URL绑定 - if (!self::$bindCheck) { - $type = Route::getBind('type'); - if ($type) { - $bind = Route::getBind($type); - if ($bind && 0 === strpos($url, $bind)) { - $url = substr($url, strlen($bind) + 1); - } - } - } - // 还原URL分隔符 - $depr = Config::get('pathinfo_depr'); - $url = str_replace('/', $depr, $url); - - // URL后缀 - $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); - // 锚点 - $anchor = !empty($anchor) ? '#' . $anchor : ''; - // 参数组装 - if (!empty($vars)) { - // 添加参数 - if (Config::get('url_common_param')) { - $vars = http_build_query($vars); - $url .= $suffix . '?' . $vars . $anchor; - } else { - $paramType = Config::get('url_param_type'); - foreach ($vars as $var => $val) { - if ('' !== trim($val)) { - if ($paramType) { - $url .= $depr . urlencode($val); - } else { - $url .= $depr . $var . $depr . urlencode($val); - } - } - } - $url .= $suffix . $anchor; - } - } else { - $url .= $suffix . $anchor; - } - // 检测域名 - $domain = self::parseDomain($url, $domain); - // URL组装 - $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); - - self::$bindCheck = false; - return $url; - } - - // 直接解析URL地址 - protected static function parseUrl($url, &$domain) - { - $request = Request::instance(); - if (0 === strpos($url, '/')) { - // 直接作为路由地址解析 - $url = substr($url, 1); - } elseif (false !== strpos($url, '\\')) { - // 解析到类 - $url = ltrim(str_replace('\\', '/', $url), '/'); - } elseif (0 === strpos($url, '@')) { - // 解析到控制器 - $url = substr($url, 1); - } else { - // 解析到 模块/控制器/操作 - $module = $request->module(); - $domains = Route::rules('domain'); - if (true === $domain && 2 == substr_count($url, '/')) { - $current = $request->host(); - $match = []; - $pos = []; - foreach ($domains as $key => $item) { - if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { - $pos[$key] = strlen($item['[bind]'][0]) + 1; - $match[] = $key; - $module = ''; - } - } - if ($match) { - $domain = current($match); - foreach ($match as $item) { - if (0 === strpos($current, $item)) { - $domain = $item; - } - } - self::$bindCheck = true; - $url = substr($url, $pos[$domain]); - } - } elseif ($domain) { - if (isset($domains[$domain]['[bind]'][0])) { - $bindModule = $domains[$domain]['[bind]'][0]; - if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { - $module = ''; - } - } - } - $module = $module ? $module . '/' : ''; - - $controller = $request->controller(); - if ('' == $url) { - // 空字符串输出当前的 模块/控制器/操作 - $action = $request->action(); - } else { - $path = explode('/', $url); - $action = array_pop($path); - $controller = empty($path) ? $controller : array_pop($path); - $module = empty($path) ? $module : array_pop($path) . '/'; - } - if (Config::get('url_convert')) { - $action = strtolower($action); - $controller = Loader::parseName($controller); - } - $url = $module . $controller . '/' . $action; - } - return $url; - } - - // 检测域名 - protected static function parseDomain(&$url, $domain) - { - if (!$domain) { - return ''; - } - $request = Request::instance(); - $rootDomain = Config::get('url_domain_root'); - if (true === $domain) { - // 自动判断域名 - $domain = Config::get('app_host') ?: $request->host(true); - - $domains = Route::rules('domain'); - if ($domains) { - $route_domain = array_keys($domains); - foreach ($route_domain as $domain_prefix) { - if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { - foreach ($domains as $key => $rule) { - $rule = is_array($rule) ? $rule[0] : $rule; - if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { - $url = ltrim($url, $rule); - $domain = $key; - // 生成对应子域名 - if (!empty($rootDomain)) { - $domain .= $rootDomain; - } - break; - } elseif (false !== strpos($key, '*')) { - if (!empty($rootDomain)) { - $domain .= $rootDomain; - } - break; - } - } - } - } - } - - } else { - if (empty($rootDomain)) { - $host = Config::get('app_host') ?: $request->host(); - $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; - } - if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { - $domain .= '.' . $rootDomain; - } - } - if (false !== strpos($domain, '://')) { - $scheme = ''; - } else { - $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; - } - return $scheme . $domain; - } - - // 解析URL后缀 - protected static function parseSuffix($suffix) - { - if ($suffix) { - $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; - if ($pos = strpos($suffix, '|')) { - $suffix = substr($suffix, 0, $pos); - } - } - return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; - } - - // 匹配路由地址 - public static function getRuleUrl($rule, &$vars = []) - { - foreach ($rule as $item) { - list($url, $pattern, $domain, $suffix) = $item; - if (empty($pattern)) { - return [rtrim($url, '$'), $domain, $suffix]; - } - $type = Config::get('url_common_param'); - foreach ($pattern as $key => $val) { - if (isset($vars[$key])) { - $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); - unset($vars[$key]); - $result = [$url, $domain, $suffix]; - } elseif (2 == $val) { - $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); - $result = [$url, $domain, $suffix]; - } else { - break; - } - } - if (isset($result)) { - return $result; - } - } - return false; - } - - // 指定当前生成URL地址的root - public static function root($root) - { - self::$root = $root; - Request::instance()->root($root); - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + // 生成URL地址的root + protected static $root; + protected static $bindCheck; + + /** + * URL生成 支持路由反射 + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public static function build($url = '', $vars = '', $suffix = true, $domain = false) + { + if (false === $domain && Route::rules('domain')) { + $domain = true; + } + // 解析URL + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + if (false === strpos($url, '://') && 0 !== strpos($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 (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $rule = Route::name(isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : '')); + if (is_null($rule) && isset($info['query'])) { + $rule = Route::name($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + if (!empty($rule) && $match = self::getRuleUrl($rule, $vars)) { + // 匹配路由命名标识 + $url = $match[0]; + // 替换可选分隔符 + $url = preg_replace(['/(\W)\?$/', '/(\W)\?/'], ['', '\1'], $url); + if (!empty($match[1])) { + $domain = $match[1]; + } + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检查别名路由 + $alias = Route::rules('alias'); + $matchAlias = false; + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $val) { + if (is_array($val)) { + $val = $val[0]; + } + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = self::parseUrl($url, $domain); + } + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 检测URL绑定 + if (!self::$bindCheck) { + $type = Route::getBind('type'); + if ($type) { + $bind = Route::getBind($type); + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } + } + } + // 还原URL分隔符 + $depr = Config::get('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + // URL后缀 + $suffix = in_array($url, ['/', '']) ? '' : self::parseSuffix($suffix); + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if (Config::get('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = Config::get('url_param_type'); + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + // 检测域名 + $domain = self::parseDomain($url, $domain); + // URL组装 + $url = $domain . rtrim(self::$root ?: Request::instance()->root(), '/') . '/' . ltrim($url, '/'); + + self::$bindCheck = false; + return $url; + } + + // 直接解析URL地址 + protected static function parseUrl($url, &$domain) + { + $request = Request::instance(); + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $domains = Route::rules('domain'); + if (true === $domain && 2 == substr_count($url, '/')) { + $current = $request->host(); + $match = []; + $pos = []; + foreach ($domains as $key => $item) { + if (isset($item['[bind]']) && 0 === strpos($url, $item['[bind]'][0])) { + $pos[$key] = strlen($item['[bind]'][0]) + 1; + $match[] = $key; + $module = ''; + } + } + if ($match) { + $domain = current($match); + foreach ($match as $item) { + if (0 === strpos($current, $item)) { + $domain = $item; + } + } + self::$bindCheck = true; + $url = substr($url, $pos[$domain]); + } + } elseif ($domain) { + if (isset($domains[$domain]['[bind]'][0])) { + $bindModule = $domains[$domain]['[bind]'][0]; + if ($bindModule && !in_array($bindModule[0], ['\\', '@'])) { + $module = ''; + } + } + } + $module = $module ? $module . '/' : ''; + + $controller = $request->controller(); + if ('' == $url) { + // 空字符串输出当前的 模块/控制器/操作 + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + if (Config::get('url_convert')) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + $url = $module . $controller . '/' . $action; + } + return $url; + } + + // 检测域名 + protected static function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + $request = Request::instance(); + $rootDomain = Config::get('url_domain_root'); + if (true === $domain) { + // 自动判断域名 + $domain = Config::get('app_host') ?: $request->host(); + + $domains = Route::rules('domain'); + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } + } + } + } + } + + } else { + if (empty($rootDomain)) { + $host = Config::get('app_host') ?: $request->host(); + $rootDomain = substr_count($host, '.') > 1 ? substr(strstr($host, '.'), 1) : $host; + } + if (substr_count($domain, '.') < 2 && !strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + } + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $request->isSsl() || Config::get('is_https') ? 'https://' : 'http://'; + } + return $scheme . $domain; + } + + // 解析URL后缀 + protected static function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? Config::get('url_html_suffix') : $suffix; + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public static function getRuleUrl($rule, &$vars = []) + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix) = $item; + if (empty($pattern)) { + return [rtrim($url, '$'), $domain, $suffix]; + } + $type = Config::get('url_common_param'); + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key . '', '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + unset($vars[$key]); + $result = [$url, $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $result = [$url, $domain, $suffix]; + } else { + break; + } + } + if (isset($result)) { + return $result; + } + } + return false; + } + + // 指定当前生成URL地址的root + public static function root($root) + { + self::$root = $root; + Request::instance()->root($root); + } +} diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php old mode 100755 new mode 100644 index 57ab485..6226892 --- a/thinkphp/library/think/Validate.php +++ b/thinkphp/library/think/Validate.php @@ -1,1335 +1,1335 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -use think\exception\ClassNotFoundException; - -class Validate -{ - // 实例 - protected static $instance; - - // 自定义的验证类型 - protected static $type = []; - - // 验证类型别名 - protected $alias = [ - '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', - ]; - - // 当前验证的规则 - protected $rule = []; - - // 验证提示信息 - protected $message = []; - // 验证字段描述 - protected $field = []; - - // 验证规则默认提示信息 - protected static $typeMsg = [ - 'require' => ':attribute require', - 'number' => ':attribute must be numeric', - 'integer' => ':attribute must be integer', - 'float' => ':attribute must be float', - 'boolean' => ':attribute must be bool', - 'email' => ':attribute not a valid email address', - 'array' => ':attribute must be a array', - 'accepted' => ':attribute must be yes,on or 1', - 'date' => ':attribute not a valid datetime', - 'file' => ':attribute not a valid file', - 'image' => ':attribute not a valid image', - 'alpha' => ':attribute must be alpha', - 'alphaNum' => ':attribute must be alpha-numeric', - 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', - 'activeUrl' => ':attribute not a valid domain or ip', - 'chs' => ':attribute must be chinese', - 'chsAlpha' => ':attribute must be chinese or alpha', - 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', - 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', - 'url' => ':attribute not a valid url', - 'ip' => ':attribute not a valid ip', - 'dateFormat' => ':attribute must be dateFormat of :rule', - 'in' => ':attribute must be in :rule', - 'notIn' => ':attribute be notin :rule', - 'between' => ':attribute must between :1 - :2', - 'notBetween' => ':attribute not between :1 - :2', - 'length' => 'size of :attribute must be :rule', - 'max' => 'max size of :attribute must be :rule', - 'min' => 'min size of :attribute must be :rule', - 'after' => ':attribute cannot be less than :rule', - 'before' => ':attribute cannot exceed :rule', - 'expire' => ':attribute not within :rule', - 'allowIp' => 'access IP is not allowed', - 'denyIp' => 'access IP denied', - 'confirm' => ':attribute out of accord with :2', - 'different' => ':attribute cannot be same with :2', - 'egt' => ':attribute must greater than or equal :rule', - 'gt' => ':attribute must greater than :rule', - 'elt' => ':attribute must less than or equal :rule', - 'lt' => ':attribute must less than :rule', - 'eq' => ':attribute must equal :rule', - 'unique' => ':attribute has exists', - 'regex' => ':attribute not conform to the rules', - 'method' => 'invalid Request method', - 'token' => 'invalid token', - 'fileSize' => 'filesize not match', - 'fileExt' => 'extensions to upload is not allowed', - 'fileMime' => 'mimetype to upload is not allowed', - ]; - - // 当前验证场景 - protected $currentScene = null; - - // 正则表达式 regex = ['zip'=>'\d{6}',...] - protected $regex = []; - - // 验证场景 scene = ['edit'=>'name1,name2,...'] - protected $scene = []; - - // 验证失败错误信息 - protected $error = []; - - // 批量验证 - protected $batch = false; - - /** - * 构造函数 - * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 - */ - public function __construct(array $rules = [], $message = [], $field = []) - { - $this->rule = array_merge($this->rule, $rules); - $this->message = array_merge($this->message, $message); - $this->field = array_merge($this->field, $field); - } - - /** - * 实例化验证 - * @access public - * @param array $rules 验证规则 - * @param array $message 验证提示信息 - * @param array $field 验证字段描述信息 - * @return Validate - */ - public static function make($rules = [], $message = [], $field = []) - { - if (is_null(self::$instance)) { - self::$instance = new self($rules, $message, $field); - } - return self::$instance; - } - - /** - * 添加字段验证规则 - * @access protected - * @param string|array $name 字段名称或者规则数组 - * @param mixed $rule 验证规则 - * @return Validate - */ - public function rule($name, $rule = '') - { - if (is_array($name)) { - $this->rule = array_merge($this->rule, $name); - } else { - $this->rule[$name] = $rule; - } - return $this; - } - - /** - * 注册验证(类型)规则 - * @access public - * @param string $type 验证规则类型 - * @param mixed $callback callback方法(或闭包) - * @return void - */ - public static function extend($type, $callback = null) - { - if (is_array($type)) { - self::$type = array_merge(self::$type, $type); - } else { - self::$type[$type] = $callback; - } - } - - /** - * 设置验证规则的默认提示信息 - * @access protected - * @param string|array $type 验证规则类型名称或者数组 - * @param string $msg 验证提示信息 - * @return void - */ - public static function setTypeMsg($type, $msg = null) - { - if (is_array($type)) { - self::$typeMsg = array_merge(self::$typeMsg, $type); - } else { - self::$typeMsg[$type] = $msg; - } - } - - /** - * 设置提示信息 - * @access public - * @param string|array $name 字段名称 - * @param string $message 提示信息 - * @return Validate - */ - public function message($name, $message = '') - { - if (is_array($name)) { - $this->message = array_merge($this->message, $name); - } else { - $this->message[$name] = $message; - } - return $this; - } - - /** - * 设置验证场景 - * @access public - * @param string|array $name 场景名或者场景设置数组 - * @param mixed $fields 要验证的字段 - * @return Validate - */ - public function scene($name, $fields = null) - { - if (is_array($name)) { - $this->scene = array_merge($this->scene, $name); - }if (is_null($fields)) { - // 设置当前场景 - $this->currentScene = $name; - } else { - // 设置验证场景 - $this->scene[$name] = $fields; - } - return $this; - } - - /** - * 判断是否存在某个验证场景 - * @access public - * @param string $name 场景名 - * @return bool - */ - public function hasScene($name) - { - return isset($this->scene[$name]); - } - - /** - * 设置批量验证 - * @access public - * @param bool $batch 是否批量验证 - * @return Validate - */ - public function batch($batch = true) - { - $this->batch = $batch; - return $this; - } - - /** - * 数据自动验证 - * @access public - * @param array $data 数据 - * @param mixed $rules 验证规则 - * @param string $scene 验证场景 - * @return bool - */ - public function check($data, $rules = [], $scene = '') - { - $this->error = []; - - if (empty($rules)) { - // 读取验证规则 - $rules = $this->rule; - } - - // 分析验证规则 - $scene = $this->getScene($scene); - if (is_array($scene)) { - // 处理场景验证字段 - $change = []; - $array = []; - foreach ($scene as $k => $val) { - if (is_numeric($k)) { - $array[] = $val; - } else { - $array[] = $k; - $change[$k] = $val; - } - } - } - - foreach ($rules as $key => $item) { - // field => rule1|rule2... field=>['rule1','rule2',...] - if (is_numeric($key)) { - // [field,rule1|rule2,msg1|msg2] - $key = $item[0]; - $rule = $item[1]; - if (isset($item[2])) { - $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; - } else { - $msg = []; - } - } else { - $rule = $item; - $msg = []; - } - if (strpos($key, '|')) { - // 字段|描述 用于指定属性名称 - list($key, $title) = explode('|', $key); - } else { - $title = isset($this->field[$key]) ? $this->field[$key] : $key; - } - - // 场景检测 - if (!empty($scene)) { - if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { - continue; - } elseif (is_array($scene)) { - if (!in_array($key, $array)) { - continue; - } elseif (isset($change[$key])) { - // 重载某个验证规则 - $rule = $change[$key]; - } - } - } - - // 获取数据 支持二维数组 - $value = $this->getDataValue($data, $key); - - // 字段验证 - if ($rule instanceof \Closure) { - // 匿名函数验证 支持传入当前字段和所有字段两个数据 - $result = call_user_func_array($rule, [$value, $data]); - } else { - $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); - } - - if (true !== $result) { - // 没有返回true 则表示验证失败 - if (!empty($this->batch)) { - // 批量验证 - if (is_array($result)) { - $this->error = array_merge($this->error, $result); - } else { - $this->error[$key] = $result; - } - } else { - $this->error = $result; - return false; - } - } - } - return !empty($this->error) ? false : true; - } - - /** - * 根据验证规则验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rules 验证规则 - * @return bool - */ - protected function checkRule($value, $rules) - { - if ($rules instanceof \Closure) { - return call_user_func_array($rules, [$value]); - } elseif (is_string($rules)) { - $rules = explode('|', $rules); - } - - foreach ($rules as $key => $rule) { - if ($rule instanceof \Closure) { - $result = call_user_func_array($rule, [$value]); - } else { - // 判断验证类型 - list($type, $rule) = $this->getValidateType($key, $rule); - - $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; - - $result = call_user_func_array($callback, [$value, $rule]); - } - - if (true !== $result) { - return $result; - } - } - - return true; - } - - /** - * 验证单个字段规则 - * @access protected - * @param string $field 字段名 - * @param mixed $value 字段值 - * @param mixed $rules 验证规则 - * @param array $data 数据 - * @param string $title 字段描述 - * @param array $msg 提示信息 - * @return mixed - */ - protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) - { - // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] - if (is_string($rules)) { - $rules = explode('|', $rules); - } - $i = 0; - foreach ($rules as $key => $rule) { - if ($rule instanceof \Closure) { - $result = call_user_func_array($rule, [$value, $data]); - $info = is_numeric($key) ? '' : $key; - } else { - // 判断验证类型 - list($type, $rule, $info) = $this->getValidateType($key, $rule); - - // 如果不是require 有数据才会行验证 - if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { - // 验证类型 - $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; - // 验证数据 - $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); - } else { - $result = true; - } - } - - if (false === $result) { - // 验证失败 返回错误信息 - if (isset($msg[$i])) { - $message = $msg[$i]; - if (is_string($message) && strpos($message, '{%') === 0) { - $message = Lang::get(substr($message, 2, -1)); - } - } else { - $message = $this->getRuleMsg($field, $title, $info, $rule); - } - return $message; - } elseif (true !== $result) { - // 返回自定义错误信息 - if (is_string($result) && false !== strpos($result, ':')) { - $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); - } - return $result; - } - $i++; - } - return $result; - } - - /** - * 获取当前验证类型及规则 - * @access public - * @param mixed $key - * @param mixed $rule - * @return array - */ - protected function getValidateType($key, $rule) - { - // 判断验证类型 - if (!is_numeric($key)) { - return [$key, $rule, $key]; - } - - if (strpos($rule, ':')) { - list($type, $rule) = explode(':', $rule, 2); - if (isset($this->alias[$type])) { - // 判断别名 - $type = $this->alias[$type]; - } - $info = $type; - } elseif (method_exists($this, $rule)) { - $type = $rule; - $info = $rule; - $rule = ''; - } else { - $type = 'is'; - $info = $rule; - } - - return [$type, $rule, $info]; - } - - /** - * 验证是否和某个字段的值一致 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @param string $field 字段名 - * @return bool - */ - protected function confirm($value, $rule, $data, $field = '') - { - if ('' == $rule) { - if (strpos($field, '_confirm')) { - $rule = strstr($field, '_confirm', true); - } else { - $rule = $field . '_confirm'; - } - } - return $this->getDataValue($data, $rule) === $value; - } - - /** - * 验证是否和某个字段的值是否不同 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function different($value, $rule, $data) - { - return $this->getDataValue($data, $rule) != $value; - } - - /** - * 验证是否大于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function egt($value, $rule, $data) - { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value >= $val; - } - - /** - * 验证是否大于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function gt($value, $rule, $data) - { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value > $val; - } - - /** - * 验证是否小于等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function elt($value, $rule, $data) - { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value <= $val; - } - - /** - * 验证是否小于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function lt($value, $rule, $data) - { - $val = $this->getDataValue($data, $rule); - return !is_null($val) && $value < $val; - } - - /** - * 验证是否等于某个值 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function eq($value, $rule) - { - return $value == $rule; - } - - /** - * 验证字段值是否为有效格式 - * @access protected - * @param mixed $value 字段值 - * @param string $rule 验证规则 - * @param array $data 验证数据 - * @return bool - */ - protected function is($value, $rule, $data = []) - { - switch ($rule) { - case 'require': - // 必须 - $result = !empty($value) || '0' == $value; - break; - case 'accepted': - // 接受 - $result = in_array($value, ['1', 'on', 'yes']); - break; - case 'date': - // 是否是一个有效日期 - $result = false !== strtotime($value); - break; - case 'alpha': - // 只允许字母 - $result = $this->regex($value, '/^[A-Za-z]+$/'); - break; - case 'alphaNum': - // 只允许字母和数字 - $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); - break; - case 'alphaDash': - // 只允许字母、数字和下划线 破折号 - $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); - break; - case 'chs': - // 只允许汉字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); - break; - case 'chsAlpha': - // 只允许汉字、字母 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); - break; - case 'chsAlphaNum': - // 只允许汉字、字母和数字 - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); - break; - case 'chsDash': - // 只允许汉字、字母、数字和下划线_及破折号- - $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); - break; - case 'activeUrl': - // 是否为有效的网址 - $result = checkdnsrr($value); - break; - case 'ip': - // 是否为IP地址 - $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); - break; - case 'url': - // 是否为一个URL地址 - $result = $this->filter($value, FILTER_VALIDATE_URL); - break; - case 'float': - // 是否为float - $result = $this->filter($value, FILTER_VALIDATE_FLOAT); - break; - case 'number': - $result = is_numeric($value); - break; - case 'integer': - // 是否为整型 - $result = $this->filter($value, FILTER_VALIDATE_INT); - break; - case 'email': - // 是否为邮箱地址 - $result = $this->filter($value, FILTER_VALIDATE_EMAIL); - break; - case 'boolean': - // 是否为布尔值 - $result = in_array($value, [true, false, 0, 1, '0', '1'], true); - break; - case 'array': - // 是否为数组 - $result = is_array($value); - break; - case 'file': - $result = $value instanceof File; - break; - case 'image': - $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); - break; - case 'token': - $result = $this->token($value, '__token__', $data); - break; - default: - if (isset(self::$type[$rule])) { - // 注册的验证规则 - $result = call_user_func_array(self::$type[$rule], [$value]); - } else { - // 正则验证 - $result = $this->regex($value, $rule); - } - } - return $result; - } - - // 判断图像类型 - protected function getImageType($image) - { - if (function_exists('exif_imagetype')) { - return exif_imagetype($image); - } else { - try { - $info = getimagesize($image); - return $info ? $info[2] : false; - } catch (\Exception $e) { - return false; - } - } - } - - /** - * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function activeUrl($value, $rule) - { - if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { - $rule = 'MX'; - } - return checkdnsrr($value, $rule); - } - - /** - * 验证是否有效IP - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 ipv4 ipv6 - * @return bool - */ - protected function ip($value, $rule) - { - if (!in_array($rule, ['ipv4', 'ipv6'])) { - $rule = 'ipv4'; - } - return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); - } - - /** - * 验证上传文件后缀 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function fileExt($file, $rule) - { - if (is_array($file)) { - foreach ($file as $item) { - if (!($item instanceof File) || !$item->checkExt($rule)) { - return false; - } - } - return true; - } elseif ($file instanceof File) { - return $file->checkExt($rule); - } else { - return false; - } - } - - /** - * 验证上传文件类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function fileMime($file, $rule) - { - if (is_array($file)) { - foreach ($file as $item) { - if (!($item instanceof File) || !$item->checkMime($rule)) { - return false; - } - } - return true; - } elseif ($file instanceof File) { - return $file->checkMime($rule); - } else { - return false; - } - } - - /** - * 验证上传文件大小 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function fileSize($file, $rule) - { - if (is_array($file)) { - foreach ($file as $item) { - if (!($item instanceof File) || !$item->checkSize($rule)) { - return false; - } - } - return true; - } elseif ($file instanceof File) { - return $file->checkSize($rule); - } else { - return false; - } - } - - /** - * 验证图片的宽高及类型 - * @access protected - * @param mixed $file 上传文件 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function image($file, $rule) - { - if (!($file instanceof File)) { - return false; - } - if ($rule) { - $rule = explode(',', $rule); - list($width, $height, $type) = getimagesize($file->getRealPath()); - if (isset($rule[2])) { - $imageType = strtolower($rule[2]); - if ('jpeg' == $imageType) { - $imageType = 'jpg'; - } - if (image_type_to_extension($type, false) != $imageType) { - return false; - } - } - - list($w, $h) = $rule; - return $w == $width && $h == $height; - } else { - return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); - } - } - - /** - * 验证请求类型 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function method($value, $rule) - { - $method = Request::instance()->method(); - return strtoupper($rule) == $method; - } - - /** - * 验证时间和日期是否符合指定格式 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function dateFormat($value, $rule) - { - $info = date_parse_from_format($rule, $value); - return 0 == $info['warning_count'] && 0 == $info['error_count']; - } - - /** - * 验证是否唯一 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 - * @param array $data 数据 - * @param string $field 验证字段名 - * @return bool - */ - protected function unique($value, $rule, $data, $field) - { - if (is_string($rule)) { - $rule = explode(',', $rule); - } - if (false !== strpos($rule[0], '\\')) { - // 指定模型类 - $db = new $rule[0]; - } else { - try { - $db = Loader::model($rule[0]); - } catch (ClassNotFoundException $e) { - $db = Db::name($rule[0]); - } - } - $key = isset($rule[1]) ? $rule[1] : $field; - - if (strpos($key, '^')) { - // 支持多个字段验证 - $fields = explode('^', $key); - foreach ($fields as $key) { - $map[$key] = $data[$key]; - } - } elseif (strpos($key, '=')) { - parse_str($key, $map); - } else { - $map[$key] = $data[$field]; - } - - $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); - if (is_string($pk)) { - if (isset($rule[2])) { - $map[$pk] = ['neq', $rule[2]]; - } elseif (isset($data[$pk])) { - $map[$pk] = ['neq', $data[$pk]]; - } - } - if ($db->where($map)->field($pk)->find()) { - return false; - } - return true; - } - - /** - * 使用行为类验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return mixed - */ - protected function behavior($value, $rule, $data) - { - return Hook::exec($rule, '', $data); - } - - /** - * 使用filter_var方式验证 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function filter($value, $rule) - { - if (is_string($rule) && strpos($rule, ',')) { - list($rule, $param) = explode(',', $rule); - } elseif (is_array($rule)) { - $param = isset($rule[1]) ? $rule[1] : null; - $rule = $rule[0]; - } else { - $param = null; - } - return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); - } - - /** - * 验证某个字段等于某个值的时候必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function requireIf($value, $rule, $data) - { - list($field, $val) = explode(',', $rule); - if ($this->getDataValue($data, $field) == $val) { - return !empty($value) || '0' == $value; - } else { - return true; - } - } - - /** - * 通过回调方法验证某个字段是否必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function requireCallback($value, $rule, $data) - { - $result = call_user_func_array($rule, [$value, $data]); - if ($result) { - return !empty($value) || '0' == $value; - } else { - return true; - } - } - - /** - * 验证某个字段有值的情况下必须 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function requireWith($value, $rule, $data) - { - $val = $this->getDataValue($data, $rule); - if (!empty($val)) { - return !empty($value) || '0' == $value; - } else { - return true; - } - } - - /** - * 验证是否在范围内 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function in($value, $rule) - { - return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); - } - - /** - * 验证是否不在某个范围 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function notIn($value, $rule) - { - return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); - } - - /** - * between验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function between($value, $rule) - { - if (is_string($rule)) { - $rule = explode(',', $rule); - } - list($min, $max) = $rule; - return $value >= $min && $value <= $max; - } - - /** - * 使用notbetween验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function notBetween($value, $rule) - { - if (is_string($rule)) { - $rule = explode(',', $rule); - } - list($min, $max) = $rule; - return $value < $min || $value > $max; - } - - /** - * 验证数据长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function length($value, $rule) - { - if (is_array($value)) { - $length = count($value); - } elseif ($value instanceof File) { - $length = $value->getSize(); - } else { - $length = mb_strlen((string) $value); - } - - if (strpos($rule, ',')) { - // 长度区间 - list($min, $max) = explode(',', $rule); - return $length >= $min && $length <= $max; - } else { - // 指定长度 - return $length == $rule; - } - } - - /** - * 验证数据最大长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function max($value, $rule) - { - if (is_array($value)) { - $length = count($value); - } elseif ($value instanceof File) { - $length = $value->getSize(); - } else { - $length = mb_strlen((string) $value); - } - return $length <= $rule; - } - - /** - * 验证数据最小长度 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function min($value, $rule) - { - if (is_array($value)) { - $length = count($value); - } elseif ($value instanceof File) { - $length = $value->getSize(); - } else { - $length = mb_strlen((string) $value); - } - return $length >= $rule; - } - - /** - * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function after($value, $rule) - { - return strtotime($value) >= strtotime($rule); - } - - /** - * 验证日期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function before($value, $rule) - { - return strtotime($value) <= strtotime($rule); - } - - /** - * 验证有效期 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @return bool - */ - protected function expire($value, $rule) - { - if (is_string($rule)) { - $rule = explode(',', $rule); - } - list($start, $end) = $rule; - if (!is_numeric($start)) { - $start = strtotime($start); - } - - if (!is_numeric($end)) { - $end = strtotime($end); - } - return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; - } - - /** - * 验证IP许可 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 - * @return mixed - */ - protected function allowIp($value, $rule) - { - return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); - } - - /** - * 验证IP禁用 - * @access protected - * @param string $value 字段值 - * @param mixed $rule 验证规则 - * @return mixed - */ - protected function denyIp($value, $rule) - { - return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); - } - - /** - * 使用正则验证数据 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 正则规则或者预定义正则名 - * @return mixed - */ - protected function regex($value, $rule) - { - if (isset($this->regex[$rule])) { - $rule = $this->regex[$rule]; - } - if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { - // 不是正则表达式则两端补上/ - $rule = '/^' . $rule . '$/'; - } - return is_scalar($value) && 1 === preg_match($rule, (string) $value); - } - - /** - * 验证表单令牌 - * @access protected - * @param mixed $value 字段值 - * @param mixed $rule 验证规则 - * @param array $data 数据 - * @return bool - */ - protected function token($value, $rule, $data) - { - $rule = !empty($rule) ? $rule : '__token__'; - if (!isset($data[$rule]) || !Session::has($rule)) { - // 令牌数据无效 - return false; - } - - // 令牌验证 - if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { - // 防止重复提交 - Session::delete($rule); // 验证完成销毁session - return true; - } - // 开启TOKEN重置 - Session::delete($rule); - return false; - } - - // 获取错误信息 - public function getError() - { - return $this->error; - } - - /** - * 获取数据值 - * @access protected - * @param array $data 数据 - * @param string $key 数据标识 支持二维 - * @return mixed - */ - protected function getDataValue($data, $key) - { - if (is_numeric($key)) { - $value = $key; - } elseif (strpos($key, '.')) { - // 支持二维数组验证 - list($name1, $name2) = explode('.', $key); - $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; - } else { - $value = isset($data[$key]) ? $data[$key] : null; - } - return $value; - } - - /** - * 获取验证规则的错误提示信息 - * @access protected - * @param string $attribute 字段英文名 - * @param string $title 字段描述名 - * @param string $type 验证规则名称 - * @param mixed $rule 验证规则数据 - * @return string - */ - protected function getRuleMsg($attribute, $title, $type, $rule) - { - if (isset($this->message[$attribute . '.' . $type])) { - $msg = $this->message[$attribute . '.' . $type]; - } elseif (isset($this->message[$attribute][$type])) { - $msg = $this->message[$attribute][$type]; - } elseif (isset($this->message[$attribute])) { - $msg = $this->message[$attribute]; - } elseif (isset(self::$typeMsg[$type])) { - $msg = self::$typeMsg[$type]; - } elseif (0 === strpos($type, 'require')) { - $msg = self::$typeMsg['require']; - } else { - $msg = $title . Lang::get('not conform to the rules'); - } - - if (is_string($msg) && 0 === strpos($msg, '{%')) { - $msg = Lang::get(substr($msg, 2, -1)); - } elseif (Lang::has($msg)) { - $msg = Lang::get($msg); - } - - if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { - // 变量替换 - if (is_string($rule) && strpos($rule, ',')) { - $array = array_pad(explode(',', $rule), 3, ''); - } else { - $array = array_pad([], 3, ''); - } - $msg = str_replace( - [':attribute', ':rule', ':1', ':2', ':3'], - [$title, (string) $rule, $array[0], $array[1], $array[2]], - $msg); - } - return $msg; - } - - /** - * 获取数据验证的场景 - * @access protected - * @param string $scene 验证场景 - * @return array - */ - protected function getScene($scene = '') - { - if (empty($scene)) { - // 读取指定场景 - $scene = $this->currentScene; - } - - if (!empty($scene) && isset($this->scene[$scene])) { - // 如果设置了验证适用场景 - $scene = $this->scene[$scene]; - if (is_string($scene)) { - $scene = explode(',', $scene); - } - } else { - $scene = []; - } - return $scene; - } - - public static function __callStatic($method, $params) - { - $class = self::make(); - if (method_exists($class, $method)) { - return call_user_func_array([$class, $method], $params); - } else { - throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Validate +{ + // 实例 + protected static $instance; + + // 自定义的验证类型 + protected static $type = []; + + // 验证类型别名 + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + // 当前验证的规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + // 验证字段描述 + protected $field = []; + + // 验证规则默认提示信息 + protected static $typeMsg = [ + 'require' => ':attribute require', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + // 当前验证场景 + protected $currentScene = null; + + // 正则表达式 regex = ['zip'=>'\d{6}',...] + protected $regex = []; + + // 验证场景 scene = ['edit'=>'name1,name2,...'] + protected $scene = []; + + // 验证失败错误信息 + protected $error = []; + + // 批量验证 + protected $batch = false; + + /** + * 构造函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], $message = [], $field = []) + { + $this->rule = array_merge($this->rule, $rules); + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 实例化验证 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make($rules = [], $message = [], $field = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($rules, $message, $field); + } + return self::$instance; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则 + * @return Validate + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = array_merge($this->rule, $name); + } else { + $this->rule[$name] = $rule; + } + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access protected + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string|array $name 场景名或者场景设置数组 + * @param mixed $fields 要验证的字段 + * @return Validate + */ + public function scene($name, $fields = null) + { + if (is_array($name)) { + $this->scene = array_merge($this->scene, $name); + }if (is_null($fields)) { + // 设置当前场景 + $this->currentScene = $name; + } else { + // 设置验证场景 + $this->scene[$name] = $fields; + } + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return Validate + */ + public function batch($batch = true) + { + $this->batch = $batch; + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 分析验证规则 + $scene = $this->getScene($scene); + if (is_array($scene)) { + // 处理场景验证字段 + $change = []; + $array = []; + foreach ($scene as $k => $val) { + if (is_numeric($k)) { + $array[] = $val; + } else { + $array[] = $k; + $change[$k] = $val; + } + } + } + + foreach ($rules as $key => $item) { + // field => rule1|rule2... field=>['rule1','rule2',...] + if (is_numeric($key)) { + // [field,rule1|rule2,msg1|msg2] + $key = $item[0]; + $rule = $item[1]; + if (isset($item[2])) { + $msg = is_string($item[2]) ? explode('|', $item[2]) : $item[2]; + } else { + $msg = []; + } + } else { + $rule = $item; + $msg = []; + } + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($scene)) { + if ($scene instanceof \Closure && !call_user_func_array($scene, [$key, $data])) { + continue; + } elseif (is_array($scene)) { + if (!in_array($key, $array)) { + continue; + } elseif (isset($change[$key])) { + // 重载某个验证规则 + $rule = $change[$key]; + } + } + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + // 匿名函数验证 支持传入当前字段和所有字段两个数据 + $result = call_user_func_array($rule, [$value, $data]); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title, $msg); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + protected function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) + { + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + $i = 0; + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + // 如果不是require 有数据才会行验证 + if (0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证类型 + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + // 验证数据 + $result = call_user_func_array($callback, [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (isset($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = Lang::get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace([':attribute', ':rule'], [$title, (string) $rule], $result); + } + return $result; + } + $i++; + } + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($type, $rule) = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + protected function confirm($value, $rule, $data, $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function different($value, $rule, $data) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function egt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value >= $val; + } + + /** + * 验证是否大于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function gt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value > $val; + } + + /** + * 验证是否小于等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function elt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value <= $val; + } + + /** + * 验证是否小于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function lt($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + return !is_null($val) && $value < $val; + } + + /** + * 验证是否等于某个值 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 验证字段值是否为有效格式 + * @access protected + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + protected function is($value, $rule, $data = []) + { + switch ($rule) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'alpha': + // 只允许字母 + $result = $this->regex($value, '/^[A-Za-z]+$/'); + break; + case 'alphaNum': + // 只允许字母和数字 + $result = $this->regex($value, '/^[A-Za-z0-9]+$/'); + break; + case 'alphaDash': + // 只允许字母、数字和下划线 破折号 + $result = $this->regex($value, '/^[A-Za-z0-9\-\_]+$/'); + break; + case 'chs': + // 只允许汉字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}]+$/u'); + break; + case 'chsAlpha': + // 只允许汉字、字母 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u'); + break; + case 'chsAlphaNum': + // 只允许汉字、字母和数字 + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u'); + break; + case 'chsDash': + // 只允许汉字、字母、数字和下划线_及破折号- + $result = $this->regex($value, '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u'); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'ip': + // 是否为IP地址 + $result = $this->filter($value, [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6]); + break; + case 'url': + // 是否为一个URL地址 + $result = $this->filter($value, FILTER_VALIDATE_URL); + break; + case 'float': + // 是否为float + $result = $this->filter($value, FILTER_VALIDATE_FLOAT); + break; + case 'number': + $result = is_numeric($value); + break; + case 'integer': + // 是否为整型 + $result = $this->filter($value, FILTER_VALIDATE_INT); + break; + case 'email': + // 是否为邮箱地址 + $result = $this->filter($value, FILTER_VALIDATE_EMAIL); + break; + case 'boolean': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } else { + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function activeUrl($value, $rule) + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + protected function ip($value, $rule) + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } else { + return false; + } + } + + /** + * 验证上传文件类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } else { + return false; + } + } + + /** + * 验证上传文件大小 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } else { + return false; + } + } + + /** + * 验证图片的宽高及类型 + * @access protected + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + if ($rule) { + $rule = explode(',', $rule); + list($width, $height, $type) = getimagesize($file->getRealPath()); + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + return $w == $width && $h == $height; + } else { + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + } + + /** + * 验证请求类型 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function method($value, $rule) + { + $method = Request::instance()->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + protected function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Loader::model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + $map[$key] = $data[$key]; + } + } elseif (strpos($key, '=')) { + parse_str($key, $map); + } else { + $map[$key] = $data[$field]; + } + + $pk = isset($rule[3]) ? $rule[3] : $db->getPk(); + if (is_string($pk)) { + if (isset($rule[2])) { + $map[$pk] = ['neq', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[$pk] = ['neq', $data[$pk]]; + } + } + if ($db->where($map)->field($pk)->find()) { + return false; + } + return true; + } + + /** + * 使用行为类验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + protected function behavior($value, $rule, $data) + { + return Hook::exec($rule, '', $data); + } + + /** + * 使用filter_var方式验证 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireCallback($value, $rule, $data) + { + $result = call_user_func_array($rule, [$value, $data]); + if ($result) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证某个字段有值的情况下必须 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + if (!empty($val)) { + return !empty($value) || '0' == $value; + } else { + return true; + } + } + + /** + * 验证是否在范围内 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } else { + // 指定长度 + return $length == $rule; + } + } + + /** + * 验证数据最大长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + return $length >= $rule; + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function after($value, $rule) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function before($value, $rule) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + protected function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($start, $end) = $rule; + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function allowIp($value, $rule) + { + return in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access protected + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + protected function denyIp($value, $rule) + { + return !in_array($_SERVER['REMOTE_ADDR'], is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return mixed + */ + protected function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + if (!isset($data[$rule]) || !Session::has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && Session::get($rule) === $data[$rule]) { + // 防止重复提交 + Session::delete($rule); // 验证完成销毁session + return true; + } + // 开启TOKEN重置 + Session::delete($rule); + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持二维数组验证 + list($name1, $name2) = explode('.', $key); + $value = isset($data[$name1][$name2]) ? $data[$name1][$name2] : null; + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $type, $rule) + { + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . Lang::get('not conform to the rules'); + } + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = Lang::get(substr($msg, 2, -1)); + } elseif (Lang::has($msg)) { + $msg = Lang::get($msg); + } + + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + $msg = str_replace( + [':attribute', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return array + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + if (!empty($scene) && isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + if (is_string($scene)) { + $scene = explode(',', $scene); + } + } else { + $scene = []; + } + return $scene; + } + + public static function __callStatic($method, $params) + { + $class = self::make(); + if (method_exists($class, $method)) { + return call_user_func_array([$class, $method], $params); + } else { + throw new \BadMethodCallException('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php old mode 100755 new mode 100644 index 5cdbb27..ca2dadb --- a/thinkphp/library/think/View.php +++ b/thinkphp/library/think/View.php @@ -1,239 +1,239 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -class View -{ - // 视图实例 - protected static $instance; - // 模板引擎实例 - public $engine; - // 模板变量 - protected $data = []; - // 用于静态赋值的模板变量 - protected static $var = []; - // 视图输出替换 - protected $replace = []; - - /** - * 构造函数 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 - */ - public function __construct($engine = [], $replace = []) - { - // 初始化模板引擎 - $this->engine($engine); - // 基础替换字符串 - $request = Request::instance(); - $base = $request->root(); - $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; - if ('' != $root) { - $root = '/' . ltrim($root, '/'); - } - $baseReplace = [ - '__ROOT__' => $root, - '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), - '__STATIC__' => $root . '/static', - '__CSS__' => $root . '/static/css', - '__JS__' => $root . '/static/js', - ]; - $this->replace = array_merge($baseReplace, (array) $replace); - } - - /** - * 初始化视图 - * @access public - * @param array $engine 模板引擎参数 - * @param array $replace 字符串替换参数 - * @return object - */ - public static function instance($engine = [], $replace = []) - { - if (is_null(self::$instance)) { - self::$instance = new self($engine, $replace); - } - return self::$instance; - } - - /** - * 模板变量静态赋值 - * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 - * @return void - */ - public static function share($name, $value = '') - { - if (is_array($name)) { - self::$var = array_merge(self::$var, $name); - } else { - self::$var[$name] = $value; - } - } - - /** - * 模板变量赋值 - * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 - * @return $this - */ - public function assign($name, $value = '') - { - if (is_array($name)) { - $this->data = array_merge($this->data, $name); - } else { - $this->data[$name] = $value; - } - return $this; - } - - /** - * 设置当前模板解析的引擎 - * @access public - * @param array|string $options 引擎参数 - * @return $this - */ - public function engine($options = []) - { - if (is_string($options)) { - $type = $options; - $options = []; - } else { - $type = !empty($options['type']) ? $options['type'] : 'Think'; - } - - $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); - if (isset($options['type'])) { - unset($options['type']); - } - $this->engine = new $class($options); - return $this; - } - - /** - * 配置模板引擎 - * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @return $this - */ - public function config($name, $value = null) - { - $this->engine->config($name, $value); - return $this; - } - - /** - * 解析和获取模板内容 用于输出 - * @param string $template 模板文件名或者内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 - * @param bool $renderContent 是否渲染内容 - * @return string - * @throws Exception - */ - public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) - { - // 模板变量 - $vars = array_merge(self::$var, $this->data, $vars); - - // 页面缓存 - ob_start(); - ob_implicit_flush(0); - - // 渲染输出 - try { - $method = $renderContent ? 'display' : 'fetch'; - // 允许用户自定义模板的字符串替换 - $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); - $this->engine->config('tpl_replace_string', $replace); - $this->engine->$method($template, $vars, $config); - } catch (\Exception $e) { - ob_end_clean(); - throw $e; - } - - // 获取并清空缓存 - $content = ob_get_clean(); - // 内容过滤标签 - Hook::listen('view_filter', $content); - return $content; - } - - /** - * 视图内容替换 - * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 - * @return $this - */ - public function replace($content, $replace = '') - { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; - } - return $this; - } - - /** - * 渲染内容输出 - * @access public - * @param string $content 内容 - * @param array $vars 模板输出变量 - * @param array $replace 替换内容 - * @param array $config 模板参数 - * @return mixed - */ - public function display($content, $vars = [], $replace = [], $config = []) - { - return $this->fetch($content, $vars, $replace, $config, true); - } - - /** - * 模板变量赋值 - * @access public - * @param string $name 变量名 - * @param mixed $value 变量值 - */ - public function __set($name, $value) - { - $this->data[$name] = $value; - } - - /** - * 取得模板显示变量的值 - * @access protected - * @param string $name 模板变量 - * @return mixed - */ - public function __get($name) - { - return $this->data[$name]; - } - - /** - * 检测模板变量是否设置 - * @access public - * @param string $name 模板变量名 - * @return bool - */ - public function __isset($name) - { - return isset($this->data[$name]); - } -} + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + // 视图实例 + protected static $instance; + // 模板引擎实例 + public $engine; + // 模板变量 + protected $data = []; + // 用于静态赋值的模板变量 + protected static $var = []; + // 视图输出替换 + protected $replace = []; + + /** + * 构造函数 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + */ + public function __construct($engine = [], $replace = []) + { + // 初始化模板引擎 + $this->engine($engine); + // 基础替换字符串 + $request = Request::instance(); + $base = $request->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DS) : $base; + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + $baseReplace = [ + '__ROOT__' => $root, + '__URL__' => $base . '/' . $request->module() . '/' . Loader::parseName($request->controller()), + '__STATIC__' => $root . '/static', + '__CSS__' => $root . '/static/css', + '__JS__' => $root . '/static/js', + ]; + $this->replace = array_merge($baseReplace, (array) $replace); + } + + /** + * 初始化视图 + * @access public + * @param array $engine 模板引擎参数 + * @param array $replace 字符串替换参数 + * @return object + */ + public static function instance($engine = [], $replace = []) + { + if (is_null(self::$instance)) { + self::$instance = new self($engine, $replace); + } + return self::$instance; + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return void + */ + public static function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + $class = false !== strpos($type, '\\') ? $type : '\\think\\view\\driver\\' . ucfirst($type); + if (isset($options['type'])) { + unset($options['type']); + } + $this->engine = new $class($options); + return $this; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws Exception + */ + public function fetch($template = '', $vars = [], $replace = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + // 允许用户自定义模板的字符串替换 + $replace = array_merge($this->replace, $replace, (array) $this->engine->config('tpl_replace_string')); + $this->engine->config('tpl_replace_string', $replace); + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + // 内容过滤标签 + Hook::listen('view_filter', $content); + return $content; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $replace 替换内容 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $replace = [], $config = []) + { + return $this->fetch($content, $vars, $replace, $config, true); + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } +} diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php old mode 100755 new mode 100644 index 84d5662..07805e4 --- a/thinkphp/library/think/cache/Driver.php +++ b/thinkphp/library/think/cache/Driver.php @@ -1,231 +1,231 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache; - -/** - * 缓存基础类 - */ -abstract class Driver -{ - protected $handler = null; - protected $options = []; - protected $tag; - - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - abstract public function has($name); - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - abstract public function get($name, $default = false); - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return boolean - */ - abstract public function set($name, $value, $expire = null); - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - abstract public function inc($name, $step = 1); - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - abstract public function dec($name, $step = 1); - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - abstract public function rm($name); - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - abstract public function clear($tag = null); - - /** - * 获取实际的缓存标识 - * @access public - * @param string $name 缓存名 - * @return string - */ - protected function getCacheKey($name) - { - return $this->options['prefix'] . $name; - } - - /** - * 读取缓存并删除 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public function pull($name) - { - $result = $this->get($name, false); - if ($result) { - $this->rm($name); - return $result; - } else { - return; - } - } - - /** - * 如果不存在则写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param int $expire 有效时间 0为永久 - * @return mixed - */ - public function remember($name, $value, $expire = null) - { - if (!$this->has($name)) { - $time = time(); - while ($time + 5 > time() && $this->has($name . '_lock')) { - // 存在锁定则等待 - usleep(200000); - } - - try { - // 锁定 - $this->set($name . '_lock', true); - if ($value instanceof \Closure) { - $value = call_user_func($value); - } - $this->set($name, $value, $expire); - // 解锁 - $this->rm($name . '_lock'); - } catch (\Exception $e) { - // 解锁 - $this->rm($name . '_lock'); - throw $e; - } catch (\throwable $e) { - $this->rm($name . '_lock'); - throw $e; - } - } else { - $value = $this->get($name); - } - return $value; - } - - /** - * 缓存标签 - * @access public - * @param string $name 标签名 - * @param string|array $keys 缓存标识 - * @param bool $overlay 是否覆盖 - * @return $this - */ - public function tag($name, $keys = null, $overlay = false) - { - if (is_null($name)) { - - } elseif (is_null($keys)) { - $this->tag = $name; - } else { - $key = 'tag_' . md5($name); - if (is_string($keys)) { - $keys = explode(',', $keys); - } - $keys = array_map([$this, 'getCacheKey'], $keys); - if ($overlay) { - $value = $keys; - } else { - $value = array_unique(array_merge($this->getTagItem($name), $keys)); - } - $this->set($key, implode(',', $value), 0); - } - return $this; - } - - /** - * 更新标签 - * @access public - * @param string $name 缓存标识 - * @return void - */ - protected function setTagItem($name) - { - if ($this->tag) { - $key = 'tag_' . md5($this->tag); - $this->tag = null; - if ($this->has($key)) { - $value = explode(',', $this->get($key)); - $value[] = $name; - $value = implode(',', array_unique($value)); - } else { - $value = $name; - } - $this->set($key, $value, 0); - } - } - - /** - * 获取标签包含的缓存标识 - * @access public - * @param string $tag 缓存标签 - * @return array - */ - protected function getTagItem($tag) - { - $key = 'tag_' . md5($tag); - $value = $this->get($key); - if ($value) { - return array_filter(explode(',', $value)); - } else { - return []; - } - } - - /** - * 返回句柄对象,可执行其它高级方法 - * - * @access public - * @return object - */ - public function handler() - { - return $this->handler; - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + protected $handler = null; + protected $options = []; + protected $tag; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + if ($value instanceof \Closure) { + $value = call_user_func($value); + } + $this->set($name, $value, $expire); + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + // 解锁 + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = 'tag_' . md5($name); + if (is_string($keys)) { + $keys = explode(',', $keys); + } + $keys = array_map([$this, 'getCacheKey'], $keys); + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + $this->set($key, implode(',', $value), 0); + } + return $this; + } + + /** + * 更新标签 + * @access public + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = 'tag_' . md5($this->tag); + $this->tag = null; + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + $this->set($key, $value, 0); + } + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = 'tag_' . md5($tag); + $value = $this->get($key); + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } +} diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php old mode 100755 new mode 100644 index a707db8..fee6489 --- a/thinkphp/library/think/cache/driver/File.php +++ b/thinkphp/library/think/cache/driver/File.php @@ -1,268 +1,268 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * 文件类型缓存类 - * @author liu21st - */ -class File extends Driver -{ - protected $options = [ - 'expire' => 0, - 'cache_subdir' => true, - 'prefix' => '', - 'path' => CACHE_PATH, - 'data_compress' => false, - ]; - - protected $expire; - - /** - * 构造函数 - * @param array $options - */ - public function __construct($options = []) - { - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; - } - $this->init(); - } - - /** - * 初始化检查 - * @access private - * @return boolean - */ - private function init() - { - // 创建项目缓存目录 - if (!is_dir($this->options['path'])) { - if (mkdir($this->options['path'], 0755, true)) { - return true; - } - } - return false; - } - - /** - * 取得变量的存储文件名 - * @access protected - * @param string $name 缓存变量名 - * @param bool $auto 是否自动创建目录 - * @return string - */ - protected function getCacheKey($name, $auto = false) - { - $name = md5($name); - if ($this->options['cache_subdir']) { - // 使用子目录 - $name = substr($name, 0, 2) . DS . substr($name, 2); - } - if ($this->options['prefix']) { - $name = $this->options['prefix'] . DS . $name; - } - $filename = $this->options['path'] . $name . '.php'; - $dir = dirname($filename); - - if ($auto && !is_dir($dir)) { - mkdir($dir, 0755, true); - } - return $filename; - } - - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - return $this->get($name) ? true : false; - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $filename = $this->getCacheKey($name); - if (!is_file($filename)) { - return $default; - } - $content = file_get_contents($filename); - $this->expire = null; - if (false !== $content) { - $expire = (int) substr($content, 8, 12); - if (0 != $expire && time() > filemtime($filename) + $expire) { - return $default; - } - $this->expire = $expire; - $content = substr($content, 32); - if ($this->options['data_compress'] && function_exists('gzcompress')) { - //启用数据压缩 - $content = gzuncompress($content); - } - $content = unserialize($content); - return $content; - } else { - return $default; - } - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return boolean - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - $filename = $this->getCacheKey($name, true); - if ($this->tag && !is_file($filename)) { - $first = true; - } - $data = serialize($value); - if ($this->options['data_compress'] && function_exists('gzcompress')) { - //数据压缩 - $data = gzcompress($data, 3); - } - $data = "\n" . $data; - $result = file_put_contents($filename, $data); - if ($result) { - isset($first) && $this->setTagItem($filename); - clearstatcache(); - return true; - } else { - return false; - } - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) + $step; - $expire = $this->expire; - } else { - $value = $step; - $expire = 0; - } - - return $this->set($name, $value, $expire) ? $value : false; - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) - $step; - $expire = $this->expire; - } else { - $value = -$step; - $expire = 0; - } - - return $this->set($name, $value, $expire) ? $value : false; - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - $filename = $this->getCacheKey($name); - try { - return $this->unlink($filename); - } catch (\Exception $e) { - } - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - $this->unlink($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } - $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); - foreach ($files as $path) { - if (is_dir($path)) { - $matches = glob($path . '/*.php'); - if (is_array($matches)) { - array_map('unlink', $matches); - } - rmdir($path); - } else { - unlink($path); - } - } - return true; - } - - /** - * 判断文件是否存在后,删除 - * @param $path - * @return bool - * @author byron sampson - * @return boolean - */ - private function unlink($path) - { - return is_file($path) && unlink($path); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => CACHE_PATH, + 'data_compress' => false, + ]; + + protected $expire; + + /** + * 构造函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + if (!is_dir($this->options['path'])) { + if (mkdir($this->options['path'], 0755, true)) { + return true; + } + } + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = md5($name); + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DS . substr($name, 2); + } + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DS . $name; + } + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + mkdir($dir, 0755, true); + } + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (!is_file($filename)) { + return $default; + } + $content = file_get_contents($filename); + $this->expire = null; + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + return $default; + } + $this->expire = $expire; + $content = substr($content, 32); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + $content = unserialize($content); + return $content; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $filename = $this->getCacheKey($name, true); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $data = serialize($value); + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + if ($result) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->expire; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $filename = $this->getCacheKey($name); + try { + return $this->unlink($filename); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*'); + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . '/*.php'); + if (is_array($matches)) { + array_map('unlink', $matches); + } + rmdir($path); + } else { + unlink($path); + } + } + return true; + } + + /** + * 判断文件是否存在后,删除 + * @param $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php old mode 100755 new mode 100644 index 5ba449c..8cbf08f --- a/thinkphp/library/think/cache/driver/Lite.php +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -1,187 +1,187 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * 文件类型缓存类 - * @author liu21st - */ -class Lite extends Driver -{ - protected $options = [ - 'prefix' => '', - 'path' => '', - 'expire' => 0, // 等于 10*365*24*3600(10年) - ]; - - /** - * 构造函数 - * @access public - * - * @param array $options - */ - public function __construct($options = []) - { - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - if (substr($this->options['path'], -1) != DS) { - $this->options['path'] .= DS; - } - - } - - /** - * 取得变量的存储文件名 - * @access protected - * @param string $name 缓存变量名 - * @return string - */ - protected function getCacheKey($name) - { - return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; - } - - /** - * 判断缓存是否存在 - * @access public - * @param string $name 缓存变量名 - * @return mixed - */ - public function has($name) - { - return $this->get($name) ? true : false; - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $filename = $this->getCacheKey($name); - if (is_file($filename)) { - // 判断是否过期 - $mtime = filemtime($filename); - if ($mtime < time()) { - // 清除已经过期的文件 - unlink($filename); - return $default; - } - return include $filename; - } else { - return $default; - } - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return bool - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp(); - } else { - $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; - $expire = time() + $expire; - } - $filename = $this->getCacheKey($name); - if ($this->tag && !is_file($filename)) { - $first = true; - } - $ret = file_put_contents($filename, ("setTagItem($filename); - touch($filename, $expire); - } - return $ret; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) + $step; - } else { - $value = $step; - } - return $this->set($name, $value, 0) ? $value : false; - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) - $step; - } else { - $value = -$step; - } - return $this->set($name, $value, 0) ? $value : false; - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - return unlink($this->getCacheKey($name)); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return bool - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - unlink($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } - array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 构造函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + if (substr($this->options['path'], -1) != DS) { + $this->options['path'] .= DS; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $filename = $this->getCacheKey($name); + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + $filename = $this->getCacheKey($name); + if ($this->tag && !is_file($filename)) { + $first = true; + } + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DS : '') . '*.php')); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php old mode 100755 new mode 100644 index e24b31e..58703ea --- a/thinkphp/library/think/cache/driver/Memcache.php +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -1,177 +1,177 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -class Memcache extends Driver -{ - protected $options = [ - 'host' => '127.0.0.1', - 'port' => 11211, - 'expire' => 0, - 'timeout' => 0, // 超时时间(单位:毫秒) - 'persistent' => true, - 'prefix' => '', - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @access public - * @throws \BadFunctionCallException - */ - public function __construct($options = []) - { - if (!extension_loaded('memcache')) { - throw new \BadFunctionCallException('not support: memcache'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $this->handler = new \Memcache; - // 支持集群 - $hosts = explode(',', $this->options['host']); - $ports = explode(',', $this->options['port']); - if (empty($ports[0])) { - $ports[0] = 11211; - } - // 建立连接 - foreach ((array) $hosts as $i => $host) { - $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; - $this->options['timeout'] > 0 ? - $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : - $this->handler->addServer($host, $port, $this->options['persistent'], 1); - } - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - $key = $this->getCacheKey($name); - return $this->handler->get($key) ? true : false; - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return bool - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - if ($this->tag && !$this->has($name)) { - $first = true; - } - $key = $this->getCacheKey($name); - if ($this->handler->set($key, $value, 0, $expire)) { - isset($first) && $this->setTagItem($key); - return true; - } - return false; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - $key = $this->getCacheKey($name); - if ($this->handler->get($key)) { - return $this->handler->increment($key, $step); - } - return $this->handler->set($key, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - $key = $this->getCacheKey($name); - $value = $this->handler->get($key) - $step; - $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } - } - - /** - * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl - * @return bool - */ - public function rm($name, $ttl = false) - { - $key = $this->getCacheKey($name); - return false === $ttl ? - $this->handler->delete($key) : - $this->handler->delete($key, $ttl); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return bool - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - $this->handler->delete($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } - return $this->handler->flush(); - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php old mode 100755 new mode 100644 index cc6932c..5aab5a3 --- a/thinkphp/library/think/cache/driver/Memcached.php +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -1,187 +1,187 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -class Memcached extends Driver -{ - protected $options = [ - 'host' => '127.0.0.1', - 'port' => 11211, - 'expire' => 0, - 'timeout' => 0, // 超时时间(单位:毫秒) - 'prefix' => '', - 'username' => '', //账号 - 'password' => '', //密码 - 'option' => [], - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @access public - */ - public function __construct($options = []) - { - if (!extension_loaded('memcached')) { - throw new \BadFunctionCallException('not support: memcached'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $this->handler = new \Memcached; - if (!empty($this->options['option'])) { - $this->handler->setOptions($this->options['option']); - } - // 设置连接超时时间(单位:毫秒) - if ($this->options['timeout'] > 0) { - $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); - } - // 支持集群 - $hosts = explode(',', $this->options['host']); - $ports = explode(',', $this->options['port']); - if (empty($ports[0])) { - $ports[0] = 11211; - } - // 建立连接 - $servers = []; - foreach ((array) $hosts as $i => $host) { - $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; - } - $this->handler->addServers($servers); - if ('' != $this->options['username']) { - $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); - $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); - } - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - $key = $this->getCacheKey($name); - return $this->handler->get($key) ? true : false; - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $result = $this->handler->get($this->getCacheKey($name)); - return false !== $result ? $result : $default; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return bool - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - if ($this->tag && !$this->has($name)) { - $first = true; - } - $key = $this->getCacheKey($name); - $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; - if ($this->handler->set($key, $value, $expire)) { - isset($first) && $this->setTagItem($key); - return true; - } - return false; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - $key = $this->getCacheKey($name); - if ($this->handler->get($key)) { - return $this->handler->increment($key, $step); - } - return $this->handler->set($key, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - $key = $this->getCacheKey($name); - $value = $this->handler->get($key) - $step; - $res = $this->handler->set($key, $value); - if (!$res) { - return false; - } else { - return $value; - } - } - - /** - * 删除缓存 - * @param string $name 缓存变量名 - * @param bool|false $ttl - * @return bool - */ - public function rm($name, $ttl = false) - { - $key = $this->getCacheKey($name); - return false === $ttl ? - $this->handler->delete($key) : - $this->handler->delete($key, $ttl); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return bool - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - $this->handler->deleteMulti($keys); - $this->rm('tag_' . md5($tag)); - return true; - } - return $this->handler->flush(); - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Memcached; + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $result = $this->handler->get($this->getCacheKey($name)); + return false !== $result ? $result : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire; + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + if (!$res) { + return false; + } else { + return $value; + } + } + + /** + * 删除缓存 + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $key = $this->getCacheKey($name); + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + $this->handler->deleteMulti($keys); + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php old mode 100755 new mode 100644 index d0eb4f8..027b3ea --- a/thinkphp/library/think/cache/driver/Redis.php +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -1,188 +1,188 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 - * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 - * - * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis - * @author 尘缘 <130775@qq.com> - */ -class Redis extends Driver -{ - protected $options = [ - 'host' => '127.0.0.1', - 'port' => 6379, - 'password' => '', - 'select' => 0, - 'timeout' => 0, - 'expire' => 0, - 'persistent' => false, - 'prefix' => '', - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @access public - */ - public function __construct($options = []) - { - if (!extension_loaded('redis')) { - throw new \BadFunctionCallException('not support: redis'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $this->handler = new \Redis; - if ($this->options['persistent']) { - $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); - } else { - $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); - } - - if ('' != $this->options['password']) { - $this->handler->auth($this->options['password']); - } - - if (0 != $this->options['select']) { - $this->handler->select($this->options['select']); - } - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - return $this->handler->get($this->getCacheKey($name)) ? true : false; - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $value = $this->handler->get($this->getCacheKey($name)); - if (is_null($value) || false === $value) { - return $default; - } - - try { - $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; - } catch (\Exception $e) { - $result = $default; - } - - return $result; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return boolean - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - if ($this->tag && !$this->has($name)) { - $first = true; - } - $key = $this->getCacheKey($name); - $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); - if ($expire) { - $result = $this->handler->setex($key, $expire, $value); - } else { - $result = $this->handler->set($key, $value); - } - isset($first) && $this->setTagItem($key); - return $result; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - $key = $this->getCacheKey($name); - - return $this->handler->incrby($key, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - $key = $this->getCacheKey($name); - - return $this->handler->decrby($key, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - return $this->handler->delete($this->getCacheKey($name)); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - $this->handler->delete($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } - return $this->handler->flushDB(); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('redis')) { + throw new \BadFunctionCallException('not support: redis'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $this->handler = new \Redis; + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->handler->exists($this->getCacheKey($name)); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $value = $this->handler->get($this->getCacheKey($name)); + if (is_null($value) || false === $value) { + return $default; + } + + try { + $result = 0 === strpos($value, 'think_serialize:') ? unserialize(substr($value, 16)) : $value; + } catch (\Exception $e) { + $result = $default; + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + $value = is_scalar($value) ? $value : 'think_serialize:' . serialize($value); + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + isset($first) && $this->setTagItem($key); + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return $this->handler->delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + return $this->handler->flushDB(); + } + +} diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php old mode 100755 new mode 100644 index e144ac9..dc2ee05 --- a/thinkphp/library/think/cache/driver/Sqlite.php +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -1,199 +1,199 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * Sqlite缓存驱动 - * @author liu21st - */ -class Sqlite extends Driver -{ - protected $options = [ - 'db' => ':memory:', - 'table' => 'sharedmemory', - 'prefix' => '', - 'expire' => 0, - 'persistent' => false, - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException - * @access public - */ - public function __construct($options = []) - { - if (!extension_loaded('sqlite')) { - throw new \BadFunctionCallException('not support: sqlite'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; - $this->handler = $func($this->options['db']); - } - - /** - * 获取实际的缓存标识 - * @access public - * @param string $name 缓存名 - * @return string - */ - protected function getCacheKey($name) - { - return $this->options['prefix'] . sqlite_escape_string($name); - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; - $result = sqlite_query($this->handler, $sql); - return sqlite_num_rows($result); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $name = $this->getCacheKey($name); - $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; - $result = sqlite_query($this->handler, $sql); - if (sqlite_num_rows($result)) { - $content = sqlite_fetch_single($result); - if (function_exists('gzcompress')) { - //启用数据压缩 - $content = gzuncompress($content); - } - return unserialize($content); - } - return $default; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return boolean - */ - public function set($name, $value, $expire = null) - { - $name = $this->getCacheKey($name); - $value = sqlite_escape_string(serialize($value)); - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp(); - } else { - $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 - } - if (function_exists('gzcompress')) { - //数据压缩 - $value = gzcompress($value, 3); - } - if ($this->tag) { - $tag = $this->tag; - $this->tag = null; - } else { - $tag = ''; - } - $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; - if (sqlite_query($this->handler, $sql)) { - return true; - } - return false; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) + $step; - } else { - $value = $step; - } - return $this->set($name, $value, 0) ? $value : false; - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - if ($this->has($name)) { - $value = $this->get($name) - $step; - } else { - $value = -$step; - } - return $this->set($name, $value, 0) ? $value : false; - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - $name = $this->getCacheKey($name); - $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; - sqlite_query($this->handler, $sql); - return true; - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public function clear($tag = null) - { - if ($tag) { - $name = sqlite_escape_string($tag); - $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; - sqlite_query($this->handler, $sql); - return true; - } - $sql = 'DELETE FROM ' . $this->options['table']; - sqlite_query($this->handler, $sql); - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $name = $this->getCacheKey($name); + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . $_SERVER['REQUEST_TIME'] . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return unserialize($content); + } + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $name = $this->getCacheKey($name); + $value = sqlite_escape_string(serialize($value)); + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + if (sqlite_query($this->handler, $sql)) { + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $name = $this->getCacheKey($name); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($tag); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + $sql = 'DELETE FROM ' . $this->options['table']; + sqlite_query($this->handler, $sql); + return true; + } +} diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php old mode 100755 new mode 100644 index 8e247be..03f8d35 --- a/thinkphp/library/think/cache/driver/Wincache.php +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -1,152 +1,152 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * Wincache缓存驱动 - * @author liu21st - */ -class Wincache extends Driver -{ - protected $options = [ - 'prefix' => '', - 'expire' => 0, - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @throws \BadFunctionCallException - * @access public - */ - public function __construct($options = []) - { - if (!function_exists('wincache_ucache_info')) { - throw new \BadFunctionCallException('not support: WinCache'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - $key = $this->getCacheKey($name); - return wincache_ucache_exists($key); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $key = $this->getCacheKey($name); - return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return boolean - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - $key = $this->getCacheKey($name); - if ($this->tag && !$this->has($name)) { - $first = true; - } - if (wincache_ucache_set($key, $value, $expire)) { - isset($first) && $this->setTagItem($key); - return true; - } - return false; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - $key = $this->getCacheKey($name); - return wincache_ucache_inc($key, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - $key = $this->getCacheKey($name); - return wincache_ucache_dec($key, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - return wincache_ucache_delete($this->getCacheKey($name)); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public function clear($tag = null) - { - if ($tag) { - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - wincache_ucache_delete($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } else { - return wincache_ucache_clear(); - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + * @access public + */ + public function __construct($options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return wincache_ucache_exists($key) ? wincache_ucache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + $key = $this->getCacheKey($name); + if ($this->tag && !$this->has($name)) { + $first = true; + } + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + wincache_ucache_delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } else { + return wincache_ucache_clear(); + } + } + +} diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php old mode 100755 new mode 100644 index 9113105..4d94c03 --- a/thinkphp/library/think/cache/driver/Xcache.php +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -1,155 +1,155 @@ - -// +---------------------------------------------------------------------- - -namespace think\cache\driver; - -use think\cache\Driver; - -/** - * Xcache缓存驱动 - * @author liu21st - */ -class Xcache extends Driver -{ - protected $options = [ - 'prefix' => '', - 'expire' => 0, - ]; - - /** - * 构造函数 - * @param array $options 缓存参数 - * @access public - * @throws \BadFunctionCallException - */ - public function __construct($options = []) - { - if (!function_exists('xcache_info')) { - throw new \BadFunctionCallException('not support: Xcache'); - } - if (!empty($options)) { - $this->options = array_merge($this->options, $options); - } - } - - /** - * 判断缓存 - * @access public - * @param string $name 缓存变量名 - * @return bool - */ - public function has($name) - { - $key = $this->getCacheKey($name); - return xcache_isset($key); - } - - /** - * 读取缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $default 默认值 - * @return mixed - */ - public function get($name, $default = false) - { - $key = $this->getCacheKey($name); - return xcache_isset($key) ? xcache_get($key) : $default; - } - - /** - * 写入缓存 - * @access public - * @param string $name 缓存变量名 - * @param mixed $value 存储数据 - * @param integer|\DateTime $expire 有效时间(秒) - * @return boolean - */ - public function set($name, $value, $expire = null) - { - if (is_null($expire)) { - $expire = $this->options['expire']; - } - if ($expire instanceof \DateTime) { - $expire = $expire->getTimestamp() - time(); - } - if ($this->tag && !$this->has($name)) { - $first = true; - } - $key = $this->getCacheKey($name); - if (xcache_set($key, $value, $expire)) { - isset($first) && $this->setTagItem($key); - return true; - } - return false; - } - - /** - * 自增缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function inc($name, $step = 1) - { - $key = $this->getCacheKey($name); - return xcache_inc($key, $step); - } - - /** - * 自减缓存(针对数值缓存) - * @access public - * @param string $name 缓存变量名 - * @param int $step 步长 - * @return false|int - */ - public function dec($name, $step = 1) - { - $key = $this->getCacheKey($name); - return xcache_dec($key, $step); - } - - /** - * 删除缓存 - * @access public - * @param string $name 缓存变量名 - * @return boolean - */ - public function rm($name) - { - return xcache_unset($this->getCacheKey($name)); - } - - /** - * 清除缓存 - * @access public - * @param string $tag 标签名 - * @return boolean - */ - public function clear($tag = null) - { - if ($tag) { - // 指定标签清除 - $keys = $this->getTagItem($tag); - foreach ($keys as $key) { - xcache_unset($key); - } - $this->rm('tag_' . md5($tag)); - return true; - } - if (function_exists('xcache_unset_by_prefix')) { - return xcache_unset_by_prefix($this->options['prefix']); - } else { - return false; - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + ]; + + /** + * 构造函数 + * @param array $options 缓存参数 + * @access public + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $key = $this->getCacheKey($name); + return xcache_isset($key) ? xcache_get($key) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + if (is_null($expire)) { + $expire = $this->options['expire']; + } + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + if ($this->tag && !$this->has($name)) { + $first = true; + } + $key = $this->getCacheKey($name); + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $key = $this->getCacheKey($name); + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + xcache_unset($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/thinkphp/library/think/config/driver/Ini.php b/thinkphp/library/think/config/driver/Ini.php old mode 100755 new mode 100644 index 713d541..bcd12b6 --- a/thinkphp/library/think/config/driver/Ini.php +++ b/thinkphp/library/think/config/driver/Ini.php @@ -1,24 +1,24 @@ - -// +---------------------------------------------------------------------- - -namespace think\config\driver; - -class Ini -{ - public function parse($config) - { - if (is_file($config)) { - return parse_ini_file($config, true); - } else { - return parse_ini_string($config, true); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + public function parse($config) + { + if (is_file($config)) { + return parse_ini_file($config, true); + } else { + return parse_ini_string($config, true); + } + } +} diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php old mode 100755 new mode 100644 index 158427b..479dcc8 --- a/thinkphp/library/think/config/driver/Json.php +++ b/thinkphp/library/think/config/driver/Json.php @@ -1,24 +1,24 @@ - -// +---------------------------------------------------------------------- - -namespace think\config\driver; - -class Json -{ - public function parse($config) - { - if (is_file($config)) { - $config = file_get_contents($config); - } - $result = json_decode($config, true); - return $result; - } -} + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + public function parse($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + $result = json_decode($config, true); + return $result; + } +} diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php old mode 100755 new mode 100644 index dc2107d..1158519 --- a/thinkphp/library/think/config/driver/Xml.php +++ b/thinkphp/library/think/config/driver/Xml.php @@ -1,31 +1,31 @@ - -// +---------------------------------------------------------------------- - -namespace think\config\driver; - -class Xml -{ - public function parse($config) - { - if (is_file($config)) { - $content = simplexml_load_file($config); - } else { - $content = simplexml_load_string($config); - } - $result = (array) $content; - foreach ($result as $key => $val) { - if (is_object($val)) { - $result[$key] = (array) $val; - } - } - return $result; - } -} + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + public function parse($config) + { + if (is_file($config)) { + $content = simplexml_load_file($config); + } else { + $content = simplexml_load_string($config); + } + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + return $result; + } +} diff --git a/thinkphp/library/think/console/Command.php b/thinkphp/library/think/console/Command.php old mode 100755 new mode 100644 index 5075fb6..d0caad2 --- a/thinkphp/library/think/console/Command.php +++ b/thinkphp/library/think/console/Command.php @@ -1,470 +1,470 @@ - -// +---------------------------------------------------------------------- - -namespace think\console; - -use think\Console; -use think\console\input\Argument; -use think\console\input\Definition; -use think\console\input\Option; - -class Command -{ - - /** @var Console */ - private $console; - private $name; - private $aliases = []; - private $definition; - private $help; - private $description; - private $ignoreValidationErrors = false; - private $consoleDefinitionMerged = false; - private $consoleDefinitionMergedWithArgs = false; - private $code; - private $synopsis = []; - private $usages = []; - - /** @var Input */ - protected $input; - - /** @var Output */ - protected $output; - - /** - * 构造方法 - * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 - * @throws \LogicException - * @api - */ - public function __construct($name = null) - { - $this->definition = new Definition(); - - if (null !== $name) { - $this->setName($name); - } - - $this->configure(); - - if (!$this->name) { - throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); - } - } - - /** - * 忽略验证错误 - */ - public function ignoreValidationErrors() - { - $this->ignoreValidationErrors = true; - } - - /** - * 设置控制台 - * @param Console $console - */ - public function setConsole(Console $console = null) - { - $this->console = $console; - } - - /** - * 获取控制台 - * @return Console - * @api - */ - public function getConsole() - { - return $this->console; - } - - /** - * 是否有效 - * @return bool - */ - public function isEnabled() - { - return true; - } - - /** - * 配置指令 - */ - protected function configure() - { - } - - /** - * 执行指令 - * @param Input $input - * @param Output $output - * @return null|int - * @throws \LogicException - * @see setCode() - */ - protected function execute(Input $input, Output $output) - { - throw new \LogicException('You must override the execute() method in the concrete command class.'); - } - - /** - * 用户验证 - * @param Input $input - * @param Output $output - */ - protected function interact(Input $input, Output $output) - { - } - - /** - * 初始化 - * @param Input $input An InputInterface instance - * @param Output $output An OutputInterface instance - */ - protected function initialize(Input $input, Output $output) - { - } - - /** - * 执行 - * @param Input $input - * @param Output $output - * @return int - * @throws \Exception - * @see setCode() - * @see execute() - */ - public function run(Input $input, Output $output) - { - $this->input = $input; - $this->output = $output; - - $this->getSynopsis(true); - $this->getSynopsis(false); - - $this->mergeConsoleDefinition(); - - try { - $input->bind($this->definition); - } catch (\Exception $e) { - if (!$this->ignoreValidationErrors) { - throw $e; - } - } - - $this->initialize($input, $output); - - if ($input->isInteractive()) { - $this->interact($input, $output); - } - - $input->validate(); - - if ($this->code) { - $statusCode = call_user_func($this->code, $input, $output); - } else { - $statusCode = $this->execute($input, $output); - } - - return is_numeric($statusCode) ? (int) $statusCode : 0; - } - - /** - * 设置执行代码 - * @param callable $code callable(InputInterface $input, OutputInterface $output) - * @return Command - * @throws \InvalidArgumentException - * @see execute() - */ - public function setCode(callable $code) - { - if (!is_callable($code)) { - throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); - } - - if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { - $r = new \ReflectionFunction($code); - if (null === $r->getClosureThis()) { - $code = \Closure::bind($code, $this); - } - } - - $this->code = $code; - - return $this; - } - - /** - * 合并参数定义 - * @param bool $mergeArgs - */ - public function mergeConsoleDefinition($mergeArgs = true) - { - if (null === $this->console - || (true === $this->consoleDefinitionMerged - && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) - ) { - return; - } - - if ($mergeArgs) { - $currentArguments = $this->definition->getArguments(); - $this->definition->setArguments($this->console->getDefinition()->getArguments()); - $this->definition->addArguments($currentArguments); - } - - $this->definition->addOptions($this->console->getDefinition()->getOptions()); - - $this->consoleDefinitionMerged = true; - if ($mergeArgs) { - $this->consoleDefinitionMergedWithArgs = true; - } - } - - /** - * 设置参数定义 - * @param array|Definition $definition - * @return Command - * @api - */ - public function setDefinition($definition) - { - if ($definition instanceof Definition) { - $this->definition = $definition; - } else { - $this->definition->setDefinition($definition); - } - - $this->consoleDefinitionMerged = false; - - return $this; - } - - /** - * 获取参数定义 - * @return Definition - * @api - */ - public function getDefinition() - { - return $this->definition; - } - - /** - * 获取当前指令的参数定义 - * @return Definition - */ - public function getNativeDefinition() - { - return $this->getDefinition(); - } - - /** - * 添加参数 - * @param string $name 名称 - * @param int $mode 类型 - * @param string $description 描述 - * @param mixed $default 默认值 - * @return Command - */ - public function addArgument($name, $mode = null, $description = '', $default = null) - { - $this->definition->addArgument(new Argument($name, $mode, $description, $default)); - - return $this; - } - - /** - * 添加选项 - * @param string $name 选项名称 - * @param string $shortcut 别名 - * @param int $mode 类型 - * @param string $description 描述 - * @param mixed $default 默认值 - * @return Command - */ - public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) - { - $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); - - return $this; - } - - /** - * 设置指令名称 - * @param string $name - * @return Command - * @throws \InvalidArgumentException - */ - public function setName($name) - { - $this->validateName($name); - - $this->name = $name; - - return $this; - } - - /** - * 获取指令名称 - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * 设置描述 - * @param string $description - * @return Command - */ - public function setDescription($description) - { - $this->description = $description; - - return $this; - } - - /** - * 获取描述 - * @return string - */ - public function getDescription() - { - return $this->description; - } - - /** - * 设置帮助信息 - * @param string $help - * @return Command - */ - public function setHelp($help) - { - $this->help = $help; - - return $this; - } - - /** - * 获取帮助信息 - * @return string - */ - public function getHelp() - { - return $this->help; - } - - /** - * 描述信息 - * @return string - */ - public function getProcessedHelp() - { - $name = $this->name; - - $placeholders = [ - '%command.name%', - '%command.full_name%', - ]; - $replacements = [ - $name, - $_SERVER['PHP_SELF'] . ' ' . $name, - ]; - - return str_replace($placeholders, $replacements, $this->getHelp()); - } - - /** - * 设置别名 - * @param string[] $aliases - * @return Command - * @throws \InvalidArgumentException - */ - public function setAliases($aliases) - { - if (!is_array($aliases) && !$aliases instanceof \Traversable) { - throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); - } - - foreach ($aliases as $alias) { - $this->validateName($alias); - } - - $this->aliases = $aliases; - - return $this; - } - - /** - * 获取别名 - * @return array - */ - public function getAliases() - { - return $this->aliases; - } - - /** - * 获取简介 - * @param bool $short 是否简单的 - * @return string - */ - public function getSynopsis($short = false) - { - $key = $short ? 'short' : 'long'; - - if (!isset($this->synopsis[$key])) { - $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); - } - - return $this->synopsis[$key]; - } - - /** - * 添加用法介绍 - * @param string $usage - * @return $this - */ - public function addUsage($usage) - { - if (0 !== strpos($usage, $this->name)) { - $usage = sprintf('%s %s', $this->name, $usage); - } - - $this->usages[] = $usage; - - return $this; - } - - /** - * 获取用法介绍 - * @return array - */ - public function getUsages() - { - return $this->usages; - } - - /** - * 验证指令名称 - * @param string $name - * @throws \InvalidArgumentException - */ - private function validateName($name) - { - if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { - throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws \LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws \Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output) + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws \InvalidArgumentException + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } +} diff --git a/thinkphp/library/think/console/Input.php b/thinkphp/library/think/console/Input.php old mode 100755 new mode 100644 index ce98007..2482dfd --- a/thinkphp/library/think/console/Input.php +++ b/thinkphp/library/think/console/Input.php @@ -1,464 +1,464 @@ - -// +---------------------------------------------------------------------- - -namespace think\console; - -use think\console\input\Argument; -use think\console\input\Definition; -use think\console\input\Option; - -class Input -{ - - /** - * @var Definition - */ - protected $definition; - - /** - * @var Option[] - */ - protected $options = []; - - /** - * @var Argument[] - */ - protected $arguments = []; - - protected $interactive = true; - - private $tokens; - private $parsed; - - public function __construct($argv = null) - { - if (null === $argv) { - $argv = $_SERVER['argv']; - // 去除命令名 - array_shift($argv); - } - - $this->tokens = $argv; - - $this->definition = new Definition(); - } - - protected function setTokens(array $tokens) - { - $this->tokens = $tokens; - } - - /** - * 绑定实例 - * @param Definition $definition A InputDefinition instance - */ - public function bind(Definition $definition) - { - $this->arguments = []; - $this->options = []; - $this->definition = $definition; - - $this->parse(); - } - - /** - * 解析参数 - */ - protected function parse() - { - $parseOptions = true; - $this->parsed = $this->tokens; - while (null !== $token = array_shift($this->parsed)) { - if ($parseOptions && '' == $token) { - $this->parseArgument($token); - } elseif ($parseOptions && '--' == $token) { - $parseOptions = false; - } elseif ($parseOptions && 0 === strpos($token, '--')) { - $this->parseLongOption($token); - } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { - $this->parseShortOption($token); - } else { - $this->parseArgument($token); - } - } - } - - /** - * 解析短选项 - * @param string $token 当前的指令. - */ - private function parseShortOption($token) - { - $name = substr($token, 1); - - if (strlen($name) > 1) { - if ($this->definition->hasShortcut($name[0]) - && $this->definition->getOptionForShortcut($name[0])->acceptValue() - ) { - $this->addShortOption($name[0], substr($name, 1)); - } else { - $this->parseShortOptionSet($name); - } - } else { - $this->addShortOption($name, null); - } - } - - /** - * 解析短选项 - * @param string $name 当前指令 - * @throws \RuntimeException - */ - private function parseShortOptionSet($name) - { - $len = strlen($name); - for ($i = 0; $i < $len; ++$i) { - if (!$this->definition->hasShortcut($name[$i])) { - throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); - } - - $option = $this->definition->getOptionForShortcut($name[$i]); - if ($option->acceptValue()) { - $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); - - break; - } else { - $this->addLongOption($option->getName(), null); - } - } - } - - /** - * 解析完整选项 - * @param string $token 当前指令 - */ - private function parseLongOption($token) - { - $name = substr($token, 2); - - if (false !== $pos = strpos($name, '=')) { - $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); - } else { - $this->addLongOption($name, null); - } - } - - /** - * 解析参数 - * @param string $token 当前指令 - * @throws \RuntimeException - */ - private function parseArgument($token) - { - $c = count($this->arguments); - - if ($this->definition->hasArgument($c)) { - $arg = $this->definition->getArgument($c); - - $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; - - } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { - $arg = $this->definition->getArgument($c - 1); - - $this->arguments[$arg->getName()][] = $token; - } else { - throw new \RuntimeException('Too many arguments.'); - } - } - - /** - * 添加一个短选项的值 - * @param string $shortcut 短名称 - * @param mixed $value 值 - * @throws \RuntimeException - */ - private function addShortOption($shortcut, $value) - { - if (!$this->definition->hasShortcut($shortcut)) { - throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); - } - - /** - * 添加一个完整选项的值 - * @param string $name 选项名 - * @param mixed $value 值 - * @throws \RuntimeException - */ - private function addLongOption($name, $value) - { - if (!$this->definition->hasOption($name)) { - throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); - } - - $option = $this->definition->getOption($name); - - if (false === $value) { - $value = null; - } - - if (null !== $value && !$option->acceptValue()) { - throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); - } - - if (null === $value && $option->acceptValue() && count($this->parsed)) { - $next = array_shift($this->parsed); - if (isset($next[0]) && '-' !== $next[0]) { - $value = $next; - } elseif (empty($next)) { - $value = ''; - } else { - array_unshift($this->parsed, $next); - } - } - - if (null === $value) { - if ($option->isValueRequired()) { - throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); - } - - if (!$option->isArray()) { - $value = $option->isValueOptional() ? $option->getDefault() : true; - } - } - - if ($option->isArray()) { - $this->options[$name][] = $value; - } else { - $this->options[$name] = $value; - } - } - - /** - * 获取第一个参数 - * @return string|null - */ - public function getFirstArgument() - { - foreach ($this->tokens as $token) { - if ($token && '-' === $token[0]) { - continue; - } - - return $token; - } - return; - } - - /** - * 检查原始参数是否包含某个值 - * @param string|array $values 需要检查的值 - * @return bool - */ - public function hasParameterOption($values) - { - $values = (array) $values; - - foreach ($this->tokens as $token) { - foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value . '=')) { - return true; - } - } - } - - return false; - } - - /** - * 获取原始选项的值 - * @param string|array $values 需要检查的值 - * @param mixed $default 默认值 - * @return mixed The option value - */ - public function getParameterOption($values, $default = false) - { - $values = (array) $values; - $tokens = $this->tokens; - - while (0 < count($tokens)) { - $token = array_shift($tokens); - - foreach ($values as $value) { - if ($token === $value || 0 === strpos($token, $value . '=')) { - if (false !== $pos = strpos($token, '=')) { - return substr($token, $pos + 1); - } - - return array_shift($tokens); - } - } - } - - return $default; - } - - /** - * 验证输入 - * @throws \RuntimeException - */ - public function validate() - { - if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { - throw new \RuntimeException('Not enough arguments.'); - } - } - - /** - * 检查输入是否是交互的 - * @return bool - */ - public function isInteractive() - { - return $this->interactive; - } - - /** - * 设置输入的交互 - * @param bool - */ - public function setInteractive($interactive) - { - $this->interactive = (bool) $interactive; - } - - /** - * 获取所有的参数 - * @return Argument[] - */ - public function getArguments() - { - return array_merge($this->definition->getArgumentDefaults(), $this->arguments); - } - - /** - * 根据名称获取参数 - * @param string $name 参数名 - * @return mixed - * @throws \InvalidArgumentException - */ - public function getArgument($name) - { - if (!$this->definition->hasArgument($name)) { - throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) - ->getDefault(); - } - - /** - * 设置参数的值 - * @param string $name 参数名 - * @param string $value 值 - * @throws \InvalidArgumentException - */ - public function setArgument($name, $value) - { - if (!$this->definition->hasArgument($name)) { - throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - $this->arguments[$name] = $value; - } - - /** - * 检查是否存在某个参数 - * @param string|int $name 参数名或位置 - * @return bool - */ - public function hasArgument($name) - { - return $this->definition->hasArgument($name); - } - - /** - * 获取所有的选项 - * @return Option[] - */ - public function getOptions() - { - return array_merge($this->definition->getOptionDefaults(), $this->options); - } - - /** - * 获取选项值 - * @param string $name 选项名称 - * @return mixed - * @throws \InvalidArgumentException - */ - public function getOption($name) - { - if (!$this->definition->hasOption($name)) { - throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); - } - - /** - * 设置选项值 - * @param string $name 选项名 - * @param string|bool $value 值 - * @throws \InvalidArgumentException - */ - public function setOption($name, $value) - { - if (!$this->definition->hasOption($name)) { - throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); - } - - $this->options[$name] = $value; - } - - /** - * 是否有某个选项 - * @param string $name 选项名 - * @return bool - */ - public function hasOption($name) - { - return $this->definition->hasOption($name) && isset($this->options[$name]); - } - - /** - * 转义指令 - * @param string $token - * @return string - */ - public function escapeToken($token) - { - return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); - } - - /** - * 返回传递给命令的参数的字符串 - * @return string - */ - public function __toString() - { - $tokens = array_map(function ($token) { - if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { - return $match[1] . $this->escapeToken($match[2]); - } - - if ($token && '-' !== $token[0]) { - return $this->escapeToken($token); - } - - return $token; - }, $this->tokens); - - return implode(' ', $tokens); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/thinkphp/library/think/console/LICENSE b/thinkphp/library/think/console/LICENSE old mode 100755 new mode 100644 index a8de861..0abe056 --- a/thinkphp/library/think/console/LICENSE +++ b/thinkphp/library/think/console/LICENSE @@ -1,19 +1,19 @@ -Copyright (c) 2004-2016 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/thinkphp/library/think/console/Output.php b/thinkphp/library/think/console/Output.php old mode 100755 new mode 100644 index 7dfdb9b..65dc9fb --- a/thinkphp/library/think/console/Output.php +++ b/thinkphp/library/think/console/Output.php @@ -1,222 +1,222 @@ - -// +---------------------------------------------------------------------- - -namespace think\console; - -use Exception; -use think\console\output\Ask; -use think\console\output\Descriptor; -use think\console\output\driver\Buffer; -use think\console\output\driver\Console; -use think\console\output\driver\Nothing; -use think\console\output\Question; -use think\console\output\question\Choice; -use think\console\output\question\Confirmation; - -/** - * Class Output - * @package think\console - * - * @see \think\console\output\driver\Console::setDecorated - * @method void setDecorated($decorated) - * - * @see \think\console\output\driver\Buffer::fetch - * @method string fetch() - * - * @method void info($message) - * @method void error($message) - * @method void comment($message) - * @method void warning($message) - * @method void highlight($message) - * @method void question($message) - */ -class Output -{ - const VERBOSITY_QUIET = 0; - const VERBOSITY_NORMAL = 1; - const VERBOSITY_VERBOSE = 2; - const VERBOSITY_VERY_VERBOSE = 3; - const VERBOSITY_DEBUG = 4; - - const OUTPUT_NORMAL = 0; - const OUTPUT_RAW = 1; - const OUTPUT_PLAIN = 2; - - private $verbosity = self::VERBOSITY_NORMAL; - - /** @var Buffer|Console|Nothing */ - private $handle = null; - - protected $styles = [ - 'info', - 'error', - 'comment', - 'question', - 'highlight', - 'warning' - ]; - - public function __construct($driver = 'console') - { - $class = '\\think\\console\\output\\driver\\' . ucwords($driver); - - $this->handle = new $class($this); - } - - public function ask(Input $input, $question, $default = null, $validator = null) - { - $question = new Question($question, $default); - $question->setValidator($validator); - - return $this->askQuestion($input, $question); - } - - public function askHidden(Input $input, $question, $validator = null) - { - $question = new Question($question); - - $question->setHidden(true); - $question->setValidator($validator); - - return $this->askQuestion($input, $question); - } - - public function confirm(Input $input, $question, $default = true) - { - return $this->askQuestion($input, new Confirmation($question, $default)); - } - - /** - * {@inheritdoc} - */ - public function choice(Input $input, $question, array $choices, $default = null) - { - if (null !== $default) { - $values = array_flip($choices); - $default = $values[$default]; - } - - return $this->askQuestion($input, new Choice($question, $choices, $default)); - } - - protected function askQuestion(Input $input, Question $question) - { - $ask = new Ask($input, $this, $question); - $answer = $ask->run(); - - if ($input->isInteractive()) { - $this->newLine(); - } - - return $answer; - } - - protected function block($style, $message) - { - $this->writeln("<{$style}>{$message}"); - } - - /** - * 输出空行 - * @param int $count - */ - public function newLine($count = 1) - { - $this->write(str_repeat(PHP_EOL, $count)); - } - - /** - * 输出信息并换行 - * @param string $messages - * @param int $type - */ - public function writeln($messages, $type = self::OUTPUT_NORMAL) - { - $this->write($messages, true, $type); - } - - /** - * 输出信息 - * @param string $messages - * @param bool $newline - * @param int $type - */ - public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) - { - $this->handle->write($messages, $newline, $type); - } - - public function renderException(\Exception $e) - { - $this->handle->renderException($e); - } - - /** - * {@inheritdoc} - */ - public function setVerbosity($level) - { - $this->verbosity = (int) $level; - } - - /** - * {@inheritdoc} - */ - public function getVerbosity() - { - return $this->verbosity; - } - - public function isQuiet() - { - return self::VERBOSITY_QUIET === $this->verbosity; - } - - public function isVerbose() - { - return self::VERBOSITY_VERBOSE <= $this->verbosity; - } - - public function isVeryVerbose() - { - return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; - } - - public function isDebug() - { - return self::VERBOSITY_DEBUG <= $this->verbosity; - } - - public function describe($object, array $options = []) - { - $descriptor = new Descriptor(); - $options = array_merge([ - 'raw_text' => false, - ], $options); - - $descriptor->describe($this, $object, $options); - } - - public function __call($method, $args) - { - if (in_array($method, $this->styles)) { - array_unshift($args, $method); - return call_user_func_array([$this, 'block'], $args); - } - - if ($this->handle && method_exists($this->handle, $method)) { - return call_user_func_array([$this->handle, $method], $args); - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning' + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + +} diff --git a/thinkphp/library/think/console/bin/README.md b/thinkphp/library/think/console/bin/README.md old mode 100755 new mode 100644 index c515914..9acc52f --- a/thinkphp/library/think/console/bin/README.md +++ b/thinkphp/library/think/console/bin/README.md @@ -1 +1 @@ -console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/thinkphp/library/think/console/bin/hiddeninput.exe old mode 100755 new mode 100644 diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php old mode 100755 new mode 100644 index ffd1dc9..39806c3 --- a/thinkphp/library/think/console/command/Build.php +++ b/thinkphp/library/think/console/command/Build.php @@ -1,56 +1,56 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command; - -use think\console\Command; -use think\console\Input; -use think\console\input\Option; -use think\console\Output; - -class Build extends Command -{ - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->setName('build') - ->setDefinition([ - new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), - new Option('module', null, Option::VALUE_OPTIONAL, "module name"), - ]) - ->setDescription('Build Application Dirs'); - } - - protected function execute(Input $input, Output $output) - { - if ($input->hasOption('module')) { - \think\Build::module($input->getOption('module')); - $output->writeln("Successed"); - return; - } - - if ($input->hasOption('config')) { - $build = include $input->getOption('config'); - } else { - $build = include APP_PATH . 'build.php'; - } - if (empty($build)) { - $output->writeln("Build Config Is Empty"); - return; - } - \think\Build::run($build); - $output->writeln("Successed"); - - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class Build extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + \think\Build::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include APP_PATH . 'build.php'; + } + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + \think\Build::run($build); + $output->writeln("Successed"); + + } +} diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php old mode 100755 new mode 100644 index 7016bd0..1b5102e --- a/thinkphp/library/think/console/command/Clear.php +++ b/thinkphp/library/think/console/command/Clear.php @@ -1,54 +1,63 @@ - -// +---------------------------------------------------------------------- -namespace think\console\command; - -use think\console\Command; -use think\console\Input; -use think\console\input\Option; -use think\console\Output; - -class Clear extends Command -{ - protected function configure() - { - // 指令配置 - $this - ->setName('clear') - ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) - ->setDescription('Clear runtime file'); - } - - protected function execute(Input $input, Output $output) - { - $path = $input->getOption('path') ?: RUNTIME_PATH; - - if (is_dir($path)) { - $this->clearPath($path); - } - - $output->writeln("Clear Successed"); - } - - protected function clearPath($path) - { - $path = realpath($path) . DS; - $files = scandir($path); - if ($files) { - foreach ($files as $file) { - if ('.' != $file && '..' != $file && is_dir($path . $file)) { - $this->clearPath($path . $file); - } elseif ('.gitignore' != $file && is_file($path . $file)) { - unlink($path . $file); - } - } - } - } -} + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\Cache; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this + ->setName('clear') + ->addArgument('type', Argument::OPTIONAL, 'type to clear', null) + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $path = $input->getOption('path') ?: RUNTIME_PATH; + + $type = $input->getArgument('type'); + + if ($type == 'route') { + Cache::clear('route_check'); + } else { + if (is_dir($path)) { + $this->clearPath($path); + } + } + + $output->writeln("Clear Successed"); + } + + protected function clearPath($path) + { + $path = realpath($path) . DS; + $files = scandir($path); + if ($files) { + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clearPath($path . $file); + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } + } +} diff --git a/thinkphp/library/think/console/command/Help.php b/thinkphp/library/think/console/command/Help.php old mode 100755 new mode 100644 index 1a3b641..bae2c65 --- a/thinkphp/library/think/console/command/Help.php +++ b/thinkphp/library/think/console/command/Help.php @@ -1,69 +1,69 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command; - -use think\console\Command; -use think\console\Input; -use think\console\input\Argument as InputArgument; -use think\console\input\Option as InputOption; -use think\console\Output; - -class Help extends Command -{ - - private $command; - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->ignoreValidationErrors(); - - $this->setName('help')->setDefinition([ - new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), - new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), - ])->setDescription('Displays help for a command')->setHelp(<<%command.name% command displays help for a given command: - - php %command.full_name% list - -To display the list of available commands, please use the list command. -EOF - ); - } - - /** - * Sets the command. - * @param Command $command The command to set - */ - public function setCommand(Command $command) - { - $this->command = $command; - } - - /** - * {@inheritdoc} - */ - protected function execute(Input $input, Output $output) - { - if (null === $this->command) { - $this->command = $this->getConsole()->find($input->getArgument('command_name')); - } - - $output->describe($this->command, [ - 'raw_text' => $input->getOption('raw'), - ]); - - $this->command = null; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Help extends Command +{ + + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this->setName('help')->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ])->setDescription('Displays help for a command')->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/thinkphp/library/think/console/command/Lists.php b/thinkphp/library/think/console/command/Lists.php old mode 100755 new mode 100644 index 1cc4b9b..084ddaa --- a/thinkphp/library/think/console/command/Lists.php +++ b/thinkphp/library/think/console/command/Lists.php @@ -1,74 +1,74 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command; - -use think\console\Command; -use think\console\Input; -use think\console\Output; -use think\console\input\Argument as InputArgument; -use think\console\input\Option as InputOption; -use think\console\input\Definition as InputDefinition; - -class Lists extends Command -{ - - /** - * {@inheritdoc} - */ - protected function configure() - { - $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: - - php %command.full_name% - -You can also display the commands for a specific namespace: - - php %command.full_name% test - -It's also possible to get raw list of commands (useful for embedding command runner): - - php %command.full_name% --raw -EOF - ); - } - - /** - * {@inheritdoc} - */ - public function getNativeDefinition() - { - return $this->createDefinition(); - } - - /** - * {@inheritdoc} - */ - protected function execute(Input $input, Output $output) - { - $output->describe($this->getConsole(), [ - 'raw_text' => $input->getOption('raw'), - 'namespace' => $input->getArgument('namespace'), - ]); - } - - /** - * {@inheritdoc} - */ - private function createDefinition() - { - return new InputDefinition([ - new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), - new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') - ]); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\input\Definition as InputDefinition; + +class Lists extends Command +{ + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list') + ]); + } +} diff --git a/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php old mode 100755 new mode 100644 index 414b6e6..d1daf34 --- a/thinkphp/library/think/console/command/Make.php +++ b/thinkphp/library/think/console/command/Make.php @@ -1,110 +1,110 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command; - -use think\App; -use think\Config; -use think\console\Command; -use think\console\Input; -use think\console\input\Argument; -use think\console\Output; - -abstract class Make extends Command -{ - - protected $type; - - abstract protected function getStub(); - - protected function configure() - { - $this->addArgument('name', Argument::REQUIRED, "The name of the class"); - } - - protected function execute(Input $input, Output $output) - { - - $name = trim($input->getArgument('name')); - - $classname = $this->getClassName($name); - - $pathname = $this->getPathName($classname); - - if (is_file($pathname)) { - $output->writeln('' . $this->type . ' already exists!'); - return false; - } - - if (!is_dir(dirname($pathname))) { - mkdir(strtolower(dirname($pathname)), 0755, true); - } - - file_put_contents($pathname, $this->buildClass($classname)); - - $output->writeln('' . $this->type . ' created successfully.'); - - } - - protected function buildClass($name) - { - $stub = file_get_contents($this->getStub()); - - $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); - - $class = str_replace($namespace . '\\', '', $name); - - return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ - $class, - $namespace, - App::$namespace, - ], $stub); - - } - - protected function getPathName($name) - { - $name = str_replace(App::$namespace . '\\', '', $name); - - return APP_PATH . str_replace('\\', '/', $name) . '.php'; - } - - protected function getClassName($name) - { - $appNamespace = App::$namespace; - - if (strpos($name, $appNamespace . '\\') === 0) { - return $name; - } - - if (Config::get('app_multi_module')) { - if (strpos($name, '/')) { - list($module, $name) = explode('/', $name, 2); - } else { - $module = 'common'; - } - } else { - $module = null; - } - - if (strpos($name, '/') !== false) { - $name = str_replace('/', '\\', $name); - } - - return $this->getNamespace($appNamespace, $module) . '\\' . $name; - } - - protected function getNamespace($appNamespace, $module) - { - return $module ? ($appNamespace . '\\' . $module) : $appNamespace; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +abstract class Make extends Command +{ + + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(strtolower(dirname($pathname)), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $namespace, + App::$namespace, + ], $stub); + + } + + protected function getPathName($name) + { + $name = str_replace(App::$namespace . '\\', '', $name); + + return APP_PATH . str_replace('\\', '/', $name) . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::$namespace; + + if (strpos($name, $appNamespace . '\\') === 0) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php old mode 100755 new mode 100644 index f337a0f..afa7be9 --- a/thinkphp/library/think/console/command/make/Controller.php +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -1,50 +1,50 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command\make; - -use think\Config; -use think\console\command\Make; -use think\console\input\Option; - -class Controller extends Make -{ - - protected $type = "Controller"; - - protected function configure() - { - parent::configure(); - $this->setName('make:controller') - ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') - ->setDescription('Create a new resource controller class'); - } - - protected function getStub() - { - if ($this->input->getOption('plain')) { - return __DIR__ . '/stubs/controller.plain.stub'; - } - - return __DIR__ . '/stubs/controller.stub'; - } - - protected function getClassName($name) - { - return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); - } - - protected function getNamespace($appNamespace, $module) - { - return parent::getNamespace($appNamespace, $module) . '\controller'; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\Config; +use think\console\command\Make; +use think\console\input\Option; + +class Controller extends Make +{ + + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + if ($this->input->getOption('plain')) { + return __DIR__ . '/stubs/controller.plain.stub'; + } + + return __DIR__ . '/stubs/controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php old mode 100755 new mode 100644 index 6a68360..d4e9b5d --- a/thinkphp/library/think/console/command/make/Model.php +++ b/thinkphp/library/think/console/command/make/Model.php @@ -1,36 +1,36 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\command\make; - -use think\console\command\Make; - -class Model extends Make -{ - protected $type = "Model"; - - protected function configure() - { - parent::configure(); - $this->setName('make:model') - ->setDescription('Create a new model class'); - } - - protected function getStub() - { - return __DIR__ . '/stubs/model.stub'; - } - - protected function getNamespace($appNamespace, $module) - { - return parent::getNamespace($appNamespace, $module) . '\model'; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Model extends Make +{ + protected $type = "Model"; + + protected function configure() + { + parent::configure(); + $this->setName('make:model') + ->setDescription('Create a new model class'); + } + + protected function getStub() + { + return __DIR__ . '/stubs/model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/thinkphp/library/think/console/command/make/stubs/controller.plain.stub b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub old mode 100755 new mode 100644 index 3fe1d2f..b7539dc --- a/thinkphp/library/think/console/command/make/stubs/controller.plain.stub +++ b/thinkphp/library/think/console/command/make/stubs/controller.plain.stub @@ -1,10 +1,10 @@ - -// +---------------------------------------------------------------------- -namespace think\console\command\optimize; - -use think\App; -use think\Config; -use think\console\Command; -use think\console\Input; -use think\console\Output; - -class Autoload extends Command -{ - - protected function configure() - { - $this->setName('optimize:autoload') - ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); - } - - protected function execute(Input $input, Output $output) - { - - $classmapFile = << realpath(rtrim(APP_PATH)), - 'think\\' => LIB_PATH . 'think', - 'behavior\\' => LIB_PATH . 'behavior', - 'traits\\' => LIB_PATH . 'traits', - '' => realpath(rtrim(EXTEND_PATH)), - ]; - - $root_namespace = Config::get('root_namespace'); - foreach ($root_namespace as $namespace => $dir) { - $namespacesToScan[$namespace . '\\'] = realpath($dir); - } - - krsort($namespacesToScan); - $classMap = []; - foreach ($namespacesToScan as $namespace => $dir) { - - if (!is_dir($dir)) { - continue; - } - - $namespaceFilter = $namespace === '' ? null : $namespace; - $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); - } - - ksort($classMap); - foreach ($classMap as $class => $code) { - $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; - } - $classmapFile .= "];\n"; - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); - } - - file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); - - $output->writeln('Succeed!'); - } - - protected function addClassMapCode($dir, $namespace, $classMap) - { - foreach ($this->createMap($dir, $namespace) as $class => $path) { - - $pathCode = $this->getPathCode($path) . ",\n"; - - if (!isset($classMap[$class])) { - $classMap[$class] = $pathCode; - } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { - $this->output->writeln( - 'Warning: Ambiguous class resolution, "' . $class . '"' . - ' was found in both "' . str_replace(["',\n"], [ - '', - ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' - ); - } - } - return $classMap; - } - - protected function getPathCode($path) - { - - $baseDir = ''; - $libPath = $this->normalizePath(realpath(LIB_PATH)); - $appPath = $this->normalizePath(realpath(APP_PATH)); - $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); - $rootPath = $this->normalizePath(realpath(ROOT_PATH)); - $path = $this->normalizePath($path); - - if ($libPath !== null && strpos($path, $libPath . '/') === 0) { - $path = substr($path, strlen(LIB_PATH)); - $baseDir = 'LIB_PATH'; - } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { - $path = substr($path, strlen($appPath) + 1); - $baseDir = 'APP_PATH'; - } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { - $path = substr($path, strlen($extendPath) + 1); - $baseDir = 'EXTEND_PATH'; - } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { - $path = substr($path, strlen($rootPath) + 1); - $baseDir = 'ROOT_PATH'; - } - - if ($path !== false) { - $baseDir .= " . "; - } - - return $baseDir . (($path !== false) ? var_export($path, true) : ""); - } - - protected function normalizePath($path) - { - if ($path === false) { - return; - } - $parts = []; - $path = strtr($path, '\\', '/'); - $prefix = ''; - $absolute = false; - - if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { - $prefix = $match[1]; - $path = substr($path, strlen($prefix)); - } - - if (substr($path, 0, 1) === '/') { - $absolute = true; - $path = substr($path, 1); - } - - $up = false; - foreach (explode('/', $path) as $chunk) { - if ('..' === $chunk && ($absolute || $up)) { - array_pop($parts); - $up = !(empty($parts) || '..' === end($parts)); - } elseif ('.' !== $chunk && '' !== $chunk) { - $parts[] = $chunk; - $up = '..' !== $chunk; - } - } - - return $prefix . ($absolute ? '/' : '') . implode('/', $parts); - } - - protected function createMap($path, $namespace = null) - { - if (is_string($path)) { - if (is_file($path)) { - $path = [new \SplFileInfo($path)]; - } elseif (is_dir($path)) { - - $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); - - $path = []; - - /** @var \SplFileInfo $object */ - foreach ($objects as $object) { - if ($object->isFile() && $object->getExtension() == 'php') { - $path[] = $object; - } - } - } else { - throw new \RuntimeException( - 'Could not scan for classes inside "' . $path . - '" which does not appear to be a file nor a folder' - ); - } - } - - $map = []; - - /** @var \SplFileInfo $file */ - foreach ($path as $file) { - $filePath = $file->getRealPath(); - - if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { - continue; - } - - $classes = $this->findClasses($filePath); - - foreach ($classes as $class) { - if (null !== $namespace && 0 !== strpos($class, $namespace)) { - continue; - } - - if (!isset($map[$class])) { - $map[$class] = $filePath; - } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { - $this->output->writeln( - 'Warning: Ambiguous class resolution, "' . $class . '"' . - ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' - ); - } - } - } - - return $map; - } - - protected function findClasses($path) - { - $extraTypes = '|trait'; - - $contents = @php_strip_whitespace($path); - if (!$contents) { - if (!file_exists($path)) { - $message = 'File at "%s" does not exist, check your classmap definitions'; - } elseif (!is_readable($path)) { - $message = 'File at "%s" is not readable, check its permissions'; - } elseif ('' === trim(file_get_contents($path))) { - return []; - } else { - $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; - } - $error = error_get_last(); - if (isset($error['message'])) { - $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; - } - throw new \RuntimeException(sprintf($message, $path)); - } - - if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { - return []; - } - - // strip heredocs/nowdocs - $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); - // strip strings - $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); - // strip leading non-php code if needed - if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); - if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) - | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] - ) - }ix', $contents, $matches); - - $classes = []; - $namespace = ''; - - for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { - if (!empty($matches['ns'][$i])) { - $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; - } else { - $name = $matches['name'][$i]; - if ($name[0] === ':') { - $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); - } elseif ($matches['type'][$i] === 'enum') { - $name = rtrim($name, ':'); - } - $classes[] = ltrim($namespace . $name, '\\'); - } - } - - return $classes; - } - -} + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\Config; +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Autoload extends Command +{ + + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = << realpath(rtrim(APP_PATH)), + 'think\\' => LIB_PATH . 'think', + 'behavior\\' => LIB_PATH . 'behavior', + 'traits\\' => LIB_PATH . 'traits', + '' => realpath(rtrim(EXTEND_PATH)), + ]; + + $root_namespace = Config::get('root_namespace'); + foreach ($root_namespace as $namespace => $dir) { + $namespacesToScan[$namespace . '\\'] = realpath($dir); + } + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = $namespace === '' ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'classmap' . EXT, $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + + $baseDir = ''; + $libPath = $this->normalizePath(realpath(LIB_PATH)); + $appPath = $this->normalizePath(realpath(APP_PATH)); + $extendPath = $this->normalizePath(realpath(EXTEND_PATH)); + $rootPath = $this->normalizePath(realpath(ROOT_PATH)); + $path = $this->normalizePath($path); + + if ($libPath !== null && strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen(LIB_PATH)); + $baseDir = 'LIB_PATH'; + } elseif ($appPath !== null && strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = 'APP_PATH'; + } elseif ($extendPath !== null && strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = 'EXTEND_PATH'; + } elseif ($rootPath !== null && strpos($path, $rootPath . '/') === 0) { + $path = substr($path, strlen($rootPath) + 1); + $baseDir = 'ROOT_PATH'; + } + + if ($path !== false) { + $baseDir .= " . "; + } + + return $baseDir . (($path !== false) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + if ($path === false) { + return; + } + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if ($name[0] === ':') { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ($matches['type'][$i] === 'enum') { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/thinkphp/library/think/console/command/optimize/Config.php b/thinkphp/library/think/console/command/optimize/Config.php old mode 100755 new mode 100644 index 050194b..59c69a8 --- a/thinkphp/library/think/console/command/optimize/Config.php +++ b/thinkphp/library/think/console/command/optimize/Config.php @@ -1,93 +1,93 @@ - -// +---------------------------------------------------------------------- -namespace think\console\command\optimize; - -use think\Config as ThinkConfig; -use think\console\Command; -use think\console\Input; -use think\console\input\Argument; -use think\console\Output; - -class Config extends Command -{ - /** @var Output */ - protected $output; - - protected function configure() - { - $this->setName('optimize:config') - ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') - ->setDescription('Build config and common file cache.'); - } - - protected function execute(Input $input, Output $output) - { - if ($input->getArgument('module')) { - $module = $input->getArgument('module') . DS; - } else { - $module = ''; - } - - $content = 'buildCacheContent($module); - - if (!is_dir(RUNTIME_PATH . $module)) { - @mkdir(RUNTIME_PATH . $module, 0755, true); - } - - file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); - - $output->writeln('Succeed!'); - } - - protected function buildCacheContent($module) - { - $content = ''; - $path = realpath(APP_PATH . $module) . DS; - - if ($module) { - // 加载模块配置 - $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); - - // 读取数据库配置文件 - $filename = CONF_PATH . $module . 'database' . CONF_EXT; - ThinkConfig::load($filename, 'database'); - - // 加载应用状态配置 - if ($config['app_status']) { - $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); - } - // 读取扩展配置文件 - if (is_dir(CONF_PATH . $module . 'extra')) { - $dir = CONF_PATH . $module . 'extra'; - $files = scandir($dir); - foreach ($files as $file) { - if (strpos($file, CONF_EXT)) { - $filename = $dir . DS . $file; - ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); - } - } - } - } - - // 加载行为扩展文件 - if (is_file(CONF_PATH . $module . 'tags' . EXT)) { - $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; - } - - // 加载公共文件 - if (is_file($path . 'common' . EXT)) { - $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; - } - - $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; - return $content; - } -} + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\Config as ThinkConfig; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Config extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DS; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + + if (!is_dir(RUNTIME_PATH . $module)) { + @mkdir(RUNTIME_PATH . $module, 0755, true); + } + + file_put_contents(RUNTIME_PATH . $module . 'init' . EXT, $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = ''; + $path = realpath(APP_PATH . $module) . DS; + + if ($module) { + // 加载模块配置 + $config = ThinkConfig::load(CONF_PATH . $module . 'config' . CONF_EXT); + + // 读取数据库配置文件 + $filename = CONF_PATH . $module . 'database' . CONF_EXT; + ThinkConfig::load($filename, 'database'); + + // 加载应用状态配置 + if ($config['app_status']) { + $config = ThinkConfig::load(CONF_PATH . $module . $config['app_status'] . CONF_EXT); + } + // 读取扩展配置文件 + if (is_dir(CONF_PATH . $module . 'extra')) { + $dir = CONF_PATH . $module . 'extra'; + $files = scandir($dir); + foreach ($files as $file) { + if (strpos($file, CONF_EXT)) { + $filename = $dir . DS . $file; + ThinkConfig::load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + } + + // 加载行为扩展文件 + if (is_file(CONF_PATH . $module . 'tags' . EXT)) { + $content .= '\think\Hook::import(' . (var_export(include CONF_PATH . $module . 'tags' . EXT, true)) . ');' . PHP_EOL; + } + + // 加载公共文件 + if (is_file($path . 'common' . EXT)) { + $content .= substr(php_strip_whitespace($path . 'common' . EXT), 5) . PHP_EOL; + } + + $content .= '\think\Config::set(' . var_export(ThinkConfig::get(), true) . ');'; + return $content; + } +} diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php old mode 100755 new mode 100644 index 95446af..6da1d9a --- a/thinkphp/library/think/console/command/optimize/Route.php +++ b/thinkphp/library/think/console/command/optimize/Route.php @@ -1,75 +1,75 @@ - -// +---------------------------------------------------------------------- -namespace think\console\command\optimize; - -use think\console\Command; -use think\console\Input; -use think\console\Output; - -class Route extends Command -{ - /** @var Output */ - protected $output; - - protected function configure() - { - $this->setName('optimize:route') - ->setDescription('Build route cache.'); - } - - protected function execute(Input $input, Output $output) - { - - if (!is_dir(RUNTIME_PATH)) { - @mkdir(RUNTIME_PATH, 0755, true); - } - - file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); - $output->writeln('Succeed!'); - } - - protected function buildRouteCache() - { - $files = \think\Config::get('route_config_file'); - foreach ($files as $file) { - if (is_file(CONF_PATH . $file . CONF_EXT)) { - $config = include CONF_PATH . $file . CONF_EXT; - if (is_array($config)) { - \think\Route::import($config); - } - } - } - $rules = \think\Route::rules(true); - array_walk_recursive($rules, [$this, 'buildClosure']); - $content = 'getStartLine(); - $endLine = $reflection->getEndLine(); - $file = $reflection->getFileName(); - $item = file($file); - $content = ''; - for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { - $content .= $item[$i]; - } - $start = strpos($content, 'function'); - $end = strrpos($content, '}'); - $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; - } - } -} + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Route extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + + if (!is_dir(RUNTIME_PATH)) { + @mkdir(RUNTIME_PATH, 0755, true); + } + + file_put_contents(RUNTIME_PATH . 'route.php', $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + $files = \think\Config::get('route_config_file'); + foreach ($files as $file) { + if (is_file(CONF_PATH . $file . CONF_EXT)) { + $config = include CONF_PATH . $file . CONF_EXT; + if (is_array($config)) { + \think\Route::import($config); + } + } + } + $rules = \think\Route::rules(true); + array_walk_recursive($rules, [$this, 'buildClosure']); + $content = 'getStartLine(); + $endLine = $reflection->getEndLine(); + $file = $reflection->getFileName(); + $item = file($file); + $content = ''; + for ($i = $startLine - 1; $i <= $endLine - 1; $i++) { + $content .= $item[$i]; + } + $start = strpos($content, 'function'); + $end = strrpos($content, '}'); + $value = '[__start__' . substr($content, $start, $end - $start + 1) . '__end__]'; + } + } +} diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php old mode 100755 new mode 100644 index 5293dcf..3353424 --- a/thinkphp/library/think/console/command/optimize/Schema.php +++ b/thinkphp/library/think/console/command/optimize/Schema.php @@ -1,118 +1,118 @@ - -// +---------------------------------------------------------------------- -namespace think\console\command\optimize; - -use think\App; -use think\console\Command; -use think\console\Input; -use think\console\input\Option; -use think\console\Output; -use think\Db; - -class Schema extends Command -{ - /** @var Output */ - protected $output; - - protected function configure() - { - $this->setName('optimize:schema') - ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') - ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') - ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') - ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') - ->setDescription('Build database schema cache.'); - } - - protected function execute(Input $input, Output $output) - { - if (!is_dir(RUNTIME_PATH . 'schema')) { - @mkdir(RUNTIME_PATH . 'schema', 0755, true); - } - $config = []; - if ($input->hasOption('config')) { - $config = $input->getOption('config'); - } - if ($input->hasOption('module')) { - $module = $input->getOption('module'); - // 读取模型 - $path = APP_PATH . $module . DS . 'model'; - $list = is_dir($path) ? scandir($path) : []; - $app = App::$namespace; - foreach ($list as $file) { - if (0 === strpos($file, '.')) { - continue; - } - $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); - $this->buildModelSchema($class); - } - $output->writeln('Succeed!'); - return; - } elseif ($input->hasOption('table')) { - $table = $input->getOption('table'); - if (!strpos($table, '.')) { - $dbName = Db::connect($config)->getConfig('database'); - } - $tables[] = $table; - } elseif ($input->hasOption('db')) { - $dbName = $input->getOption('db'); - $tables = Db::connect($config)->getTables($dbName); - } elseif (!\think\Config::get('app_multi_module')) { - $app = App::$namespace; - $path = APP_PATH . 'model'; - $list = is_dir($path) ? scandir($path) : []; - foreach ($list as $file) { - if (0 === strpos($file, '.')) { - continue; - } - $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); - $this->buildModelSchema($class); - } - $output->writeln('Succeed!'); - return; - } else { - $tables = Db::connect($config)->getTables(); - } - - $db = isset($dbName) ? $dbName . '.' : ''; - $this->buildDataBaseSchema($tables, $db, $config); - - $output->writeln('Succeed!'); - } - - protected function buildModelSchema($class) - { - $reflect = new \ReflectionClass($class); - if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { - $table = $class::getTable(); - $dbName = $class::getConfig('database'); - $content = 'getFields($table); - $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); - } - } - - protected function buildDataBaseSchema($tables, $db, $config) - { - if ('' == $db) { - $dbName = Db::connect($config)->getConfig('database') . '.'; - } else { - $dbName = $db; - } - foreach ($tables as $table) { - $content = 'getFields($db . $table); - $content .= var_export($info, true) . ';'; - file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); - } - } -} + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\App; +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; + +class Schema extends Command +{ + /** @var Output */ + protected $output; + + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('config', null, Option::VALUE_REQUIRED, 'db config .') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(RUNTIME_PATH . 'schema')) { + @mkdir(RUNTIME_PATH . 'schema', 0755, true); + } + $config = []; + if ($input->hasOption('config')) { + $config = $input->getOption('config'); + } + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = APP_PATH . $module . DS . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $app = App::$namespace; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (!strpos($table, '.')) { + $dbName = Db::connect($config)->getConfig('database'); + } + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::connect($config)->getTables($dbName); + } elseif (!\think\Config::get('app_multi_module')) { + $app = App::$namespace; + $path = APP_PATH . 'model'; + $list = is_dir($path) ? scandir($path) : []; + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $app . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::connect($config)->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db, $config); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . '.' . $table . EXT, $content); + } + } + + protected function buildDataBaseSchema($tables, $db, $config) + { + if ('' == $db) { + $dbName = Db::connect($config)->getConfig('database') . '.'; + } else { + $dbName = $db; + } + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(RUNTIME_PATH . 'schema' . DS . $dbName . $table . EXT, $content); + } + } +} diff --git a/thinkphp/library/think/console/input/Argument.php b/thinkphp/library/think/console/input/Argument.php old mode 100755 new mode 100644 index ffd5a85..16223bb --- a/thinkphp/library/think/console/input/Argument.php +++ b/thinkphp/library/think/console/input/Argument.php @@ -1,115 +1,115 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\input; - -class Argument -{ - - const REQUIRED = 1; - const OPTIONAL = 2; - const IS_ARRAY = 4; - - private $name; - private $mode; - private $default; - private $description; - - /** - * 构造方法 - * @param string $name 参数名 - * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL - * @param string $description 描述 - * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) - * @throws \InvalidArgumentException - */ - public function __construct($name, $mode = null, $description = '', $default = null) - { - if (null === $mode) { - $mode = self::OPTIONAL; - } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { - throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); - } - - $this->name = $name; - $this->mode = $mode; - $this->description = $description; - - $this->setDefault($default); - } - - /** - * 获取参数名 - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * 是否必须 - * @return bool - */ - public function isRequired() - { - return self::REQUIRED === (self::REQUIRED & $this->mode); - } - - /** - * 该参数是否接受数组 - * @return bool - */ - public function isArray() - { - return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); - } - - /** - * 设置默认值 - * @param mixed $default 默认值 - * @throws \LogicException - */ - public function setDefault($default = null) - { - if (self::REQUIRED === $this->mode && null !== $default) { - throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); - } - - if ($this->isArray()) { - if (null === $default) { - $default = []; - } elseif (!is_array($default)) { - throw new \LogicException('A default value for an array argument must be an array.'); - } - } - - $this->default = $default; - } - - /** - * 获取默认值 - * @return mixed - */ - public function getDefault() - { - return $this->default; - } - - /** - * 获取描述 - * @return string - */ - public function getDescription() - { - return $this->description; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 参数名 + * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL + * @param string $description 描述 + * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) + * @throws \InvalidArgumentException + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/thinkphp/library/think/console/input/Definition.php b/thinkphp/library/think/console/input/Definition.php old mode 100755 new mode 100644 index 396b74f..c71977e --- a/thinkphp/library/think/console/input/Definition.php +++ b/thinkphp/library/think/console/input/Definition.php @@ -1,375 +1,375 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\input; - -class Definition -{ - - /** - * @var Argument[] - */ - private $arguments; - - private $requiredCount; - private $hasAnArrayArgument = false; - private $hasOptional; - - /** - * @var Option[] - */ - private $options; - private $shortcuts; - - /** - * 构造方法 - * @param array $definition - * @api - */ - public function __construct(array $definition = []) - { - $this->setDefinition($definition); - } - - /** - * 设置指令的定义 - * @param array $definition 定义的数组 - */ - public function setDefinition(array $definition) - { - $arguments = []; - $options = []; - foreach ($definition as $item) { - if ($item instanceof Option) { - $options[] = $item; - } else { - $arguments[] = $item; - } - } - - $this->setArguments($arguments); - $this->setOptions($options); - } - - /** - * 设置参数 - * @param Argument[] $arguments 参数数组 - */ - public function setArguments($arguments = []) - { - $this->arguments = []; - $this->requiredCount = 0; - $this->hasOptional = false; - $this->hasAnArrayArgument = false; - $this->addArguments($arguments); - } - - /** - * 添加参数 - * @param Argument[] $arguments 参数数组 - * @api - */ - public function addArguments($arguments = []) - { - if (null !== $arguments) { - foreach ($arguments as $argument) { - $this->addArgument($argument); - } - } - } - - /** - * 添加一个参数 - * @param Argument $argument 参数 - * @throws \LogicException - */ - public function addArgument(Argument $argument) - { - if (isset($this->arguments[$argument->getName()])) { - throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); - } - - if ($this->hasAnArrayArgument) { - throw new \LogicException('Cannot add an argument after an array argument.'); - } - - if ($argument->isRequired() && $this->hasOptional) { - throw new \LogicException('Cannot add a required argument after an optional one.'); - } - - if ($argument->isArray()) { - $this->hasAnArrayArgument = true; - } - - if ($argument->isRequired()) { - ++$this->requiredCount; - } else { - $this->hasOptional = true; - } - - $this->arguments[$argument->getName()] = $argument; - } - - /** - * 根据名称或者位置获取参数 - * @param string|int $name 参数名或者位置 - * @return Argument 参数 - * @throws \InvalidArgumentException - */ - public function getArgument($name) - { - if (!$this->hasArgument($name)) { - throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); - } - - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - return $arguments[$name]; - } - - /** - * 根据名称或位置检查是否具有某个参数 - * @param string|int $name 参数名或者位置 - * @return bool - * @api - */ - public function hasArgument($name) - { - $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; - - return isset($arguments[$name]); - } - - /** - * 获取所有的参数 - * @return Argument[] 参数数组 - */ - public function getArguments() - { - return $this->arguments; - } - - /** - * 获取参数数量 - * @return int - */ - public function getArgumentCount() - { - return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); - } - - /** - * 获取必填的参数的数量 - * @return int - */ - public function getArgumentRequiredCount() - { - return $this->requiredCount; - } - - /** - * 获取参数默认值 - * @return array - */ - public function getArgumentDefaults() - { - $values = []; - foreach ($this->arguments as $argument) { - $values[$argument->getName()] = $argument->getDefault(); - } - - return $values; - } - - /** - * 设置选项 - * @param Option[] $options 选项数组 - */ - public function setOptions($options = []) - { - $this->options = []; - $this->shortcuts = []; - $this->addOptions($options); - } - - /** - * 添加选项 - * @param Option[] $options 选项数组 - * @api - */ - public function addOptions($options = []) - { - foreach ($options as $option) { - $this->addOption($option); - } - } - - /** - * 添加一个选项 - * @param Option $option 选项 - * @throws \LogicException - * @api - */ - public function addOption(Option $option) - { - if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { - throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); - } - - if ($option->getShortcut()) { - foreach (explode('|', $option->getShortcut()) as $shortcut) { - if (isset($this->shortcuts[$shortcut]) - && !$option->equals($this->options[$this->shortcuts[$shortcut]]) - ) { - throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); - } - } - } - - $this->options[$option->getName()] = $option; - if ($option->getShortcut()) { - foreach (explode('|', $option->getShortcut()) as $shortcut) { - $this->shortcuts[$shortcut] = $option->getName(); - } - } - } - - /** - * 根据名称获取选项 - * @param string $name 选项名 - * @return Option - * @throws \InvalidArgumentException - * @api - */ - public function getOption($name) - { - if (!$this->hasOption($name)) { - throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); - } - - return $this->options[$name]; - } - - /** - * 根据名称检查是否有这个选项 - * @param string $name 选项名 - * @return bool - * @api - */ - public function hasOption($name) - { - return isset($this->options[$name]); - } - - /** - * 获取所有选项 - * @return Option[] - * @api - */ - public function getOptions() - { - return $this->options; - } - - /** - * 根据名称检查某个选项是否有短名称 - * @param string $name 短名称 - * @return bool - */ - public function hasShortcut($name) - { - return isset($this->shortcuts[$name]); - } - - /** - * 根据短名称获取选项 - * @param string $shortcut 短名称 - * @return Option - */ - public function getOptionForShortcut($shortcut) - { - return $this->getOption($this->shortcutToName($shortcut)); - } - - /** - * 获取所有选项的默认值 - * @return array - */ - public function getOptionDefaults() - { - $values = []; - foreach ($this->options as $option) { - $values[$option->getName()] = $option->getDefault(); - } - - return $values; - } - - /** - * 根据短名称获取选项名 - * @param string $shortcut 短名称 - * @return string - * @throws \InvalidArgumentException - */ - private function shortcutToName($shortcut) - { - if (!isset($this->shortcuts[$shortcut])) { - throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); - } - - return $this->shortcuts[$shortcut]; - } - - /** - * 获取该指令的介绍 - * @param bool $short 是否简洁介绍 - * @return string - */ - public function getSynopsis($short = false) - { - $elements = []; - - if ($short && $this->getOptions()) { - $elements[] = '[options]'; - } elseif (!$short) { - foreach ($this->getOptions() as $option) { - $value = ''; - if ($option->acceptValue()) { - $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); - } - - $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; - $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); - } - } - - if (count($elements) && $this->getArguments()) { - $elements[] = '[--]'; - } - - foreach ($this->getArguments() as $argument) { - $element = '<' . $argument->getName() . '>'; - if (!$argument->isRequired()) { - $element = '[' . $element . ']'; - } elseif ($argument->isArray()) { - $element .= ' (' . $element . ')'; - } - - if ($argument->isArray()) { - $element .= '...'; - } - - $elements[] = $element; - } - - return implode(' ', $elements); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Definition +{ + + /** + * @var Argument[] + */ + private $arguments; + + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + + /** + * @var Option[] + */ + private $options; + private $shortcuts; + + /** + * 构造方法 + * @param array $definition + * @api + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * 设置指令的定义 + * @param array $definition 定义的数组 + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis($short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/thinkphp/library/think/console/input/Option.php b/thinkphp/library/think/console/input/Option.php old mode 100755 new mode 100644 index db64bc7..e5707c9 --- a/thinkphp/library/think/console/input/Option.php +++ b/thinkphp/library/think/console/input/Option.php @@ -1,190 +1,190 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\input; - -class Option -{ - - const VALUE_NONE = 1; - const VALUE_REQUIRED = 2; - const VALUE_OPTIONAL = 4; - const VALUE_IS_ARRAY = 8; - - private $name; - private $shortcut; - private $mode; - private $default; - private $description; - - /** - * 构造方法 - * @param string $name 选项名 - * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 - * @param int $mode 选项类型(可选类型为 self::VALUE_*) - * @param string $description 描述 - * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) - * @throws \InvalidArgumentException - */ - public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) - { - if (0 === strpos($name, '--')) { - $name = substr($name, 2); - } - - if (empty($name)) { - throw new \InvalidArgumentException('An option name cannot be empty.'); - } - - if (empty($shortcut)) { - $shortcut = null; - } - - if (null !== $shortcut) { - if (is_array($shortcut)) { - $shortcut = implode('|', $shortcut); - } - $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); - $shortcuts = array_filter($shortcuts); - $shortcut = implode('|', $shortcuts); - - if (empty($shortcut)) { - throw new \InvalidArgumentException('An option shortcut cannot be empty.'); - } - } - - if (null === $mode) { - $mode = self::VALUE_NONE; - } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { - throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); - } - - $this->name = $name; - $this->shortcut = $shortcut; - $this->mode = $mode; - $this->description = $description; - - if ($this->isArray() && !$this->acceptValue()) { - throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); - } - - $this->setDefault($default); - } - - /** - * 获取短名称 - * @return string - */ - public function getShortcut() - { - return $this->shortcut; - } - - /** - * 获取选项名 - * @return string - */ - public function getName() - { - return $this->name; - } - - /** - * 是否可以设置值 - * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false - */ - public function acceptValue() - { - return $this->isValueRequired() || $this->isValueOptional(); - } - - /** - * 是否必须 - * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false - */ - public function isValueRequired() - { - return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); - } - - /** - * 是否可选 - * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false - */ - public function isValueOptional() - { - return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); - } - - /** - * 选项值是否接受数组 - * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false - */ - public function isArray() - { - return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); - } - - /** - * 设置默认值 - * @param mixed $default 默认值 - * @throws \LogicException - */ - public function setDefault($default = null) - { - if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { - throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); - } - - if ($this->isArray()) { - if (null === $default) { - $default = []; - } elseif (!is_array($default)) { - throw new \LogicException('A default value for an array option must be an array.'); - } - } - - $this->default = $this->acceptValue() ? $default : false; - } - - /** - * 获取默认值 - * @return mixed - */ - public function getDefault() - { - return $this->default; - } - - /** - * 获取描述文字 - * @return string - */ - public function getDescription() - { - return $this->description; - } - - /** - * 检查所给选项是否是当前这个 - * @param Option $option - * @return bool - */ - public function equals(Option $option) - { - return $option->getName() === $this->getName() - && $option->getShortcut() === $this->getShortcut() - && $option->getDefault() === $this->getDefault() - && $option->isArray() === $this->isArray() - && $option->isValueRequired() === $this->isValueRequired() - && $option->isValueOptional() === $this->isValueOptional(); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 选项名 + * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 + * @param int $mode 选项类型(可选类型为 self::VALUE_*) + * @param string $description 描述 + * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) + * @throws \InvalidArgumentException + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * 获取短名称 + * @return string + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述文字 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional(); + } +} diff --git a/thinkphp/library/think/console/output/Ask.php b/thinkphp/library/think/console/output/Ask.php old mode 100755 new mode 100644 index 3eff1f0..3933eb2 --- a/thinkphp/library/think/console/output/Ask.php +++ b/thinkphp/library/think/console/output/Ask.php @@ -1,340 +1,340 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output; - -use think\console\Input; -use think\console\Output; -use think\console\output\question\Choice; -use think\console\output\question\Confirmation; - -class Ask -{ - private static $stty; - - private static $shell; - - /** @var Input */ - protected $input; - - /** @var Output */ - protected $output; - - /** @var Question */ - protected $question; - - public function __construct(Input $input, Output $output, Question $question) - { - $this->input = $input; - $this->output = $output; - $this->question = $question; - } - - public function run() - { - if (!$this->input->isInteractive()) { - return $this->question->getDefault(); - } - - if (!$this->question->getValidator()) { - return $this->doAsk(); - } - - $that = $this; - - $interviewer = function () use ($that) { - return $that->doAsk(); - }; - - return $this->validateAttempts($interviewer); - } - - protected function doAsk() - { - $this->writePrompt(); - - $inputStream = STDIN; - $autocomplete = $this->question->getAutocompleterValues(); - - if (null === $autocomplete || !$this->hasSttyAvailable()) { - $ret = false; - if ($this->question->isHidden()) { - try { - $ret = trim($this->getHiddenResponse($inputStream)); - } catch (\RuntimeException $e) { - if (!$this->question->isHiddenFallback()) { - throw $e; - } - } - } - - if (false === $ret) { - $ret = fgets($inputStream, 4096); - if (false === $ret) { - throw new \RuntimeException('Aborted'); - } - $ret = trim($ret); - } - } else { - $ret = trim($this->autocomplete($inputStream)); - } - - $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); - - if ($normalizer = $this->question->getNormalizer()) { - return $normalizer($ret); - } - - return $ret; - } - - private function autocomplete($inputStream) - { - $autocomplete = $this->question->getAutocompleterValues(); - $ret = ''; - - $i = 0; - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - - $sttyMode = shell_exec('stty -g'); - - shell_exec('stty -icanon -echo'); - - while (!feof($inputStream)) { - $c = fread($inputStream, 1); - - if ("\177" === $c) { - if (0 === $numMatches && 0 !== $i) { - --$i; - $this->output->write("\033[1D"); - } - - if ($i === 0) { - $ofs = -1; - $matches = $autocomplete; - $numMatches = count($matches); - } else { - $numMatches = 0; - } - - $ret = substr($ret, 0, $i); - } elseif ("\033" === $c) { - $c .= fread($inputStream, 2); - - if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { - if ('A' === $c[2] && -1 === $ofs) { - $ofs = 0; - } - - if (0 === $numMatches) { - continue; - } - - $ofs += ('A' === $c[2]) ? -1 : 1; - $ofs = ($numMatches + $ofs) % $numMatches; - } - } elseif (ord($c) < 32) { - if ("\t" === $c || "\n" === $c) { - if ($numMatches > 0 && -1 !== $ofs) { - $ret = $matches[$ofs]; - $this->output->write(substr($ret, $i)); - $i = strlen($ret); - } - - if ("\n" === $c) { - $this->output->write($c); - break; - } - - $numMatches = 0; - } - - continue; - } else { - $this->output->write($c); - $ret .= $c; - ++$i; - - $numMatches = 0; - $ofs = 0; - - foreach ($autocomplete as $value) { - if (0 === strpos($value, $ret) && $i !== strlen($value)) { - $matches[$numMatches++] = $value; - } - } - } - - $this->output->write("\033[K"); - - if ($numMatches > 0 && -1 !== $ofs) { - $this->output->write("\0337"); - $this->output->highlight(substr($matches[$ofs], $i)); - $this->output->write("\0338"); - } - } - - shell_exec(sprintf('stty %s', $sttyMode)); - - return $ret; - } - - protected function getHiddenResponse($inputStream) - { - if ('\\' === DIRECTORY_SEPARATOR) { - $exe = __DIR__ . '/../bin/hiddeninput.exe'; - - $value = rtrim(shell_exec($exe)); - $this->output->writeln(''); - - if (isset($tmpExe)) { - unlink($tmpExe); - } - - return $value; - } - - if ($this->hasSttyAvailable()) { - $sttyMode = shell_exec('stty -g'); - - shell_exec('stty -echo'); - $value = fgets($inputStream, 4096); - shell_exec(sprintf('stty %s', $sttyMode)); - - if (false === $value) { - throw new \RuntimeException('Aborted'); - } - - $value = trim($value); - $this->output->writeln(''); - - return $value; - } - - if (false !== $shell = $this->getShell()) { - $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; - $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); - $value = rtrim(shell_exec($command)); - $this->output->writeln(''); - - return $value; - } - - throw new \RuntimeException('Unable to hide the response.'); - } - - protected function validateAttempts($interviewer) - { - /** @var \Exception $error */ - $error = null; - $attempts = $this->question->getMaxAttempts(); - while (null === $attempts || $attempts--) { - if (null !== $error) { - $this->output->error($error->getMessage()); - } - - try { - return call_user_func($this->question->getValidator(), $interviewer()); - } catch (\Exception $error) { - } - } - - throw $error; - } - - /** - * 显示问题的提示信息 - */ - protected function writePrompt() - { - $text = $this->question->getQuestion(); - $default = $this->question->getDefault(); - - switch (true) { - case null === $default: - $text = sprintf(' %s:', $text); - - break; - - case $this->question instanceof Confirmation: - $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); - - break; - - case $this->question instanceof Choice && $this->question->isMultiselect(): - $choices = $this->question->getChoices(); - $default = explode(',', $default); - - foreach ($default as $key => $value) { - $default[$key] = $choices[trim($value)]; - } - - $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); - - break; - - case $this->question instanceof Choice: - $choices = $this->question->getChoices(); - $text = sprintf(' %s [%s]:', $text, $choices[$default]); - - break; - - default: - $text = sprintf(' %s [%s]:', $text, $default); - } - - $this->output->writeln($text); - - if ($this->question instanceof Choice) { - $width = max(array_map('strlen', array_keys($this->question->getChoices()))); - - foreach ($this->question->getChoices() as $key => $value) { - $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); - } - } - - $this->output->write(' > '); - } - - private function getShell() - { - if (null !== self::$shell) { - return self::$shell; - } - - self::$shell = false; - - if (file_exists('/usr/bin/env')) { - $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; - foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { - if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { - self::$shell = $sh; - break; - } - } - } - - return self::$shell; - } - - private function hasSttyAvailable() - { - if (null !== self::$stty) { - return self::$stty; - } - - exec('stty 2>&1', $output, $exitcode); - - return self::$stty = $exitcode === 0; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $this->question instanceof Choice && $this->question->isMultiselect(): + $choices = $this->question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $this->output->writeln($text); + + if ($this->question instanceof Choice) { + $width = max(array_map('strlen', array_keys($this->question->getChoices()))); + + foreach ($this->question->getChoices() as $key => $value) { + $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $this->output->write(' > '); + } + + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php old mode 100755 new mode 100644 index 4d33e8a..23dc648 --- a/thinkphp/library/think/console/output/Descriptor.php +++ b/thinkphp/library/think/console/output/Descriptor.php @@ -1,319 +1,319 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output; - -use think\Console; -use think\console\Command; -use think\console\input\Argument as InputArgument; -use think\console\input\Definition as InputDefinition; -use think\console\input\Option as InputOption; -use think\console\Output; -use think\console\output\descriptor\Console as ConsoleDescription; - -class Descriptor -{ - - /** - * @var Output - */ - protected $output; - - /** - * {@inheritdoc} - */ - public function describe(Output $output, $object, array $options = []) - { - $this->output = $output; - - switch (true) { - case $object instanceof InputArgument: - $this->describeInputArgument($object, $options); - break; - case $object instanceof InputOption: - $this->describeInputOption($object, $options); - break; - case $object instanceof InputDefinition: - $this->describeInputDefinition($object, $options); - break; - case $object instanceof Command: - $this->describeCommand($object, $options); - break; - case $object instanceof Console: - $this->describeConsole($object, $options); - break; - default: - throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); - } - } - - /** - * 输出内容 - * @param string $content - * @param bool $decorated - */ - protected function write($content, $decorated = false) - { - $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); - } - - /** - * 描述参数 - * @param InputArgument $argument - * @param array $options - * @return void - */ - protected function describeInputArgument(InputArgument $argument, array $options = []) - { - if (null !== $argument->getDefault() - && (!is_array($argument->getDefault()) - || count($argument->getDefault())) - ) { - $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); - } else { - $default = ''; - } - - $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); - $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; - - $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces - preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); - } - - /** - * 描述选项 - * @param InputOption $option - * @param array $options - * @return void - */ - protected function describeInputOption(InputOption $option, array $options = []) - { - if ($option->acceptValue() && null !== $option->getDefault() - && (!is_array($option->getDefault()) - || count($option->getDefault())) - ) { - $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); - } else { - $default = ''; - } - - $value = ''; - if ($option->acceptValue()) { - $value = '=' . strtoupper($option->getName()); - - if ($option->isValueOptional()) { - $value = '[' . $value . ']'; - } - } - - $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); - $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); - - $spacingWidth = $totalWidth - strlen($synopsis) + 2; - - $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces - preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); - } - - /** - * 描述输入 - * @param InputDefinition $definition - * @param array $options - * @return void - */ - protected function describeInputDefinition(InputDefinition $definition, array $options = []) - { - $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); - foreach ($definition->getArguments() as $argument) { - $totalWidth = max($totalWidth, strlen($argument->getName())); - } - - if ($definition->getArguments()) { - $this->writeText('Arguments:', $options); - $this->writeText("\n"); - foreach ($definition->getArguments() as $argument) { - $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); - $this->writeText("\n"); - } - } - - if ($definition->getArguments() && $definition->getOptions()) { - $this->writeText("\n"); - } - - if ($definition->getOptions()) { - $laterOptions = []; - - $this->writeText('Options:', $options); - foreach ($definition->getOptions() as $option) { - if (strlen($option->getShortcut()) > 1) { - $laterOptions[] = $option; - continue; - } - $this->writeText("\n"); - $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); - } - foreach ($laterOptions as $option) { - $this->writeText("\n"); - $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); - } - } - } - - /** - * 描述指令 - * @param Command $command - * @param array $options - * @return void - */ - protected function describeCommand(Command $command, array $options = []) - { - $command->getSynopsis(true); - $command->getSynopsis(false); - $command->mergeConsoleDefinition(false); - - $this->writeText('Usage:', $options); - foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { - $this->writeText("\n"); - $this->writeText(' ' . $usage, $options); - } - $this->writeText("\n"); - - $definition = $command->getNativeDefinition(); - if ($definition->getOptions() || $definition->getArguments()) { - $this->writeText("\n"); - $this->describeInputDefinition($definition, $options); - $this->writeText("\n"); - } - - if ($help = $command->getProcessedHelp()) { - $this->writeText("\n"); - $this->writeText('Help:', $options); - $this->writeText("\n"); - $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); - $this->writeText("\n"); - } - } - - /** - * 描述控制台 - * @param Console $console - * @param array $options - * @return void - */ - protected function describeConsole(Console $console, array $options = []) - { - $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; - $description = new ConsoleDescription($console, $describedNamespace); - - if (isset($options['raw_text']) && $options['raw_text']) { - $width = $this->getColumnWidth($description->getCommands()); - - foreach ($description->getCommands() as $command) { - $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); - $this->writeText("\n"); - } - } else { - if ('' != $help = $console->getHelp()) { - $this->writeText("$help\n\n", $options); - } - - $this->writeText("Usage:\n", $options); - $this->writeText(" command [options] [arguments]\n\n", $options); - - $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); - - $this->writeText("\n"); - $this->writeText("\n"); - - $width = $this->getColumnWidth($description->getCommands()); - - if ($describedNamespace) { - $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); - } else { - $this->writeText('Available commands:', $options); - } - - // add commands by namespace - foreach ($description->getNamespaces() as $namespace) { - if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { - $this->writeText("\n"); - $this->writeText(' ' . $namespace['id'] . '', $options); - } - - foreach ($namespace['commands'] as $name) { - $this->writeText("\n"); - $spacingWidth = $width - strlen($name); - $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) - ->getDescription()), $options); - } - } - - $this->writeText("\n"); - } - } - - /** - * {@inheritdoc} - */ - private function writeText($content, array $options = []) - { - $this->write(isset($options['raw_text']) - && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); - } - - /** - * 格式化 - * @param mixed $default - * @return string - */ - private function formatDefaultValue($default) - { - return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); - } - - /** - * @param Command[] $commands - * @return int - */ - private function getColumnWidth(array $commands) - { - $width = 0; - foreach ($commands as $command) { - $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; - } - - return $width + 2; - } - - /** - * @param InputOption[] $options - * @return int - */ - private function calculateTotalWidthForOptions($options) - { - $totalWidth = 0; - foreach ($options as $option) { - $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- - - if ($option->acceptValue()) { - $valueLength = 1 + strlen($option->getName()); // = + value - $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] - - $nameLength += $valueLength; - } - $totalWidth = max($totalWidth, $nameLength); - } - - return $totalWidth; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\Console; +use think\console\Command; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\descriptor\Console as ConsoleDescription; + +class Descriptor +{ + + /** + * @var Output + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(Output $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Console: + $this->describeConsole($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * 输出内容 + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); + } + + /** + * 描述参数 + * @param InputArgument $argument + * @param array $options + * @return void + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return void + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return void + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * 描述指令 + * @param Command $command + * @param array $options + * @return void + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' ' . $usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return void + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $console->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) + ->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = []) + { + $this->write(isset($options['raw_text']) + && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); + } + + /** + * 格式化 + * @param mixed $default + * @return string + */ + private function formatDefaultValue($default) + { + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } + + /** + * @param InputOption[] $options + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/thinkphp/library/think/console/output/Formatter.php b/thinkphp/library/think/console/output/Formatter.php old mode 100755 new mode 100644 index 0d987df..f8bee55 --- a/thinkphp/library/think/console/output/Formatter.php +++ b/thinkphp/library/think/console/output/Formatter.php @@ -1,198 +1,198 @@ - -// +---------------------------------------------------------------------- -namespace think\console\output; - -use think\console\output\formatter\Stack as StyleStack; -use think\console\output\formatter\Style; - -class Formatter -{ - - private $decorated = false; - private $styles = []; - private $styleStack; - - /** - * 转义 - * @param string $text - * @return string - */ - public static function escape($text) - { - return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); - $this->setStyle('info', new Style('green')); - $this->setStyle('comment', new Style('yellow')); - $this->setStyle('question', new Style('black', 'cyan')); - $this->setStyle('highlight', new Style('red')); - $this->setStyle('warning', new Style('black', 'yellow')); - - $this->styleStack = new StyleStack(); - } - - /** - * 设置外观标识 - * @param bool $decorated 是否美化文字 - */ - public function setDecorated($decorated) - { - $this->decorated = (bool) $decorated; - } - - /** - * 获取外观标识 - * @return bool - */ - public function isDecorated() - { - return $this->decorated; - } - - /** - * 添加一个新样式 - * @param string $name 样式名 - * @param Style $style 样式实例 - */ - public function setStyle($name, Style $style) - { - $this->styles[strtolower($name)] = $style; - } - - /** - * 是否有这个样式 - * @param string $name - * @return bool - */ - public function hasStyle($name) - { - return isset($this->styles[strtolower($name)]); - } - - /** - * 获取样式 - * @param string $name - * @return Style - * @throws \InvalidArgumentException - */ - public function getStyle($name) - { - if (!$this->hasStyle($name)) { - throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); - } - - return $this->styles[strtolower($name)]; - } - - /** - * 使用所给的样式格式化文字 - * @param string $message 文字 - * @return string - */ - public function format($message) - { - $offset = 0; - $output = ''; - $tagRegex = '[a-z][a-z0-9_=;-]*'; - preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); - foreach ($matches[0] as $i => $match) { - $pos = $match[1]; - $text = $match[0]; - - if (0 != $pos && '\\' == $message[$pos - 1]) { - continue; - } - - $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); - $offset = $pos + strlen($text); - - if ($open = '/' != $text[1]) { - $tag = $matches[1][$i][0]; - } else { - $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; - } - - if (!$open && !$tag) { - // - $this->styleStack->pop(); - } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { - $output .= $this->applyCurrentStyle($text); - } elseif ($open) { - $this->styleStack->push($style); - } else { - $this->styleStack->pop($style); - } - } - - $output .= $this->applyCurrentStyle(substr($message, $offset)); - - return str_replace('\\<', '<', $output); - } - - /** - * @return StyleStack - */ - public function getStyleStack() - { - return $this->styleStack; - } - - /** - * 根据字符串创建新的样式实例 - * @param string $string - * @return Style|bool - */ - private function createStyleFromString($string) - { - if (isset($this->styles[$string])) { - return $this->styles[$string]; - } - - if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { - return false; - } - - $style = new Style(); - foreach ($matches as $match) { - array_shift($match); - - if ('fg' == $match[0]) { - $style->setForeground($match[1]); - } elseif ('bg' == $match[0]) { - $style->setBackground($match[1]); - } else { - try { - $style->setOption($match[1]); - } catch (\InvalidArgumentException $e) { - return false; - } - } - } - - return $style; - } - - /** - * 从堆栈应用样式到文字 - * @param string $text 文字 - * @return string - */ - private function applyCurrentStyle($text) - { - return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; - } -} + +// +---------------------------------------------------------------------- +namespace think\console\output; + +use think\console\output\formatter\Stack as StyleStack; +use think\console\output\formatter\Style; + +class Formatter +{ + + private $decorated = false; + private $styles = []; + private $styleStack; + + /** + * 转义 + * @param string $text + * @return string + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); + $this->setStyle('info', new Style('green')); + $this->setStyle('comment', new Style('yellow')); + $this->setStyle('question', new Style('black', 'cyan')); + $this->setStyle('highlight', new Style('red')); + $this->setStyle('warning', new Style('black', 'yellow')); + + $this->styleStack = new StyleStack(); + } + + /** + * 设置外观标识 + * @param bool $decorated 是否美化文字 + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * 获取外观标识 + * @return bool + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * 添加一个新样式 + * @param string $name 样式名 + * @param Style $style 样式实例 + */ + public function setStyle($name, Style $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * 是否有这个样式 + * @param string $name + * @return bool + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * 获取样式 + * @param string $name + * @return Style + * @throws \InvalidArgumentException + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * 使用所给的样式格式化文字 + * @param string $message 文字 + * @return string + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/thinkphp/library/think/console/output/Question.php b/thinkphp/library/think/console/output/Question.php old mode 100755 new mode 100644 index bd0917d..03975f2 --- a/thinkphp/library/think/console/output/Question.php +++ b/thinkphp/library/think/console/output/Question.php @@ -1,211 +1,211 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output; - -class Question -{ - - private $question; - private $attempts; - private $hidden = false; - private $hiddenFallback = true; - private $autocompleterValues; - private $validator; - private $default; - private $normalizer; - - /** - * 构造方法 - * @param string $question 问题 - * @param mixed $default 默认答案 - */ - public function __construct($question, $default = null) - { - $this->question = $question; - $this->default = $default; - } - - /** - * 获取问题 - * @return string - */ - public function getQuestion() - { - return $this->question; - } - - /** - * 获取默认答案 - * @return mixed - */ - public function getDefault() - { - return $this->default; - } - - /** - * 是否隐藏答案 - * @return bool - */ - public function isHidden() - { - return $this->hidden; - } - - /** - * 隐藏答案 - * @param bool $hidden - * @return Question - */ - public function setHidden($hidden) - { - if ($this->autocompleterValues) { - throw new \LogicException('A hidden question cannot use the autocompleter.'); - } - - $this->hidden = (bool) $hidden; - - return $this; - } - - /** - * 不能被隐藏是否撤销 - * @return bool - */ - public function isHiddenFallback() - { - return $this->hiddenFallback; - } - - /** - * 设置不能被隐藏的时候的操作 - * @param bool $fallback - * @return Question - */ - public function setHiddenFallback($fallback) - { - $this->hiddenFallback = (bool) $fallback; - - return $this; - } - - /** - * 获取自动完成 - * @return null|array|\Traversable - */ - public function getAutocompleterValues() - { - return $this->autocompleterValues; - } - - /** - * 设置自动完成的值 - * @param null|array|\Traversable $values - * @return Question - * @throws \InvalidArgumentException - * @throws \LogicException - */ - public function setAutocompleterValues($values) - { - if (is_array($values) && $this->isAssoc($values)) { - $values = array_merge(array_keys($values), array_values($values)); - } - - if (null !== $values && !is_array($values)) { - if (!$values instanceof \Traversable || $values instanceof \Countable) { - throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); - } - } - - if ($this->hidden) { - throw new \LogicException('A hidden question cannot use the autocompleter.'); - } - - $this->autocompleterValues = $values; - - return $this; - } - - /** - * 设置答案的验证器 - * @param null|callable $validator - * @return Question The current instance - */ - public function setValidator($validator) - { - $this->validator = $validator; - - return $this; - } - - /** - * 获取验证器 - * @return null|callable - */ - public function getValidator() - { - return $this->validator; - } - - /** - * 设置最大重试次数 - * @param null|int $attempts - * @return Question - * @throws \InvalidArgumentException - */ - public function setMaxAttempts($attempts) - { - if (null !== $attempts && $attempts < 1) { - throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); - } - - $this->attempts = $attempts; - - return $this; - } - - /** - * 获取最大重试次数 - * @return null|int - */ - public function getMaxAttempts() - { - return $this->attempts; - } - - /** - * 设置响应的回调 - * @param string|\Closure $normalizer - * @return Question - */ - public function setNormalizer($normalizer) - { - $this->normalizer = $normalizer; - - return $this; - } - - /** - * 获取响应回调 - * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. - * @return string|\Closure - */ - public function getNormalizer() - { - return $this->normalizer; - } - - protected function isAssoc($array) - { - return (bool) count(array_filter(array_keys($array), 'is_string')); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +class Question +{ + + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * 构造方法 + * @param string $question 问题 + * @param mixed $default 默认答案 + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * 获取问题 + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * 获取默认答案 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 是否隐藏答案 + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * 隐藏答案 + * @param bool $hidden + * @return Question + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * 不能被隐藏是否撤销 + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * 设置不能被隐藏的时候的操作 + * @param bool $fallback + * @return Question + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * 获取自动完成 + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * 设置自动完成的值 + * @param null|array|\Traversable $values + * @return Question + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * 设置答案的验证器 + * @param null|callable $validator + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * 获取验证器 + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * 设置最大重试次数 + * @param null|int $attempts + * @return Question + * @throws \InvalidArgumentException + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * 获取最大重试次数 + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * 设置响应的回调 + * @param string|\Closure $normalizer + * @return Question + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * 获取响应回调 + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/thinkphp/library/think/console/output/descriptor/Console.php b/thinkphp/library/think/console/output/descriptor/Console.php old mode 100755 new mode 100644 index f7edad5..4648b68 --- a/thinkphp/library/think/console/output/descriptor/Console.php +++ b/thinkphp/library/think/console/output/descriptor/Console.php @@ -1,149 +1,149 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\descriptor; - -use think\Console as ThinkConsole; -use think\console\Command; - -class Console -{ - - const GLOBAL_NAMESPACE = '_global'; - - /** - * @var ThinkConsole - */ - private $console; - - /** - * @var null|string - */ - private $namespace; - - /** - * @var array - */ - private $namespaces; - - /** - * @var Command[] - */ - private $commands; - - /** - * @var Command[] - */ - private $aliases; - - /** - * 构造方法 - * @param ThinkConsole $console - * @param string|null $namespace - */ - public function __construct(ThinkConsole $console, $namespace = null) - { - $this->console = $console; - $this->namespace = $namespace; - } - - /** - * @return array - */ - public function getNamespaces() - { - if (null === $this->namespaces) { - $this->inspectConsole(); - } - - return $this->namespaces; - } - - /** - * @return Command[] - */ - public function getCommands() - { - if (null === $this->commands) { - $this->inspectConsole(); - } - - return $this->commands; - } - - /** - * @param string $name - * @return Command - * @throws \InvalidArgumentException - */ - public function getCommand($name) - { - if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { - throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); - } - - return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; - } - - private function inspectConsole() - { - $this->commands = []; - $this->namespaces = []; - - $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); - foreach ($this->sortCommands($all) as $namespace => $commands) { - $names = []; - - /** @var Command $command */ - foreach ($commands as $name => $command) { - if (!$command->getName()) { - continue; - } - - if ($command->getName() === $name) { - $this->commands[$name] = $command; - } else { - $this->aliases[$name] = $command; - } - - $names[] = $name; - } - - $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; - } - } - - /** - * @param array $commands - * @return array - */ - private function sortCommands(array $commands) - { - $namespacedCommands = []; - foreach ($commands as $name => $command) { - $key = $this->console->extractNamespace($name, 1); - if (!$key) { - $key = self::GLOBAL_NAMESPACE; - } - - $namespacedCommands[$key][$name] = $command; - } - ksort($namespacedCommands); - - foreach ($namespacedCommands as &$commandsSet) { - ksort($commandsSet); - } - // unset reference to keep scope clear - unset($commandsSet); - - return $namespacedCommands; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/thinkphp/library/think/console/output/driver/Buffer.php b/thinkphp/library/think/console/output/driver/Buffer.php old mode 100755 new mode 100644 index d0f397d..c77a2ec --- a/thinkphp/library/think/console/output/driver/Buffer.php +++ b/thinkphp/library/think/console/output/driver/Buffer.php @@ -1,52 +1,52 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\driver; - -use think\console\Output; - -class Buffer -{ - /** - * @var string - */ - private $buffer = ''; - - public function __construct(Output $output) - { - // do nothing - } - - public function fetch() - { - $content = $this->buffer; - $this->buffer = ''; - return $content; - } - - public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) - { - $messages = (array) $messages; - - foreach ($messages as $message) { - $this->buffer .= $message; - } - if ($newline) { - $this->buffer .= "\n"; - } - } - - public function renderException(\Exception $e) - { - // do nothing - } - -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php old mode 100755 new mode 100644 index 7d556ab..8f29fd0 --- a/thinkphp/library/think/console/output/driver/Console.php +++ b/thinkphp/library/think/console/output/driver/Console.php @@ -1,373 +1,373 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\driver; - -use think\console\Output; -use think\console\output\Formatter; - -class Console -{ - - /** @var Resource */ - private $stdout; - - /** @var Formatter */ - private $formatter; - - private $terminalDimensions; - - /** @var Output */ - private $output; - - public function __construct(Output $output) - { - $this->output = $output; - $this->formatter = new Formatter(); - $this->stdout = $this->openOutputStream(); - $decorated = $this->hasColorSupport($this->stdout); - $this->formatter->setDecorated($decorated); - } - - public function getFormatter() - { - return $this->formatter; - } - - public function setDecorated($decorated) - { - $this->formatter->setDecorated($decorated); - } - - public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) - { - if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { - return; - } - - $messages = (array) $messages; - - foreach ($messages as $message) { - switch ($type) { - case Output::OUTPUT_NORMAL: - $message = $this->formatter->format($message); - break; - case Output::OUTPUT_RAW: - break; - case Output::OUTPUT_PLAIN: - $message = strip_tags($this->formatter->format($message)); - break; - default: - throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); - } - - $this->doWrite($message, $newline, $stream); - } - } - - public function renderException(\Exception $e) - { - $stderr = $this->openErrorStream(); - $decorated = $this->hasColorSupport($stderr); - $this->formatter->setDecorated($decorated); - - do { - $title = sprintf(' [%s] ', get_class($e)); - - $len = $this->stringWidth($title); - - $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; - - if (defined('HHVM_VERSION') && $width > 1 << 31) { - $width = 1 << 31; - } - $lines = []; - foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { - foreach ($this->splitStringByWidth($line, $width - 4) as $line) { - - $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; - $lines[] = [$line, $lineLength]; - - $len = max($lineLength, $len); - } - } - - $messages = ['', '']; - $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); - $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); - foreach ($lines as $line) { - $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); - } - $messages[] = $emptyLine; - $messages[] = ''; - $messages[] = ''; - - $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); - - if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { - $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); - - // exception related properties - $trace = $e->getTrace(); - array_unshift($trace, [ - 'function' => '', - 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', - 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', - 'args' => [], - ]); - - for ($i = 0, $count = count($trace); $i < $count; ++$i) { - $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; - $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; - $function = $trace[$i]['function']; - $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; - $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; - - $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); - } - - $this->write('', true, Output::OUTPUT_NORMAL, $stderr); - $this->write('', true, Output::OUTPUT_NORMAL, $stderr); - } - } while ($e = $e->getPrevious()); - - } - - /** - * 获取终端宽度 - * @return int|null - */ - protected function getTerminalWidth() - { - $dimensions = $this->getTerminalDimensions(); - - return $dimensions[0]; - } - - /** - * 获取终端高度 - * @return int|null - */ - protected function getTerminalHeight() - { - $dimensions = $this->getTerminalDimensions(); - - return $dimensions[1]; - } - - /** - * 获取当前终端的尺寸 - * @return array - */ - public function getTerminalDimensions() - { - if ($this->terminalDimensions) { - return $this->terminalDimensions; - } - - if ('\\' === DS) { - if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { - return [(int) $matches[1], (int) $matches[2]]; - } - if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { - return [(int) $matches[1], (int) $matches[2]]; - } - } - - if ($sttyString = $this->getSttyColumns()) { - if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { - return [(int) $matches[2], (int) $matches[1]]; - } - if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { - return [(int) $matches[2], (int) $matches[1]]; - } - } - - return [null, null]; - } - - /** - * 获取stty列数 - * @return string - */ - private function getSttyColumns() - { - if (!function_exists('proc_open')) { - return; - } - - $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - return $info; - } - return; - } - - /** - * 获取终端模式 - * @return string x 或 null - */ - private function getMode() - { - if (!function_exists('proc_open')) { - return; - } - - $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; - $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); - if (is_resource($process)) { - $info = stream_get_contents($pipes[1]); - fclose($pipes[1]); - fclose($pipes[2]); - proc_close($process); - - if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { - return $matches[2] . 'x' . $matches[1]; - } - } - return; - } - - private function stringWidth($string) - { - if (!function_exists('mb_strwidth')) { - return strlen($string); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return strlen($string); - } - - return mb_strwidth($string, $encoding); - } - - private function splitStringByWidth($string, $width) - { - if (!function_exists('mb_strwidth')) { - return str_split($string, $width); - } - - if (false === $encoding = mb_detect_encoding($string)) { - return str_split($string, $width); - } - - $utf8String = mb_convert_encoding($string, 'utf8', $encoding); - $lines = []; - $line = ''; - foreach (preg_split('//u', $utf8String) as $char) { - if (mb_strwidth($line . $char, 'utf8') <= $width) { - $line .= $char; - continue; - } - $lines[] = str_pad($line, $width); - $line = $char; - } - if (strlen($line)) { - $lines[] = count($lines) ? str_pad($line, $width) : $line; - } - - mb_convert_variables($encoding, 'utf8', $lines); - - return $lines; - } - - private function isRunningOS400() - { - $checks = [ - function_exists('php_uname') ? php_uname('s') : '', - getenv('OSTYPE'), - PHP_OS, - ]; - return false !== stripos(implode(';', $checks), 'OS400'); - } - - /** - * 当前环境是否支持写入控制台输出到stdout. - * - * @return bool - */ - protected function hasStdoutSupport() - { - return false === $this->isRunningOS400(); - } - - /** - * 当前环境是否支持写入控制台输出到stderr. - * - * @return bool - */ - protected function hasStderrSupport() - { - return false === $this->isRunningOS400(); - } - - /** - * @return resource - */ - private function openOutputStream() - { - if (!$this->hasStdoutSupport()) { - return fopen('php://output', 'w'); - } - return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); - } - - /** - * @return resource - */ - private function openErrorStream() - { - return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); - } - - /** - * 将消息写入到输出。 - * @param string $message 消息 - * @param bool $newline 是否另起一行 - * @param null $stream - */ - protected function doWrite($message, $newline, $stream = null) - { - if (null === $stream) { - $stream = $this->stdout; - } - if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { - throw new \RuntimeException('Unable to write output.'); - } - - fflush($stream); - } - - /** - * 是否支持着色 - * @param $stream - * @return bool - */ - protected function hasColorSupport($stream) - { - if (DIRECTORY_SEPARATOR === '\\') { - return - '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD - || false !== getenv('ANSICON') - || 'ON' === getenv('ConEmuANSI') - || 'xterm' === getenv('TERM'); - } - - return function_exists('posix_isatty') && @posix_isatty($stream); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function getFormatter() + { + return $this->formatter; + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Exception $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DS) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream) + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/thinkphp/library/think/console/output/driver/Nothing.php b/thinkphp/library/think/console/output/driver/Nothing.php old mode 100755 new mode 100644 index 4004ca7..9a55f77 --- a/thinkphp/library/think/console/output/driver/Nothing.php +++ b/thinkphp/library/think/console/output/driver/Nothing.php @@ -1,33 +1,33 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\driver; - -use think\console\Output; - -class Nothing -{ - - public function __construct(Output $output) - { - // do nothing - } - - public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) - { - // do nothing - } - - public function renderException(\Exception $e) - { - // do nothing - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/thinkphp/library/think/console/output/formatter/Stack.php old mode 100755 new mode 100644 index d1de670..4864a3f --- a/thinkphp/library/think/console/output/formatter/Stack.php +++ b/thinkphp/library/think/console/output/formatter/Stack.php @@ -1,116 +1,116 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\formatter; - -class Stack -{ - - /** - * @var Style[] - */ - private $styles; - - /** - * @var Style - */ - private $emptyStyle; - - /** - * 构造方法 - * @param Style|null $emptyStyle - */ - public function __construct(Style $emptyStyle = null) - { - $this->emptyStyle = $emptyStyle ?: new Style(); - $this->reset(); - } - - /** - * 重置堆栈 - */ - public function reset() - { - $this->styles = []; - } - - /** - * 推一个样式进入堆栈 - * @param Style $style - */ - public function push(Style $style) - { - $this->styles[] = $style; - } - - /** - * 从堆栈中弹出一个样式 - * @param Style|null $style - * @return Style - * @throws \InvalidArgumentException - */ - public function pop(Style $style = null) - { - if (empty($this->styles)) { - return $this->emptyStyle; - } - - if (null === $style) { - return array_pop($this->styles); - } - - /** - * @var int $index - * @var Style $stackedStyle - */ - foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { - if ($style->apply('') === $stackedStyle->apply('')) { - $this->styles = array_slice($this->styles, 0, $index); - - return $stackedStyle; - } - } - - throw new \InvalidArgumentException('Incorrectly nested style tag found.'); - } - - /** - * 计算堆栈的当前样式。 - * @return Style - */ - public function getCurrent() - { - if (empty($this->styles)) { - return $this->emptyStyle; - } - - return $this->styles[count($this->styles) - 1]; - } - - /** - * @param Style $emptyStyle - * @return Stack - */ - public function setEmptyStyle(Style $emptyStyle) - { - $this->emptyStyle = $emptyStyle; - - return $this; - } - - /** - * @return Style - */ - public function getEmptyStyle() - { - return $this->emptyStyle; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Stack +{ + + /** + * @var Style[] + */ + private $styles; + + /** + * @var Style + */ + private $emptyStyle; + + /** + * 构造方法 + * @param Style|null $emptyStyle + */ + public function __construct(Style $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new Style(); + $this->reset(); + } + + /** + * 重置堆栈 + */ + public function reset() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/thinkphp/library/think/console/output/formatter/Style.php old mode 100755 new mode 100644 index 505fb09..d9b0999 --- a/thinkphp/library/think/console/output/formatter/Style.php +++ b/thinkphp/library/think/console/output/formatter/Style.php @@ -1,189 +1,189 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\formatter; - -class Style -{ - - private static $availableForegroundColors = [ - 'black' => ['set' => 30, 'unset' => 39], - 'red' => ['set' => 31, 'unset' => 39], - 'green' => ['set' => 32, 'unset' => 39], - 'yellow' => ['set' => 33, 'unset' => 39], - 'blue' => ['set' => 34, 'unset' => 39], - 'magenta' => ['set' => 35, 'unset' => 39], - 'cyan' => ['set' => 36, 'unset' => 39], - 'white' => ['set' => 37, 'unset' => 39], - ]; - private static $availableBackgroundColors = [ - 'black' => ['set' => 40, 'unset' => 49], - 'red' => ['set' => 41, 'unset' => 49], - 'green' => ['set' => 42, 'unset' => 49], - 'yellow' => ['set' => 43, 'unset' => 49], - 'blue' => ['set' => 44, 'unset' => 49], - 'magenta' => ['set' => 45, 'unset' => 49], - 'cyan' => ['set' => 46, 'unset' => 49], - 'white' => ['set' => 47, 'unset' => 49], - ]; - private static $availableOptions = [ - 'bold' => ['set' => 1, 'unset' => 22], - 'underscore' => ['set' => 4, 'unset' => 24], - 'blink' => ['set' => 5, 'unset' => 25], - 'reverse' => ['set' => 7, 'unset' => 27], - 'conceal' => ['set' => 8, 'unset' => 28], - ]; - - private $foreground; - private $background; - private $options = []; - - /** - * 初始化输出的样式 - * @param string|null $foreground 字体颜色 - * @param string|null $background 背景色 - * @param array $options 格式 - * @api - */ - public function __construct($foreground = null, $background = null, array $options = []) - { - if (null !== $foreground) { - $this->setForeground($foreground); - } - if (null !== $background) { - $this->setBackground($background); - } - if (count($options)) { - $this->setOptions($options); - } - } - - /** - * 设置字体颜色 - * @param string|null $color 颜色名 - * @throws \InvalidArgumentException - * @api - */ - public function setForeground($color = null) - { - if (null === $color) { - $this->foreground = null; - - return; - } - - if (!isset(static::$availableForegroundColors[$color])) { - throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); - } - - $this->foreground = static::$availableForegroundColors[$color]; - } - - /** - * 设置背景色 - * @param string|null $color 颜色名 - * @throws \InvalidArgumentException - * @api - */ - public function setBackground($color = null) - { - if (null === $color) { - $this->background = null; - - return; - } - - if (!isset(static::$availableBackgroundColors[$color])) { - throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); - } - - $this->background = static::$availableBackgroundColors[$color]; - } - - /** - * 设置字体格式 - * @param string $option 格式名 - * @throws \InvalidArgumentException When the option name isn't defined - * @api - */ - public function setOption($option) - { - if (!isset(static::$availableOptions[$option])) { - throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); - } - - if (!in_array(static::$availableOptions[$option], $this->options)) { - $this->options[] = static::$availableOptions[$option]; - } - } - - /** - * 重置字体格式 - * @param string $option 格式名 - * @throws \InvalidArgumentException - */ - public function unsetOption($option) - { - if (!isset(static::$availableOptions[$option])) { - throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); - } - - $pos = array_search(static::$availableOptions[$option], $this->options); - if (false !== $pos) { - unset($this->options[$pos]); - } - } - - /** - * 批量设置字体格式 - * @param array $options - */ - public function setOptions(array $options) - { - $this->options = []; - - foreach ($options as $option) { - $this->setOption($option); - } - } - - /** - * 应用样式到文字 - * @param string $text 文字 - * @return string - */ - public function apply($text) - { - $setCodes = []; - $unsetCodes = []; - - if (null !== $this->foreground) { - $setCodes[] = $this->foreground['set']; - $unsetCodes[] = $this->foreground['unset']; - } - if (null !== $this->background) { - $setCodes[] = $this->background['set']; - $unsetCodes[] = $this->background['unset']; - } - if (count($this->options)) { - foreach ($this->options as $option) { - $setCodes[] = $option['set']; - $unsetCodes[] = $option['unset']; - } - } - - if (0 === count($setCodes)) { - return $text; - } - - return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply($text) + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/thinkphp/library/think/console/output/question/Choice.php b/thinkphp/library/think/console/output/question/Choice.php old mode 100755 new mode 100644 index 6d732c1..f6760e5 --- a/thinkphp/library/think/console/output/question/Choice.php +++ b/thinkphp/library/think/console/output/question/Choice.php @@ -1,163 +1,163 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\question; - -use think\console\output\Question; - -class Choice extends Question -{ - - private $choices; - private $multiselect = false; - private $prompt = ' > '; - private $errorMessage = 'Value "%s" is invalid'; - - /** - * 构造方法 - * @param string $question 问题 - * @param array $choices 选项 - * @param mixed $default 默认答案 - */ - public function __construct($question, array $choices, $default = null) - { - parent::__construct($question, $default); - - $this->choices = $choices; - $this->setValidator($this->getDefaultValidator()); - $this->setAutocompleterValues($choices); - } - - /** - * 可选项 - * @return array - */ - public function getChoices() - { - return $this->choices; - } - - /** - * 设置可否多选 - * @param bool $multiselect - * @return self - */ - public function setMultiselect($multiselect) - { - $this->multiselect = $multiselect; - $this->setValidator($this->getDefaultValidator()); - - return $this; - } - - public function isMultiselect() - { - return $this->multiselect; - } - - /** - * 获取提示 - * @return string - */ - public function getPrompt() - { - return $this->prompt; - } - - /** - * 设置提示 - * @param string $prompt - * @return self - */ - public function setPrompt($prompt) - { - $this->prompt = $prompt; - - return $this; - } - - /** - * 设置错误提示信息 - * @param string $errorMessage - * @return self - */ - public function setErrorMessage($errorMessage) - { - $this->errorMessage = $errorMessage; - $this->setValidator($this->getDefaultValidator()); - - return $this; - } - - /** - * 获取默认的验证方法 - * @return callable - */ - private function getDefaultValidator() - { - $choices = $this->choices; - $errorMessage = $this->errorMessage; - $multiselect = $this->multiselect; - $isAssoc = $this->isAssoc($choices); - - return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { - // Collapse all spaces. - $selectedChoices = str_replace(' ', '', $selected); - - if ($multiselect) { - // Check for a separated comma values - if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { - throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); - } - $selectedChoices = explode(',', $selectedChoices); - } else { - $selectedChoices = [$selected]; - } - - $multiselectChoices = []; - foreach ($selectedChoices as $value) { - $results = []; - foreach ($choices as $key => $choice) { - if ($choice === $value) { - $results[] = $key; - } - } - - if (count($results) > 1) { - throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); - } - - $result = array_search($value, $choices); - - if (!$isAssoc) { - if (!empty($result)) { - $result = $choices[$result]; - } elseif (isset($choices[$value])) { - $result = $choices[$value]; - } - } elseif (empty($result) && array_key_exists($value, $choices)) { - $result = $value; - } - - if (empty($result)) { - throw new \InvalidArgumentException(sprintf($errorMessage, $value)); - } - array_push($multiselectChoices, $result); - } - - if ($multiselect) { - return $multiselectChoices; - } - - return current($multiselectChoices); - }; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Choice extends Question +{ + + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * 构造方法 + * @param string $question 问题 + * @param array $choices 选项 + * @param mixed $default 默认答案 + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * 可选项 + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (empty($result)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/thinkphp/library/think/console/output/question/Confirmation.php old mode 100755 new mode 100644 index 6d73156..6598f9b --- a/thinkphp/library/think/console/output/question/Confirmation.php +++ b/thinkphp/library/think/console/output/question/Confirmation.php @@ -1,57 +1,57 @@ - -// +---------------------------------------------------------------------- - -namespace think\console\output\question; - -use think\console\output\Question; - -class Confirmation extends Question -{ - - private $trueAnswerRegex; - - /** - * 构造方法 - * @param string $question 问题 - * @param bool $default 默认答案 - * @param string $trueAnswerRegex 验证正则 - */ - public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') - { - parent::__construct($question, (bool) $default); - - $this->trueAnswerRegex = $trueAnswerRegex; - $this->setNormalizer($this->getDefaultNormalizer()); - } - - /** - * 获取默认的答案回调 - * @return callable - */ - private function getDefaultNormalizer() - { - $default = $this->getDefault(); - $regex = $this->trueAnswerRegex; - - return function ($answer) use ($default, $regex) { - if (is_bool($answer)) { - return $answer; - } - - $answerIsTrue = (bool) preg_match($regex, $answer); - if (false === $default) { - return $answer && $answerIsTrue; - } - - return !$answer || $answerIsTrue; - }; - } -} + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Confirmation extends Question +{ + + private $trueAnswerRegex; + + /** + * 构造方法 + * @param string $question 问题 + * @param bool $default 默认答案 + * @param string $trueAnswerRegex 验证正则 + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/thinkphp/library/think/controller/Rest.php b/thinkphp/library/think/controller/Rest.php old mode 100755 new mode 100644 index 8e49415..43ab2f6 --- a/thinkphp/library/think/controller/Rest.php +++ b/thinkphp/library/think/controller/Rest.php @@ -1,99 +1,99 @@ - -// +---------------------------------------------------------------------- - -namespace think\controller; - -use think\App; -use think\Request; -use think\Response; - -abstract class Rest -{ - - protected $method; // 当前请求类型 - protected $type; // 当前资源类型 - // 输出类型 - protected $restMethodList = 'get|post|put|delete'; - protected $restDefaultMethod = 'get'; - protected $restTypeList = 'html|xml|json|rss'; - protected $restDefaultType = 'html'; - protected $restOutputType = [ // REST允许输出的资源类型列表 - 'xml' => 'application/xml', - 'json' => 'application/json', - 'html' => 'text/html', - ]; - - /** - * 构造函数 取得模板对象实例 - * @access public - */ - public function __construct() - { - // 资源类型检测 - $request = Request::instance(); - $ext = $request->ext(); - if ('' == $ext) { - // 自动检测资源类型 - $this->type = $request->type(); - } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { - // 资源类型非法 则用默认资源类型访问 - $this->type = $this->restDefaultType; - } else { - $this->type = $ext; - } - // 请求方式检测 - $method = strtolower($request->method()); - if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { - // 请求方式非法 则用默认请求方法 - $method = $this->restDefaultMethod; - } - $this->method = $method; - } - - /** - * REST 调用 - * @access public - * @param string $method 方法名 - * @return mixed - * @throws \Exception - */ - public function _empty($method) - { - if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { - // RESTFul方法支持 - $fun = $method . '_' . $this->method . '_' . $this->type; - } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { - $fun = $method . '_' . $this->type; - } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { - $fun = $method . '_' . $this->method; - } - if (isset($fun)) { - return App::invokeMethod([$this, $fun]); - } else { - // 抛出异常 - throw new \Exception('error action :' . $method); - } - } - - /** - * 输出返回数据 - * @access protected - * @param mixed $data 要返回的数据 - * @param String $type 返回类型 JSON XML - * @param integer $code HTTP状态码 - * @return Response - */ - protected function response($data, $type = 'json', $code = 200) - { - return Response::create($data, $type)->code($code); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\controller; + +use think\App; +use think\Request; +use think\Response; + +abstract class Rest +{ + + protected $method; // 当前请求类型 + protected $type; // 当前资源类型 + // 输出类型 + protected $restMethodList = 'get|post|put|delete'; + protected $restDefaultMethod = 'get'; + protected $restTypeList = 'html|xml|json|rss'; + protected $restDefaultType = 'html'; + protected $restOutputType = [ // REST允许输出的资源类型列表 + 'xml' => 'application/xml', + 'json' => 'application/json', + 'html' => 'text/html', + ]; + + /** + * 构造函数 取得模板对象实例 + * @access public + */ + public function __construct() + { + // 资源类型检测 + $request = Request::instance(); + $ext = $request->ext(); + if ('' == $ext) { + // 自动检测资源类型 + $this->type = $request->type(); + } elseif (!preg_match('/(' . $this->restTypeList . ')$/i', $ext)) { + // 资源类型非法 则用默认资源类型访问 + $this->type = $this->restDefaultType; + } else { + $this->type = $ext; + } + // 请求方式检测 + $method = strtolower($request->method()); + if (!preg_match('/(' . $this->restMethodList . ')$/i', $method)) { + // 请求方式非法 则用默认请求方法 + $method = $this->restDefaultMethod; + } + $this->method = $method; + } + + /** + * REST 调用 + * @access public + * @param string $method 方法名 + * @return mixed + * @throws \Exception + */ + public function _empty($method) + { + if (method_exists($this, $method . '_' . $this->method . '_' . $this->type)) { + // RESTFul方法支持 + $fun = $method . '_' . $this->method . '_' . $this->type; + } elseif ($this->method == $this->restDefaultMethod && method_exists($this, $method . '_' . $this->type)) { + $fun = $method . '_' . $this->type; + } elseif ($this->type == $this->restDefaultType && method_exists($this, $method . '_' . $this->method)) { + $fun = $method . '_' . $this->method; + } + if (isset($fun)) { + return App::invokeMethod([$this, $fun]); + } else { + // 抛出异常 + throw new \Exception('error action :' . $method); + } + } + + /** + * 输出返回数据 + * @access protected + * @param mixed $data 要返回的数据 + * @param String $type 返回类型 JSON XML + * @param integer $code HTTP状态码 + * @return Response + */ + protected function response($data, $type = 'json', $code = 200) + { + return Response::create($data, $type)->code($code); + } + +} diff --git a/thinkphp/library/think/controller/Yar.php b/thinkphp/library/think/controller/Yar.php old mode 100755 new mode 100644 index ad4d959..af4e9a1 --- a/thinkphp/library/think/controller/Yar.php +++ b/thinkphp/library/think/controller/Yar.php @@ -1,51 +1,51 @@ - -// +---------------------------------------------------------------------- - -namespace think\controller; - -/** - * ThinkPHP Yar控制器类 - */ -abstract class Yar -{ - - /** - * 构造函数 - * @access public - */ - public function __construct() - { - //控制器初始化 - if (method_exists($this, '_initialize')) { - $this->_initialize(); - } - - //判断扩展是否存在 - if (!extension_loaded('yar')) { - throw new \Exception('not support yar'); - } - - //实例化Yar_Server - $server = new \Yar_Server($this); - // 启动server - $server->handle(); - } - - /** - * 魔术方法 有不存在的操作的时候执行 - * @access public - * @param string $method 方法名 - * @param array $args 参数 - * @return mixed - */ - public function __call($method, $args) - {} -} + +// +---------------------------------------------------------------------- + +namespace think\controller; + +/** + * ThinkPHP Yar控制器类 + */ +abstract class Yar +{ + + /** + * 构造函数 + * @access public + */ + public function __construct() + { + //控制器初始化 + if (method_exists($this, '_initialize')) { + $this->_initialize(); + } + + //判断扩展是否存在 + if (!extension_loaded('yar')) { + throw new \Exception('not support yar'); + } + + //实例化Yar_Server + $server = new \Yar_Server($this); + // 启动server + $server->handle(); + } + + /** + * 魔术方法 有不存在的操作的时候执行 + * @access public + * @param string $method 方法名 + * @param array $args 参数 + * @return mixed + */ + public function __call($method, $args) + {} +} diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php old mode 100755 new mode 100644 index fd09e6a..58b45aa --- a/thinkphp/library/think/db/Builder.php +++ b/thinkphp/library/think/db/Builder.php @@ -1,895 +1,899 @@ - -// +---------------------------------------------------------------------- - -namespace think\db; - -use PDO; -use think\Exception; - -abstract class Builder -{ - // connection对象实例 - protected $connection; - // 查询对象实例 - protected $query; - - // 数据库表达式 - protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; - - // SQL表达式 - protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; - protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; - protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; - protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; - - /** - * 构造函数 - * @access public - * @param Connection $connection 数据库连接对象实例 - * @param Query $query 数据库查询对象实例 - */ - public function __construct(Connection $connection, Query $query) - { - $this->connection = $connection; - $this->query = $query; - } - - /** - * 获取当前的连接对象实例 - * @access public - * @return Connection - */ - public function getConnection() - { - return $this->connection; - } - - /** - * 获取当前的Query对象实例 - * @access public - * @return Query - */ - public function getQuery() - { - return $this->query; - } - - /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) - * @access protected - * @param string $sql sql语句 - * @return string - */ - protected function parseSqlTable($sql) - { - return $this->query->parseSqlTable($sql); - } - - /** - * 数据分析 - * @access protected - * @param array $data 数据 - * @param array $options 查询参数 - * @return array - * @throws Exception - */ - protected function parseData($data, $options) - { - if (empty($data)) { - return []; - } - - // 获取绑定信息 - $bind = $this->query->getFieldsBind($options['table']); - if ('*' == $options['field']) { - $fields = array_keys($bind); - } else { - $fields = $options['field']; - } - - $result = []; - foreach ($data as $key => $val) { - $item = $this->parseKey($key, $options, true); - if ($val instanceof Expression) { - $result[$item] = $val->getValue(); - continue; - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $val = $val->__toString(); - } - if (false === strpos($key, '.') && !in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - } elseif (is_null($val)) { - $result[$item] = 'NULL'; - } elseif (is_array($val) && !empty($val)) { - switch (strtolower($val[0])) { - case 'inc': - $result[$item] = $item . '+' . floatval($val[1]); - break; - case 'dec': - $result[$item] = $item . '-' . floatval($val[1]); - break; - case 'exp': - throw new Exception('not support data:[' . $val[0] . ']'); - } - } elseif (is_scalar($val)) { - // 过滤非标量数据 - if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { - $result[$item] = $val; - } else { - $key = str_replace('.', '_', $key); - $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); - $result[$item] = ':data__' . $key; - } - } - } - return $result; - } - - /** - * 字段名分析 - * @access protected - * @param string $key - * @param array $options - * @return string - */ - protected function parseKey($key, $options = [], $strict = false) - { - return $key; - } - - /** - * value分析 - * @access protected - * @param mixed $value - * @param string $field - * @return string|array - */ - protected function parseValue($value, $field = '') - { - if (is_string($value)) { - $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); - } elseif (is_array($value)) { - $value = array_map([$this, 'parseValue'], $value); - } elseif (is_bool($value)) { - $value = $value ? '1' : '0'; - } elseif (is_null($value)) { - $value = 'null'; - } - return $value; - } - - /** - * field分析 - * @access protected - * @param mixed $fields - * @param array $options - * @return string - */ - protected function parseField($fields, $options = []) - { - if ('*' == $fields || empty($fields)) { - $fieldsStr = '*'; - } elseif (is_array($fields)) { - // 支持 'field1'=>'field2' 这样的字段别名定义 - $array = []; - foreach ($fields as $key => $field) { - if ($field instanceof Expression) { - $array[] = $field->getValue(); - } elseif (!is_numeric($key)) { - $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); - } else { - $array[] = $this->parseKey($field, $options); - } - } - $fieldsStr = implode(',', $array); - } - return $fieldsStr; - } - - /** - * table分析 - * @access protected - * @param mixed $tables - * @param array $options - * @return string - */ - protected function parseTable($tables, $options = []) - { - $item = []; - foreach ((array) $tables as $key => $table) { - if (!is_numeric($key)) { - $key = $this->parseSqlTable($key); - $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); - } else { - $table = $this->parseSqlTable($table); - if (isset($options['alias'][$table])) { - $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); - } else { - $item[] = $this->parseKey($table); - } - } - } - return implode(',', $item); - } - - /** - * where分析 - * @access protected - * @param mixed $where 查询条件 - * @param array $options 查询参数 - * @return string - */ - protected function parseWhere($where, $options) - { - $whereStr = $this->buildWhere($where, $options); - if (!empty($options['soft_delete'])) { - // 附加软删除条件 - list($field, $condition) = $options['soft_delete']; - - $binds = $this->query->getFieldsBind($options['table']); - $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; - $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); - } - return empty($whereStr) ? '' : ' WHERE ' . $whereStr; - } - - /** - * 生成查询条件SQL - * @access public - * @param mixed $where - * @param array $options - * @return string - */ - public function buildWhere($where, $options) - { - if (empty($where)) { - $where = []; - } - - if ($where instanceof Query) { - return $this->buildWhere($where->getOptions('where'), $options); - } - - $whereStr = ''; - $binds = $this->query->getFieldsBind($options['table']); - foreach ($where as $key => $val) { - $str = []; - foreach ($val as $field => $value) { - if ($value instanceof Expression) { - $str[] = ' ' . $key . ' ( ' . $field . ' ' . $value->getValue() . ' )'; - } elseif ($value instanceof \Closure) { - // 使用闭包查询 - $query = new Query($this->connection); - call_user_func_array($value, [ & $query]); - $whereClause = $this->buildWhere($query->getOptions('where'), $options); - if (!empty($whereClause)) { - $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; - } - } elseif (strpos($field, '|')) { - // 不同字段使用相同查询条件(OR) - $array = explode('|', $field); - $item = []; - foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); - } - $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; - } elseif (strpos($field, '&')) { - // 不同字段使用相同查询条件(AND) - $array = explode('&', $field); - $item = []; - foreach ($array as $k) { - $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); - } - $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; - } else { - // 对字段使用表达式查询 - $field = is_string($field) ? $field : ''; - $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); - } - } - - $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); - } - - return $whereStr; - } - - // where子单元分析 - protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) - { - // 字段分析 - $key = $field ? $this->parseKey($field, $options, true) : ''; - - // 查询规则和条件 - if (!is_array($val)) { - $val = is_null($val) ? ['null', ''] : ['=', $val]; - } - list($exp, $value) = $val; - - // 对一个字段使用多个查询条件 - if (is_array($exp)) { - $item = array_pop($val); - // 传入 or 或者 and - if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { - $rule = $item; - } else { - array_push($val, $item); - } - foreach ($val as $k => $item) { - $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; - $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); - } - return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; - } - - // 检测操作符 - if (!in_array($exp, $this->exp)) { - $exp = strtolower($exp); - if (isset($this->exp[$exp])) { - $exp = $this->exp[$exp]; - } else { - throw new Exception('where express error:' . $exp); - } - } - $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); - if (preg_match('/\W/', $bindName)) { - // 处理带非单词字符的字段名 - $bindName = md5($bindName); - } - - if ($value instanceof Expression) { - - } elseif (is_object($value) && method_exists($value, '__toString')) { - // 对象数据写入 - $value = $value->__toString(); - } - - $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; - if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { - if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { - if ($this->query->isBind($bindName)) { - $bindName .= '_' . str_replace('.', '_', uniqid('', true)); - } - $this->query->bind($bindName, $value, $bindType); - $value = ':' . $bindName; - } - } - - $whereStr = ''; - if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { - // 比较运算 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); - } - } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { - // 模糊匹配 - if (is_array($value)) { - foreach ($value as $item) { - $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); - } - $logic = isset($val[2]) ? $val[2] : 'AND'; - $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; - } else { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); - } - } elseif ('EXP' == $exp) { - // 表达式查询 - if ($value instanceof Expression) { - $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; - } else { - throw new Exception('where express error:' . $exp); - } - } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { - // NULL 查询 - $whereStr .= $key . ' IS ' . $exp; - } elseif (in_array($exp, ['NOT IN', 'IN'])) { - // IN 查询 - if ($value instanceof \Closure) { - $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); - } else { - $value = array_unique(is_array($value) ? $value : explode(',', $value)); - if (array_key_exists($field, $binds)) { - $bind = []; - $array = []; - $i = 0; - foreach ($value as $v) { - $i++; - if ($this->query->isBind($bindName . '_in_' . $i)) { - $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; - } else { - $bindKey = $bindName . '_in_' . $i; - } - $bind[$bindKey] = [$v, $bindType]; - $array[] = ':' . $bindKey; - } - $this->query->bind($bind); - $zone = implode(',', $array); - } else { - $zone = implode(',', $this->parseValue($value, $field)); - } - $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; - } - } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { - // BETWEEN 查询 - $data = is_array($value) ? $value : explode(',', $value); - if (array_key_exists($field, $binds)) { - if ($this->query->isBind($bindName . '_between_1')) { - $bindKey1 = $bindName . '_between_1' . uniqid(); - $bindKey2 = $bindName . '_between_2' . uniqid(); - } else { - $bindKey1 = $bindName . '_between_1'; - $bindKey2 = $bindName . '_between_2'; - } - $bind = [ - $bindKey1 => [$data[0], $bindType], - $bindKey2 => [$data[1], $bindType], - ]; - $this->query->bind($bind); - $between = ':' . $bindKey1 . ' AND :' . $bindKey2; - } else { - $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); - } - $whereStr .= $key . ' ' . $exp . ' ' . $between; - } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { - // EXISTS 查询 - if ($value instanceof \Closure) { - $whereStr .= $exp . ' ' . $this->parseClosure($value); - } else { - $whereStr .= $exp . ' (' . $value . ')'; - } - } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { - $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); - } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { - if (is_string($value)) { - $value = explode(',', $value); - } - - $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); - } - return $whereStr; - } - - // 执行闭包子查询 - protected function parseClosure($call, $show = true) - { - $query = new Query($this->connection); - call_user_func_array($call, [ & $query]); - return $query->buildSql($show); - } - - /** - * 日期时间条件解析 - * @access protected - * @param string $value - * @param string $key - * @param array $options - * @param string $bindName - * @param integer $bindType - * @return string - */ - protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) - { - // 获取时间字段类型 - if (strpos($key, '.')) { - list($table, $key) = explode('.', $key); - if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { - $table = $pos; - } - } else { - $table = $options['table']; - } - $type = $this->query->getTableInfo($table, 'type'); - if (isset($type[$key])) { - $info = $type[$key]; - } - if (isset($info)) { - if (is_string($value)) { - $value = strtotime($value) ?: $value; - } - - if (preg_match('/(datetime|timestamp)/is', $info)) { - // 日期及时间戳类型 - $value = date('Y-m-d H:i:s', $value); - } elseif (preg_match('/(date)/is', $info)) { - // 日期及时间戳类型 - $value = date('Y-m-d', $value); - } - } - $bindName = $bindName ?: $key; - - if ($this->query->isBind($bindName)) { - $bindName .= '_' . str_replace('.', '_', uniqid('', true)); - } - - $this->query->bind($bindName, $value, $bindType); - return ':' . $bindName; - } - - /** - * limit分析 - * @access protected - * @param mixed $limit - * @return string - */ - protected function parseLimit($limit) - { - return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; - } - - /** - * join分析 - * @access protected - * @param array $join - * @param array $options 查询条件 - * @return string - */ - protected function parseJoin($join, $options = []) - { - $joinStr = ''; - if (!empty($join)) { - foreach ($join as $item) { - list($table, $type, $on) = $item; - $condition = []; - foreach ((array) $on as $val) { - if ($val instanceof Expression) { - $condition[] = $val->getValue(); - } elseif (strpos($val, '=')) { - list($val1, $val2) = explode('=', $val, 2); - $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); - } else { - $condition[] = $val; - } - } - - $table = $this->parseTable($table, $options); - $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); - } - } - return $joinStr; - } - - /** - * order分析 - * @access protected - * @param mixed $order - * @param array $options 查询条件 - * @return string - */ - protected function parseOrder($order, $options = []) - { - if (empty($order)) { - return ''; - } - - $array = []; - foreach ($order as $key => $val) { - if ($val instanceof Expression) { - $array[] = $val->getValue(); - } elseif ('[rand]' == $val) { - $array[] = $this->parseRand(); - } else { - if (is_numeric($key)) { - list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); - } else { - $sort = $val; - } - $sort = strtoupper($sort); - $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; - $array[] = $this->parseKey($key, $options, true) . $sort; - } - } - $order = implode(',', $array); - - return !empty($order) ? ' ORDER BY ' . $order : ''; - } - - /** - * group分析 - * @access protected - * @param mixed $group - * @return string - */ - protected function parseGroup($group) - { - return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; - } - - /** - * having分析 - * @access protected - * @param string $having - * @return string - */ - protected function parseHaving($having) - { - return !empty($having) ? ' HAVING ' . $having : ''; - } - - /** - * comment分析 - * @access protected - * @param string $comment - * @return string - */ - protected function parseComment($comment) - { - if (false !== strpos($comment, '*/')) { - $comment = strstr($coment, '*/', true); - } - return !empty($comment) ? ' /* ' . $comment . ' */' : ''; - } - - /** - * distinct分析 - * @access protected - * @param mixed $distinct - * @return string - */ - protected function parseDistinct($distinct) - { - return !empty($distinct) ? ' DISTINCT ' : ''; - } - - /** - * union分析 - * @access protected - * @param mixed $union - * @return string - */ - protected function parseUnion($union) - { - if (empty($union)) { - return ''; - } - $type = $union['type']; - unset($union['type']); - foreach ($union as $u) { - if ($u instanceof \Closure) { - $sql[] = $type . ' ' . $this->parseClosure($u); - } elseif (is_string($u)) { - $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; - } - } - return ' ' . implode(' ', $sql); - } - - /** - * index分析,可在操作链中指定需要强制使用的索引 - * @access protected - * @param mixed $index - * @return string - */ - protected function parseForce($index) - { - if (empty($index)) { - return ''; - } - - return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); - } - - /** - * 设置锁机制 - * @access protected - * @param bool|string $lock - * @return string - */ - protected function parseLock($lock = false) - { - if (is_bool($lock)) { - return $lock ? ' FOR UPDATE ' : ''; - } elseif (is_string($lock)) { - return ' ' . trim($lock) . ' '; - } - } - - /** - * 生成查询SQL - * @access public - * @param array $options 表达式 - * @return string - */ - public function select($options = []) - { - $sql = str_replace( - ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], - [ - $this->parseTable($options['table'], $options), - $this->parseDistinct($options['distinct']), - $this->parseField($options['field'], $options), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseGroup($options['group']), - $this->parseHaving($options['having']), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseUnion($options['union']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - $this->parseForce($options['force']), - ], $this->selectSql); - return $sql; - } - - /** - * 生成insert SQL - * @access public - * @param array $data 数据 - * @param array $options 表达式 - * @param bool $replace 是否replace - * @return string - */ - public function insert(array $data, $options = [], $replace = false) - { - // 分析并处理数据 - $data = $this->parseData($data, $options); - if (empty($data)) { - return 0; - } - $fields = array_keys($data); - $values = array_values($data); - - $sql = str_replace( - ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], - [ - $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $fields), - implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertSql); - - return $sql; - } - - /** - * 生成insertall SQL - * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace - * @return string - * @throws Exception - */ - public function insertAll($dataSet, $options = [], $replace = false) - { - // 获取合法的字段 - if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); - } else { - $fields = $options['field']; - } - - foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = 'SELECT ' . implode(',', $value); - - if (!isset($insertFields)) { - $insertFields = array_keys($data); - } - } - - foreach ($insertFields as $field) { - $fields[] = $this->parseKey($query, $field, true); - } - - return str_replace( - ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], - [ - $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), - implode(' UNION ALL ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); - } - - /** - * 生成select insert SQL - * @access public - * @param array $fields 数据 - * @param string $table 数据表 - * @param array $options 表达式 - * @return string - */ - public function selectInsert($fields, $table, $options) - { - if (is_string($fields)) { - $fields = explode(',', $fields); - } - - $fields = array_map([$this, 'parseKey'], $fields); - $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); - return $sql; - } - - /** - * 生成update SQL - * @access public - * @param array $data 数据 - * @param array $options 表达式 - * @return string - */ - public function update($data, $options) - { - $table = $this->parseTable($options['table'], $options); - $data = $this->parseData($data, $options); - if (empty($data)) { - return ''; - } - foreach ($data as $key => $val) { - $set[] = $key . '=' . $val; - } - - $sql = str_replace( - ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], - [ - $this->parseTable($options['table'], $options), - implode(',', $set), - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->updateSql); - - return $sql; - } - - /** - * 生成delete SQL - * @access public - * @param array $options 表达式 - * @return string - */ - public function delete($options) - { - $sql = str_replace( - ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], - [ - $this->parseTable($options['table'], $options), - !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', - $this->parseJoin($options['join'], $options), - $this->parseWhere($options['where'], $options), - $this->parseOrder($options['order'], $options), - $this->parseLimit($options['limit']), - $this->parseLock($options['lock']), - $this->parseComment($options['comment']), - ], $this->deleteSql); - - return $sql; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + // 查询对象实例 + protected $query; + + // 数据库表达式 + protected $exp = ['eq' => '=', 'neq' => '<>', 'gt' => '>', 'egt' => '>=', 'lt' => '<', 'elt' => '<=', 'notlike' => 'NOT LIKE', 'not like' => 'NOT LIKE', 'like' => 'LIKE', 'in' => 'IN', 'exp' => 'EXP', 'notin' => 'NOT IN', 'not in' => 'NOT IN', 'between' => 'BETWEEN', 'not between' => 'NOT BETWEEN', 'notbetween' => 'NOT BETWEEN', 'exists' => 'EXISTS', 'notexists' => 'NOT EXISTS', 'not exists' => 'NOT EXISTS', 'null' => 'NULL', 'notnull' => 'NOT NULL', 'not null' => 'NOT NULL', '> time' => '> TIME', '< time' => '< TIME', '>= time' => '>= TIME', '<= time' => '<= TIME', 'between time' => 'BETWEEN TIME', 'not between time' => 'NOT BETWEEN TIME', 'notbetween time' => 'NOT BETWEEN TIME']; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT%%LOCK%%COMMENT%'; + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% %JOIN% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + * @param Query $query 数据库查询对象实例 + */ + public function __construct(Connection $connection, Query $query) + { + $this->connection = $connection; + $this->query = $query; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 获取当前的Query对象实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access protected + * @param string $sql sql语句 + * @return string + */ + protected function parseSqlTable($sql) + { + return $this->query->parseSqlTable($sql); + } + + /** + * 数据分析 + * @access protected + * @param array $data 数据 + * @param array $options 查询参数 + * @return array + * @throws Exception + */ + protected function parseData($data, $options) + { + if (empty($data)) { + return []; + } + + // 获取绑定信息 + $bind = $this->query->getFieldsBind($options['table']); + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + + $result = []; + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($key, $options, true); + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + if (false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val)) { + switch (strtolower($val[0])) { + case 'inc': + $result[$item] = $item . '+' . floatval($val[1]); + break; + case 'dec': + $result[$item] = $item . '-' . floatval($val[1]); + break; + case 'exp': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + if (0 === strpos($val, ':') && $this->query->isBind(substr($val, 1))) { + $result[$item] = $val; + } else { + $key = str_replace('.', '_', $key); + $this->query->bind('data__' . $key, $val, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + $result[$item] = ':data__' . $key; + } + } + } + return $result; + } + + /** + * 字段名分析 + * @access protected + * @param string $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + return $key; + } + + /** + * value分析 + * @access protected + * @param mixed $value + * @param string $field + * @return string|array + */ + protected function parseValue($value, $field = '') + { + if (is_string($value)) { + $value = strpos($value, ':') === 0 && $this->query->isBind(substr($value, 1)) ? $value : $this->connection->quote($value); + } elseif (is_array($value)) { + $value = array_map([$this, 'parseValue'], $value); + } elseif (is_bool($value)) { + $value = $value ? '1' : '0'; + } elseif (is_null($value)) { + $value = 'null'; + } + return $value; + } + + /** + * field分析 + * @access protected + * @param mixed $fields + * @param array $options + * @return string + */ + protected function parseField($fields, $options = []) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + foreach ($fields as $key => $field) { + if ($field instanceof Expression) { + $array[] = $field->getValue(); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($key, $options) . ' AS ' . $this->parseKey($field, $options, true); + } else { + $array[] = $this->parseKey($field, $options); + } + } + $fieldsStr = implode(',', $array); + } + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param mixed $tables + * @param array $options + * @return string + */ + protected function parseTable($tables, $options = []) + { + $item = []; + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->parseSqlTable($key); + $item[] = $this->parseKey($key) . ' ' . (isset($options['alias'][$table]) ? $this->parseKey($options['alias'][$table]) : $this->parseKey($table)); + } else { + $table = $this->parseSqlTable($table); + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($table) . ' ' . $this->parseKey($options['alias'][$table]); + } else { + $item[] = $this->parseKey($table); + } + } + } + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param mixed $where 查询条件 + * @param array $options 查询参数 + * @return string + */ + protected function parseWhere($where, $options) + { + $whereStr = $this->buildWhere($where, $options); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->query->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($field, $condition, '', $options, $binds); + } + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param mixed $where + * @param array $options + * @return string + */ + public function buildWhere($where, $options) + { + if (empty($where)) { + $where = []; + } + + if ($where instanceof Query) { + return $this->buildWhere($where->getOptions('where'), $options); + } + + $whereStr = ''; + $binds = $this->query->getFieldsBind($options['table']); + foreach ($where as $key => $val) { + $str = []; + foreach ($val as $field => $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $key . ' ( ' . $value->getValue() . ' )'; + } elseif ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $whereClause = $this->buildWhere($query->getOptions('where'), $options); + if (!empty($whereClause)) { + $str[] = ' ' . $key . ' ( ' . $whereClause . ' )'; + } + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + foreach ($array as $k) { + $item[] = $this->parseWhereItem($k, $value, '', $options, $binds); + } + $str[] = ' ' . $key . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $key . ' ' . $this->parseWhereItem($field, $value, $key, $options, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($key) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem($field, $val, $rule = '', $options = [], $binds = [], $bindName = null) + { + // 字段分析 + $key = $field ? $this->parseKey($field, $options, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['null', ''] : ['=', $val]; + } + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + foreach ($val as $k => $item) { + $bindName = 'where_' . str_replace('.', '_', $field) . '_' . $k; + $str[] = $this->parseWhereItem($field, $item, $rule, $options, $binds, $bindName); + } + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } else { + throw new Exception('where express error:' . $exp); + } + } + $bindName = $bindName ?: 'where_' . $rule . '_' . str_replace(['.', '-'], '_', $field); + if (preg_match('/\W/', $bindName)) { + // 处理带非单词字符的字段名 + $bindName = md5($bindName); + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + if (is_scalar($value) && array_key_exists($field, $binds) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (strpos($value, ':') !== 0 || !$this->query->isBind(substr($value, 1))) { + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + $this->query->bind($bindName, $value, $bindType); + $value = ':' . $bindName; + } + } + + $whereStr = ''; + if (in_array($exp, ['=', '<>', '>', '>=', '<', '<='])) { + // 比较运算 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('LIKE' == $exp || 'NOT LIKE' == $exp) { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $array[] = $key . ' ' . $exp . ' ' . $this->parseValue($item, $field); + } + $logic = isset($val[2]) ? $val[2] : 'AND'; + $whereStr .= '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseValue($value, $field); + } + } elseif ('EXP' == $exp) { + // 表达式查询 + if ($value instanceof Expression) { + $whereStr .= '( ' . $key . ' ' . $value->getValue() . ' )'; + } else { + throw new Exception('where express error:' . $exp); + } + } elseif (in_array($exp, ['NOT NULL', 'NULL'])) { + // NULL 查询 + $whereStr .= $key . ' IS ' . $exp; + } elseif (in_array($exp, ['NOT IN', 'IN'])) { + // IN 查询 + if ($value instanceof \Closure) { + $whereStr .= $key . ' ' . $exp . ' ' . $this->parseClosure($value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + if (array_key_exists($field, $binds)) { + $bind = []; + $array = []; + $i = 0; + foreach ($value as $v) { + $i++; + if ($this->query->isBind($bindName . '_in_' . $i)) { + $bindKey = $bindName . '_in_' . uniqid() . '_' . $i; + } else { + $bindKey = $bindName . '_in_' . $i; + } + $bind[$bindKey] = [$v, $bindType]; + $array[] = ':' . $bindKey; + } + $this->query->bind($bind); + $zone = implode(',', $array); + } else { + $zone = implode(',', $this->parseValue($value, $field)); + } + $whereStr .= $key . ' ' . $exp . ' (' . (empty($zone) ? "''" : $zone) . ')'; + } + } elseif (in_array($exp, ['NOT BETWEEN', 'BETWEEN'])) { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + if (array_key_exists($field, $binds)) { + if ($this->query->isBind($bindName . '_between_1')) { + $bindKey1 = $bindName . '_between_1' . uniqid(); + $bindKey2 = $bindName . '_between_2' . uniqid(); + } else { + $bindKey1 = $bindName . '_between_1'; + $bindKey2 = $bindName . '_between_2'; + } + $bind = [ + $bindKey1 => [$data[0], $bindType], + $bindKey2 => [$data[1], $bindType], + ]; + $this->query->bind($bind); + $between = ':' . $bindKey1 . ' AND :' . $bindKey2; + } else { + $between = $this->parseValue($data[0], $field) . ' AND ' . $this->parseValue($data[1], $field); + } + $whereStr .= $key . ' ' . $exp . ' ' . $between; + } elseif (in_array($exp, ['NOT EXISTS', 'EXISTS'])) { + // EXISTS 查询 + if ($value instanceof \Closure) { + $whereStr .= $exp . ' ' . $this->parseClosure($value); + } else { + $whereStr .= $exp . ' (' . $value . ')'; + } + } elseif (in_array($exp, ['< TIME', '> TIME', '<= TIME', '>= TIME'])) { + $whereStr .= $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($value, $field, $options, $bindName, $bindType); + } elseif (in_array($exp, ['BETWEEN TIME', 'NOT BETWEEN TIME'])) { + if (is_string($value)) { + $value = explode(',', $value); + } + + $whereStr .= $key . ' ' . substr($exp, 0, -4) . $this->parseDateTime($value[0], $field, $options, $bindName . '_between_1', $bindType) . ' AND ' . $this->parseDateTime($value[1], $field, $options, $bindName . '_between_2', $bindType); + } + return $whereStr; + } + + // 执行闭包子查询 + protected function parseClosure($call, $show = true) + { + $query = new Query($this->connection); + call_user_func_array($call, [ & $query]); + return $query->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param string $value + * @param string $key + * @param array $options + * @param string $bindName + * @param integer $bindType + * @return string + */ + protected function parseDateTime($value, $key, $options = [], $bindName = null, $bindType = null) + { + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + $type = $this->query->getTableInfo($table, 'type'); + if (isset($type[$key])) { + $info = $type[$key]; + } + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + $bindName = $bindName ?: $key; + + if ($this->query->isBind($bindName)) { + $bindName .= '_' . str_replace('.', '_', uniqid('', true)); + } + + $this->query->bind($bindName, $value, $bindType); + return ':' . $bindName; + } + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param array $join + * @param array $options 查询条件 + * @return string + */ + protected function parseJoin($join, $options = []) + { + $joinStr = ''; + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + $condition = []; + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + $condition[] = $this->parseKey($val1, $options) . '=' . $this->parseKey($val2, $options); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($table, $options); + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options 查询条件 + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ''; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($key, $options, true) . $sort; + } + } + $order = implode(',', $array); + + return !empty($order) ? ' ORDER BY ' . $order : ''; + } + + /** + * group分析 + * @access protected + * @param mixed $group + * @return string + */ + protected function parseGroup($group) + { + return !empty($group) ? ' GROUP BY ' . $this->parseKey($group) : ''; + } + + /** + * having分析 + * @access protected + * @param string $having + * @return string + */ + protected function parseHaving($having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param string $comment + * @return string + */ + protected function parseComment($comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param mixed $distinct + * @return string + */ + protected function parseDistinct($distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param mixed $union + * @return string + */ + protected function parseUnion($union) + { + if (empty($union)) { + return ''; + } + $type = $union['type']; + unset($union['type']); + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->parseSqlTable($u) . ' )'; + } + } + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param mixed $index + * @return string + */ + protected function parseForce($index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param bool|string $lock + * @return string + */ + protected function parseLock($lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function select($options = []) + { + $sql = str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($options['table'], $options), + $this->parseDistinct($options['distinct']), + $this->parseField($options['field'], $options), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseGroup($options['group']), + $this->parseHaving($options['having']), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseUnion($options['union']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + $this->parseForce($options['force']), + ], $this->selectSql); + return $sql; + } + + /** + * 生成insert SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + */ + public function insert(array $data, $options = [], $replace = false) + { + // 分析并处理数据 + $data = $this->parseData($data, $options); + if (empty($data)) { + return 0; + } + $fields = array_keys($data); + $values = array_values($data); + + $sql = str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertSql); + + return $sql; + } + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = 'SELECT ' . implode(',', $value); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($field, $options, true); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' UNION ALL ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 生成select insert SQL + * @access public + * @param array $fields 数据 + * @param string $table 数据表 + * @param array $options 表达式 + * @return string + */ + public function selectInsert($fields, $table, $options) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $fields = array_map([$this, 'parseKey'], $fields); + $sql = 'INSERT INTO ' . $this->parseTable($table, $options) . ' (' . implode(',', $fields) . ') ' . $this->select($options); + return $sql; + } + + /** + * 生成update SQL + * @access public + * @param array $data 数据 + * @param array $options 表达式 + * @return string + */ + public function update($data, $options) + { + $table = $this->parseTable($options['table'], $options); + $data = $this->parseData($data, $options); + if (empty($data)) { + return ''; + } + foreach ($data as $key => $val) { + $set[] = $key . '=' . $val; + } + + $sql = str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + implode(',', $set), + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->updateSql); + + return $sql; + } + + /** + * 生成delete SQL + * @access public + * @param array $options 表达式 + * @return string + */ + public function delete($options) + { + $sql = str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($options['table'], $options), + !empty($options['using']) ? ' USING ' . $this->parseTable($options['using'], $options) . ' ' : '', + $this->parseJoin($options['join'], $options), + $this->parseWhere($options['where'], $options), + $this->parseOrder($options['order'], $options), + $this->parseLimit($options['limit']), + $this->parseLock($options['lock']), + $this->parseComment($options['comment']), + ], $this->deleteSql); + + return $sql; + } +} diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php old mode 100755 new mode 100644 index 0503e79..7720282 --- a/thinkphp/library/think/db/Connection.php +++ b/thinkphp/library/think/db/Connection.php @@ -1,1070 +1,1070 @@ - -// +---------------------------------------------------------------------- - -namespace think\db; - -use PDO; -use PDOStatement; -use think\Db; -use think\db\exception\BindParamException; -use think\Debug; -use think\Exception; -use think\exception\PDOException; -use think\Log; - -/** - * Class Connection - * @package think - * @method Query table(string $table) 指定数据表(含前缀) - * @method Query name(string $name) 指定数据表(不含前缀) - * - */ -abstract class Connection -{ - - /** @var PDOStatement PDO操作实例 */ - protected $PDOStatement; - - /** @var string 当前SQL指令 */ - protected $queryStr = ''; - // 返回或者影响记录数 - protected $numRows = 0; - // 事务指令数 - protected $transTimes = 0; - // 错误信息 - protected $error = ''; - - /** @var PDO[] 数据库连接ID 支持多个连接 */ - protected $links = []; - - /** @var PDO 当前连接ID */ - protected $linkID; - protected $linkRead; - protected $linkWrite; - - // 查询结果类型 - protected $fetchType = PDO::FETCH_ASSOC; - // 字段属性大小写 - protected $attrCase = PDO::CASE_LOWER; - // 监听回调 - protected static $event = []; - // 使用Builder类 - protected $builder; - // 数据库连接参数配置 - protected $config = [ - // 数据库类型 - 'type' => '', - // 服务器地址 - 'hostname' => '', - // 数据库名 - 'database' => '', - // 用户名 - 'username' => '', - // 密码 - 'password' => '', - // 端口 - 'hostport' => '', - // 连接dsn - 'dsn' => '', - // 数据库连接参数 - 'params' => [], - // 数据库编码默认采用utf8 - 'charset' => 'utf8', - // 数据库表前缀 - 'prefix' => '', - // 数据库调试模式 - 'debug' => false, - // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) - 'deploy' => 0, - // 数据库读写是否分离 主从式有效 - 'rw_separate' => false, - // 读写分离后 主服务器数量 - 'master_num' => 1, - // 指定从服务器序号 - 'slave_no' => '', - // 模型写入后自动读取主服务器 - 'read_master' => false, - // 是否严格检查字段是否存在 - 'fields_strict' => true, - // 数据返回类型 - 'result_type' => PDO::FETCH_ASSOC, - // 数据集返回类型 - 'resultset_type' => 'array', - // 自动写入时间戳字段 - 'auto_timestamp' => false, - // 时间字段取出后的默认时间格式 - 'datetime_format' => 'Y-m-d H:i:s', - // 是否需要进行SQL性能分析 - 'sql_explain' => false, - // Builder类 - 'builder' => '', - // Query类 - 'query' => '\\think\\db\\Query', - // 是否需要断线重连 - 'break_reconnect' => false, - ]; - - // PDO连接参数 - protected $params = [ - PDO::ATTR_CASE => PDO::CASE_NATURAL, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, - PDO::ATTR_STRINGIFY_FETCHES => false, - PDO::ATTR_EMULATE_PREPARES => false, - ]; - - // 绑定参数 - protected $bind = []; - - /** - * 构造函数 读取数据库配置信息 - * @access public - * @param array $config 数据库配置数组 - */ - public function __construct(array $config = []) - { - if (!empty($config)) { - $this->config = array_merge($this->config, $config); - } - } - - /** - * 获取新的查询对象 - * @access protected - * @return Query - */ - protected function getQuery() - { - $class = $this->config['query']; - return new $class($this); - } - - /** - * 获取当前连接器类对应的Builder类 - * @access public - * @return string - */ - public function getBuilder() - { - if (!empty($this->builder)) { - return $this->builder; - } else { - return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); - } - } - - /** - * 调用Query类的查询方法 - * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 - * @return mixed - */ - public function __call($method, $args) - { - return call_user_func_array([$this->getQuery(), $method], $args); - } - - /** - * 解析pdo连接的dsn信息 - * @access protected - * @param array $config 连接信息 - * @return string - */ - abstract protected function parseDsn($config); - - /** - * 取得数据表的字段信息 - * @access public - * @param string $tableName - * @return array - */ - abstract public function getFields($tableName); - - /** - * 取得数据库的表信息 - * @access public - * @param string $dbName - * @return array - */ - abstract public function getTables($dbName); - - /** - * SQL性能分析 - * @access protected - * @param string $sql - * @return array - */ - abstract protected function getExplain($sql); - - /** - * 对返数据表字段信息进行大小写转换出来 - * @access public - * @param array $info 字段信息 - * @return array - */ - public function fieldCase($info) - { - // 字段大小写转换 - switch ($this->attrCase) { - case PDO::CASE_LOWER: - $info = array_change_key_case($info); - break; - case PDO::CASE_UPPER: - $info = array_change_key_case($info, CASE_UPPER); - break; - case PDO::CASE_NATURAL: - default: - // 不做转换 - } - return $info; - } - - /** - * 获取数据库的配置参数 - * @access public - * @param string $config 配置名称 - * @return mixed - */ - public function getConfig($config = '') - { - return $config ? $this->config[$config] : $this->config; - } - - /** - * 设置数据库的配置参数 - * @access public - * @param string|array $config 配置名称 - * @param mixed $value 配置值 - * @return void - */ - public function setConfig($config, $value = '') - { - if (is_array($config)) { - $this->config = array_merge($this->config, $config); - } else { - $this->config[$config] = $value; - } - } - - /** - * 连接数据库方法 - * @access public - * @param array $config 连接参数 - * @param integer $linkNum 连接序号 - * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) - * @return PDO - * @throws Exception - */ - public function connect(array $config = [], $linkNum = 0, $autoConnection = false) - { - if (!isset($this->links[$linkNum])) { - if (!$config) { - $config = $this->config; - } else { - $config = array_merge($this->config, $config); - } - // 连接参数 - if (isset($config['params']) && is_array($config['params'])) { - $params = $config['params'] + $this->params; - } else { - $params = $this->params; - } - // 记录当前字段属性大小写设置 - $this->attrCase = $params[PDO::ATTR_CASE]; - - // 数据返回类型 - if (isset($config['result_type'])) { - $this->fetchType = $config['result_type']; - } - try { - if (empty($config['dsn'])) { - $config['dsn'] = $this->parseDsn($config); - } - if ($config['debug']) { - $startTime = microtime(true); - } - $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); - if ($config['debug']) { - // 记录数据库连接信息 - Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); - } - } catch (\PDOException $e) { - if ($autoConnection) { - Log::record($e->getMessage(), 'error'); - return $this->connect($autoConnection, $linkNum); - } else { - throw $e; - } - } - } - return $this->links[$linkNum]; - } - - /** - * 释放查询结果 - * @access public - */ - public function free() - { - $this->PDOStatement = null; - } - - /** - * 获取PDO对象 - * @access public - * @return \PDO|false - */ - public function getPdo() - { - if (!$this->linkID) { - return false; - } else { - return $this->linkID; - } - } - - /** - * 执行查询 返回数据集 - * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param bool $master 是否在主服务器读操作 - * @param bool $pdo 是否返回PDO对象 - * @return mixed - * @throws PDOException - * @throws \Exception - */ - public function query($sql, $bind = [], $master = false, $pdo = false) - { - $this->initConnect($master); - if (!$this->linkID) { - return false; - } - - // 记录SQL语句 - $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } - - Db::$queryTimes++; - try { - // 调试开始 - $this->debug(true); - - // 释放前次的查询结果 - if (!empty($this->PDOStatement)) { - $this->free(); - } - // 预处理 - if (empty($this->PDOStatement)) { - $this->PDOStatement = $this->linkID->prepare($sql); - } - // 是否为存储过程调用 - $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); - // 参数绑定 - if ($procedure) { - $this->bindParam($bind); - } else { - $this->bindValue($bind); - } - // 执行查询 - $this->PDOStatement->execute(); - // 调试结束 - $this->debug(false, '', $master); - // 返回结果集 - return $this->getResult($pdo, $procedure); - } catch (\PDOException $e) { - if ($this->isBreak($e)) { - return $this->close()->query($sql, $bind, $master, $pdo); - } - throw new PDOException($e, $this->config, $this->getLastsql()); - } catch (\Throwable $e) { - if ($this->isBreak($e)) { - return $this->close()->query($sql, $bind, $master, $pdo); - } - throw $e; - } catch (\Exception $e) { - if ($this->isBreak($e)) { - return $this->close()->query($sql, $bind, $master, $pdo); - } - throw $e; - } - } - - /** - * 执行语句 - * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param Query $query 查询对象 - * @return int - * @throws PDOException - * @throws \Exception - */ - public function execute($sql, $bind = [], Query $query = null) - { - $this->initConnect(true); - if (!$this->linkID) { - return false; - } - - // 记录SQL语句 - $this->queryStr = $sql; - if ($bind) { - $this->bind = $bind; - } - - Db::$executeTimes++; - try { - // 调试开始 - $this->debug(true); - - //释放前次的查询结果 - if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { - $this->free(); - } - // 预处理 - if (empty($this->PDOStatement)) { - $this->PDOStatement = $this->linkID->prepare($sql); - } - // 是否为存储过程调用 - $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); - // 参数绑定 - if ($procedure) { - $this->bindParam($bind); - } else { - $this->bindValue($bind); - } - // 执行语句 - $this->PDOStatement->execute(); - // 调试结束 - $this->debug(false, '', true); - - if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { - $query->readMaster(); - } - - $this->numRows = $this->PDOStatement->rowCount(); - return $this->numRows; - } catch (\PDOException $e) { - if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind, $query); - } - throw new PDOException($e, $this->config, $this->getLastsql()); - } catch (\Throwable $e) { - if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind, $query); - } - throw $e; - } catch (\Exception $e) { - if ($this->isBreak($e)) { - return $this->close()->execute($sql, $bind, $query); - } - throw $e; - } - } - - /** - * 根据参数绑定组装最终的SQL语句 便于调试 - * @access public - * @param string $sql 带参数绑定的sql语句 - * @param array $bind 参数绑定列表 - * @return string - */ - public function getRealSql($sql, array $bind = []) - { - if (is_array($sql)) { - $sql = implode(';', $sql); - } - - foreach ($bind as $key => $val) { - $value = is_array($val) ? $val[0] : $val; - $type = is_array($val) ? $val[1] : PDO::PARAM_STR; - if (PDO::PARAM_STR == $type) { - $value = $this->quote($value); - } elseif (PDO::PARAM_INT == $type) { - $value = (float) $value; - } - // 判断占位符 - $sql = is_numeric($key) ? - substr_replace($sql, $value, strpos($sql, '?'), 1) : - str_replace( - [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], - [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], - $sql . ' '); - } - return rtrim($sql); - } - - /** - * 参数绑定 - * 支持 ['name'=>'value','id'=>123] 对应命名占位符 - * 或者 ['value',123] 对应问号占位符 - * @access public - * @param array $bind 要绑定的参数列表 - * @return void - * @throws BindParamException - */ - protected function bindValue(array $bind = []) - { - foreach ($bind as $key => $val) { - // 占位符 - $param = is_numeric($key) ? $key + 1 : ':' . $key; - if (is_array($val)) { - if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { - $val[0] = 0; - } - $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); - } else { - $result = $this->PDOStatement->bindValue($param, $val); - } - if (!$result) { - throw new BindParamException( - "Error occurred when binding parameters '{$param}'", - $this->config, - $this->getLastsql(), - $bind - ); - } - } - } - - /** - * 存储过程的输入输出参数绑定 - * @access public - * @param array $bind 要绑定的参数列表 - * @return void - * @throws BindParamException - */ - protected function bindParam($bind) - { - foreach ($bind as $key => $val) { - $param = is_numeric($key) ? $key + 1 : ':' . $key; - if (is_array($val)) { - array_unshift($val, $param); - $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); - } else { - $result = $this->PDOStatement->bindValue($param, $val); - } - if (!$result) { - $param = array_shift($val); - throw new BindParamException( - "Error occurred when binding parameters '{$param}'", - $this->config, - $this->getLastsql(), - $bind - ); - } - } - } - - /** - * 获得数据集数组 - * @access protected - * @param bool $pdo 是否返回PDOStatement - * @param bool $procedure 是否存储过程 - * @return PDOStatement|array - */ - protected function getResult($pdo = false, $procedure = false) - { - if ($pdo) { - // 返回PDOStatement对象处理 - return $this->PDOStatement; - } - if ($procedure) { - // 存储过程返回结果 - return $this->procedure(); - } - $result = $this->PDOStatement->fetchAll($this->fetchType); - $this->numRows = count($result); - return $result; - } - - /** - * 获得存储过程数据集 - * @access protected - * @return array - */ - protected function procedure() - { - $item = []; - do { - $result = $this->getResult(); - if ($result) { - $item[] = $result; - } - } while ($this->PDOStatement->nextRowset()); - $this->numRows = count($item); - return $item; - } - - /** - * 执行数据库事务 - * @access public - * @param callable $callback 数据操作方法回调 - * @return mixed - * @throws PDOException - * @throws \Exception - * @throws \Throwable - */ - public function transaction($callback) - { - $this->startTrans(); - try { - $result = null; - if (is_callable($callback)) { - $result = call_user_func_array($callback, [$this]); - } - $this->commit(); - return $result; - } catch (\Exception $e) { - $this->rollback(); - throw $e; - } catch (\Throwable $e) { - $this->rollback(); - throw $e; - } - } - - /** - * 启动事务 - * @access public - * @return bool|mixed - * @throws \Exception - */ - public function startTrans() - { - $this->initConnect(true); - if (!$this->linkID) { - return false; - } - - ++$this->transTimes; - try { - if (1 == $this->transTimes) { - $this->linkID->beginTransaction(); - } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { - $this->linkID->exec( - $this->parseSavepoint('trans' . $this->transTimes) - ); - } - - } catch (\PDOException $e) { - if ($this->isBreak($e)) { - return $this->close()->startTrans(); - } - throw $e; - } catch (\Exception $e) { - if ($this->isBreak($e)) { - return $this->close()->startTrans(); - } - throw $e; - } catch (\Error $e) { - if ($this->isBreak($e)) { - return $this->close()->startTrans(); - } - throw $e; - } - } - - /** - * 用于非自动提交状态下面的查询提交 - * @access public - * @return void - * @throws PDOException - */ - public function commit() - { - $this->initConnect(true); - - if (1 == $this->transTimes) { - $this->linkID->commit(); - } - - --$this->transTimes; - } - - /** - * 事务回滚 - * @access public - * @return void - * @throws PDOException - */ - public function rollback() - { - $this->initConnect(true); - - if (1 == $this->transTimes) { - $this->linkID->rollBack(); - } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { - $this->linkID->exec( - $this->parseSavepointRollBack('trans' . $this->transTimes) - ); - } - - $this->transTimes = max(0, $this->transTimes - 1); - } - - /** - * 是否支持事务嵌套 - * @return bool - */ - protected function supportSavepoint() - { - return false; - } - - /** - * 生成定义保存点的SQL - * @param $name - * @return string - */ - protected function parseSavepoint($name) - { - return 'SAVEPOINT ' . $name; - } - - /** - * 生成回滚到保存点的SQL - * @param $name - * @return string - */ - protected function parseSavepointRollBack($name) - { - return 'ROLLBACK TO SAVEPOINT ' . $name; - } - - /** - * 批处理执行SQL语句 - * 批处理的指令都认为是execute操作 - * @access public - * @param array $sqlArray SQL批处理指令 - * @return boolean - */ - public function batchQuery($sqlArray = [], $bind = [], Query $query = null) - { - if (!is_array($sqlArray)) { - return false; - } - // 自动启动事务支持 - $this->startTrans(); - try { - foreach ($sqlArray as $sql) { - $this->execute($sql, $bind, $query); - } - // 提交事务 - $this->commit(); - } catch (\Exception $e) { - $this->rollback(); - throw $e; - } - - return true; - } - - /** - * 获得查询次数 - * @access public - * @param boolean $execute 是否包含所有查询 - * @return integer - */ - public function getQueryTimes($execute = false) - { - return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; - } - - /** - * 获得执行次数 - * @access public - * @return integer - */ - public function getExecuteTimes() - { - return Db::$executeTimes; - } - - /** - * 关闭数据库(或者重新连接) - * @access public - * @return $this - */ - public function close() - { - $this->linkID = null; - $this->linkWrite = null; - $this->linkRead = null; - $this->links = []; - return $this; - } - - /** - * 是否断线 - * @access protected - * @param \PDOException|\Exception $e 异常对象 - * @return bool - */ - protected function isBreak($e) - { - if (!$this->config['break_reconnect']) { - return false; - } - - $info = [ - 'server has gone away', - 'no connection to the server', - 'Lost connection', - 'is dead or not enabled', - 'Error while sending', - 'decryption failed or bad record mac', - 'server closed the connection unexpectedly', - 'SSL connection has been closed unexpectedly', - 'Error writing data to the connection', - 'Resource deadlock avoided', - 'failed with errno', - ]; - - $error = $e->getMessage(); - - foreach ($info as $msg) { - if (false !== stripos($error, $msg)) { - return true; - } - } - return false; - } - - /** - * 获取最近一次查询的sql语句 - * @access public - * @return string - */ - public function getLastSql() - { - return $this->getRealSql($this->queryStr, $this->bind); - } - - /** - * 获取最近插入的ID - * @access public - * @param string $sequence 自增序列名 - * @return string - */ - public function getLastInsID($sequence = null) - { - return $this->linkID->lastInsertId($sequence); - } - - /** - * 获取返回或者影响的记录数 - * @access public - * @return integer - */ - public function getNumRows() - { - return $this->numRows; - } - - /** - * 获取最近的错误信息 - * @access public - * @return string - */ - public function getError() - { - if ($this->PDOStatement) { - $error = $this->PDOStatement->errorInfo(); - $error = $error[1] . ':' . $error[2]; - } else { - $error = ''; - } - if ('' != $this->queryStr) { - $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); - } - return $error; - } - - /** - * SQL指令安全过滤 - * @access public - * @param string $str SQL字符串 - * @param bool $master 是否主库查询 - * @return string - */ - public function quote($str, $master = true) - { - $this->initConnect($master); - return $this->linkID ? $this->linkID->quote($str) : $str; - } - - /** - * 数据库调试 记录当前SQL及分析性能 - * @access protected - * @param boolean $start 调试开始标记 true 开始 false 结束 - * @param string $sql 执行的SQL语句 留空自动获取 - * @param boolean $master 主从标记 - * @return void - */ - protected function debug($start, $sql = '', $master = false) - { - if (!empty($this->config['debug'])) { - // 开启数据库调试模式 - if ($start) { - Debug::remark('queryStartTime', 'time'); - } else { - // 记录操作结束时间 - Debug::remark('queryEndTime', 'time'); - $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); - $sql = $sql ?: $this->getLastsql(); - $result = []; - // SQL性能分析 - if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { - $result = $this->getExplain($sql); - } - // SQL监听 - $this->trigger($sql, $runtime, $result, $master); - } - } - } - - /** - * 监听SQL执行 - * @access public - * @param callable $callback 回调方法 - * @return void - */ - public function listen($callback) - { - self::$event[] = $callback; - } - - /** - * 触发SQL事件 - * @access protected - * @param string $sql SQL语句 - * @param float $runtime SQL运行时间 - * @param mixed $explain SQL分析 - * @param bool $master 主从标记 - * @return void - */ - protected function trigger($sql, $runtime, $explain = [], $master = false) - { - if (!empty(self::$event)) { - foreach (self::$event as $callback) { - if (is_callable($callback)) { - call_user_func_array($callback, [$sql, $runtime, $explain, $master]); - } - } - } else { - // 未注册监听则记录到日志中 - if ($this->config['deploy']) { - // 分布式记录当前操作的主从 - $master = $master ? 'master|' : 'slave|'; - } else { - $master = ''; - } - - Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); - if (!empty($explain)) { - Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); - } - } - } - - /** - * 初始化数据库连接 - * @access protected - * @param boolean $master 是否主服务器 - * @return void - */ - protected function initConnect($master = true) - { - if (!empty($this->config['deploy'])) { - // 采用分布式数据库 - if ($master || $this->transTimes) { - if (!$this->linkWrite) { - $this->linkWrite = $this->multiConnect(true); - } - $this->linkID = $this->linkWrite; - } else { - if (!$this->linkRead) { - $this->linkRead = $this->multiConnect(false); - } - $this->linkID = $this->linkRead; - } - } elseif (!$this->linkID) { - // 默认单数据库 - $this->linkID = $this->connect(); - } - } - - /** - * 连接分布式服务器 - * @access protected - * @param boolean $master 主服务器 - * @return PDO - */ - protected function multiConnect($master = false) - { - $_config = []; - // 分布式数据库配置解析 - foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { - $_config[$name] = explode(',', $this->config[$name]); - } - - // 主服务器序号 - $m = floor(mt_rand(0, $this->config['master_num'] - 1)); - - if ($this->config['rw_separate']) { - // 主从式采用读写分离 - if ($master) // 主服务器写入 - { - $r = $m; - } elseif (is_numeric($this->config['slave_no'])) { - // 指定服务器读 - $r = $this->config['slave_no']; - } else { - // 读操作连接从服务器 每次随机连接的数据库 - $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); - } - } else { - // 读写操作不区分服务器 每次随机连接的数据库 - $r = floor(mt_rand(0, count($_config['hostname']) - 1)); - } - $dbMaster = false; - if ($m != $r) { - $dbMaster = []; - foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { - $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; - } - } - $dbConfig = []; - foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { - $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; - } - return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); - } - - /** - * 析构方法 - * @access public - */ - public function __destruct() - { - // 释放查询 - if ($this->PDOStatement) { - $this->free(); - } - // 关闭连接 - $this->close(); - } -} + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use PDOStatement; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Log; + +/** + * Class Connection + * @package think + * @method Query table(string $table) 指定数据表(含前缀) + * @method Query name(string $name) 指定数据表(不含前缀) + * + */ +abstract class Connection +{ + + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + // 使用Builder类 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据返回类型 + 'result_type' => PDO::FETCH_ASSOC, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + ]; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 构造函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 获取新的查询对象 + * @access protected + * @return Query + */ + protected function getQuery() + { + $class = $this->config['query']; + return new $class($this); + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilder() + { + if (!empty($this->builder)) { + return $this->builder; + } else { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + } + + /** + * 调用Query类的查询方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + */ + public function __call($method, $args) + { + return call_user_func_array([$this->getQuery(), $method], $args); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + return $info; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (!isset($this->links[$linkNum])) { + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + // 数据返回类型 + if (isset($config['result_type'])) { + $this->fetchType = $config['result_type']; + } + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + if ($config['debug']) { + $startTime = microtime(true); + } + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + if ($config['debug']) { + // 记录数据库连接信息 + Log::record('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn'], 'sql'); + } + } catch (\PDOException $e) { + if ($autoConnection) { + Log::record($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + return $this->links[$linkNum]; + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } else { + return $this->linkID; + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws PDOException + * @throws \Exception + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$queryTimes++; + try { + // 调试开始 + $this->debug(true); + + // 释放前次的查询结果 + if (!empty($this->PDOStatement)) { + $this->free(); + } + // 预处理 + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行查询 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', $master); + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws PDOException + * @throws \Exception + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + if ($bind) { + $this->bind = $bind; + } + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + //释放前次的查询结果 + if (!empty($this->PDOStatement) && $this->PDOStatement->queryString != $sql) { + $this->free(); + } + // 预处理 + if (empty($this->PDOStatement)) { + $this->PDOStatement = $this->linkID->prepare($sql); + } + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + // 执行语句 + $this->PDOStatement->execute(); + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + throw $e; + } + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + if (PDO::PARAM_STR == $type) { + $value = $this->quote($value); + } elseif (PDO::PARAM_INT == $type) { + $value = (float) $value; + } + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + str_replace( + [':' . $key . ')', ':' . $key . ',', ':' . $key . ' ', ':' . $key . PHP_EOL], + [$value . ')', $value . ',', $value . ' ', $value . PHP_EOL], + $sql . ' '); + } + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_numeric($key) ? $key + 1 : ':' . $key; + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + if (!$result) { + $param = array_shift($val); + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return PDOStatement|array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + $result = $this->PDOStatement->fetchAll($this->fetchType); + $this->numRows = count($result); + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + do { + $result = $this->getResult(); + if ($result) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + $this->numRows = count($item); + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction($callback) + { + $this->startTrans(); + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return bool|mixed + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->startTrans(); + } + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->startTrans(); + } + throw $e; + } catch (\Error $e) { + if ($this->isBreak($e)) { + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = [], Query $query = null) + { + if (!is_array($sqlArray)) { + return false; + } + // 自动启动事务支持 + $this->startTrans(); + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind, $query); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $info = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + + $error = $e->getMessage(); + + foreach ($info as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + return $error; + } + + /** + * SQL指令安全过滤 + * @access public + * @param string $str SQL字符串 + * @param bool $master 是否主库查询 + * @return string + */ + public function quote($str, $master = true) + { + $this->initConnect($master); + return $this->linkID ? $this->linkID->quote($str) : $str; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param boolean $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + if ($start) { + Debug::remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + Debug::remark('queryEndTime', 'time'); + $runtime = Debug::getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + // SQL监听 + $this->trigger($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + // 未注册监听则记录到日志中 + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + Log::record('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]', 'sql'); + if (!empty($explain)) { + Log::record('[ EXPLAIN : ' . var_export($explain, true) . ' ]', 'sql'); + } + } + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect($master = false) + { + $_config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = explode(',', $this->config[$name]); + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($_config['hostname']) - 1)); + } + $dbMaster = false; + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + $dbConfig = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 释放查询 + if ($this->PDOStatement) { + $this->free(); + } + // 关闭连接 + $this->close(); + } +} diff --git a/thinkphp/library/think/db/Expression.php b/thinkphp/library/think/db/Expression.php old mode 100755 new mode 100644 index 681e505..f1b92ab --- a/thinkphp/library/think/db/Expression.php +++ b/thinkphp/library/think/db/Expression.php @@ -1,48 +1,48 @@ - -// +---------------------------------------------------------------------- - -namespace think\db; - -class Expression -{ - /** - * 查询表达式 - * - * @var string - */ - protected $value; - - /** - * 创建一个查询表达式 - * - * @param string $value - * @return void - */ - public function __construct($value) - { - $this->value = $value; - } - - /** - * 获取表达式 - * - * @return string - */ - public function getValue() - { - return $this->value; - } - - public function __toString() - { - return (string) $this->value; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php old mode 100755 new mode 100644 index f5fe484..ba4eff8 --- a/thinkphp/library/think/db/Query.php +++ b/thinkphp/library/think/db/Query.php @@ -1,2998 +1,3025 @@ - -// +---------------------------------------------------------------------- - -namespace think\db; - -use PDO; -use think\App; -use think\Cache; -use think\Collection; -use think\Config; -use think\Db; -use think\db\exception\BindParamException; -use think\db\exception\DataNotFoundException; -use think\db\exception\ModelNotFoundException; -use think\Exception; -use think\exception\DbException; -use think\exception\PDOException; -use think\Loader; -use think\Model; -use think\model\Relation; -use think\model\relation\OneToOne; -use think\Paginator; - -class Query -{ - // 数据库Connection对象实例 - protected $connection; - // 数据库Builder对象实例 - protected $builder; - // 当前模型类名称 - protected $model; - // 当前数据表名称(含前缀) - protected $table = ''; - // 当前数据表名称(不含前缀) - protected $name = ''; - // 当前数据表主键 - protected $pk; - // 当前数据表前缀 - protected $prefix = ''; - // 查询参数 - protected $options = []; - // 参数绑定 - protected $bind = []; - // 数据表信息 - protected static $info = []; - // 回调事件 - private static $event = []; - // 读取主库 - private static $readMaster = []; - - /** - * 构造函数 - * @access public - * @param Connection $connection 数据库对象实例 - * @param Model $model 模型对象 - */ - public function __construct(Connection $connection = null, $model = null) - { - $this->connection = $connection ?: Db::connect([], true); - $this->prefix = $this->connection->getConfig('prefix'); - $this->model = $model; - // 设置当前连接的Builder对象 - $this->setBuilder(); - } - - /** - * 利用__call方法实现一些特殊的Model方法 - * @access public - * @param string $method 方法名称 - * @param array $args 调用参数 - * @return mixed - * @throws DbException - * @throws Exception - */ - public function __call($method, $args) - { - if (strtolower(substr($method, 0, 5)) == 'getby') { - // 根据某个字段获取记录 - $field = Loader::parseName(substr($method, 5)); - $where[$field] = $args[0]; - return $this->where($where)->find(); - } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { - // 根据某个字段获取记录的某个值 - $name = Loader::parseName(substr($method, 10)); - $where[$name] = $args[0]; - return $this->where($where)->value($args[1]); - } else { - throw new Exception('method not exist:' . __CLASS__ . '->' . $method); - } - } - - /** - * 获取当前的数据库Connection对象 - * @access public - * @return Connection - */ - public function getConnection() - { - return $this->connection; - } - - /** - * 切换当前的数据库连接 - * @access public - * @param mixed $config - * @return $this - */ - public function connect($config) - { - $this->connection = Db::connect($config); - $this->setBuilder(); - $this->prefix = $this->connection->getConfig('prefix'); - return $this; - } - - /** - * 设置当前的数据库Builder对象 - * @access protected - * @return void - */ - protected function setBuilder() - { - $class = $this->connection->getBuilder(); - $this->builder = new $class($this->connection, $this); - } - - /** - * 获取当前的模型对象实例 - * @access public - * @return Model|null - */ - public function getModel() - { - return $this->model; - } - - /** - * 设置后续从主库读取数据 - * @access public - * @param bool $allTable - * @return void - */ - public function readMaster($allTable = false) - { - if ($allTable) { - $table = '*'; - } else { - $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); - } - - static::$readMaster[$table] = true; - - return $this; - } - - /** - * 获取当前的builder实例对象 - * @access public - * @return Builder - */ - public function getBuilder() - { - return $this->builder; - } - - /** - * 指定默认的数据表名(不含前缀) - * @access public - * @param string $name - * @return $this - */ - public function name($name) - { - $this->name = $name; - return $this; - } - - /** - * 指定默认数据表名(含前缀) - * @access public - * @param string $table 表名 - * @return $this - */ - public function setTable($table) - { - $this->table = $table; - return $this; - } - - /** - * 得到当前或者指定名称的数据表 - * @access public - * @param string $name - * @return string - */ - public function getTable($name = '') - { - if ($name || empty($this->table)) { - $name = $name ?: $this->name; - $tableName = $this->prefix; - if ($name) { - $tableName .= Loader::parseName($name); - } - } else { - $tableName = $this->table; - } - return $tableName; - } - - /** - * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) - * @access public - * @param string $sql sql语句 - * @return string - */ - public function parseSqlTable($sql) - { - if (false !== strpos($sql, '__')) { - $prefix = $this->prefix; - $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { - return $prefix . strtolower($match[1]); - }, $sql); - } - return $sql; - } - - /** - * 执行查询 返回数据集 - * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @param boolean $master 是否在主服务器读操作 - * @param bool|string $class 指定返回的数据集对象 - * @return mixed - * @throws BindParamException - * @throws PDOException - */ - public function query($sql, $bind = [], $master = false, $class = false) - { - return $this->connection->query($sql, $bind, $master, $class); - } - - /** - * 执行语句 - * @access public - * @param string $sql sql指令 - * @param array $bind 参数绑定 - * @return int - * @throws BindParamException - * @throws PDOException - */ - public function execute($sql, $bind = []) - { - return $this->connection->execute($sql, $bind, $this); - } - - /** - * 获取最近插入的ID - * @access public - * @param string $sequence 自增序列名 - * @return string - */ - public function getLastInsID($sequence = null) - { - return $this->connection->getLastInsID($sequence); - } - - /** - * 获取最近一次查询的sql语句 - * @access public - * @return string - */ - public function getLastSql() - { - return $this->connection->getLastSql(); - } - - /** - * 执行数据库事务 - * @access public - * @param callable $callback 数据操作方法回调 - * @return mixed - */ - public function transaction($callback) - { - return $this->connection->transaction($callback); - } - - /** - * 启动事务 - * @access public - * @return void - */ - public function startTrans() - { - $this->connection->startTrans(); - } - - /** - * 用于非自动提交状态下面的查询提交 - * @access public - * @return void - * @throws PDOException - */ - public function commit() - { - $this->connection->commit(); - } - - /** - * 事务回滚 - * @access public - * @return void - * @throws PDOException - */ - public function rollback() - { - $this->connection->rollback(); - } - - /** - * 批处理执行SQL语句 - * 批处理的指令都认为是execute操作 - * @access public - * @param array $sql SQL批处理指令 - * @return boolean - */ - public function batchQuery($sql = [], $bind = []) - { - return $this->connection->batchQuery($sql, $bind); - } - - /** - * 获取数据库的配置参数 - * @access public - * @param string $name 参数名称 - * @return boolean - */ - public function getConfig($name = '') - { - return $this->connection->getConfig($name); - } - - /** - * 得到分表的的数据表名 - * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 - * @return string - */ - public function getPartitionTableName($data, $field, $rule = []) - { - // 对数据表进行分区 - if ($field && isset($data[$field])) { - $value = $data[$field]; - $type = $rule['type']; - switch ($type) { - case 'id': - // 按照id范围分表 - $step = $rule['expr']; - $seq = floor($value / $step) + 1; - break; - case 'year': - // 按照年份分表 - if (!is_numeric($value)) { - $value = strtotime($value); - } - $seq = date('Y', $value) - $rule['expr'] + 1; - break; - case 'mod': - // 按照id的模数分表 - $seq = ($value % $rule['num']) + 1; - break; - case 'md5': - // 按照md5的序列分表 - $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; - break; - default: - if (function_exists($type)) { - // 支持指定函数哈希 - $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; - } else { - // 按照字段的首字母的值分表 - $seq = (ord($value{0}) % $rule['num']) + 1; - } - } - return $this->getTable() . '_' . $seq; - } else { - // 当设置的分表字段不在查询条件或者数据中 - // 进行联合查询,必须设定 partition['num'] - $tableName = []; - for ($i = 0; $i < $rule['num']; $i++) { - $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); - } - - $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; - return $tableName; - } - } - - /** - * 得到某个字段的值 - * @access public - * @param string $field 字段名 - * @param mixed $default 默认值 - * @param bool $force 强制转为数字类型 - * @return mixed - */ - public function value($field, $default = null, $force = false) - { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($key); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - $pdo = $this->field($field)->limit(1)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } - $result = $pdo->fetchColumn(); - if ($force) { - $result += 0; - } - - if (isset($cache)) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; - } - return false !== $result ? $result : $default; - } - - /** - * 得到某个列的数组 - * @access public - * @param string $field 字段名 多个字段用逗号分隔 - * @param string $key 索引 - * @return array - */ - public function column($field, $key = '') - { - $result = false; - if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { - // 判断查询缓存 - $cache = $this->options['cache']; - if (empty($this->options['table'])) { - $this->options['table'] = $this->getTable(); - } - $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); - $result = Cache::get($guid); - } - if (false === $result) { - if (isset($this->options['field'])) { - unset($this->options['field']); - } - if (is_null($field)) { - $field = '*'; - } elseif ($key && '*' != $field) { - $field = $key . ',' . $field; - } - $pdo = $this->field($field)->getPdo(); - if (is_string($pdo)) { - // 返回SQL语句 - return $pdo; - } - if (1 == $pdo->columnCount()) { - $result = $pdo->fetchAll(PDO::FETCH_COLUMN); - } else { - $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); - if ($resultSet) { - $fields = array_keys($resultSet[0]); - $count = count($fields); - $key1 = array_shift($fields); - $key2 = $fields ? array_shift($fields) : ''; - $key = $key ?: $key1; - if (strpos($key, '.')) { - list($alias, $key) = explode('.', $key); - } - foreach ($resultSet as $val) { - if ($count > 2) { - $result[$val[$key]] = $val; - } elseif (2 == $count) { - $result[$val[$key]] = $val[$key2]; - } elseif (1 == $count) { - $result[$val[$key]] = $val[$key1]; - } - } - } else { - $result = []; - } - } - if (isset($cache) && isset($guid)) { - // 缓存数据 - $this->cacheData($guid, $result, $cache); - } - } else { - // 清空查询条件 - $this->options = []; - } - return $result; - } - - /** - * COUNT查询 - * @access public - * @param string $field 字段名 - * @return integer|string - */ - public function count($field = '*') - { - if (isset($this->options['group'])) { - // 支持GROUP - $options = $this->getOptions(); - $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); - return $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0, true); - } - - return $this->value('COUNT(' . $field . ') AS tp_count', 0, true); - } - - /** - * SUM查询 - * @access public - * @param string $field 字段名 - * @return float|int - */ - public function sum($field) - { - return $this->value('SUM(' . $field . ') AS tp_sum', 0, true); - } - - /** - * MIN查询 - * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 - * @return mixed - */ - public function min($field, $force = true) - { - return $this->value('MIN(' . $field . ') AS tp_min', 0, $force); - } - - /** - * MAX查询 - * @access public - * @param string $field 字段名 - * @param bool $force 强制转为数字类型 - * @return mixed - */ - public function max($field, $force = true) - { - return $this->value('MAX(' . $field . ') AS tp_max', 0, $force); - } - - /** - * AVG查询 - * @access public - * @param string $field 字段名 - * @return float|int - */ - public function avg($field) - { - return $this->value('AVG(' . $field . ') AS tp_avg', 0, true); - } - - /** - * 设置记录的某个字段值 - * 支持使用数据库字段和方法 - * @access public - * @param string|array $field 字段名 - * @param mixed $value 字段值 - * @return integer - */ - public function setField($field, $value = '') - { - if (is_array($field)) { - $data = $field; - } else { - $data[$field] = $value; - } - return $this->update($data); - } - - /** - * 字段值(延迟)增长 - * @access public - * @param string $field 字段名 - * @param integer $step 增长值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setInc($field, $step = 1, $lazyTime = 0) - { - $condition = !empty($this->options['where']) ? $this->options['where'] : []; - if (empty($condition)) { - // 没有条件不做任何更新 - throw new Exception('no data to update'); - } - if ($lazyTime > 0) { - // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); - $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); - if (false === $step) { - // 清空查询条件 - $this->options = []; - return true; - } - } - return $this->setField($field, ['inc', $step]); - } - - /** - * 字段值(延迟)减少 - * @access public - * @param string $field 字段名 - * @param integer $step 减少值 - * @param integer $lazyTime 延时时间(s) - * @return integer|true - * @throws Exception - */ - public function setDec($field, $step = 1, $lazyTime = 0) - { - $condition = !empty($this->options['where']) ? $this->options['where'] : []; - if (empty($condition)) { - // 没有条件不做任何更新 - throw new Exception('no data to update'); - } - if ($lazyTime > 0) { - // 延迟写入 - $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); - $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); - if (false === $step) { - // 清空查询条件 - $this->options = []; - return true; - } - return $this->setField($field, ['inc', $step]); - } - return $this->setField($field, ['dec', $step]); - } - - /** - * 延时更新检查 返回false表示需要延时 - * 否则返回实际写入的数值 - * @access protected - * @param string $type 自增或者自减 - * @param string $guid 写入标识 - * @param integer $step 写入步进值 - * @param integer $lazyTime 延时时间(s) - * @return false|integer - */ - protected function lazyWrite($type, $guid, $step, $lazyTime) - { - if (!Cache::has($guid . '_time')) { - // 计时开始 - Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); - Cache::$type($guid, $step); - } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { - // 删除缓存 - $value = Cache::$type($guid, $step); - Cache::rm($guid); - Cache::rm($guid . '_time'); - return 0 === $value ? false : $value; - } else { - // 更新缓存 - Cache::$type($guid, $step); - } - return false; - } - - /** - * 查询SQL组装 join - * @access public - * @param mixed $join 关联的表名 - * @param mixed $condition 条件 - * @param string $type JOIN类型 - * @return $this - */ - public function join($join, $condition = null, $type = 'INNER') - { - if (empty($condition)) { - // 如果为组数,则循环调用join - foreach ($join as $key => $value) { - if (is_array($value) && 2 <= count($value)) { - $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); - } - } - } else { - $table = $this->getJoinTable($join); - - $this->options['join'][] = [$table, strtoupper($type), $condition]; - } - return $this; - } - - /** - * 获取Join表名及别名 支持 - * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' - * @access public - * @param array|string $join - * @return array|string - */ - protected function getJoinTable($join, &$alias = null) - { - // 传入的表名为数组 - if (is_array($join)) { - $table = $join; - $alias = array_shift($join); - } else { - $join = trim($join); - if (false !== strpos($join, '(')) { - // 使用子查询 - $table = $join; - } else { - $prefix = $this->prefix; - if (strpos($join, ' ')) { - // 使用别名 - list($table, $alias) = explode(' ', $join); - } else { - $table = $join; - if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { - $alias = $join; - } - } - if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { - $table = $this->getTable($table); - } - } - if (isset($alias) && $table != $alias) { - $table = [$table => $alias]; - } - } - return $table; - } - - /** - * 查询SQL组装 union - * @access public - * @param mixed $union - * @param boolean $all - * @return $this - */ - public function union($union, $all = false) - { - $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; - - if (is_array($union)) { - $this->options['union'] = array_merge($this->options['union'], $union); - } else { - $this->options['union'][] = $union; - } - return $this; - } - - /** - * 指定查询字段 支持字段排除和指定数据表 - * @access public - * @param mixed $field - * @param boolean $except 是否排除 - * @param string $tableName 数据表名 - * @param string $prefix 字段前缀 - * @param string $alias 别名前缀 - * @return $this - */ - public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') - { - if (empty($field)) { - return $this; - } elseif ($field instanceof Expression) { - $this->options['field'][] = $field; - return $this; - } - - if (is_string($field)) { - if (preg_match('/[\<\'\"\(]/', $field)) { - return $this->fieldRaw($field); - } - $field = array_map('trim', explode(',', $field)); - } - if (true === $field) { - // 获取全部字段 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); - $field = $fields ?: ['*']; - } elseif ($except) { - // 字段排除 - $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); - $field = $fields ? array_diff($fields, $field) : $field; - } - if ($tableName) { - // 添加统一的前缀 - $prefix = $prefix ?: $tableName; - foreach ($field as $key => $val) { - if (is_numeric($key)) { - $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); - } - $field[$key] = $val; - } - } - - if (isset($this->options['field'])) { - $field = array_merge((array) $this->options['field'], $field); - } - $this->options['field'] = array_unique($field); - return $this; - } - - /** - * 表达式方式指定查询字段 - * @access public - * @param string $field 字段名 - * @param array $bind 参数绑定 - * @return $this - */ - public function fieldRaw($field, array $bind = []) - { - $this->options['field'][] = $this->raw($field); - - if ($bind) { - $this->bind($bind); - } - - return $this; - } - - /** - * 设置数据 - * @access public - * @param mixed $field 字段名或者数据 - * @param mixed $value 字段值 - * @return $this - */ - public function data($field, $value = null) - { - if (is_array($field)) { - $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; - } else { - $this->options['data'][$field] = $value; - } - return $this; - } - - /** - * 字段值增长 - * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 - * @return $this - */ - public function inc($field, $step = 1) - { - $fields = is_string($field) ? explode(',', $field) : $field; - foreach ($fields as $field) { - $this->data($field, ['inc', $step]); - } - return $this; - } - - /** - * 字段值减少 - * @access public - * @param string|array $field 字段名 - * @param integer $step 增长值 - * @return $this - */ - public function dec($field, $step = 1) - { - $fields = is_string($field) ? explode(',', $field) : $field; - foreach ($fields as $field) { - $this->data($field, ['dec', $step]); - } - return $this; - } - - /** - * 使用表达式设置数据 - * @access public - * @param string $field 字段名 - * @param string $value 字段值 - * @return $this - */ - public function exp($field, $value) - { - $this->data($field, $this->raw($value)); - return $this; - } - - /** - * 使用表达式设置数据 - * @access public - * @param mixed $value 表达式 - * @return Expression - */ - public function raw($value) - { - return new Expression($value); - } - - /** - * 指定JOIN查询字段 - * @access public - * @param string|array $table 数据表 - * @param string|array $field 查询字段 - * @param mixed $on JOIN条件 - * @param string $type JOIN类型 - * @return $this - */ - public function view($join, $field = true, $on = null, $type = 'INNER') - { - $this->options['view'] = true; - if (is_array($join) && key($join) === 0) { - foreach ($join as $key => $val) { - $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); - } - } else { - $fields = []; - $table = $this->getJoinTable($join, $alias); - - if (true === $field) { - $fields = $alias . '.*'; - } else { - if (is_string($field)) { - $field = explode(',', $field); - } - foreach ($field as $key => $val) { - if (is_numeric($key)) { - $fields[] = $alias . '.' . $val; - $this->options['map'][$val] = $alias . '.' . $val; - } else { - if (preg_match('/[,=\.\'\"\(\s]/', $key)) { - $name = $key; - } else { - $name = $alias . '.' . $key; - } - $fields[$name] = $val; - $this->options['map'][$val] = $name; - } - } - } - $this->field($fields); - if ($on) { - $this->join($table, $on, $type); - } else { - $this->table($table); - } - } - return $this; - } - - /** - * 设置分表规则 - * @access public - * @param array $data 操作的数据 - * @param string $field 分表依据的字段 - * @param array $rule 分表规则 - * @return $this - */ - public function partition($data, $field, $rule = []) - { - $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); - return $this; - } - - /** - * 指定AND查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @return $this - */ - public function where($field, $op = null, $condition = null) - { - $param = func_get_args(); - array_shift($param); - $this->parseWhereExp('AND', $field, $op, $condition, $param); - return $this; - } - - /** - * 指定OR查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @return $this - */ - public function whereOr($field, $op = null, $condition = null) - { - $param = func_get_args(); - array_shift($param); - $this->parseWhereExp('OR', $field, $op, $condition, $param); - return $this; - } - - /** - * 指定XOR查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @return $this - */ - public function whereXor($field, $op = null, $condition = null) - { - $param = func_get_args(); - array_shift($param); - $this->parseWhereExp('XOR', $field, $op, $condition, $param); - return $this; - } - - /** - * 指定表达式查询条件 - * @access public - * @param string $where 查询条件 - * @param array $bind 参数绑定 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereRaw($where, $bind = [], $logic = 'AND') - { - $this->options['where'][$logic][] = $this->raw($where); - - if ($bind) { - $this->bind($bind); - } - - return $this; - } - - /** - * 指定表达式查询条件 OR - * @access public - * @param string $where 查询条件 - * @param array $bind 参数绑定 - * @return $this - */ - public function whereOrRaw($where, $bind = []) - { - return $this->whereRaw($where, $bind, 'OR'); - } - - /** - * 指定Null查询条件 - * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNull($field, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'null', null, [], true); - return $this; - } - - /** - * 指定NotNull查询条件 - * @access public - * @param mixed $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotNull($field, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'notnull', null, [], true); - return $this; - } - - /** - * 指定Exists查询条件 - * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereExists($condition, $logic = 'AND') - { - $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; - return $this; - } - - /** - * 指定NotExists查询条件 - * @access public - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotExists($condition, $logic = 'AND') - { - $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; - return $this; - } - - /** - * 指定In查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereIn($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'in', $condition, [], true); - return $this; - } - - /** - * 指定NotIn查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotIn($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); - return $this; - } - - /** - * 指定Like查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereLike($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'like', $condition, [], true); - return $this; - } - - /** - * 指定NotLike查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotLike($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); - return $this; - } - - /** - * 指定Between查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereBetween($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'between', $condition, [], true); - return $this; - } - - /** - * 指定NotBetween查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereNotBetween($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); - return $this; - } - - /** - * 指定Exp查询条件 - * @access public - * @param mixed $field 查询字段 - * @param mixed $condition 查询条件 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function whereExp($field, $condition, $logic = 'AND') - { - $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); - return $this; - } - - /** - * 设置软删除字段及条件 - * @access public - * @param false|string $field 查询字段 - * @param mixed $condition 查询条件 - * @return $this - */ - public function useSoftDelete($field, $condition = null) - { - if ($field) { - $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; - } - return $this; - } - - /** - * 分析查询表达式 - * @access public - * @param string $logic 查询逻辑 and or xor - * @param string|array|\Closure $field 查询字段 - * @param mixed $op 查询表达式 - * @param mixed $condition 查询条件 - * @param array $param 查询参数 - * @param bool $strict 严格模式 - * @return void - */ - protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) - { - $logic = strtoupper($logic); - if ($field instanceof \Closure) { - $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; - return; - } - - if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { - $field = $this->options['via'] . '.' . $field; - } - - if ($field instanceof Expression) { - return $this->whereRaw($field, is_array($op) ? $op : []); - } elseif ($strict) { - // 使用严格模式查询 - $where[$field] = [$op, $condition]; - - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; - } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { - $where[] = ['exp', $this->raw($field)]; - if (is_array($op)) { - // 参数绑定 - $this->bind($op); - } - } elseif (is_null($op) && is_null($condition)) { - if (is_array($field)) { - // 数组批量查询 - $where = $field; - foreach ($where as $k => $val) { - $this->options['multi'][$logic][$k][] = $val; - } - } elseif ($field && is_string($field)) { - // 字符串查询 - $where[$field] = ['null', '']; - $this->options['multi'][$logic][$field][] = $where[$field]; - } - } elseif (is_array($op)) { - $where[$field] = $param; - } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { - // null查询 - $where[$field] = [$op, '']; - - $this->options['multi'][$logic][$field][] = $where[$field]; - } elseif (is_null($condition)) { - // 字段相等查询 - $where[$field] = ['eq', $op]; - - $this->options['multi'][$logic][$field][] = $where[$field]; - } else { - if ('exp' == strtolower($op)) { - $where[$field] = ['exp', $this->raw($condition)]; - // 参数绑定 - if (isset($param[2]) && is_array($param[2])) { - $this->bind($param[2]); - } - } else { - $where[$field] = [$op, $condition]; - } - // 记录一个字段多次查询条件 - $this->options['multi'][$logic][$field][] = $where[$field]; - } - - if (!empty($where)) { - if (!isset($this->options['where'][$logic])) { - $this->options['where'][$logic] = []; - } - if (is_string($field) && $this->checkMultiField($field, $logic)) { - $where[$field] = $this->options['multi'][$logic][$field]; - } elseif (is_array($field)) { - foreach ($field as $key => $val) { - if ($this->checkMultiField($key, $logic)) { - $where[$key] = $this->options['multi'][$logic][$key]; - } - } - } - $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); - } - } - - /** - * 检查是否存在一个字段多次查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return bool - */ - private function checkMultiField($field, $logic) - { - return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; - } - - /** - * 去除某个查询条件 - * @access public - * @param string $field 查询字段 - * @param string $logic 查询逻辑 and or xor - * @return $this - */ - public function removeWhereField($field, $logic = 'AND') - { - $logic = strtoupper($logic); - if (isset($this->options['where'][$logic][$field])) { - unset($this->options['where'][$logic][$field]); - unset($this->options['multi'][$logic][$field]); - } - return $this; - } - - /** - * 去除查询参数 - * @access public - * @param string|bool $option 参数名 true 表示去除所有参数 - * @return $this - */ - public function removeOption($option = true) - { - if (true === $option) { - $this->options = []; - } elseif (is_string($option) && isset($this->options[$option])) { - unset($this->options[$option]); - } - return $this; - } - - /** - * 指定查询数量 - * @access public - * @param mixed $offset 起始位置 - * @param mixed $length 查询数量 - * @return $this - */ - public function limit($offset, $length = null) - { - if (is_null($length) && strpos($offset, ',')) { - list($offset, $length) = explode(',', $offset); - } - $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); - return $this; - } - - /** - * 指定分页 - * @access public - * @param mixed $page 页数 - * @param mixed $listRows 每页数量 - * @return $this - */ - public function page($page, $listRows = null) - { - if (is_null($listRows) && strpos($page, ',')) { - list($page, $listRows) = explode(',', $page); - } - $this->options['page'] = [intval($page), intval($listRows)]; - return $this; - } - - /** - * 分页查询 - * @param int|array $listRows 每页数量 数组表示配置参数 - * @param int|bool $simple 是否简洁模式或者总记录数 - * @param array $config 配置参数 - * page:当前页, - * path:url路径, - * query:url额外参数, - * fragment:url锚点, - * var_page:分页变量, - * list_rows:每页数量 - * type:分页类名 - * @return \think\Paginator - * @throws DbException - */ - public function paginate($listRows = null, $simple = false, $config = []) - { - if (is_int($simple)) { - $total = $simple; - $simple = false; - } - if (is_array($listRows)) { - $config = array_merge(Config::get('paginate'), $listRows); - $listRows = $config['list_rows']; - } else { - $config = array_merge(Config::get('paginate'), $config); - $listRows = $listRows ?: $config['list_rows']; - } - - /** @var Paginator $class */ - $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); - $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ - $class, - 'getCurrentPage', - ], $config['var_page']); - - $page = $page < 1 ? 1 : $page; - - $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); - - if (!isset($total) && !$simple) { - $options = $this->getOptions(); - - unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); - - $bind = $this->bind; - $total = $this->count(); - $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); - } elseif ($simple) { - $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); - $total = null; - } else { - $results = $this->page($page, $listRows)->select(); - } - return $class::make($results, $listRows, $page, $total, $simple, $config); - } - - /** - * 指定当前操作的数据表 - * @access public - * @param mixed $table 表名 - * @return $this - */ - public function table($table) - { - if (is_string($table)) { - if (strpos($table, ')')) { - // 子查询 - } elseif (strpos($table, ',')) { - $tables = explode(',', $table); - $table = []; - foreach ($tables as $item) { - list($item, $alias) = explode(' ', trim($item)); - if ($alias) { - $this->alias([$item => $alias]); - $table[$item] = $alias; - } else { - $table[] = $item; - } - } - } elseif (strpos($table, ' ')) { - list($table, $alias) = explode(' ', $table); - - $table = [$table => $alias]; - $this->alias($table); - } - } else { - $tables = $table; - $table = []; - foreach ($tables as $key => $val) { - if (is_numeric($key)) { - $table[] = $val; - } else { - $this->alias([$key => $val]); - $table[$key] = $val; - } - } - } - $this->options['table'] = $table; - return $this; - } - - /** - * USING支持 用于多表删除 - * @access public - * @param mixed $using - * @return $this - */ - public function using($using) - { - $this->options['using'] = $using; - return $this; - } - - /** - * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) - * @access public - * @param string|array $field 排序字段 - * @param string $order 排序 - * @return $this - */ - public function order($field, $order = null) - { - if (empty($field)) { - return $this; - } elseif ($field instanceof Expression) { - $this->options['order'][] = $field; - return $this; - } - - if (is_string($field)) { - if (!empty($this->options['via'])) { - $field = $this->options['via'] . '.' . $field; - } - if (strpos($field, ',')) { - $field = array_map('trim', explode(',', $field)); - } else { - $field = empty($order) ? $field : [$field => $order]; - } - } elseif (!empty($this->options['via'])) { - foreach ($field as $key => $val) { - if (is_numeric($key)) { - $field[$key] = $this->options['via'] . '.' . $val; - } else { - $field[$this->options['via'] . '.' . $key] = $val; - unset($field[$key]); - } - } - } - if (!isset($this->options['order'])) { - $this->options['order'] = []; - } - if (is_array($field)) { - $this->options['order'] = array_merge($this->options['order'], $field); - } else { - $this->options['order'][] = $field; - } - - return $this; - } - - /** - * 表达式方式指定Field排序 - * @access public - * @param string $field 排序字段 - * @param array $bind 参数绑定 - * @return $this - */ - public function orderRaw($field, array $bind = []) - { - $this->options['order'][] = $this->raw($field); - - if ($bind) { - $this->bind($bind); - } - - return $this; - } - - /** - * 查询缓存 - * @access public - * @param mixed $key 缓存key - * @param integer|\DateTime $expire 缓存有效期 - * @param string $tag 缓存标签 - * @return $this - */ - public function cache($key = true, $expire = null, $tag = null) - { - // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) - if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { - $expire = $key; - $key = true; - } - - if (false !== $key) { - $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; - } - return $this; - } - - /** - * 指定group查询 - * @access public - * @param string $group GROUP - * @return $this - */ - public function group($group) - { - $this->options['group'] = $group; - return $this; - } - - /** - * 指定having查询 - * @access public - * @param string $having having - * @return $this - */ - public function having($having) - { - $this->options['having'] = $having; - return $this; - } - - /** - * 指定查询lock - * @access public - * @param bool|string $lock 是否lock - * @return $this - */ - public function lock($lock = false) - { - $this->options['lock'] = $lock; - $this->options['master'] = true; - return $this; - } - - /** - * 指定distinct查询 - * @access public - * @param string $distinct 是否唯一 - * @return $this - */ - public function distinct($distinct) - { - $this->options['distinct'] = $distinct; - return $this; - } - - /** - * 指定数据表别名 - * @access public - * @param mixed $alias 数据表别名 - * @return $this - */ - public function alias($alias) - { - if (is_array($alias)) { - foreach ($alias as $key => $val) { - if (false !== strpos($key, '__')) { - $table = $this->parseSqlTable($key); - } else { - $table = $key; - } - $this->options['alias'][$table] = $val; - } - } else { - if (isset($this->options['table'])) { - $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; - if (false !== strpos($table, '__')) { - $table = $this->parseSqlTable($table); - } - } else { - $table = $this->getTable(); - } - - $this->options['alias'][$table] = $alias; - } - return $this; - } - - /** - * 指定强制索引 - * @access public - * @param string $force 索引名称 - * @return $this - */ - public function force($force) - { - $this->options['force'] = $force; - return $this; - } - - /** - * 查询注释 - * @access public - * @param string $comment 注释 - * @return $this - */ - public function comment($comment) - { - $this->options['comment'] = $comment; - return $this; - } - - /** - * 获取执行的SQL语句 - * @access public - * @param boolean $fetch 是否返回sql - * @return $this - */ - public function fetchSql($fetch = true) - { - $this->options['fetch_sql'] = $fetch; - return $this; - } - - /** - * 不主动获取数据集 - * @access public - * @param bool $pdo 是否返回 PDOStatement 对象 - * @return $this - */ - public function fetchPdo($pdo = true) - { - $this->options['fetch_pdo'] = $pdo; - return $this; - } - - /** - * 设置从主服务器读取数据 - * @access public - * @return $this - */ - public function master() - { - $this->options['master'] = true; - return $this; - } - - /** - * 设置是否严格检查字段名 - * @access public - * @param bool $strict 是否严格检查字段 - * @return $this - */ - public function strict($strict = true) - { - $this->options['strict'] = $strict; - return $this; - } - - /** - * 设置查询数据不存在是否抛出异常 - * @access public - * @param bool $fail 数据不存在是否抛出异常 - * @return $this - */ - public function failException($fail = true) - { - $this->options['fail'] = $fail; - return $this; - } - - /** - * 设置自增序列名 - * @access public - * @param string $sequence 自增序列名 - * @return $this - */ - public function sequence($sequence = null) - { - $this->options['sequence'] = $sequence; - return $this; - } - - /** - * 指定数据表主键 - * @access public - * @param string $pk 主键 - * @return $this - */ - public function pk($pk) - { - $this->pk = $pk; - return $this; - } - - /** - * 查询日期或者时间 - * @access public - * @param string $field 日期字段名 - * @param string|array $op 比较运算符或者表达式 - * @param string|array $range 比较范围 - * @return $this - */ - public function whereTime($field, $op, $range = null) - { - if (is_null($range)) { - if (is_array($op)) { - $range = $op; - } else { - // 使用日期表达式 - switch (strtolower($op)) { - case 'today': - case 'd': - $range = ['today', 'tomorrow']; - break; - case 'week': - case 'w': - $range = ['this week 00:00:00', 'next week 00:00:00']; - break; - case 'month': - case 'm': - $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; - break; - case 'year': - case 'y': - $range = ['this year 1/1', 'next year 1/1']; - break; - case 'yesterday': - $range = ['yesterday', 'today']; - break; - case 'last week': - $range = ['last week 00:00:00', 'this week 00:00:00']; - break; - case 'last month': - $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; - break; - case 'last year': - $range = ['last year 1/1', 'this year 1/1']; - break; - default: - $range = $op; - } - } - $op = is_array($range) ? 'between' : '>'; - } - $this->where($field, strtolower($op) . ' time', $range); - return $this; - } - - /** - * 获取数据表信息 - * @access public - * @param mixed $tableName 数据表名 留空自动获取 - * @param string $fetch 获取信息类型 包括 fields type bind pk - * @return mixed - */ - public function getTableInfo($tableName = '', $fetch = '') - { - if (!$tableName) { - $tableName = $this->getTable(); - } - if (is_array($tableName)) { - $tableName = key($tableName) ?: current($tableName); - } - - if (strpos($tableName, ',')) { - // 多表不获取字段信息 - return false; - } else { - $tableName = $this->parseSqlTable($tableName); - } - - // 修正子查询作为表名的问题 - if (strpos($tableName, ')')) { - return []; - } - - list($guid) = explode(' ', $tableName); - $db = $this->getConfig('database'); - if (!isset(self::$info[$db . '.' . $guid])) { - if (!strpos($guid, '.')) { - $schema = $db . '.' . $guid; - } else { - $schema = $guid; - } - // 读取缓存 - if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { - $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; - } else { - $info = $this->connection->getFields($guid); - } - $fields = array_keys($info); - $bind = $type = []; - foreach ($info as $key => $val) { - // 记录字段类型 - $type[$key] = $val['type']; - $bind[$key] = $this->getFieldBindType($val['type']); - if (!empty($val['primary'])) { - $pk[] = $key; - } - } - if (isset($pk)) { - // 设置主键 - $pk = count($pk) > 1 ? $pk : $pk[0]; - } else { - $pk = null; - } - self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; - } - return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; - } - - /** - * 获取当前数据表的主键 - * @access public - * @param string|array $options 数据表名或者查询参数 - * @return string|array - */ - public function getPk($options = '') - { - if (!empty($this->pk)) { - $pk = $this->pk; - } else { - $pk = $this->getTableInfo(is_array($options) ? $options['table'] : $options, 'pk'); - } - return $pk; - } - - // 获取当前数据表字段信息 - public function getTableFields($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); - } - - // 获取当前数据表字段类型 - public function getFieldsType($table = '') - { - return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); - } - - // 获取当前数据表绑定信息 - public function getFieldsBind($table = '') - { - $types = $this->getFieldsType($table); - $bind = []; - if ($types) { - foreach ($types as $key => $type) { - $bind[$key] = $this->getFieldBindType($type); - } - } - return $bind; - } - - /** - * 获取字段绑定类型 - * @access public - * @param string $type 字段类型 - * @return integer - */ - protected function getFieldBindType($type) - { - if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { - $bind = PDO::PARAM_STR; - } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { - $bind = PDO::PARAM_INT; - } elseif (preg_match('/bool/is', $type)) { - $bind = PDO::PARAM_BOOL; - } else { - $bind = PDO::PARAM_STR; - } - return $bind; - } - - /** - * 参数绑定 - * @access public - * @param mixed $key 参数名 - * @param mixed $value 绑定变量值 - * @param integer $type 绑定类型 - * @return $this - */ - public function bind($key, $value = false, $type = PDO::PARAM_STR) - { - if (is_array($key)) { - $this->bind = array_merge($this->bind, $key); - } else { - $this->bind[$key] = [$value, $type]; - } - return $this; - } - - /** - * 检测参数是否已经绑定 - * @access public - * @param string $key 参数名 - * @return bool - */ - public function isBind($key) - { - return isset($this->bind[$key]); - } - - /** - * 查询参数赋值 - * @access protected - * @param array $options 表达式参数 - * @return $this - */ - protected function options(array $options) - { - $this->options = $options; - return $this; - } - - /** - * 获取当前的查询参数 - * @access public - * @param string $name 参数名 - * @return mixed - */ - public function getOptions($name = '') - { - if ('' === $name) { - return $this->options; - } else { - return isset($this->options[$name]) ? $this->options[$name] : null; - } - } - - /** - * 设置关联查询JOIN预查询 - * @access public - * @param string|array $with 关联方法名称 - * @return $this - */ - public function with($with) - { - if (empty($with)) { - return $this; - } - - if (is_string($with)) { - $with = explode(',', $with); - } - - $first = true; - - /** @var Model $class */ - $class = $this->model; - foreach ($with as $key => $relation) { - $subRelation = ''; - $closure = false; - if ($relation instanceof \Closure) { - // 支持闭包查询过滤关联条件 - $closure = $relation; - $relation = $key; - $with[$key] = $key; - } elseif (is_array($relation)) { - $subRelation = $relation; - $relation = $key; - } elseif (is_string($relation) && strpos($relation, '.')) { - $with[$key] = $relation; - list($relation, $subRelation) = explode('.', $relation, 2); - } - - /** @var Relation $model */ - $relation = Loader::parseName($relation, 1, false); - $model = $class->$relation(); - if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { - $model->eagerly($this, $relation, $subRelation, $closure, $first); - $first = false; - } elseif ($closure) { - $with[$key] = $closure; - } - } - $this->via(); - if (isset($this->options['with'])) { - $this->options['with'] = array_merge($this->options['with'], $with); - } else { - $this->options['with'] = $with; - } - return $this; - } - - /** - * 关联统计 - * @access public - * @param string|array $relation 关联方法名 - * @param bool $subQuery 是否使用子查询 - * @return $this - */ - public function withCount($relation, $subQuery = true) - { - if (!$subQuery) { - $this->options['with_count'] = $relation; - } else { - $relations = is_string($relation) ? explode(',', $relation) : $relation; - if (!isset($this->options['field'])) { - $this->field('*'); - } - foreach ($relations as $key => $relation) { - $closure = false; - if ($relation instanceof \Closure) { - $closure = $relation; - $relation = $key; - } - $relation = Loader::parseName($relation, 1, false); - $count = '(' . $this->model->$relation()->getRelationCountQuery($closure) . ')'; - $this->field([$count => Loader::parseName($relation) . '_count']); - } - } - return $this; - } - - /** - * 关联预加载中 获取关联指定字段值 - * example: - * Model::with(['relation' => function($query){ - * $query->withField("id,name"); - * }]) - * - * @param string | array $field 指定获取的字段 - * @return $this - */ - public function withField($field) - { - $this->options['with_field'] = $field; - return $this; - } - - /** - * 设置当前字段添加的表别名 - * @access public - * @param string $via - * @return $this - */ - public function via($via = '') - { - $this->options['via'] = $via; - return $this; - } - - /** - * 设置关联查询 - * @access public - * @param string|array $relation 关联名称 - * @return $this - */ - public function relation($relation) - { - if (empty($relation)) { - return $this; - } - if (is_string($relation)) { - $relation = explode(',', $relation); - } - if (isset($this->options['relation'])) { - $this->options['relation'] = array_merge($this->options['relation'], $relation); - } else { - $this->options['relation'] = $relation; - } - return $this; - } - - /** - * 把主键值转换为查询条件 支持复合主键 - * @access public - * @param array|string $data 主键数据 - * @param mixed $options 表达式参数 - * @return void - * @throws Exception - */ - protected function parsePkWhere($data, &$options) - { - $pk = $this->getPk($options); - // 获取当前数据表 - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - if (!empty($options['alias'][$table])) { - $alias = $options['alias'][$table]; - } - if (is_string($pk)) { - $key = isset($alias) ? $alias . '.' . $pk : $pk; - // 根据主键查询 - if (is_array($data)) { - $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; - } else { - $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; - } - } elseif (is_array($pk) && is_array($data) && !empty($data)) { - // 根据复合主键查询 - foreach ($pk as $key) { - if (isset($data[$key])) { - $attr = isset($alias) ? $alias . '.' . $key : $key; - $where[$attr] = $data[$key]; - } else { - throw new Exception('miss complex primary data'); - } - } - } - - if (!empty($where)) { - if (isset($options['where']['AND'])) { - $options['where']['AND'] = array_merge($options['where']['AND'], $where); - } else { - $options['where']['AND'] = $where; - } - } - return; - } - - /** - * 插入记录 - * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param boolean $getLastInsID 返回自增主键 - * @param string $sequence 自增序列名 - * @return integer|string - */ - public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) - { - // 分析查询表达式 - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - // 生成SQL语句 - $sql = $this->builder->insert($data, $options, $replace); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - - // 执行操作 - $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); - if ($result) { - $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); - $lastInsId = $this->getLastInsID($sequence); - if ($lastInsId) { - $pk = $this->getPk($options); - if (is_string($pk)) { - $data[$pk] = $lastInsId; - } - } - $options['data'] = $data; - $this->trigger('after_insert', $options); - - if ($getLastInsID) { - return $lastInsId; - } - } - return $result; - } - - /** - * 插入记录并获取自增ID - * @access public - * @param mixed $data 数据 - * @param boolean $replace 是否replace - * @param string $sequence 自增序列名 - * @return integer|string - */ - public function insertGetId(array $data, $replace = false, $sequence = null) - { - return $this->insert($data, $replace, true, $sequence); - } - - /** - * 批量插入记录 - * @access public - * @param mixed $dataSet 数据集 - * @param boolean $replace 是否replace - * @param integer $limit 每次写入数据限制 - * @return integer|string - */ - public function insertAll(array $dataSet, $replace = false, $limit = null) - { - // 分析查询表达式 - $options = $this->parseExpress(); - if (!is_array(reset($dataSet))) { - return false; - } - - // 生成SQL语句 - if (is_null($limit)) { - $sql = $this->builder->insertAll($dataSet, $options, $replace); - } else { - $array = array_chunk($dataSet, $limit, true); - foreach ($array as $item) { - $sql[] = $this->builder->insertAll($item, $options, $replace); - } - } - - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } elseif (is_array($sql)) { - // 执行操作 - return $this->batchQuery($sql, $bind, $this); - } else { - // 执行操作 - return $this->execute($sql, $bind, $this); - } - } - - /** - * 通过Select方式插入记录 - * @access public - * @param string $fields 要插入的数据表字段名 - * @param string $table 要插入的数据表名 - * @return integer|string - * @throws PDOException - */ - public function selectInsert($fields, $table) - { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成SQL语句 - $table = $this->parseSqlTable($table); - $sql = $this->builder->selectInsert($fields, $table, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 执行操作 - return $this->execute($sql, $bind, $this); - } - } - - /** - * 更新记录 - * @access public - * @param mixed $data 数据 - * @return integer|string - * @throws Exception - * @throws PDOException - */ - public function update(array $data = []) - { - $options = $this->parseExpress(); - $data = array_merge($options['data'], $data); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; - } - - if (empty($options['where'])) { - // 如果存在主键数据 则自动作为更新条件 - if (is_string($pk) && isset($data[$pk])) { - $where[$pk] = $data[$pk]; - if (!isset($key)) { - $key = 'think:' . $options['table'] . '|' . $data[$pk]; - } - unset($data[$pk]); - } elseif (is_array($pk)) { - // 增加复合主键支持 - foreach ($pk as $field) { - if (isset($data[$field])) { - $where[$field] = $data[$field]; - } else { - // 如果缺少复合主键数据则不执行 - throw new Exception('miss complex primary data'); - } - unset($data[$field]); - } - } - if (!isset($where)) { - // 如果没有任何更新条件则不执行 - throw new Exception('miss update condition'); - } else { - $options['where']['AND'] = $where; - } - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); - } - - // 生成UPDATE SQL语句 - $sql = $this->builder->update($data, $options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } else { - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); - if ($result) { - if (is_string($pk) && isset($where[$pk])) { - $data[$pk] = $where[$pk]; - } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $data[$pk] = $val; - } - $options['data'] = $data; - $this->trigger('after_update', $options); - } - return $result; - } - } - - /** - * 执行查询但只返回PDOStatement对象 - * @access public - * @return \PDOStatement|string - */ - public function getPdo() - { - // 分析查询表达式 - $options = $this->parseExpress(); - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - // 执行查询操作 - return $this->query($sql, $bind, $options['master'], true); - } - - /** - * 查找记录 - * @access public - * @param array|string|Query|\Closure $data - * @return Collection|false|\PDOStatement|string - * @throws DbException - * @throws ModelNotFoundException - * @throws DataNotFoundException - */ - public function select($data = null) - { - if ($data instanceof Query) { - return $data->select(); - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); - $data = null; - } - // 分析查询表达式 - $options = $this->parseExpress(); - - if (false === $data) { - // 用于子查询 不查询只返回SQL - $options['fetch_sql'] = true; - } elseif (!is_null($data)) { - // 主键条件分析 - $this->parsePkWhere($data, $options); - } - - $resultSet = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - unset($options['cache']); - $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); - $resultSet = Cache::get($key); - } - if (false === $resultSet) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - - $options['data'] = $data; - if ($resultSet = $this->trigger('before_select', $options)) { - } else { - // 执行查询操作 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - } - - if (isset($cache) && false !== $resultSet) { - // 缓存数据集 - $this->cacheData($key, $resultSet, $cache); - } - } - - // 数据列表读取后的处理 - if (!empty($this->model)) { - // 生成模型对象 - if (count($resultSet) > 0) { - foreach ($resultSet as $key => $result) { - /** @var Model $model */ - $model = $this->model->newInstance($result); - $model->isUpdate(true); - - // 关联查询 - if (!empty($options['relation'])) { - $model->relationQuery($options['relation']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $model->relationCount($model, $options['with_count']); - } - $resultSet[$key] = $model; - } - if (!empty($options['with'])) { - // 预载入 - $model->eagerlyResultSet($resultSet, $options['with']); - } - // 模型数据集转换 - $resultSet = $model->toCollection($resultSet); - } else { - $resultSet = $this->model->toCollection($resultSet); - } - } elseif ('collection' == $this->connection->getConfig('resultset_type')) { - // 返回Collection对象 - $resultSet = new Collection($resultSet); - } - // 返回结果处理 - if (!empty($options['fail']) && count($resultSet) == 0) { - $this->throwNotFound($options); - } - return $resultSet; - } - - /** - * 缓存数据 - * @access public - * @param string $key 缓存标识 - * @param mixed $data 缓存数据 - * @param array $config 缓存参数 - */ - protected function cacheData($key, $data, $config = []) - { - if (isset($config['tag'])) { - Cache::tag($config['tag'])->set($key, $data, $config['expire']); - } else { - Cache::set($key, $data, $config['expire']); - } - } - - /** - * 生成缓存标识 - * @access public - * @param mixed $value 缓存数据 - * @param array $options 缓存参数 - * @param array $bind 绑定参数 - * @return string - */ - protected function getCacheKey($value, $options, $bind = []) - { - if (is_scalar($value)) { - $data = $value; - } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { - $data = $value[1]; - } - $prefix = $this->connection->getConfig('database') . '.'; - - if (isset($data)) { - return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } - - try { - return md5($prefix . serialize($options) . serialize($bind)); - } catch (\Exception $e) { - throw new Exception('closure not support cache(true)'); - } - } - - /** - * 查找单条记录 - * @access public - * @param array|string|Query|\Closure $data - * @return array|false|\PDOStatement|string|Model - * @throws DbException - * @throws ModelNotFoundException - * @throws DataNotFoundException - */ - public function find($data = null) - { - if ($data instanceof Query) { - return $data->find(); - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $this]); - $data = null; - } - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); - if (!is_null($data)) { - // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); - } - - $options['limit'] = 1; - $result = false; - if (empty($options['fetch_sql']) && !empty($options['cache'])) { - // 判断查询缓存 - $cache = $options['cache']; - if (true === $cache['key'] && !is_null($data) && !is_array($data)) { - $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; - } elseif (is_string($cache['key'])) { - $key = $cache['key']; - } elseif (!isset($key)) { - $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); - } - $result = Cache::get($key); - } - if (false === $result) { - // 生成查询SQL - $sql = $this->builder->select($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - if (is_string($pk)) { - if (!is_array($data)) { - if (isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - } else { - $item[$pk] = $data; - } - $data = $item; - } - } - $options['data'] = $data; - // 事件回调 - if ($result = $this->trigger('before_find', $options)) { - } else { - // 执行查询 - $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); - - if ($resultSet instanceof \PDOStatement) { - // 返回PDOStatement对象 - return $resultSet; - } - $result = isset($resultSet[0]) ? $resultSet[0] : null; - } - - if (isset($cache) && $result) { - // 缓存数据 - $this->cacheData($key, $result, $cache); - } - } - - // 数据处理 - if (!empty($result)) { - if (!empty($this->model)) { - // 返回模型对象 - $result = $this->model->newInstance($result); - $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); - // 关联查询 - if (!empty($options['relation'])) { - $result->relationQuery($options['relation']); - } - // 预载入查询 - if (!empty($options['with'])) { - $result->eagerlyResult($result, $options['with']); - } - // 关联统计 - if (!empty($options['with_count'])) { - $result->relationCount($result, $options['with_count']); - } - } - } elseif (!empty($options['fail'])) { - $this->throwNotFound($options); - } - return $result; - } - - /** - * 查询失败 抛出异常 - * @access public - * @param array $options 查询参数 - * @throws ModelNotFoundException - * @throws DataNotFoundException - */ - protected function throwNotFound($options = []) - { - if (!empty($this->model)) { - $class = get_class($this->model); - throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); - } else { - $table = is_array($options['table']) ? key($options['table']) : $options['table']; - throw new DataNotFoundException('table data not Found:' . $table, $table, $options); - } - } - - /** - * 查找多条记录 如果不存在则抛出异常 - * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model - * @throws DbException - * @throws ModelNotFoundException - * @throws DataNotFoundException - */ - public function selectOrFail($data = null) - { - return $this->failException(true)->select($data); - } - - /** - * 查找单条记录 如果不存在则抛出异常 - * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model - * @throws DbException - * @throws ModelNotFoundException - * @throws DataNotFoundException - */ - public function findOrFail($data = null) - { - return $this->failException(true)->find($data); - } - - /** - * 分批数据返回处理 - * @access public - * @param integer $count 每次处理的数据数量 - * @param callable $callback 处理回调方法 - * @param string $column 分批处理的字段名 - * @param string $order 排序规则 - * @return boolean - * @throws \LogicException - */ - public function chunk($count, $callback, $column = null, $order = 'asc') - { - $options = $this->getOptions(); - if (empty($options['table'])) { - $options['table'] = $this->getTable(); - } - $column = $column ?: $this->getPk($options); - - if (isset($options['order'])) { - if (App::$debug) { - throw new \LogicException('chunk not support call order'); - } - unset($options['order']); - } - $bind = $this->bind; - if (is_array($column)) { - $times = 1; - $query = $this->options($options)->page($times, $count); - } else { - if (strpos($column, '.')) { - list($alias, $key) = explode('.', $column); - } else { - $key = $column; - } - $query = $this->options($options)->limit($count); - } - $resultSet = $query->order($column, $order)->select(); - - while (count($resultSet) > 0) { - if ($resultSet instanceof Collection) { - $resultSet = $resultSet->all(); - } - - if (false === call_user_func($callback, $resultSet)) { - return false; - } - - if (is_array($column)) { - $times++; - $query = $this->options($options)->page($times, $count); - } else { - $end = end($resultSet); - $lastId = is_array($end) ? $end[$key] : $end->getData($key); - $query = $this->options($options) - ->limit($count) - ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); - } - - $resultSet = $query->bind($bind)->order($column, $order)->select(); - } - - return true; - } - - /** - * 获取绑定的参数 并清空 - * @access public - * @return array - */ - public function getBind() - { - $bind = $this->bind; - $this->bind = []; - return $bind; - } - - /** - * 创建子查询SQL - * @access public - * @param bool $sub - * @return string - * @throws DbException - */ - public function buildSql($sub = true) - { - return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); - } - - /** - * 删除记录 - * @access public - * @param mixed $data 表达式 true 表示强制删除 - * @return int - * @throws Exception - * @throws PDOException - */ - public function delete($data = null) - { - // 分析查询表达式 - $options = $this->parseExpress(); - $pk = $this->getPk($options); - if (isset($options['cache']) && is_string($options['cache']['key'])) { - $key = $options['cache']['key']; - } - - if (!is_null($data) && true !== $data) { - if (!isset($key) && !is_array($data)) { - // 缓存标识 - $key = 'think:' . $options['table'] . '|' . $data; - } - // AR模式分析主键条件 - $this->parsePkWhere($data, $options); - } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { - $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); - } - - if (true !== $data && empty($options['where'])) { - // 如果条件为空 不进行删除操作 除非设置 1=1 - throw new Exception('delete without condition'); - } - // 生成删除SQL语句 - $sql = $this->builder->delete($options); - // 获取参数绑定 - $bind = $this->getBind(); - if ($options['fetch_sql']) { - // 获取实际执行的SQL语句 - return $this->connection->getRealSql($sql, $bind); - } - - // 检测缓存 - if (isset($key) && Cache::get($key)) { - // 删除缓存 - Cache::rm($key); - } elseif (!empty($options['cache']['tag'])) { - Cache::clear($options['cache']['tag']); - } - // 执行操作 - $result = $this->execute($sql, $bind, $this); - if ($result) { - if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { - list($a, $val) = explode('|', $key); - $item[$pk] = $val; - $data = $item; - } - $options['data'] = $data; - $this->trigger('after_delete', $options); - } - return $result; - } - - /** - * 分析表达式(可用于查询或者写入操作) - * @access protected - * @return array - */ - protected function parseExpress() - { - $options = $this->options; - - // 获取数据表 - if (empty($options['table'])) { - $options['table'] = $this->getTable(); - } - - if (!isset($options['where'])) { - $options['where'] = []; - } elseif (isset($options['view'])) { - // 视图查询条件处理 - foreach (['AND', 'OR'] as $logic) { - if (isset($options['where'][$logic])) { - foreach ($options['where'][$logic] as $key => $val) { - if (array_key_exists($key, $options['map'])) { - $options['where'][$logic][$options['map'][$key]] = $val; - unset($options['where'][$logic][$key]); - } - } - } - } - - if (isset($options['order'])) { - // 视图查询排序处理 - if (is_string($options['order'])) { - $options['order'] = explode(',', $options['order']); - } - foreach ($options['order'] as $key => $val) { - if (is_numeric($key)) { - if (strpos($val, ' ')) { - list($field, $sort) = explode(' ', $val); - if (array_key_exists($field, $options['map'])) { - $options['order'][$options['map'][$field]] = $sort; - unset($options['order'][$key]); - } - } elseif (array_key_exists($val, $options['map'])) { - $options['order'][$options['map'][$val]] = 'asc'; - unset($options['order'][$key]); - } - } elseif (array_key_exists($key, $options['map'])) { - $options['order'][$options['map'][$key]] = $val; - unset($options['order'][$key]); - } - } - } - } - - if (!isset($options['field'])) { - $options['field'] = '*'; - } - - if (!isset($options['data'])) { - $options['data'] = []; - } - - if (!isset($options['strict'])) { - $options['strict'] = $this->getConfig('fields_strict'); - } - - foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { - if (!isset($options[$name])) { - $options[$name] = false; - } - } - - if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { - $options['master'] = true; - } - - foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { - if (!isset($options[$name])) { - $options[$name] = ''; - } - } - - if (isset($options['page'])) { - // 根据页数计算limit - list($page, $listRows) = $options['page']; - $page = $page > 0 ? $page : 1; - $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); - $offset = $listRows * ($page - 1); - $options['limit'] = $offset . ',' . $listRows; - } - - $this->options = []; - return $options; - } - - /** - * 注册回调方法 - * @access public - * @param string $event 事件名 - * @param callable $callback 回调方法 - * @return void - */ - public static function event($event, $callback) - { - self::$event[$event] = $callback; - } - - /** - * 触发事件 - * @access protected - * @param string $event 事件名 - * @param mixed $params 额外参数 - * @return bool - */ - protected function trigger($event, $params = []) - { - $result = false; - if (isset(self::$event[$event])) { - $callback = self::$event[$event]; - $result = call_user_func_array($callback, [$params, $this]); - } - return $result; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\App; +use think\Cache; +use think\Collection; +use think\Config; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + // 数据库Connection对象实例 + protected $connection; + // 数据库Builder对象实例 + protected $builder; + // 当前模型类名称 + protected $model; + // 当前数据表名称(含前缀) + protected $table = ''; + // 当前数据表名称(不含前缀) + protected $name = ''; + // 当前数据表主键 + protected $pk; + // 当前数据表前缀 + protected $prefix = ''; + // 查询参数 + protected $options = []; + // 参数绑定 + protected $bind = []; + // 数据表信息 + protected static $info = []; + // 回调事件 + private static $event = []; + // 读取主库 + private static $readMaster = []; + + /** + * 构造函数 + * @access public + * @param Connection $connection 数据库对象实例 + * @param Model $model 模型对象 + */ + public function __construct(Connection $connection = null, $model = null) + { + $this->connection = $connection ?: Db::connect([], true); + $this->prefix = $this->connection->getConfig('prefix'); + $this->model = $model; + // 设置当前连接的Builder对象 + $this->setBuilder(); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + $where[$field] = $args[0]; + return $this->where($where)->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + $where[$name] = $args[0]; + return $this->where($where)->value($args[1]); + } else { + throw new Exception('method not exist:' . __CLASS__ . '->' . $method); + } + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 切换当前的数据库连接 + * @access public + * @param mixed $config + * @return $this + */ + public function connect($config) + { + $this->connection = Db::connect($config); + $this->setBuilder(); + $this->prefix = $this->connection->getConfig('prefix'); + return $this; + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @return void + */ + protected function setBuilder() + { + $class = $this->connection->getBuilder(); + $this->builder = new $class($this->connection, $this); + } + + /** + * 获取当前的模型对象实例 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置后续从主库读取数据 + * @access public + * @param bool $allTable + * @return void + */ + public function readMaster($allTable = false) + { + if ($allTable) { + $table = '*'; + } else { + $table = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 指定默认的数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 指定默认数据表名(含前缀) + * @access public + * @param string $table 表名 + * @return $this + */ + public function setTable($table) + { + $this->table = $table; + return $this; + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if ($name || empty($this->table)) { + $name = $name ?: $this->name; + $tableName = $this->prefix; + if ($name) { + $tableName .= Loader::parseName($name); + } + } else { + $tableName = $this->table; + } + return $tableName; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $prefix = $this->prefix; + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) use ($prefix) { + return $prefix . strtolower($match[1]); + }, $sql); + } + return $sql; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool|string $class 指定返回的数据集对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $class = false) + { + return $this->connection->query($sql, $bind, $master, $class); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = [], $bind = []) + { + return $this->connection->batchQuery($sql, $bind); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return boolean + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return string + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; + } else { + // 按照字段的首字母的值分表 + $seq = (ord($value{0}) % $rule['num']) + 1; + } + } + return $this->getTable() . '_' . $seq; + } else { + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + $tableName = '( ' . implode(" UNION ", $tableName) . ') AS ' . $this->name; + return $tableName; + } + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function value($field, $default = null, $force = false) + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($key); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + $pdo = $this->field($field)->limit(1)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + + $result = $pdo->fetchColumn(); + if ($force) { + $result = (float) $result; + } + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $result = false; + if (empty($this->options['fetch_sql']) && !empty($this->options['cache'])) { + // 判断查询缓存 + $cache = $this->options['cache']; + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + $guid = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . $field . serialize($this->options) . serialize($this->bind)); + $result = Cache::get($guid); + } + if (false === $result) { + if (isset($this->options['field'])) { + unset($this->options['field']); + } + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + $pdo = $this->field($field)->getPdo(); + if (is_string($pdo)) { + // 返回SQL语句 + return $pdo; + } + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + if ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + foreach ($resultSet as $val) { + if ($count > 2) { + $result[$val[$key]] = $val; + } elseif (2 == $count) { + $result[$val[$key]] = $val[$key2]; + } elseif (1 == $count) { + $result[$val[$key]] = $val[$key1]; + } + } + } else { + $result = []; + } + } + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + } else { + // 清空查询条件 + $this->options = []; + } + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer|string + */ + public function count($field = '*') + { + if (isset($this->options['group'])) { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options)->field('count(' . $field . ')')->bind($this->bind)->buildSql(); + + $count = $this->table([$subSql => '_group_count_'])->value('COUNT(*) AS tp_count', 0); + } else { + $count = $this->aggregate('COUNT', $field); + } + + return is_string($count) ? $count : (int) $count; + + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + if (!preg_match('/^[\w\.\*]+$/', $field)) { + throw new Exception('not support data:' . $field); + } + + $result = $this->value($aggregate . '(' . $field . ') AS tp_' . strtolower($aggregate), 0, $force); + + return $result; + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return float|int + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + return $this->setField($field, ['inc', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition) . serialize($this->bind)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + return $this->setField($field, ['inc', $step]); + } + return $this->setField($field, ['dec', $step]); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + if (!Cache::has($guid . '_time')) { + // 计时开始 + Cache::set($guid . '_time', $_SERVER['REQUEST_TIME'], 0); + Cache::$type($guid, $step); + } elseif ($_SERVER['REQUEST_TIME'] > Cache::get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = Cache::$type($guid, $step); + Cache::rm($guid); + Cache::rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + Cache::$type($guid, $step); + } + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER') + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + return $this; + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'prefix_table alias' 'table alias' + * @access public + * @param array|string $join + * @return array|string + */ + protected function getJoinTable($join, &$alias = null) + { + // 传入的表名为数组 + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + return $this; + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + $field = array_map('trim', explode(',', $field)); + } + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableInfo($tableName ?: (isset($this->options['table']) ? $this->options['table'] : ''), 'fields'); + $field = $fields ? array_diff($fields, $field) : $field; + } + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $val = $prefix . '.' . $val . ($alias ? ' AS ' . $alias . $val : ''); + } + $field[$key] = $val; + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + $this->options['field'] = array_unique($field); + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @param array $bind 参数绑定 + * @return $this + */ + public function fieldRaw($field, array $bind = []) + { + $this->options['field'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['inc', $step]); + } + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + $fields = is_string($field) ? explode(',', $field) : $field; + foreach ($fields as $field) { + $this->data($field, ['dec', $step]); + } + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + $fields[$name] = $val; + $this->options['map'][$val] = $name; + } + } + } + $this->field($fields); + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('AND', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('OR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + $this->parseWhereExp('XOR', $field, $op, $condition, $param); + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + $this->options['where'][$logic][] = $this->raw($where); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'null', null, [], true); + return $this; + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'notnull', null, [], true); + return $this; + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['exists', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + $this->options['where'][strtoupper($logic)][] = ['not exists', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'in', $condition, [], true); + return $this; + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not in', $condition, [], true); + return $this; + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'like', $condition, [], true); + return $this; + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not like', $condition, [], true); + return $this; + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'between', $condition, [], true); + return $this; + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'not between', $condition, [], true); + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $condition, $logic = 'AND') + { + $this->parseWhereExp($logic, $field, 'exp', $this->raw($condition), [], true); + return $this; + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition ?: ['null', '']]; + } + return $this; + } + + /** + * 分析查询表达式 + * @access public + * @param string $logic 查询逻辑 and or xor + * @param string|array|\Closure $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return void + */ + protected function parseWhereExp($logic, $field, $op, $condition, $param = [], $strict = false) + { + $logic = strtoupper($logic); + if ($field instanceof \Closure) { + $this->options['where'][$logic][] = is_string($op) ? [$op, $field] : $field; + return; + } + + if (is_string($field) && !empty($this->options['via']) && !strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : []); + } elseif ($strict) { + // 使用严格模式查询 + $where[$field] = [$op, $condition]; + + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_string($field) && preg_match('/[,=\>\<\'\"\(\s]/', $field)) { + $where[] = ['exp', $this->raw($field)]; + if (is_array($op)) { + // 参数绑定 + $this->bind($op); + } + } elseif (is_null($op) && is_null($condition)) { + if (is_array($field)) { + // 数组批量查询 + $where = $field; + foreach ($where as $k => $val) { + $this->options['multi'][$logic][$k][] = $val; + } + } elseif ($field && is_string($field)) { + // 字符串查询 + $where[$field] = ['null', '']; + $this->options['multi'][$logic][$field][] = $where[$field]; + } + } elseif (is_array($op)) { + $where[$field] = $param; + } elseif (in_array(strtolower($op), ['null', 'notnull', 'not null'])) { + // null查询 + $where[$field] = [$op, '']; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } elseif (is_null($condition)) { + // 字段相等查询 + $where[$field] = ['eq', $op]; + + $this->options['multi'][$logic][$field][] = $where[$field]; + } else { + if ('exp' == strtolower($op)) { + $where[$field] = ['exp', $this->raw($condition)]; + // 参数绑定 + if (isset($param[2]) && is_array($param[2])) { + $this->bind($param[2]); + } + } else { + $where[$field] = [$op, $condition]; + } + // 记录一个字段多次查询条件 + $this->options['multi'][$logic][$field][] = $where[$field]; + } + + if (!empty($where)) { + if (!isset($this->options['where'][$logic])) { + $this->options['where'][$logic] = []; + } + if (is_string($field) && $this->checkMultiField($field, $logic)) { + $where[$field] = $this->options['multi'][$logic][$field]; + } elseif (is_array($field)) { + foreach ($field as $key => $val) { + if ($this->checkMultiField($key, $logic)) { + $where[$key] = $this->options['multi'][$logic][$key]; + } + } + } + $this->options['where'][$logic] = array_merge($this->options['where'][$logic], $where); + } + } + + /** + * 检查是否存在一个字段多次查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return bool + */ + private function checkMultiField($field, $logic) + { + return isset($this->options['multi'][$logic][$field]) && count($this->options['multi'][$logic][$field]) > 1; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + if (isset($this->options['where'][$logic][$field])) { + unset($this->options['where'][$logic][$field]); + unset($this->options['multi'][$logic][$field]); + } + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + $this->options['page'] = [intval($page), intval($listRows)]; + return $this; + } + + /** + * 分页查询 + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + if (is_array($listRows)) { + $config = array_merge(Config::get('paginate'), $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge(Config::get('paginate'), $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + $this->options['table'] = $table; + return $this; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, array $bind = []) + { + $this->options['order'][] = $this->raw($field); + + if ($bind) { + $this->bind($bind); + } + + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param mixed $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @return $this + */ + public function whereTime($field, $op, $range = null) + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + // 使用日期表达式 + switch (strtolower($op)) { + case 'today': + case 'd': + $range = ['today', 'tomorrow']; + break; + case 'week': + case 'w': + $range = ['this week 00:00:00', 'next week 00:00:00']; + break; + case 'month': + case 'm': + $range = ['first Day of this month 00:00:00', 'first Day of next month 00:00:00']; + break; + case 'year': + case 'y': + $range = ['this year 1/1', 'next year 1/1']; + break; + case 'yesterday': + $range = ['yesterday', 'today']; + break; + case 'last week': + $range = ['last week 00:00:00', 'this week 00:00:00']; + break; + case 'last month': + $range = ['first Day of last month 00:00:00', 'first Day of this month 00:00:00']; + break; + case 'last year': + $range = ['last year 1/1', 'this year 1/1']; + break; + default: + $range = $op; + } + } + $op = is_array($range) ? 'between' : '>'; + } + $this->where($field, strtolower($op) . ' time', $range); + return $this; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName = '', $fetch = '') + { + if (!$tableName) { + $tableName = $this->getTable(); + } + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($guid) = explode(' ', $tableName); + $db = $this->getConfig('database'); + if (!isset(self::$info[$db . '.' . $guid])) { + if (!strpos($guid, '.')) { + $schema = $db . '.' . $guid; + } else { + $schema = $guid; + } + // 读取缓存 + if (!App::$debug && is_file(RUNTIME_PATH . 'schema/' . $schema . '.php')) { + $info = include RUNTIME_PATH . 'schema/' . $schema . '.php'; + } else { + $info = $this->connection->getFields($guid); + } + $fields = array_keys($info); + $bind = $type = []; + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + self::$info[$db . '.' . $guid] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + return $fetch ? self::$info[$db . '.' . $guid][$fetch] : self::$info[$db . '.' . $guid]; + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->getTableInfo(is_array($options) ? $options['table'] : $options, 'pk'); + } + return $pk; + } + + // 获取当前数据表字段信息 + public function getTableFields($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'fields'); + } + + // 获取当前数据表字段类型 + public function getFieldsType($table = '') + { + return $this->getTableInfo($table ?: $this->getOptions('table'), 'type'); + } + + // 获取当前数据表绑定信息 + public function getFieldsBind($table = '') + { + $types = $this->getFieldsType($table); + $bind = []; + if ($types) { + foreach ($types as $key => $type) { + $bind[$key] = $this->getFieldBindType($type); + } + } + return $bind; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + protected function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(int|double|float|decimal|real|numeric|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + return $bind; + } + + /** + * 参数绑定 + * @access public + * @param mixed $key 参数名 + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @return $this + */ + public function bind($key, $value = false, $type = PDO::PARAM_STR) + { + if (is_array($key)) { + $this->bind = array_merge($this->bind, $key); + } else { + $this->bind[$key] = [$value, $type]; + } + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions($name = '') + { + if ('' === $name) { + return $this->options; + } else { + return isset($this->options[$name]) ? $this->options[$name] : null; + } + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $subRelation = ''; + $closure = false; + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + $with[$key] = $key; + } elseif (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $with[$key] = $relation; + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $model->eagerly($this, $relation, $subRelation, $closure, $first); + $first = false; + } elseif ($closure) { + $with[$key] = $closure; + } + } + $this->via(); + if (isset($this->options['with'])) { + $this->options['with'] = array_merge($this->options['with'], $with); + } else { + $this->options['with'] = $with; + } + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'] = $relation; + } else { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + if (!isset($this->options['field'])) { + $this->field('*'); + } + foreach ($relations as $key => $relation) { + $closure = false; + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + $relation = Loader::parseName($relation, 1, false); + $count = '(' . $this->model->$relation()->getRelationCountQuery($closure) . ')'; + $this->field([$count => Loader::parseName($relation) . '_count']); + } + } + return $this; + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + if (is_string($relation)) { + $relation = explode(',', $relation); + } + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + return $this; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @param mixed $options 表达式参数 + * @return void + * @throws Exception + */ + protected function parsePkWhere($data, &$options) + { + $pk = $this->getPk($options); + // 获取当前数据表 + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + if (!empty($options['alias'][$table])) { + $alias = $options['alias'][$table]; + } + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$key] = isset($data[$pk]) ? $data[$pk] : ['in', $data]; + } else { + $where[$key] = strpos($data, ',') ? ['IN', $data] : $data; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$attr] = $data[$key]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($options['where']['AND'])) { + $options['where']['AND'] = array_merge($options['where']['AND'], $where); + } else { + $options['where']['AND'] = $where; + } + } + return; + } + + /** + * 插入记录 + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + // 生成SQL语句 + $sql = $this->builder->insert($data, $options, $replace); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 执行操作 + $result = 0 === $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + if ($lastInsId) { + $pk = $this->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + $options['data'] = $data; + $this->trigger('after_insert', $options); + + if ($getLastInsID) { + return $lastInsId; + } + } + return $result; + } + + /** + * 插入记录并获取自增ID + * @access public + * @param mixed $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param mixed $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet, $replace = false, $limit = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + if (!is_array(reset($dataSet))) { + return false; + } + + // 生成SQL语句 + if (is_null($limit)) { + $sql = $this->builder->insertAll($dataSet, $options, $replace); + } else { + $array = array_chunk($dataSet, $limit, true); + foreach ($array as $item) { + $sql[] = $this->builder->insertAll($item, $options, $replace); + } + } + + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } elseif (is_array($sql)) { + // 执行操作 + return $this->batchQuery($sql, $bind, $this); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成SQL语句 + $table = $this->parseSqlTable($table); + $sql = $this->builder->selectInsert($fields, $table, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 执行操作 + return $this->execute($sql, $bind, $this); + } + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $options = $this->parseExpress(); + $data = array_merge($options['data'], $data); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = $data[$pk]; + if (!isset($key)) { + $key = 'think:' . $options['table'] . '|' . $data[$pk]; + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = $data[$field]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($data, $options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } else { + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $this); + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + $options['data'] = $data; + $this->trigger('after_update', $options); + } + return $result; + } + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + // 分析查询表达式 + $options = $this->parseExpress(); + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|false|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data, $options); + } + + $resultSet = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + unset($options['cache']); + $key = is_string($cache['key']) ? $cache['key'] : md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + $resultSet = Cache::get($key); + } + if (false === $resultSet) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + $options['data'] = $data; + if ($resultSet = $this->trigger('before_select', $options)) { + } else { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (isset($cache) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $cache); + } + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + if (count($resultSet) > 0) { + foreach ($resultSet as $key => $result) { + /** @var Model $model */ + $model = $this->model->newInstance($result); + $model->isUpdate(true); + + // 关联查询 + if (!empty($options['relation'])) { + $model->relationQuery($options['relation']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $model->relationCount($model, $options['with_count']); + } + $resultSet[$key] = $model; + } + if (!empty($options['with'])) { + // 预载入 + $model->eagerlyResultSet($resultSet, $options['with']); + } + // 模型数据集转换 + $resultSet = $model->toCollection($resultSet); + } else { + $resultSet = $this->model->toCollection($resultSet); + } + } elseif ('collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + // 返回结果处理 + if (!empty($options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($options); + } + return $resultSet; + } + + /** + * 缓存数据 + * @access public + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + if (isset($config['tag'])) { + Cache::tag($config['tag'])->set($key, $data, $config['expire']); + } else { + Cache::set($key, $data, $config['expire']); + } + } + + /** + * 生成缓存标识 + * @access public + * @param mixed $value 缓存数据 + * @param array $options 缓存参数 + * @param array $bind 绑定参数 + * @return string + */ + protected function getCacheKey($value, $options, $bind = []) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && is_string($value[0]) && 'eq' == strtolower($value[0])) { + $data = $value[1]; + } + $prefix = $this->connection->getConfig('database') . '.'; + + if (isset($data)) { + return 'think:' . $prefix . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } + + try { + return md5($prefix . serialize($options) . serialize($bind)); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|false|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $this]); + $data = null; + } + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!empty($options['cache']) && true === $options['cache']['key'] && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + $options['limit'] = 1; + $result = false; + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + if (true === $cache['key'] && !is_null($data) && !is_array($data)) { + $key = 'think:' . $this->connection->getConfig('database') . '.' . (is_array($options['table']) ? key($options['table']) : $options['table']) . '|' . $data; + } elseif (is_string($cache['key'])) { + $key = $cache['key']; + } elseif (!isset($key)) { + $key = md5($this->connection->getConfig('database') . '.' . serialize($options) . serialize($this->bind)); + } + $result = Cache::get($key); + } + if (false === $result) { + // 生成查询SQL + $sql = $this->builder->select($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + if (is_string($pk)) { + if (!is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + } + $options['data'] = $data; + // 事件回调 + if ($result = $this->trigger('before_find', $options)) { + } else { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + } + + // 数据处理 + if (!empty($result)) { + if (!empty($this->model)) { + // 返回模型对象 + $result = $this->model->newInstance($result); + $result->isUpdate(true, isset($options['where']['AND']) ? $options['where']['AND'] : null); + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation']); + } + // 预载入查询 + if (!empty($options['with'])) { + $result->eagerlyResult($result, $options['with']); + } + // 关联统计 + if (!empty($options['with_count'])) { + $result->relationCount($result, $options['with_count']); + } + } + } elseif (!empty($options['fail'])) { + $this->throwNotFound($options); + } + return $result; + } + + /** + * 查询失败 抛出异常 + * @access public + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } else { + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string $column 分批处理的字段名 + * @param string $order 排序规则 + * @return boolean + * @throws \LogicException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (App::$debug) { + throw new \LogicException('chunk not support call order'); + } + unset($options['order']); + } + $bind = $this->bind; + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + $query = $this->options($options)->limit($count); + } + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (is_array($column)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @return array + */ + public function getBind() + { + $bind = $this->bind; + $this->bind = []; + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + // 分析查询表达式 + $options = $this->parseExpress(); + $pk = $this->getPk($options); + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + if (!is_null($data) && true !== $data) { + if (!isset($key) && !is_array($data)) { + // 缓存标识 + $key = 'think:' . $options['table'] . '|' . $data; + } + // AR模式分析主键条件 + $this->parsePkWhere($data, $options); + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'][$pk])) { + $key = $this->getCacheKey($options['where']['AND'][$pk], $options, $this->bind); + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + // 生成删除SQL语句 + $sql = $this->builder->delete($options); + // 获取参数绑定 + $bind = $this->getBind(); + if ($options['fetch_sql']) { + // 获取实际执行的SQL语句 + return $this->connection->getRealSql($sql, $bind); + } + + // 检测缓存 + if (isset($key) && Cache::get($key)) { + // 删除缓存 + Cache::rm($key); + } elseif (!empty($options['cache']['tag'])) { + Cache::clear($options['cache']['tag']); + } + // 执行操作 + $result = $this->execute($sql, $bind, $this); + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + $options['data'] = $data; + $this->trigger('after_delete', $options); + } + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseExpress() + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + if (!isset($options['data'])) { + $options['data'] = []; + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['join', 'union', 'group', 'having', 'limit', 'order', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($page, $listRows) = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = []; + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @param mixed $params 额外参数 + * @return bool + */ + protected function trigger($event, $params = []) + { + $result = false; + if (isset(self::$event[$event])) { + $callback = self::$event[$event]; + $result = call_user_func_array($callback, [$params, $this]); + } + return $result; + } +} diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php old mode 100755 new mode 100644 index 3ecff1e..be2af71 --- a/thinkphp/library/think/db/builder/Mysql.php +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -1,134 +1,137 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\builder; - -use think\db\Builder; -use think\Exception; - -/** - * mysql数据库驱动 - */ -class Mysql extends Builder -{ - - protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; - protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; - - /** - * 生成insertall SQL - * @access public - * @param array $dataSet 数据集 - * @param array $options 表达式 - * @param bool $replace 是否replace - * @return string - * @throws Exception - */ - public function insertAll($dataSet, $options = [], $replace = false) - { - // 获取合法的字段 - if ('*' == $options['field']) { - $fields = array_keys($this->query->getFieldsType($options['table'])); - } else { - $fields = $options['field']; - } - - foreach ($dataSet as $data) { - foreach ($data as $key => $val) { - if (!in_array($key, $fields, true)) { - if ($options['strict']) { - throw new Exception('fields not exists:[' . $key . ']'); - } - unset($data[$key]); - } elseif (is_null($val)) { - $data[$key] = 'NULL'; - } elseif (is_scalar($val)) { - $data[$key] = $this->parseValue($val, $key); - } elseif (is_object($val) && method_exists($val, '__toString')) { - // 对象数据写入 - $data[$key] = $val->__toString(); - } else { - // 过滤掉非标量数据 - unset($data[$key]); - } - } - $value = array_values($data); - $values[] = '( ' . implode(',', $value) . ' )'; - - if (!isset($insertFields)) { - $insertFields = array_map([$this, 'parseKey'], array_keys($data)); - } - } - - return str_replace( - ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], - [ - $replace ? 'REPLACE' : 'INSERT', - $this->parseTable($options['table'], $options), - implode(' , ', $insertFields), - implode(' , ', $values), - $this->parseComment($options['comment']), - ], $this->insertAllSql); - } - - /** - * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options - * @return string - */ - protected function parseKey($key, $options = [], $strict = false) - { - if (is_numeric($key)) { - return $key; - } elseif ($key instanceof Expression) { - return $key->getValue(); - } - - $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { - // JSON字段支持 - list($field, $name) = explode('$.', $key); - return 'json_extract(' . $field . ', \'$.' . $name . '\')'; - } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { - list($table, $key) = explode('.', $key, 2); - if ('__TABLE__' == $table) { - $table = $this->query->getTable(); - } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; - } - } - - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { - $key = '`' . $key . '`'; - } - if (isset($table)) { - if (strpos($table, '.')) { - $table = str_replace('.', '`.`', $table); - } - $key = '`' . $table . '`.' . $key; - } - return $key; - } - - /** - * 随机排序 - * @access protected - * @return string - */ - protected function parseRand() - { - return 'rand()'; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param array $dataSet 数据集 + * @param array $options 表达式 + * @param bool $replace 是否replace + * @return string + * @throws Exception + */ + public function insertAll($dataSet, $options = [], $replace = false) + { + // 获取合法的字段 + if ('*' == $options['field']) { + $fields = array_keys($this->query->getFieldsType($options['table'])); + } else { + $fields = $options['field']; + } + + foreach ($dataSet as $data) { + foreach ($data as $key => $val) { + if (!in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + unset($data[$key]); + } elseif (is_null($val)) { + $data[$key] = 'NULL'; + } elseif (is_scalar($val)) { + $data[$key] = $this->parseValue($val, $key); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $data[$key] = $val->__toString(); + } else { + // 过滤掉非标量数据 + unset($data[$key]); + } + } + $value = array_values($data); + $values[] = '( ' . implode(',', $value) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_map([$this, 'parseKey'], array_keys($data)); + } + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($options['table'], $options), + implode(' , ', $insertFields), + implode(' , ', $values), + $this->parseComment($options['comment']), + ], $this->insertAllSql); + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + return 'json_extract(' . $field . ', \'$.' . $name . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + $key = '`' . $key . '`'; + } + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + $key = '`' . $table . '`.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php old mode 100755 new mode 100644 index 167305a..acc2289 --- a/thinkphp/library/think/db/builder/Pgsql.php +++ b/thinkphp/library/think/db/builder/Pgsql.php @@ -1,89 +1,89 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\builder; - -use think\db\Builder; - -/** - * Pgsql数据库驱动 - */ -class Pgsql extends Builder -{ - protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; - protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; - - /** - * limit分析 - * @access protected - * @param mixed $limit - * @return string - */ - public function parseLimit($limit) - { - $limitStr = ''; - if (!empty($limit)) { - $limit = explode(',', $limit); - if (count($limit) > 1) { - $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; - } else { - $limitStr .= ' LIMIT ' . $limit[0] . ' '; - } - } - return $limitStr; - } - - /** - * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options - * @return string - */ - protected function parseKey($key, $options = [], $strict = false) - { - if (is_numeric($key)) { - return $key; - } elseif ($key instanceof Expression) { - return $key->getValue(); - } - - $key = trim($key); - if (strpos($key, '$.') && false === strpos($key, '(')) { - // JSON字段支持 - list($field, $name) = explode('$.', $key); - $key = $field . '->>\'' . $name . '\''; - } elseif (strpos($key, '.')) { - list($table, $key) = explode('.', $key, 2); - if ('__TABLE__' == $table) { - $table = $this->query->getTable(); - } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; - } - } - if (isset($table)) { - $key = $table . '.' . $key; - } - return $key; - } - - /** - * 随机排序 - * @access protected - * @return string - */ - protected function parseRand() - { - return 'RANDOM()'; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param mixed $limit + * @return string + */ + public function parseLimit($limit) + { + $limitStr = ''; + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + return $limitStr; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '$.') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('$.', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php old mode 100755 new mode 100644 index 690ca0b..c727f04 --- a/thinkphp/library/think/db/builder/Sqlite.php +++ b/thinkphp/library/think/db/builder/Sqlite.php @@ -1,82 +1,82 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\builder; - -use think\db\Builder; - -/** - * Sqlite数据库驱动 - */ -class Sqlite extends Builder -{ - - /** - * limit - * @access public - * @param string $limit - * @return string - */ - public function parseLimit($limit) - { - $limitStr = ''; - if (!empty($limit)) { - $limit = explode(',', $limit); - if (count($limit) > 1) { - $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; - } else { - $limitStr .= ' LIMIT ' . $limit[0] . ' '; - } - } - return $limitStr; - } - - /** - * 随机排序 - * @access protected - * @return string - */ - protected function parseRand() - { - return 'RANDOM()'; - } - - /** - * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options - * @return string - */ - protected function parseKey($key, $options = [], $strict = false) - { - if (is_numeric($key)) { - return $key; - } elseif ($key instanceof Expression) { - return $key->getValue(); - } - - $key = trim($key); - if (strpos($key, '.')) { - list($table, $key) = explode('.', $key, 2); - if ('__TABLE__' == $table) { - $table = $this->query->getTable(); - } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; - } - } - if (isset($table)) { - $key = $table . '.' . $key; - } - return $key; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param string $limit + * @return string + */ + public function parseLimit($limit) + { + $limitStr = ''; + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + if (isset($table)) { + $key = $table . '.' . $key; + } + return $key; + } +} diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php old mode 100755 new mode 100644 index 3acc9b5..dc425d9 --- a/thinkphp/library/think/db/builder/Sqlsrv.php +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -1,133 +1,137 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\builder; - -use think\db\Builder; -use think\db\Expression; - -/** - * Sqlsrv数据库驱动 - */ -class Sqlsrv extends Builder -{ - protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; - protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; - protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; - protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; - protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; - protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; - - /** - * order分析 - * @access protected - * @param mixed $order - * @param array $options - * @return string - */ - protected function parseOrder($order, $options = []) - { - if (empty($order)) { - return ' ORDER BY rand()'; - } - - $array = []; - foreach ($order as $key => $val) { - if ($val instanceof Expression) { - $array[] = $val->getValue(); - } elseif (is_numeric($key)) { - if (false === strpos($val, '(')) { - $array[] = $this->parseKey($val, $options); - } elseif ('[rand]' == $val) { - $array[] = $this->parseRand(); - } else { - $array[] = $val; - } - } else { - $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; - $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; - } - } - - return ' ORDER BY ' . implode(',', $array); - } - - /** - * 随机排序 - * @access protected - * @return string - */ - protected function parseRand() - { - return 'rand()'; - } - - /** - * 字段和表名处理 - * @access protected - * @param mixed $key - * @param array $options - * @return string - */ - protected function parseKey($key, $options = [], $strict = false) - { - if (is_numeric($key)) { - return $key; - } elseif ($key instanceof Expression) { - return $key->getValue(); - } - $key = trim($key); - if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { - list($table, $key) = explode('.', $key, 2); - if ('__TABLE__' == $table) { - $table = $this->query->getTable(); - } - if (isset($options['alias'][$table])) { - $table = $options['alias'][$table]; - } - } - if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { - $key = '[' . $key . ']'; - } - if (isset($table)) { - $key = '[' . $table . '].' . $key; - } - return $key; - } - - /** - * limit - * @access protected - * @param mixed $limit - * @return string - */ - protected function parseLimit($limit) - { - if (empty($limit)) { - return ''; - } - - $limit = explode(',', $limit); - if (count($limit) > 1) { - $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; - } else { - $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; - } - return 'WHERE ' . $limitStr; - } - - public function selectInsert($fields, $table, $options) - { - $this->selectSql = $this->selectInsertSql; - return parent::selectInsert($fields, $table, $options); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param mixed $order + * @param array $options + * @return string + */ + protected function parseOrder($order, $options = []) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_numeric($key)) { + if (false === strpos($val, '(')) { + $array[] = $this->parseKey($val, $options); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand(); + } else { + $array[] = $val; + } + } else { + $sort = in_array(strtolower(trim($val)), ['asc', 'desc'], true) ? ' ' . $val : ''; + $array[] = $this->parseKey($key, $options, true) . ' ' . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @return string + */ + protected function parseRand() + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access protected + * @param mixed $key + * @param array $options + * @return string + */ + protected function parseKey($key, $options = [], $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + $key = trim($key); + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + if ('__TABLE__' == $table) { + $table = $this->query->getTable(); + } + if (isset($options['alias'][$table])) { + $table = $options['alias'][$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + $key = '[' . $key . ']'; + } + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + return $key; + } + + /** + * limit + * @access protected + * @param mixed $limit + * @return string + */ + protected function parseLimit($limit) + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + return 'WHERE ' . $limitStr; + } + + public function selectInsert($fields, $table, $options) + { + $this->selectSql = $this->selectInsertSql; + return parent::selectInsert($fields, $table, $options); + } + +} diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php old mode 100755 new mode 100644 index 26f04a7..be1a85c --- a/thinkphp/library/think/db/connector/Mysql.php +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -1,126 +1,126 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\connector; - -use PDO; -use think\db\Connection; -use think\Log; - -/** - * mysql数据库驱动 - */ -class Mysql extends Connection -{ - - protected $builder = '\\think\\db\\builder\\Mysql'; - - /** - * 解析pdo连接的dsn信息 - * @access protected - * @param array $config 连接信息 - * @return string - */ - protected function parseDsn($config) - { - if (!empty($config['socket'])) { - $dsn = 'mysql:unix_socket=' . $config['socket']; - } elseif (!empty($config['hostport'])) { - $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; - } else { - $dsn = 'mysql:host=' . $config['hostname']; - } - $dsn .= ';dbname=' . $config['database']; - - if (!empty($config['charset'])) { - $dsn .= ';charset=' . $config['charset']; - } - return $dsn; - } - - /** - * 取得数据表的字段信息 - * @access public - * @param string $tableName - * @return array - */ - public function getFields($tableName) - { - list($tableName) = explode(' ', $tableName); - if (false === strpos($tableName, '`')) { - if (strpos($tableName, '.')) { - $tableName = str_replace('.', '`.`', $tableName); - } - $tableName = '`' . $tableName . '`'; - } - $sql = 'SHOW COLUMNS FROM ' . $tableName; - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - if ($result) { - foreach ($result as $key => $val) { - $val = array_change_key_case($val); - $info[$val['field']] = [ - 'name' => $val['field'], - 'type' => $val['type'], - 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes - 'default' => $val['default'], - 'primary' => (strtolower($val['key']) == 'pri'), - 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), - ]; - } - } - return $this->fieldCase($info); - } - - /** - * 取得数据库的表信息 - * @access public - * @param string $dbName - * @return array - */ - public function getTables($dbName = '') - { - $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - foreach ($result as $key => $val) { - $info[$key] = current($val); - } - return $info; - } - - /** - * SQL性能分析 - * @access protected - * @param string $sql - * @return array - */ - protected function getExplain($sql) - { - $pdo = $this->linkID->query("EXPLAIN " . $sql); - $result = $pdo->fetch(PDO::FETCH_ASSOC); - $result = array_change_key_case($result); - if (isset($result['extra'])) { - if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { - Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); - } - } - return $result; - } - - protected function supportSavepoint() - { - return true; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\Log; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->query("EXPLAIN " . $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + Log::record('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + return $result; + } + + protected function supportSavepoint() + { + return true; + } + +} diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php old mode 100755 new mode 100644 index 7ca0ef8..bbcf576 --- a/thinkphp/library/think/db/connector/Pgsql.php +++ b/thinkphp/library/think/db/connector/Pgsql.php @@ -1,103 +1,103 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\connector; - -use PDO; -use think\db\Connection; - -/** - * Pgsql数据库驱动 - */ -class Pgsql extends Connection -{ - protected $builder = '\\think\\db\\builder\\Pgsql'; - - /** - * 解析pdo连接的dsn信息 - * @access protected - * @param array $config 连接信息 - * @return string - */ - protected function parseDsn($config) - { - $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; - if (!empty($config['hostport'])) { - $dsn .= ';port=' . $config['hostport']; - } - return $dsn; - } - - /** - * 取得数据表的字段信息 - * @access public - * @param string $tableName - * @return array - */ - public function getFields($tableName) - { - - list($tableName) = explode(' ', $tableName); - $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; - - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - if ($result) { - foreach ($result as $key => $val) { - $val = array_change_key_case($val); - $info[$val['field']] = [ - 'name' => $val['field'], - 'type' => $val['type'], - 'notnull' => (bool) ('' !== $val['null']), - 'default' => $val['default'], - 'primary' => !empty($val['key']), - 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), - ]; - } - } - return $this->fieldCase($info); - } - - /** - * 取得数据库的表信息 - * @access public - * @param string $dbName - * @return array - */ - public function getTables($dbName = '') - { - $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - foreach ($result as $key => $val) { - $info[$key] = current($val); - } - return $info; - } - - /** - * SQL性能分析 - * @access protected - * @param string $sql - * @return array - */ - protected function getExplain($sql) - { - return []; - } - - protected function supportSavepoint() - { - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + + list($tableName) = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php old mode 100755 new mode 100644 index 73f7376..c4e3a72 --- a/thinkphp/library/think/db/connector/Sqlite.php +++ b/thinkphp/library/think/db/connector/Sqlite.php @@ -1,104 +1,104 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\connector; - -use PDO; -use think\db\Connection; - -/** - * Sqlite数据库驱动 - */ -class Sqlite extends Connection -{ - - protected $builder = '\\think\\db\\builder\\Sqlite'; - - /** - * 解析pdo连接的dsn信息 - * @access protected - * @param array $config 连接信息 - * @return string - */ - protected function parseDsn($config) - { - $dsn = 'sqlite:' . $config['database']; - return $dsn; - } - - /** - * 取得数据表的字段信息 - * @access public - * @param string $tableName - * @return array - */ - public function getFields($tableName) - { - list($tableName) = explode(' ', $tableName); - $sql = 'PRAGMA table_info( ' . $tableName . ' )'; - - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - if ($result) { - foreach ($result as $key => $val) { - $val = array_change_key_case($val); - $info[$val['name']] = [ - 'name' => $val['name'], - 'type' => $val['type'], - 'notnull' => 1 === $val['notnull'], - 'default' => $val['dflt_value'], - 'primary' => '1' == $val['pk'], - 'autoinc' => '1' == $val['pk'], - ]; - } - } - return $this->fieldCase($info); - } - - /** - * 取得数据库的表信息 - * @access public - * @param string $dbName - * @return array - */ - public function getTables($dbName = '') - { - - $sql = "SELECT name FROM sqlite_master WHERE type='table' " - . "UNION ALL SELECT name FROM sqlite_temp_master " - . "WHERE type='table' ORDER BY name"; - - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - foreach ($result as $key => $val) { - $info[$key] = current($val); - } - return $info; - } - - /** - * SQL性能分析 - * @access protected - * @param string $sql - * @return array - */ - protected function getExplain($sql) - { - return []; - } - - protected function supportSavepoint() - { - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php old mode 100755 new mode 100644 index b1fb6f8..08ad45e --- a/thinkphp/library/think/db/connector/Sqlsrv.php +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -1,122 +1,122 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\connector; - -use PDO; -use think\db\Connection; - -/** - * Sqlsrv数据库驱动 - */ -class Sqlsrv extends Connection -{ - // PDO连接参数 - protected $params = [ - PDO::ATTR_CASE => PDO::CASE_NATURAL, - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - PDO::ATTR_STRINGIFY_FETCHES => false, - ]; - protected $builder = '\\think\\db\\builder\\Sqlsrv'; - /** - * 解析pdo连接的dsn信息 - * @access protected - * @param array $config 连接信息 - * @return string - */ - protected function parseDsn($config) - { - $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; - if (!empty($config['hostport'])) { - $dsn .= ',' . $config['hostport']; - } - return $dsn; - } - - /** - * 取得数据表的字段信息 - * @access public - * @param string $tableName - * @return array - */ - public function getFields($tableName) - { - list($tableName) = explode(' ', $tableName); - $sql = "SELECT column_name, data_type, column_default, is_nullable - FROM information_schema.tables AS t - JOIN information_schema.columns AS c - ON t.table_catalog = c.table_catalog - AND t.table_schema = c.table_schema - AND t.table_name = c.table_name - WHERE t.table_name = '$tableName'"; - - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - if ($result) { - foreach ($result as $key => $val) { - $val = array_change_key_case($val); - $info[$val['column_name']] = [ - 'name' => $val['column_name'], - 'type' => $val['data_type'], - 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes - 'default' => $val['column_default'], - 'primary' => false, - 'autoinc' => false, - ]; - } - } - $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; - // 调试开始 - $this->debug(true); - $pdo = $this->linkID->query($sql); - // 调试结束 - $this->debug(false, $sql); - $result = $pdo->fetch(PDO::FETCH_ASSOC); - if ($result) { - $info[$result['column_name']]['primary'] = true; - } - return $this->fieldCase($info); - } - - /** - * 取得数据表的字段信息 - * @access public - * @param string $dbName - * @return array - */ - public function getTables($dbName = '') - { - $sql = "SELECT TABLE_NAME - FROM INFORMATION_SCHEMA.TABLES - WHERE TABLE_TYPE = 'BASE TABLE' - "; - - $pdo = $this->query($sql, [], false, true); - $result = $pdo->fetchAll(PDO::FETCH_ASSOC); - $info = []; - foreach ($result as $key => $val) { - $info[$key] = current($val); - } - return $info; - } - - /** - * SQL性能分析 - * @access protected - * @param string $sql - * @return array - */ - protected function getExplain($sql) - { - return []; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + // 调试开始 + $this->debug(true); + $pdo = $this->linkID->query($sql); + // 调试结束 + $this->debug(false, $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql old mode 100755 new mode 100644 index f940052..e1a09a3 --- a/thinkphp/library/think/db/connector/pgsql.sql +++ b/thinkphp/library/think/db/connector/pgsql.sql @@ -1,117 +1,117 @@ -CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS -$BODY$ -DECLARE - v_type varchar; -BEGIN - IF a_type='int8' THEN - v_type:='bigint'; - ELSIF a_type='int4' THEN - v_type:='integer'; - ELSIF a_type='int2' THEN - v_type:='smallint'; - ELSIF a_type='bpchar' THEN - v_type:='char'; - ELSE - v_type:=a_type; - END IF; - RETURN v_type; -END; -$BODY$ -LANGUAGE PLPGSQL; - -CREATE TYPE "public"."tablestruct" AS ( - "fields_key_name" varchar(100), - "fields_name" VARCHAR(200), - "fields_type" VARCHAR(20), - "fields_length" BIGINT, - "fields_not_null" VARCHAR(10), - "fields_default" VARCHAR(500), - "fields_comment" VARCHAR(1000) -); - -CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS -$body$ -DECLARE - v_ret tablestruct; - v_oid oid; - v_sql varchar; - v_rec RECORD; - v_key varchar; -BEGIN - SELECT - pg_class.oid INTO v_oid - FROM - pg_class - INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) - WHERE - pg_class.relname=a_table_name; - IF NOT FOUND THEN - RETURN; - END IF; - - v_sql=' - SELECT - pg_attribute.attname AS fields_name, - pg_attribute.attnum AS fields_index, - pgsql_type(pg_type.typname::varchar) AS fields_type, - pg_attribute.atttypmod-4 as fields_length, - CASE WHEN pg_attribute.attnotnull THEN ''not null'' - ELSE '''' - END AS fields_not_null, - pg_attrdef.adsrc AS fields_default, - pg_description.description AS fields_comment - FROM - pg_attribute - INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid - INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid - LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum - LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum - WHERE - pg_attribute.attnum > 0 - AND attisdropped <> ''t'' - AND pg_class.oid = ' || v_oid || ' - ORDER BY pg_attribute.attnum' ; - - FOR v_rec IN EXECUTE v_sql LOOP - v_ret.fields_name=v_rec.fields_name; - v_ret.fields_type=v_rec.fields_type; - IF v_rec.fields_length > 0 THEN - v_ret.fields_length:=v_rec.fields_length; - ELSE - v_ret.fields_length:=NULL; - END IF; - v_ret.fields_not_null=v_rec.fields_not_null; - v_ret.fields_default=v_rec.fields_default; - v_ret.fields_comment=v_rec.fields_comment; - SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; - IF FOUND THEN - v_ret.fields_key_name=v_key; - ELSE - v_ret.fields_key_name=''; - END IF; - RETURN NEXT v_ret; - END LOOP; - RETURN ; -END; -$body$ -LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; - -COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) -IS '获得表信息'; - ----重载一个函数 -CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS -$body$ -DECLARE - v_ret tablestruct; -BEGIN - FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP - RETURN NEXT v_ret; - END LOOP; - RETURN; -END; -$body$ -LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; - -COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) IS '获得表信息'; \ No newline at end of file diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php old mode 100755 new mode 100644 index e1e2662..4ed1954 --- a/thinkphp/library/think/db/exception/BindParamException.php +++ b/thinkphp/library/think/db/exception/BindParamException.php @@ -1,35 +1,35 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\exception; - -use think\exception\DbException; - -/** - * PDO参数绑定异常 - */ -class BindParamException extends DbException -{ - - /** - * BindParamException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param array $bind - * @param int $code - */ - public function __construct($message, $config, $sql, $bind, $code = 10502) - { - $this->setData('Bind Param', $bind); - parent::__construct($message, $config, $sql, $code); - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php old mode 100755 new mode 100644 index 11054c8..f2542ac --- a/thinkphp/library/think/db/exception/DataNotFoundException.php +++ b/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -1,43 +1,43 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\exception; - -use think\exception\DbException; - -class DataNotFoundException extends DbException -{ - protected $table; - - /** - * DbException constructor. - * @param string $message - * @param string $table - * @param array $config - */ - public function __construct($message, $table = '', array $config = []) - { - $this->message = $message; - $this->table = $table; - - $this->setData('Database Config', $config); - } - - /** - * 获取数据表名 - * @access public - * @return string - */ - public function getTable() - { - return $this->table; - } -} + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php old mode 100755 new mode 100644 index 21c415a..6e5f930 --- a/thinkphp/library/think/db/exception/ModelNotFoundException.php +++ b/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -1,43 +1,43 @@ - -// +---------------------------------------------------------------------- - -namespace think\db\exception; - -use think\exception\DbException; - -class ModelNotFoundException extends DbException -{ - protected $model; - - /** - * 构造方法 - * @param string $message - * @param string $model - */ - public function __construct($message, $model = '', array $config = []) - { - $this->message = $message; - $this->model = $model; - - $this->setData('Database Config', $config); - } - - /** - * 获取模型类名 - * @access public - * @return string - */ - public function getModel() - { - return $this->model; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @param string $message + * @param string $model + */ + public function __construct($message, $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php old mode 100755 new mode 100644 index 5303e3b..c17911b --- a/thinkphp/library/think/debug/Console.php +++ b/thinkphp/library/think/debug/Console.php @@ -1,160 +1,160 @@ - -// +---------------------------------------------------------------------- - -namespace think\debug; - -use think\Cache; -use think\Config; -use think\Db; -use think\Debug; -use think\Request; -use think\Response; - -/** - * 浏览器调试输出 - */ -class Console -{ - protected $config = [ - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], - ]; - - // 实例化并传入参数 - public function __construct($config = []) - { - if (is_array($config)) { - $this->config = array_merge($this->config, $config); - } - } - - /** - * 调试输出接口 - * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 - * @return bool - */ - public function output(Response $response, array $log = []) - { - $request = Request::instance(); - $contentType = $response->getHeader('Content-Type'); - $accept = $request->header('accept'); - if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { - return false; - } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { - return false; - } - // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - - if (isset($_SERVER['HTTP_HOST'])) { - $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $uri = 'cmd:' . implode(' ', $_SERVER['argv']); - } - - // 页面Trace信息 - $base = [ - '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, - '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), - '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), - ]; - - if (session_id()) { - $base['会话信息'] = 'SESSION_ID=' . session_id(); - } - - $info = Debug::getFile(true); - - // 页面Trace信息 - $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { - $name = strtolower($name); - switch ($name) { - case 'base': // 基本信息 - $trace[$title] = $base; - break; - case 'file': // 文件信息 - $trace[$title] = $info; - break; - default: // 调试信息 - if (strpos($name, '|')) { - // 多组信息 - $names = explode('|', $name); - $result = []; - foreach ($names as $name) { - $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); - } - $trace[$title] = $result; - } else { - $trace[$title] = isset($log[$name]) ? $log[$name] : ''; - } - } - } - - //输出到控制台 - $lines = ''; - foreach ($trace as $type => $msg) { - $lines .= $this->console($type, $msg); - } - $js = << -{$lines} - -JS; - return $js; - } - - protected function console($type, $msg) - { - $type = strtolower($type); - $trace_tabs = array_values($this->config['trace_tabs']); - $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) - ? "console.group('{$type}');" - : "console.groupCollapsed('{$type}');"; - - foreach ((array) $msg as $key => $m) { - switch ($type) { - case '调试': - $var_type = gettype($m); - if (in_array($var_type, ['array', 'string'])) { - $line[] = "console.log(" . json_encode($m) . ");"; - } else { - $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; - } - break; - case '错误': - $msg = str_replace("\n", '\n', json_encode($m)); - $style = 'color:#F4006B;font-size:14px;'; - $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; - break; - case 'sql': - $msg = str_replace("\n", '\n', $m); - $style = "color:#009bb4;"; - $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; - break; - default: - $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; - $msg = json_encode($m); - $line[] = "console.log({$msg});"; - break; - } - } - $line[] = "console.groupEnd();"; - return implode(PHP_EOL, $line); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['trace_tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', json_encode($m)); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', $m); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php old mode 100755 new mode 100644 index 428dc79..b6be7ad --- a/thinkphp/library/think/debug/Html.php +++ b/thinkphp/library/think/debug/Html.php @@ -1,111 +1,111 @@ - -// +---------------------------------------------------------------------- - -namespace think\debug; - -use think\Cache; -use think\Config; -use think\Db; -use think\Debug; -use think\Request; -use think\Response; - -/** - * 页面Trace调试 - */ -class Html -{ - protected $config = [ - 'trace_file' => '', - 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], - ]; - - // 实例化并传入参数 - public function __construct(array $config = []) - { - $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; - $this->config = array_merge($this->config, $config); - } - - /** - * 调试输出接口 - * @access public - * @param Response $response Response对象 - * @param array $log 日志信息 - * @return bool - */ - public function output(Response $response, array $log = []) - { - $request = Request::instance(); - $contentType = $response->getHeader('Content-Type'); - $accept = $request->header('accept'); - if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { - return false; - } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { - return false; - } - // 获取基本信息 - $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - - // 页面Trace信息 - if (isset($_SERVER['HTTP_HOST'])) { - $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $uri = 'cmd:' . implode(' ', $_SERVER['argv']); - } - $base = [ - '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, - '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), - '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', - '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', - '配置加载' => count(Config::get()), - ]; - - if (session_id()) { - $base['会话信息'] = 'SESSION_ID=' . session_id(); - } - - $info = Debug::getFile(true); - - // 页面Trace信息 - $trace = []; - foreach ($this->config['trace_tabs'] as $name => $title) { - $name = strtolower($name); - switch ($name) { - case 'base': // 基本信息 - $trace[$title] = $base; - break; - case 'file': // 文件信息 - $trace[$title] = $info; - break; - default: // 调试信息 - if (strpos($name, '|')) { - // 多组信息 - $names = explode('|', $name); - $result = []; - foreach ($names as $name) { - $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); - } - $trace[$title] = $result; - } else { - $trace[$title] = isset($log[$name]) ? $log[$name] : ''; - } - } - } - // 调用Trace页面模板 - ob_start(); - include $this->config['trace_file']; - return ob_get_clean(); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Cache; +use think\Config; +use think\Db; +use think\Debug; +use think\Request; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'trace_file' => '', + 'trace_tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config['trace_file'] = THINK_PATH . 'tpl/page_trace.tpl'; + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Request::instance(); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - THINK_START_TIME, 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + // 页面Trace信息 + if (isset($_SERVER['HTTP_HOST'])) { + $uri = $_SERVER['SERVER_PROTOCOL'] . ' ' . $_SERVER['REQUEST_METHOD'] . ' : ' . $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Cache::$readTimes . ' reads,' . Cache::$writeTimes . ' writes', + '配置加载' => count(Config::get()), + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Debug::getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['trace_tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['trace_file']; + return ob_get_clean(); + } + +} diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/thinkphp/library/think/exception/ClassNotFoundException.php old mode 100755 new mode 100644 index 20f532c..eb22e73 --- a/thinkphp/library/think/exception/ClassNotFoundException.php +++ b/thinkphp/library/think/exception/ClassNotFoundException.php @@ -1,32 +1,32 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class ClassNotFoundException extends \RuntimeException -{ - protected $class; - public function __construct($message, $class = '') - { - $this->message = $message; - $this->class = $class; - } - - /** - * 获取类名 - * @access public - * @return string - */ - public function getClass() - { - return $this->class; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php old mode 100755 new mode 100644 index 5e8d12b..0ae80ad --- a/thinkphp/library/think/exception/DbException.php +++ b/thinkphp/library/think/exception/DbException.php @@ -1,43 +1,43 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -use think\Exception; - -/** - * Database相关异常处理类 - */ -class DbException extends Exception -{ - /** - * DbException constructor. - * @param string $message - * @param array $config - * @param string $sql - * @param int $code - */ - public function __construct($message, array $config, $sql, $code = 10500) - { - $this->message = $message; - $this->code = $code; - - $this->setData('Database Status', [ - 'Error Code' => $code, - 'Error Message' => $message, - 'Error SQL' => $sql, - ]); - - unset($config['username'], $config['password']); - $this->setData('Database Config', $config); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + +} diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php old mode 100755 new mode 100644 index 189876d..b3a9a30 --- a/thinkphp/library/think/exception/ErrorException.php +++ b/thinkphp/library/think/exception/ErrorException.php @@ -1,57 +1,57 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -use think\Exception; - -/** - * ThinkPHP错误异常 - * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 - * 除开从 think\Exception 继承的功能 - * 其他和PHP系统\ErrorException功能基本一样 - */ -class ErrorException extends Exception -{ - /** - * 用于保存错误级别 - * @var integer - */ - protected $severity; - - /** - * 错误异常构造函数 - * @param integer $severity 错误级别 - * @param string $message 错误详细信息 - * @param string $file 出错文件路径 - * @param integer $line 出错行号 - * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 - */ - public function __construct($severity, $message, $file, $line, array $context = []) - { - $this->severity = $severity; - $this->message = $message; - $this->file = $file; - $this->line = $line; - $this->code = 0; - - empty($context) || $this->setData('Error Context', $context); - } - - /** - * 获取错误级别 - * @return integer 错误级别 - */ - final public function getSeverity() - { - return $this->severity; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + * @param array $context 错误上下文,会包含错误触发处作用域内所有变量的数组 + */ + public function __construct($severity, $message, $file, $line, array $context = []) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + + empty($context) || $this->setData('Error Context', $context); + } + + /** + * 获取错误级别 + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php old mode 100755 new mode 100644 index fc4ea5b..f523db0 --- a/thinkphp/library/think/exception/Handle.php +++ b/thinkphp/library/think/exception/Handle.php @@ -1,282 +1,282 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -use Exception; -use think\App; -use think\Config; -use think\console\Output; -use think\Lang; -use think\Log; -use think\Response; - -class Handle -{ - protected $render; - protected $ignoreReport = [ - '\\think\\exception\\HttpException', - ]; - - public function setRender($render) - { - $this->render = $render; - } - - /** - * Report or log an exception. - * - * @param \Exception $exception - * @return void - */ - public function report(Exception $exception) - { - if (!$this->isIgnoreReport($exception)) { - // 收集异常数据 - if (App::$debug) { - $data = [ - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - 'message' => $this->getMessage($exception), - 'code' => $this->getCode($exception), - ]; - $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; - } else { - $data = [ - 'code' => $this->getCode($exception), - 'message' => $this->getMessage($exception), - ]; - $log = "[{$data['code']}]{$data['message']}"; - } - - if (Config::get('record_trace')) { - $log .= "\r\n" . $exception->getTraceAsString(); - } - - Log::record($log, 'error'); - } - } - - protected function isIgnoreReport(Exception $exception) - { - foreach ($this->ignoreReport as $class) { - if ($exception instanceof $class) { - return true; - } - } - return false; - } - - /** - * Render an exception into an HTTP response. - * - * @param \Exception $e - * @return Response - */ - public function render(Exception $e) - { - if ($this->render && $this->render instanceof \Closure) { - $result = call_user_func_array($this->render, [$e]); - if ($result) { - return $result; - } - } - - if ($e instanceof HttpException) { - return $this->renderHttpException($e); - } else { - return $this->convertExceptionToResponse($e); - } - } - - /** - * @param Output $output - * @param Exception $e - */ - public function renderForConsole(Output $output, Exception $e) - { - if (App::$debug) { - $output->setVerbosity(Output::VERBOSITY_DEBUG); - } - $output->renderException($e); - } - - /** - * @param HttpException $e - * @return Response - */ - protected function renderHttpException(HttpException $e) - { - $status = $e->getStatusCode(); - $template = Config::get('http_exception_template'); - if (!App::$debug && !empty($template[$status])) { - return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); - } else { - return $this->convertExceptionToResponse($e); - } - } - - /** - * @param Exception $exception - * @return Response - */ - protected function convertExceptionToResponse(Exception $exception) - { - // 收集异常数据 - if (App::$debug) { - // 调试模式,获取详细的错误信息 - $data = [ - 'name' => get_class($exception), - 'file' => $exception->getFile(), - 'line' => $exception->getLine(), - 'message' => $this->getMessage($exception), - 'trace' => $exception->getTrace(), - 'code' => $this->getCode($exception), - 'source' => $this->getSourceCode($exception), - 'datas' => $this->getExtendData($exception), - 'tables' => [ - 'GET Data' => $_GET, - 'POST Data' => $_POST, - 'Files' => $_FILES, - 'Cookies' => $_COOKIE, - 'Session' => isset($_SESSION) ? $_SESSION : [], - 'Server/Request Data' => $_SERVER, - 'Environment Variables' => $_ENV, - 'ThinkPHP Constants' => $this->getConst(), - ], - ]; - } else { - // 部署模式仅显示 Code 和 Message - $data = [ - 'code' => $this->getCode($exception), - 'message' => $this->getMessage($exception), - ]; - - if (!Config::get('show_error_msg')) { - // 不显示详细错误信息 - $data['message'] = Config::get('error_message'); - } - } - - //保留一层 - while (ob_get_level() > 1) { - ob_end_clean(); - } - - $data['echo'] = ob_get_clean(); - - ob_start(); - extract($data); - include Config::get('exception_tmpl'); - // 获取并清空缓存 - $content = ob_get_clean(); - $response = new Response($content, 'html'); - - if ($exception instanceof HttpException) { - $statusCode = $exception->getStatusCode(); - $response->header($exception->getHeaders()); - } - - if (!isset($statusCode)) { - $statusCode = 500; - } - $response->code($statusCode); - return $response; - } - - /** - * 获取错误编码 - * ErrorException则使用错误级别作为错误编码 - * @param \Exception $exception - * @return integer 错误编码 - */ - protected function getCode(Exception $exception) - { - $code = $exception->getCode(); - if (!$code && $exception instanceof ErrorException) { - $code = $exception->getSeverity(); - } - return $code; - } - - /** - * 获取错误信息 - * ErrorException则使用错误级别作为错误编码 - * @param \Exception $exception - * @return string 错误信息 - */ - protected function getMessage(Exception $exception) - { - $message = $exception->getMessage(); - if (IS_CLI) { - return $message; - } - - if (strpos($message, ':')) { - $name = strstr($message, ':', true); - $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; - } elseif (strpos($message, ',')) { - $name = strstr($message, ',', true); - $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; - } elseif (Lang::has($message)) { - $message = Lang::get($message); - } - return $message; - } - - /** - * 获取出错文件内容 - * 获取错误的前9行和后9行 - * @param \Exception $exception - * @return array 错误文件内容 - */ - protected function getSourceCode(Exception $exception) - { - // 读取前9行和后9行 - $line = $exception->getLine(); - $first = ($line - 9 > 0) ? $line - 9 : 1; - - try { - $contents = file($exception->getFile()); - $source = [ - 'first' => $first, - 'source' => array_slice($contents, $first - 1, 19), - ]; - } catch (Exception $e) { - $source = []; - } - return $source; - } - - /** - * 获取异常扩展信息 - * 用于非调试模式html返回类型显示 - * @param \Exception $exception - * @return array 异常类定义的扩展数据 - */ - protected function getExtendData(Exception $exception) - { - $data = []; - if ($exception instanceof \think\Exception) { - $data = $exception->getData(); - } - return $data; - } - - /** - * 获取常量列表 - * @return array 常量列表 - */ - private static function getConst() - { - return get_defined_constants(true)['user']; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\App; +use think\Config; +use think\console\Output; +use think\Lang; +use think\Log; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (App::$debug) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if (Config::get('record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Log::record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (App::$debug) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + $output->renderException($e); + } + + /** + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Config::get('http_exception_template'); + if (!App::$debug && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (App::$debug) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Config::get('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Config::get('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Config::get('exception_tmpl'); + // 获取并清空缓存 + $content = ob_get_clean(); + $response = new Response($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + if (IS_CLI) { + return $message; + } + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = Lang::has($name) ? Lang::get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = Lang::has($name) ? Lang::get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif (Lang::has($message)) { + $message = Lang::get($message); + } + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + return $data; + } + + /** + * 获取常量列表 + * @return array 常量列表 + */ + private static function getConst() + { + return get_defined_constants(true)['user']; + } +} diff --git a/thinkphp/library/think/exception/HttpException.php b/thinkphp/library/think/exception/HttpException.php old mode 100755 new mode 100644 index 1d72170..01a27fc --- a/thinkphp/library/think/exception/HttpException.php +++ b/thinkphp/library/think/exception/HttpException.php @@ -1,36 +1,36 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class HttpException extends \RuntimeException -{ - private $statusCode; - private $headers; - - public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) - { - $this->statusCode = $statusCode; - $this->headers = $headers; - - parent::__construct($message, $code, $previous); - } - - public function getStatusCode() - { - return $this->statusCode; - } - - public function getHeaders() - { - return $this->headers; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/thinkphp/library/think/exception/HttpResponseException.php b/thinkphp/library/think/exception/HttpResponseException.php old mode 100755 new mode 100644 index d1d57ab..5297286 --- a/thinkphp/library/think/exception/HttpResponseException.php +++ b/thinkphp/library/think/exception/HttpResponseException.php @@ -1,33 +1,33 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -use think\Response; - -class HttpResponseException extends \RuntimeException -{ - /** - * @var Response - */ - protected $response; - - public function __construct(Response $response) - { - $this->response = $response; - } - - public function getResponse() - { - return $this->response; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php old mode 100755 new mode 100644 index d468349..044f82a --- a/thinkphp/library/think/exception/PDOException.php +++ b/thinkphp/library/think/exception/PDOException.php @@ -1,39 +1,39 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -/** - * PDO异常处理类 - * 重新封装了系统的\PDOException类 - */ -class PDOException extends DbException -{ - /** - * PDOException constructor. - * @param \PDOException $exception - * @param array $config - * @param string $sql - * @param int $code - */ - public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) - { - $error = $exception->errorInfo; - - $this->setData('PDO Error Info', [ - 'SQLSTATE' => $error[0], - 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, - 'Driver Error Message' => isset($error[2]) ? $error[2] : '', - ]); - - parent::__construct($exception->getMessage(), $config, $sql, $code); - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/exception/RouteNotFoundException.php b/thinkphp/library/think/exception/RouteNotFoundException.php old mode 100755 new mode 100644 index b5fc7aa..d22e3a6 --- a/thinkphp/library/think/exception/RouteNotFoundException.php +++ b/thinkphp/library/think/exception/RouteNotFoundException.php @@ -1,22 +1,22 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class RouteNotFoundException extends HttpException -{ - - public function __construct() - { - parent::__construct(404, 'Route Not Found'); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/thinkphp/library/think/exception/TemplateNotFoundException.php old mode 100755 new mode 100644 index f4ad0c4..4202069 --- a/thinkphp/library/think/exception/TemplateNotFoundException.php +++ b/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -1,33 +1,33 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class TemplateNotFoundException extends \RuntimeException -{ - protected $template; - - public function __construct($message, $template = '') - { - $this->message = $message; - $this->template = $template; - } - - /** - * 获取模板文件 - * @access public - * @return string - */ - public function getTemplate() - { - return $this->template; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/thinkphp/library/think/exception/ThrowableError.php b/thinkphp/library/think/exception/ThrowableError.php old mode 100755 new mode 100644 index 169365f..87b6b9d --- a/thinkphp/library/think/exception/ThrowableError.php +++ b/thinkphp/library/think/exception/ThrowableError.php @@ -1,47 +1,47 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class ThrowableError extends \ErrorException -{ - public function __construct(\Throwable $e) - { - - if ($e instanceof \ParseError) { - $message = 'Parse error: ' . $e->getMessage(); - $severity = E_PARSE; - } elseif ($e instanceof \TypeError) { - $message = 'Type error: ' . $e->getMessage(); - $severity = E_RECOVERABLE_ERROR; - } else { - $message = 'Fatal error: ' . $e->getMessage(); - $severity = E_ERROR; - } - - parent::__construct( - $message, - $e->getCode(), - $severity, - $e->getFile(), - $e->getLine() - ); - - $this->setTrace($e->getTrace()); - } - - protected function setTrace($trace) - { - $traceReflector = new \ReflectionProperty('Exception', 'trace'); - $traceReflector->setAccessible(true); - $traceReflector->setValue($this, $trace); - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php old mode 100755 new mode 100644 index 3cc5ff6..b368416 --- a/thinkphp/library/think/exception/ValidateException.php +++ b/thinkphp/library/think/exception/ValidateException.php @@ -1,33 +1,33 @@ - -// +---------------------------------------------------------------------- - -namespace think\exception; - -class ValidateException extends \RuntimeException -{ - protected $error; - - public function __construct($error) - { - $this->error = $error; - $this->message = is_array($error) ? implode("\n\r", $error) : $error; - } - - /** - * 获取验证错误信息 - * @access public - * @return array|string - */ - public function getError() - { - return $this->error; - } -} + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php old mode 100755 new mode 100644 index ae5e209..f2296cf --- a/thinkphp/library/think/log/driver/File.php +++ b/thinkphp/library/think/log/driver/File.php @@ -1,146 +1,270 @@ - -// +---------------------------------------------------------------------- - -namespace think\log\driver; - -use think\App; -use think\Request; - -/** - * 本地化调试输出到文件 - */ -class File -{ - protected $config = [ - 'time_format' => ' c ', - 'single' => false, - 'file_size' => 2097152, - 'path' => LOG_PATH, - 'apart_level' => [], - 'max_files' => 0, - ]; - - protected $writed = []; - - // 实例化并传入参数 - public function __construct($config = []) - { - if (is_array($config)) { - $this->config = array_merge($this->config, $config); - } - } - - /** - * 日志写入接口 - * @access public - * @param array $log 日志信息 - * @return bool - */ - public function save(array $log = []) - { - if ($this->config['single']) { - $destination = $this->config['path'] . 'single.log'; - } else { - $cli = IS_CLI ? '_cli' : ''; - - if ($this->config['max_files']) { - $filename = date('Ymd') . $cli . '.log'; - $files = glob($this->config['path'] . '*.log'); - - if (count($files) > $this->config['max_files']) { - unlink($files[0]); - } - } else { - $filename = date('Ym') . '/' . date('d') . $cli . '.log'; - } - - $destination = $this->config['path'] . $filename; - } - - $path = dirname($destination); - !is_dir($path) && mkdir($path, 0755, true); - - $info = ''; - foreach ($log as $type => $val) { - $level = ''; - foreach ($val as $msg) { - if (!is_string($msg)) { - $msg = var_export($msg, true); - } - $level .= '[ ' . $type . ' ] ' . $msg . "\r\n"; - } - if (in_array($type, $this->config['apart_level'])) { - // 独立记录的日志级别 - if ($this->config['single']) { - $filename = $path . DS . $type . '.log'; - } elseif ($this->config['max_files']) { - $filename = $path . DS . date('Ymd') . '_' . $type . $cli . '.log'; - } else { - $filename = $path . DS . date('d') . '_' . $type . $cli . '.log'; - } - $this->write($level, $filename, true); - } else { - $info .= $level; - } - } - if ($info) { - return $this->write($info, $destination); - } - return true; - } - - protected function write($message, $destination, $apart = false) - { - //检测日志文件大小,超过配置大小则备份日志文件重新生成 - if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { - try { - rename($destination, dirname($destination) . DS . time() . '-' . basename($destination)); - } catch (\Exception $e) { - } - $this->writed[$destination] = false; - } - - if (empty($this->writed[$destination]) && !IS_CLI) { - if (App::$debug && !$apart) { - // 获取基本信息 - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = "cmd:" . implode(' ', $_SERVER['argv']); - } - - $runtime = round(microtime(true) - THINK_START_TIME, 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - $message = '[ info ] ' . $current_uri . $time_str . $memory_str . $file_load . "\r\n" . $message; - } - $now = date($this->config['time_format']); - $ip = Request::instance()->ip(); - $method = isset($_SERVER['REQUEST_METHOD']) ? $_SERVER['REQUEST_METHOD'] : 'CLI'; - $uri = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : ''; - $message = "---------------------------------------------------------------\r\n[{$now}] {$ip} {$method} {$uri}\r\n" . $message; - - $this->writed[$destination] = true; - } - - if (IS_CLI) { - $now = date($this->config['time_format']); - $message = "[{$now}]" . $message; - } - - return error_log($message, 3, $destination); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; +use think\Request; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => LOG_PATH, + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . '.log'; + } else { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type . $cli; + } else { + $name = date('d') . '_' . $type . $cli; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $request = Request::instance(); + $requestInfo = [ + 'ip' => $request->ip(), + 'method' => $request->method(), + 'host' => $request->host(), + 'uri' => $request->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if (App::$debug && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php old mode 100755 new mode 100644 index 2beac62..4f62915 --- a/thinkphp/library/think/log/driver/Socket.php +++ b/thinkphp/library/think/log/driver/Socket.php @@ -1,250 +1,250 @@ - -// +---------------------------------------------------------------------- - -namespace think\log\driver; - -use think\App; - -/** - * github: https://github.com/luofei614/SocketLog - * @author luofei614 - */ -class Socket -{ - public $port = 1116; //SocketLog 服务的http的端口号 - - protected $config = [ - // socket服务器地址 - 'host' => 'localhost', - // 是否显示加载的文件列表 - 'show_included_files' => false, - // 日志强制记录到配置的client_id - 'force_client_ids' => [], - // 限制允许读取日志的client_id - 'allow_client_ids' => [], - ]; - - protected $css = [ - 'sql' => 'color:#009bb4;', - 'sql_warn' => 'color:#009bb4;font-size:14px;', - 'error' => 'color:#f4006b;font-size:14px;', - 'page' => 'color:#40e2ff;background:#171717;', - 'big' => 'font-size:20px;color:red;', - ]; - - protected $allowForceClientIds = []; //配置强制推送且被授权的client_id - - /** - * 构造函数 - * @param array $config 缓存参数 - * @access public - */ - public function __construct(array $config = []) - { - if (!empty($config)) { - $this->config = array_merge($this->config, $config); - } - } - - /** - * 调试输出接口 - * @access public - * @param array $log 日志信息 - * @return bool - */ - public function save(array $log = []) - { - if (!$this->check()) { - return false; - } - $trace = []; - if (App::$debug) { - $runtime = round(microtime(true) - THINK_START_TIME, 10); - $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; - $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; - $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); - $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; - $file_load = ' [文件加载:' . count(get_included_files()) . ']'; - - if (isset($_SERVER['HTTP_HOST'])) { - $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; - } else { - $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); - } - // 基本信息 - $trace[] = [ - 'type' => 'group', - 'msg' => $current_uri . $time_str . $memory_str . $file_load, - 'css' => $this->css['page'], - ]; - } - - foreach ($log as $type => $val) { - $trace[] = [ - 'type' => 'groupCollapsed', - 'msg' => '[ ' . $type . ' ]', - 'css' => isset($this->css[$type]) ? $this->css[$type] : '', - ]; - foreach ($val as $msg) { - if (!is_string($msg)) { - $msg = var_export($msg, true); - } - $trace[] = [ - 'type' => 'log', - 'msg' => $msg, - 'css' => '', - ]; - } - $trace[] = [ - 'type' => 'groupEnd', - 'msg' => '', - 'css' => '', - ]; - } - - if ($this->config['show_included_files']) { - $trace[] = [ - 'type' => 'groupCollapsed', - 'msg' => '[ file ]', - 'css' => '', - ]; - $trace[] = [ - 'type' => 'log', - 'msg' => implode("\n", get_included_files()), - 'css' => '', - ]; - $trace[] = [ - 'type' => 'groupEnd', - 'msg' => '', - 'css' => '', - ]; - } - - $trace[] = [ - 'type' => 'groupEnd', - 'msg' => '', - 'css' => '', - ]; - - $tabid = $this->getClientArg('tabid'); - if (!$client_id = $this->getClientArg('client_id')) { - $client_id = ''; - } - - if (!empty($this->allowForceClientIds)) { - //强制推送到多个client_id - foreach ($this->allowForceClientIds as $force_client_id) { - $client_id = $force_client_id; - $this->sendToClient($tabid, $client_id, $trace, $force_client_id); - } - } else { - $this->sendToClient($tabid, $client_id, $trace, ''); - } - return true; - } - - /** - * 发送给指定客户端 - * @author Zjmainstay - * @param $tabid - * @param $client_id - * @param $logs - * @param $force_client_id - */ - protected function sendToClient($tabid, $client_id, $logs, $force_client_id) - { - $logs = [ - 'tabid' => $tabid, - 'client_id' => $client_id, - 'logs' => $logs, - 'force_client_id' => $force_client_id, - ]; - $msg = @json_encode($logs); - $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 - $this->send($this->config['host'], $msg, $address); - } - - protected function check() - { - $tabid = $this->getClientArg('tabid'); - //是否记录日志的检查 - if (!$tabid && !$this->config['force_client_ids']) { - return false; - } - //用户认证 - $allow_client_ids = $this->config['allow_client_ids']; - if (!empty($allow_client_ids)) { - //通过数组交集得出授权强制推送的client_id - $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); - if (!$tabid && count($this->allowForceClientIds)) { - return true; - } - - $client_id = $this->getClientArg('client_id'); - if (!in_array($client_id, $allow_client_ids)) { - return false; - } - } else { - $this->allowForceClientIds = $this->config['force_client_ids']; - } - return true; - } - - protected function getClientArg($name) - { - static $args = []; - - $key = 'HTTP_USER_AGENT'; - - if (isset($_SERVER['HTTP_SOCKETLOG'])) { - $key = 'HTTP_SOCKETLOG'; - } - - if (!isset($_SERVER[$key])) { - return; - } - if (empty($args)) { - if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { - $args = ['tabid' => null]; - return; - } - parse_str($match[1], $args); - } - if (isset($args[$name])) { - return $args[$name]; - } - return; - } - - /** - * @param string $host - $host of socket server - * @param string $message - 发送的消息 - * @param string $address - 地址 - * @return bool - */ - protected function send($host, $message = '', $address = '/') - { - $url = 'http://' . $host . ':' . $this->port . $address; - $ch = curl_init(); - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, true); - curl_setopt($ch, CURLOPT_POSTFIELDS, $message); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); - curl_setopt($ch, CURLOPT_TIMEOUT, 10); - $headers = [ - "Content-Type: application/json;charset=UTF-8", - ]; - curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header - return curl_exec($ch); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + + /** + * 构造函数 + * @param array $config 缓存参数 + * @access public + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + $trace = []; + if (App::$debug) { + $runtime = round(microtime(true) - THINK_START_TIME, 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - THINK_START_MEM) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $this->css[$type] : '', + ]; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + if (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + return true; + } + + /** + * 发送给指定客户端 + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + if (isset($args[$name])) { + return $args[$name]; + } + return; + } + + /** + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->port . $address; + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + return curl_exec($ch); + } + +} diff --git a/thinkphp/library/think/log/driver/Test.php b/thinkphp/library/think/log/driver/Test.php old mode 100755 new mode 100644 index 9aee33e..7f66338 --- a/thinkphp/library/think/log/driver/Test.php +++ b/thinkphp/library/think/log/driver/Test.php @@ -1,30 +1,30 @@ - -// +---------------------------------------------------------------------- - -namespace think\log\driver; - -/** - * 模拟测试输出 - */ -class Test -{ - /** - * 日志写入接口 - * @access public - * @param array $log 日志信息 - * @return bool - */ - public function save(array $log = []) - { - return true; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +/** + * 模拟测试输出 + */ +class Test +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []) + { + return true; + } + +} diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php old mode 100755 new mode 100644 index 4d50821..0406533 --- a/thinkphp/library/think/model/Collection.php +++ b/thinkphp/library/think/model/Collection.php @@ -1,79 +1,79 @@ - -// +---------------------------------------------------------------------- - -namespace think\model; - -use think\Collection as BaseCollection; -use think\Model; - -class Collection extends BaseCollection -{ - /** - * 延迟预载入关联查询 - * @access public - * @param mixed $relation 关联 - * @return $this - */ - public function load($relation) - { - $item = current($this->items); - $item->eagerlyResultSet($this->items, $relation); - return $this; - } - - /** - * 设置需要隐藏的输出属性 - * @access public - * @param array $hidden 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function hidden($hidden = [], $override = false) - { - $this->each(function ($model) use ($hidden, $override) { - /** @var Model $model */ - $model->hidden($hidden, $override); - }); - return $this; - } - - /** - * 设置需要输出的属性 - * @param array $visible - * @param bool $override 是否覆盖 - * @return $this - */ - public function visible($visible = [], $override = false) - { - $this->each(function ($model) use ($visible, $override) { - /** @var Model $model */ - $model->visible($visible, $override); - }); - return $this; - } - - /** - * 设置需要追加的输出属性 - * @access public - * @param array $append 属性列表 - * @param bool $override 是否覆盖 - * @return $this - */ - public function append($append = [], $override = false) - { - $this->each(function ($model) use ($append, $override) { - /** @var Model $model */ - $model && $model->append($append, $override); - }); - return $this; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + return $this; + } + + /** + * 设置需要输出的属性 + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + return $this; + } + +} diff --git a/thinkphp/library/think/model/Merge.php b/thinkphp/library/think/model/Merge.php old mode 100755 new mode 100644 index 864a55d..4a9da81 --- a/thinkphp/library/think/model/Merge.php +++ b/thinkphp/library/think/model/Merge.php @@ -1,322 +1,322 @@ - -// +---------------------------------------------------------------------- - -namespace think\model; - -use think\Db; -use think\db\Query; -use think\Model; - -class Merge extends Model -{ - - protected $relationModel = []; // HAS ONE 关联的模型列表 - protected $fk = ''; // 外键名 默认为主表名_id - protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) - - /** - * 构造函数 - * @access public - * @param array|object $data 数据 - */ - public function __construct($data = []) - { - parent::__construct($data); - - // 设置默认外键名 仅支持单一外键 - if (empty($this->fk)) { - $this->fk = strtolower($this->name) . '_id'; - } - } - - /** - * 查找单条记录 - * @access public - * @param mixed $data 主键值或者查询条件(闭包) - * @param string|array $with 关联预查询 - * @param bool $cache 是否缓存 - * @return \think\Model - */ - public static function get($data = null, $with = [], $cache = false) - { - $query = self::parseQuery($data, $with, $cache); - $query = self::attachQuery($query); - return $query->find($data); - } - - /** - * 附加查询表达式 - * @access protected - * @param \think\db\Query $query 查询对象 - * @return \think\db\Query - */ - protected static function attachQuery($query) - { - $class = new static(); - $master = $class->name; - $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); - $query->alias($master)->field($fields); - - foreach ($class->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $query->getTable($name) : $model; - $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); - $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); - $query->field($fields); - } - return $query; - } - - /** - * 获取关联模型的字段 并解决混淆 - * @access protected - * @param \think\db\Query $query 查询对象 - * @param string $name 模型名称 - * @param string $table 关联表名称 - * @param array $map 字段映射 - * @param array $fields 查询字段 - * @return array - */ - protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) - { - // 获取模型的字段信息 - $fields = $fields ?: $query->getTableInfo($table, 'fields'); - $array = []; - foreach ($fields as $field) { - if ($key = array_search($name . '.' . $field, $map)) { - // 需要处理映射字段 - $array[] = $name . '.' . $field . ' AS ' . $key; - } else { - $array[] = $field; - } - } - return $array; - } - - /** - * 查找所有记录 - * @access public - * @param mixed $data 主键列表或者查询条件(闭包) - * @param array|string $with 关联预查询 - * @param bool $cache - * @return array|false|string - */ - public static function all($data = null, $with = [], $cache = false) - { - $query = self::parseQuery($data, $with, $cache); - $query = self::attachQuery($query); - return $query->select($data); - } - - /** - * 处理写入的模型数据 - * @access public - * @param string $model 模型名称 - * @param array $data 数据 - * @return array - */ - protected function parseData($model, $data) - { - $item = []; - foreach ($data as $key => $val) { - if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { - list($name, $key) = explode('.', $this->mapFields[$key]); - if ($model == $name) { - $item[$key] = $val; - } - } else { - $item[$key] = $val; - } - } - return $item; - } - - /** - * 保存模型数据 以及关联数据 - * @access public - * @param mixed $data 数据 - * @param array $where 更新条件 - * @param string $sequence 自增序列名 - * @return false|int - * @throws \Exception - */ - public function save($data = [], $where = [], $sequence = null) - { - if (!empty($data)) { - // 数据自动验证 - if (!$this->validateData($data)) { - return false; - } - // 数据对象赋值 - foreach ($data as $key => $value) { - $this->setAttr($key, $value, $data); - } - if (!empty($where)) { - $this->isUpdate = true; - } - } - - // 数据自动完成 - $this->autoCompleteData($this->auto); - - // 自动写入更新时间 - if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { - $this->setAttr($this->updateTime, null); - } - - // 事件回调 - if (false === $this->trigger('before_write', $this)) { - return false; - } - - $db = $this->db(); - $db->startTrans(); - $pk = $this->getPk(); - try { - if ($this->isUpdate) { - // 自动写入 - $this->autoCompleteData($this->update); - - if (false === $this->trigger('before_update', $this)) { - return false; - } - - if (empty($where) && !empty($this->updateWhere)) { - $where = $this->updateWhere; - } - - // 获取有更新的数据 - $data = $this->getChangedData(); - // 保留主键数据 - foreach ($this->data as $key => $val) { - if ($this->isPk($key)) { - $data[$key] = $val; - } - } - // 处理模型数据 - $data = $this->parseData($this->name, $data); - if (is_string($pk) && isset($data[$pk])) { - if (!isset($where[$pk])) { - unset($where); - $where[$pk] = $data[$pk]; - } - unset($data[$pk]); - } - // 写入主表数据 - $result = $db->strict(false)->where($where)->update($data); - - // 写入附表数据 - foreach ($this->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $db->getTable($model) : $model; - // 处理关联模型数据 - $data = $this->parseData($name, $data); - if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { - $result = 1; - } - } - - // 新增回调 - $this->trigger('after_update', $this); - } else { - // 自动写入 - $this->autoCompleteData($this->insert); - - // 自动写入创建时间 - if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { - $this->setAttr($this->createTime, null); - } - - if (false === $this->trigger('before_insert', $this)) { - return false; - } - - // 处理模型数据 - $data = $this->parseData($this->name, $this->data); - // 写入主表数据 - $result = $db->name($this->name)->strict(false)->insert($data); - if ($result) { - $insertId = $db->getLastInsID($sequence); - // 写入外键数据 - if ($insertId) { - if (is_string($pk)) { - $this->data[$pk] = $insertId; - } - $this->data[$this->fk] = $insertId; - } - - // 写入附表数据 - $source = $this->data; - if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { - unset($source[$pk]); - } - foreach ($this->relationModel as $key => $model) { - $name = is_int($key) ? $model : $key; - $table = is_int($key) ? $db->getTable($model) : $model; - // 处理关联模型数据 - $data = $this->parseData($name, $source); - Db::table($table)->strict(false)->insert($data); - } - } - // 标记为更新 - $this->isUpdate = true; - // 新增回调 - $this->trigger('after_insert', $this); - } - $db->commit(); - // 写入回调 - $this->trigger('after_write', $this); - - $this->origin = $this->data; - return $result; - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - - /** - * 删除当前的记录 并删除关联数据 - * @access public - * @return int - * @throws \Exception - */ - public function delete() - { - if (false === $this->trigger('before_delete', $this)) { - return false; - } - - $db = $this->db(); - $db->startTrans(); - try { - $result = $db->delete($this->data); - if ($result) { - // 获取主键数据 - $pk = $this->data[$this->getPk()]; - - // 删除关联数据 - foreach ($this->relationModel as $key => $model) { - $table = is_int($key) ? $db->getTable($model) : $model; - $query = new Query; - $query->table($table)->where($this->fk, $pk)->delete(); - } - } - $this->trigger('after_delete', $this); - $db->commit(); - return $result; - } catch (\Exception $e) { - $db->rollback(); - throw $e; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Db; +use think\db\Query; +use think\Model; + +class Merge extends Model +{ + + protected $relationModel = []; // HAS ONE 关联的模型列表 + protected $fk = ''; // 外键名 默认为主表名_id + protected $mapFields = []; // 需要处理的模型映射字段,避免混淆 array( id => 'user.id' ) + + /** + * 构造函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + parent::__construct($data); + + // 设置默认外键名 仅支持单一外键 + if (empty($this->fk)) { + $this->fk = strtolower($this->name) . '_id'; + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param string|array $with 关联预查询 + * @param bool $cache 是否缓存 + * @return \think\Model + */ + public static function get($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->find($data); + } + + /** + * 附加查询表达式 + * @access protected + * @param \think\db\Query $query 查询对象 + * @return \think\db\Query + */ + protected static function attachQuery($query) + { + $class = new static(); + $master = $class->name; + $fields = self::getModelField($query, $master, '', $class->mapFields, $class->field); + $query->alias($master)->field($fields); + + foreach ($class->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $query->getTable($name) : $model; + $query->join($table . ' ' . $name, $name . '.' . $class->fk . '=' . $master . '.' . $class->getPk()); + $fields = self::getModelField($query, $name, $table, $class->mapFields, $class->field); + $query->field($fields); + } + return $query; + } + + /** + * 获取关联模型的字段 并解决混淆 + * @access protected + * @param \think\db\Query $query 查询对象 + * @param string $name 模型名称 + * @param string $table 关联表名称 + * @param array $map 字段映射 + * @param array $fields 查询字段 + * @return array + */ + protected static function getModelField($query, $name, $table = '', $map = [], $fields = []) + { + // 获取模型的字段信息 + $fields = $fields ?: $query->getTableInfo($table, 'fields'); + $array = []; + foreach ($fields as $field) { + if ($key = array_search($name . '.' . $field, $map)) { + // 需要处理映射字段 + $array[] = $name . '.' . $field . ' AS ' . $key; + } else { + $array[] = $field; + } + } + return $array; + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache + * @return array|false|string + */ + public static function all($data = null, $with = [], $cache = false) + { + $query = self::parseQuery($data, $with, $cache); + $query = self::attachQuery($query); + return $query->select($data); + } + + /** + * 处理写入的模型数据 + * @access public + * @param string $model 模型名称 + * @param array $data 数据 + * @return array + */ + protected function parseData($model, $data) + { + $item = []; + foreach ($data as $key => $val) { + if ($this->fk != $key && array_key_exists($key, $this->mapFields)) { + list($name, $key) = explode('.', $this->mapFields[$key]); + if ($model == $name) { + $item[$key] = $val; + } + } else { + $item[$key] = $val; + } + } + return $item; + } + + /** + * 保存模型数据 以及关联数据 + * @access public + * @param mixed $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return false|int + * @throws \Exception + */ + public function save($data = [], $where = [], $sequence = null) + { + if (!empty($data)) { + // 数据自动验证 + if (!$this->validateData($data)) { + return false; + } + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + if (!empty($where)) { + $this->isUpdate = true; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 自动写入更新时间 + if ($this->autoWriteTimestamp && $this->updateTime && !isset($this->data[$this->updateTime])) { + $this->setAttr($this->updateTime, null); + } + + // 事件回调 + if (false === $this->trigger('before_write', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + $pk = $this->getPk(); + try { + if ($this->isUpdate) { + // 自动写入 + $this->autoCompleteData($this->update); + + if (false === $this->trigger('before_update', $this)) { + return false; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + // 处理模型数据 + $data = $this->parseData($this->name, $data); + if (is_string($pk) && isset($data[$pk])) { + if (!isset($where[$pk])) { + unset($where); + $where[$pk] = $data[$pk]; + } + unset($data[$pk]); + } + // 写入主表数据 + $result = $db->strict(false)->where($where)->update($data); + + // 写入附表数据 + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $data); + if (Db::table($table)->strict(false)->where($this->fk, $this->data[$this->getPk()])->update($data)) { + $result = 1; + } + } + + // 新增回调 + $this->trigger('after_update', $this); + } else { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 自动写入创建时间 + if ($this->autoWriteTimestamp && $this->createTime && !isset($this->data[$this->createTime])) { + $this->setAttr($this->createTime, null); + } + + if (false === $this->trigger('before_insert', $this)) { + return false; + } + + // 处理模型数据 + $data = $this->parseData($this->name, $this->data); + // 写入主表数据 + $result = $db->name($this->name)->strict(false)->insert($data); + if ($result) { + $insertId = $db->getLastInsID($sequence); + // 写入外键数据 + if ($insertId) { + if (is_string($pk)) { + $this->data[$pk] = $insertId; + } + $this->data[$this->fk] = $insertId; + } + + // 写入附表数据 + $source = $this->data; + if ($insertId && is_string($pk) && isset($source[$pk]) && $this->fk != $pk) { + unset($source[$pk]); + } + foreach ($this->relationModel as $key => $model) { + $name = is_int($key) ? $model : $key; + $table = is_int($key) ? $db->getTable($model) : $model; + // 处理关联模型数据 + $data = $this->parseData($name, $source); + Db::table($table)->strict(false)->insert($data); + } + } + // 标记为更新 + $this->isUpdate = true; + // 新增回调 + $this->trigger('after_insert', $this); + } + $db->commit(); + // 写入回调 + $this->trigger('after_write', $this); + + $this->origin = $this->data; + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 删除当前的记录 并删除关联数据 + * @access public + * @return int + * @throws \Exception + */ + public function delete() + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $db = $this->db(); + $db->startTrans(); + try { + $result = $db->delete($this->data); + if ($result) { + // 获取主键数据 + $pk = $this->data[$this->getPk()]; + + // 删除关联数据 + foreach ($this->relationModel as $key => $model) { + $table = is_int($key) ? $db->getTable($model) : $model; + $query = new Query; + $query->table($table)->where($this->fk, $pk)->delete(); + } + } + $this->trigger('after_delete', $this); + $db->commit(); + return $result; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + +} diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php old mode 100755 new mode 100644 index e06fa16..13525cd --- a/thinkphp/library/think/model/Pivot.php +++ b/thinkphp/library/think/model/Pivot.php @@ -1,42 +1,42 @@ - -// +---------------------------------------------------------------------- - -namespace think\model; - -use think\Model; - -class Pivot extends Model -{ - - /** @var Model */ - public $parent; - - protected $autoWriteTimestamp = false; - - /** - * 架构函数 - * @access public - * @param array|object $data 数据 - * @param Model $parent 上级模型 - * @param string $table 中间数据表名 - */ - public function __construct($data = [], Model $parent = null, $table = '') - { - $this->parent = $parent; - - if (is_null($this->name)) { - $this->name = $table; - } - - parent::__construct($data); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php old mode 100755 new mode 100644 index ff319dc..25fe88d --- a/thinkphp/library/think/model/Relation.php +++ b/thinkphp/library/think/model/Relation.php @@ -1,155 +1,155 @@ - -// +---------------------------------------------------------------------- - -namespace think\model; - -use think\db\Query; -use think\Exception; -use think\Model; - -/** - * Class Relation - * @package think\model - * - * @mixin Query - */ -abstract class Relation -{ - // 父模型对象 - protected $parent; - /** @var Model 当前关联的模型类 */ - protected $model; - /** @var Query 关联模型查询对象 */ - protected $query; - // 关联表外键 - protected $foreignKey; - // 关联表主键 - protected $localKey; - // 基础查询 - protected $baseQuery; - // 是否为自关联 - protected $selfRelation; - - /** - * 获取关联的所属模型 - * @access public - * @return Model - */ - public function getParent() - { - return $this->parent; - } - - /** - * 获取当前的关联模型对象实例 - * @access public - * @return Model - */ - public function getModel() - { - return $this->query->getModel(); - } - - /** - * 获取关联的查询对象 - * @access public - * @return Query - */ - public function getQuery() - { - return $this->query; - } - - /** - * 设置当前关联为自关联 - * @access public - * @param bool $self 是否自关联 - * @return $this - */ - public function selfRelation($self = true) - { - $this->selfRelation = $self; - return $this; - } - - /** - * 当前关联是否为自关联 - * @access public - * @return bool - */ - public function isSelfRelation() - { - return $this->selfRelation; - } - - /** - * 封装关联数据集 - * @access public - * @param array $resultSet 数据集 - * @return mixed - */ - protected function resultSetBuild($resultSet) - { - return (new $this->model)->toCollection($resultSet); - } - - protected function getQueryFields($model) - { - $fields = $this->query->getOptions('field'); - return $this->getRelationQueryFields($fields, $model); - } - - protected function getRelationQueryFields($fields, $model) - { - if ($fields) { - - if (is_string($fields)) { - $fields = explode(',', $fields); - } - - foreach ($fields as &$field) { - if (false === strpos($field, '.')) { - $field = $model . '.' . $field; - } - } - } else { - $fields = $model . '.*'; - } - - return $fields; - } - - /** - * 执行基础查询(仅执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - {} - - public function __call($method, $args) - { - if ($this->query) { - // 执行基础查询 - $this->baseQuery(); - - $result = call_user_func_array([$this->query, $method], $args); - if ($result instanceof Query) { - return $this; - } else { - $this->baseQuery = false; - return $result; - } - } else { - throw new Exception('method not exists:' . __CLASS__ . '->' . $method); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型对象实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 获取关联的查询对象 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + if ($result instanceof Query) { + return $this; + } else { + $this->baseQuery = false; + return $result; + } + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php old mode 100755 new mode 100644 index f8928d3..c1cbab9 --- a/thinkphp/library/think/model/relation/BelongsTo.php +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -1,243 +1,243 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Loader; -use think\Model; - -class BelongsTo extends OneToOne -{ - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 关联主键 - * @param string $joinType JOIN类型 - * @param string $relation 关联名 - */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) - { - $this->parent = $parent; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - $this->relation = $relation; - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @access public - * @return array|false|\PDOStatement|string|Model - */ - public function getRelation($subRelation = '', $closure = null) - { - $foreignKey = $this->foreignKey; - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $relationModel = $this->query - ->removeWhereField($this->localKey) - ->where($this->localKey, $this->parent->$foreignKey) - ->relation($subRelation) - ->find(); - - if ($relationModel) { - $relationModel->setParent(clone $this->parent); - } - - return $relationModel; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*') - { - return $this->parent; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); - - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } - } - $fields = $this->getRelationQueryFields($fields, $model); - - return $this->parent->db()->alias($model) - ->field($fields) - ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) - ->where($where); - } - - /** - * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$foreignKey)) { - $range[] = $result->$foreignKey; - } - } - - if (!empty($range)) { - $this->query->removeWhereField($localKey); - $data = $this->eagerlyWhere($this->query, [ - $localKey => [ - 'in', - $range, - ], - ], $localKey, $relation, $subRelation, $closure); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - // 关联模型 - if (!isset($data[$result->$foreignKey])) { - $relationModel = null; - } else { - $relationModel = $data[$result->$foreignKey]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } else { - // 设置关联属性 - $result->setRelation($attr, $relationModel); - } - } - } - } - - /** - * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - $this->query->removeWhereField($localKey); - $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); - // 关联模型 - if (!isset($data[$result->$foreignKey])) { - $relationModel = null; - } else { - $relationModel = $data[$result->$foreignKey]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } else { - // 设置关联属性 - $result->setRelation(Loader::parseName($relation), $relationModel); - } - } - - /** - * 添加关联数据 - * @access public - * @param Model $model 关联模型对象 - * @return Model - */ - public function associate($model) - { - $foreignKey = $this->foreignKey; - $pk = $model->getPk(); - - $this->parent->setAttr($foreignKey, $model->$pk); - $this->parent->save(); - - return $this->parent->setRelation($this->relation, $model); - } - - /** - * 注销关联数据 - * @access public - * @return Model - */ - public function dissociate() - { - $foreignKey = $this->foreignKey; - - $this->parent->setAttr($foreignKey, null); - $this->parent->save(); - - return $this->parent->setRelation($this->relation, null); - } - - /** - * 执行基础查询(仅执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery)) { - if (isset($this->parent->{$this->foreignKey})) { - // 关联查询带入关联条件 - $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); - } - - $this->baseQuery = true; - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $joinType JOIN类型 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER', $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + $this->relation = $relation; + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @access public + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [ + $localKey => [ + 'in', + $range, + ], + ], $localKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($localKey); + $data = $this->eagerlyWhere($this->query, [$localKey => $result->$foreignKey], $localKey, $relation, $subRelation, $closure); + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $foreignKey = $this->foreignKey; + $pk = $model->getPk(); + + $this->parent->setAttr($foreignKey, $model->$pk); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $foreignKey = $this->foreignKey; + + $this->parent->setAttr($foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php old mode 100755 new mode 100644 index baeeb3c..787edc0 --- a/thinkphp/library/think/model/relation/BelongsToMany.php +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -1,599 +1,599 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\Collection; -use think\Db; -use think\db\Query; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Pivot; -use think\model\Relation; -use think\Paginator; - -class BelongsToMany extends Relation -{ - // 中间表表名 - protected $middle; - // 中间表模型名称 - protected $pivotName; - // 中间表模型对象 - protected $pivot; - - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $table 中间表名 - * @param string $foreignKey 关联模型外键 - * @param string $localKey 当前模型关联键 - */ - public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) - { - $this->parent = $parent; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - if (false !== strpos($table, '\\')) { - $this->pivotName = $table; - $this->middle = basename(str_replace('\\', '/', $table)); - } else { - $this->middle = $table; - } - $this->query = (new $model)->db(); - $this->pivot = $this->newPivot(); - - if ('think\model\Pivot' == get_class($this->pivot)) { - $this->pivot->name($this->middle); - } - } - - /** - * 设置中间表模型 - * @param $pivot - * @return $this - */ - public function pivot($pivot) - { - $this->pivotName = $pivot; - return $this; - } - - /** - * 获取中间表更新条件 - * @param $data - * @return array - */ - protected function getUpdateWhere($data) - { - return [ - $this->localKey => $data[$this->localKey], - $this->foreignKey => $data[$this->foreignKey], - ]; - } - - /** - * 实例化中间表模型 - * @param array $data - * @param bool $isUpdate - * @return Pivot - * @throws Exception - */ - protected function newPivot($data = [], $isUpdate = false) - { - $class = $this->pivotName ?: '\\think\\model\\Pivot'; - $pivot = new $class($data, $this->parent, $this->middle); - if ($pivot instanceof Pivot) { - return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; - } else { - throw new Exception('pivot model must extends: \think\model\Pivot'); - } - } - - /** - * 合成中间表模型 - * @param array|Collection|Paginator $models - */ - protected function hydratePivot($models) - { - foreach ($models as $model) { - $pivot = []; - foreach ($model->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($model->$key); - } - } - } - $model->setRelation('pivot', $this->newPivot($pivot, true)); - } - } - - /** - * 创建关联查询Query对象 - * @return Query - */ - protected function buildQuery() - { - $foreignKey = $this->foreignKey; - $localKey = $this->localKey; - $pk = $this->parent->getPk(); - // 关联查询 - $condition['pivot.' . $localKey] = $this->parent->$pk; - return $this->belongsToManyQuery($foreignKey, $localKey, $condition); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection - */ - public function getRelation($subRelation = '', $closure = null) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $result = $this->buildQuery()->relation($subRelation)->select(); - $this->hydratePivot($result); - return $result; - } - - /** - * 重载select方法 - * @param null $data - * @return false|\PDOStatement|string|Collection - */ - public function select($data = null) - { - $result = $this->buildQuery()->select($data); - $this->hydratePivot($result); - return $result; - } - - /** - * 重载paginate方法 - * @param null $listRows - * @param bool $simple - * @param array $config - * @return Paginator - */ - public function paginate($listRows = null, $simple = false, $config = []) - { - $result = $this->buildQuery()->paginate($listRows, $simple, $config); - $this->hydratePivot($result); - return $result; - } - - /** - * 重载find方法 - * @param null $data - * @return array|false|\PDOStatement|string|Model - */ - public function find($data = null) - { - $result = $this->buildQuery()->find($data); - if ($result) { - $this->hydratePivot([$result]); - } - return $result; - } - - /** - * 查找多条记录 如果不存在则抛出异常 - * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model - */ - public function selectOrFail($data = null) - { - return $this->failException(true)->select($data); - } - - /** - * 查找单条记录 如果不存在则抛出异常 - * @access public - * @param array|string|Query|\Closure $data - * @return array|\PDOStatement|string|Model - */ - public function findOrFail($data = null) - { - return $this->failException(true)->find($data); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - return $this->parent; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - * @throws Exception - */ - public function hasWhere($where = [], $fields = null) - { - throw new Exception('relation not support: hasWhere'); - } - - /** - * 设置中间表的查询条件 - * @param $field - * @param null $op - * @param null $condition - * @return $this - */ - public function wherePivot($field, $op = null, $condition = null) - { - $field = 'pivot.' . $field; - $this->query->where($field, $op, $condition); - return $this; - } - - /** - * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - - $pk = $resultSet[0]->getPk(); - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - // 查询关联数据 - $data = $this->eagerlyManyToMany([ - 'pivot.' . $localKey => [ - 'in', - $range, - ], - ], $relation, $subRelation); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - - $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); - } - } - } - - /** - * 预载入关联查询(单个数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - $pk = $result->getPk(); - if (isset($result->$pk)) { - $pk = $result->$pk; - // 查询管理数据 - $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); - - // 关联数据封装 - if (!isset($data[$pk])) { - $data[$pk] = []; - } - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - $pk = $result->getPk(); - $count = 0; - if (isset($result->$pk)) { - $pk = $result->$pk; - $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); - } - return $count; - } - - /** - * 获取关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @return string - */ - public function getRelationCountQuery($closure) - { - return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ - 'pivot.' . $this->localKey => [ - 'exp', - Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), - ], - ])->fetchSql()->count(); - } - - /** - * 多对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @return array - */ - protected function eagerlyManyToMany($where, $relation, $subRelation = '') - { - // 预载入关联查询 支持嵌套预载入 - $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $pivot = []; - foreach ($set->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ('pivot' == $name) { - $pivot[$attr] = $val; - unset($set->$key); - } - } - } - $set->setRelation('pivot', $this->newPivot($pivot, true)); - $data[$pivot[$this->localKey]][] = $set; - } - return $data; - } - - /** - * BELONGS TO MANY 关联查询 - * @access public - * @param string $foreignKey 关联模型关联键 - * @param string $localKey 当前模型关联键 - * @param array $condition 关联查询条件 - * @return Query - */ - protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) - { - // 关联查询封装 - $tableName = $this->query->getTable(); - $table = $this->pivot->getTable(); - $fields = $this->getQueryFields($tableName); - - $query = $this->query->field($fields) - ->field(true, false, $table, 'pivot', 'pivot__'); - - if (empty($this->baseQuery)) { - $relationFk = $this->query->getPk(); - $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) - ->where($condition); - } - return $query; - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return integer - */ - public function save($data, array $pivot = []) - { - // 保存关联表/中间表数据 - return $this->attach($data, $pivot); - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @param array $pivot 中间表额外数据 - * @param bool $samePivot 额外数据是否相同 - * @return integer - */ - public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) - { - $result = false; - foreach ($dataSet as $key => $data) { - if (!$samePivot) { - $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; - } else { - $pivotData = $pivot; - } - $result = $this->attach($data, $pivotData); - } - return $result; - } - - /** - * 附加关联的一个中间表数据 - * @access public - * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 - * @param array $pivot 中间表额外数据 - * @return array|Pivot - * @throws Exception - */ - public function attach($data, $pivot = []) - { - if (is_array($data)) { - if (key($data) === 0) { - $id = $data; - } else { - // 保存关联表数据 - $model = new $this->model; - $model->save($data); - $id = $model->getLastInsID(); - } - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - - if ($id) { - // 保存中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - $ids = (array) $id; - foreach ($ids as $id) { - $pivot[$this->foreignKey] = $id; - $this->pivot->insert($pivot, true); - $result[] = $this->newPivot($pivot, true); - } - if (count($result) == 1) { - // 返回中间表模型对象 - $result = $result[0]; - } - return $result; - } else { - throw new Exception('miss relation data'); - } - } - - /** - * 解除关联的一个中间表数据 - * @access public - * @param integer|array $data 数据 可以使用关联对象的主键 - * @param bool $relationDel 是否同时删除关联表数据 - * @return integer - */ - public function detach($data = null, $relationDel = false) - { - if (is_array($data)) { - $id = $data; - } elseif (is_numeric($data) || is_string($data)) { - // 根据关联表主键直接写入中间表 - $id = $data; - } elseif ($data instanceof Model) { - // 根据关联表主键直接写入中间表 - $relationFk = $data->getPk(); - $id = $data->$relationFk; - } - // 删除中间表数据 - $pk = $this->parent->getPk(); - $pivot[$this->localKey] = $this->parent->$pk; - if (isset($id)) { - $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; - } - $this->pivot->where($pivot)->delete(); - // 删除关联表数据 - if (isset($id) && $relationDel) { - $model = $this->model; - $model::destroy($id); - } - } - - /** - * 数据同步 - * @param array $ids - * @param bool $detaching - * @return array - */ - public function sync($ids, $detaching = true) - { - $changes = [ - 'attached' => [], - 'detached' => [], - 'updated' => [], - ]; - $pk = $this->parent->getPk(); - $current = $this->pivot->where($this->localKey, $this->parent->$pk) - ->column($this->foreignKey); - $records = []; - - foreach ($ids as $key => $value) { - if (!is_array($value)) { - $records[$value] = []; - } else { - $records[$key] = $value; - } - } - - $detach = array_diff($current, array_keys($records)); - - if ($detaching && count($detach) > 0) { - $this->detach($detach); - - $changes['detached'] = $detach; - } - - foreach ($records as $id => $attributes) { - if (!in_array($id, $current)) { - $this->attach($id, $attributes); - $changes['attached'][] = $id; - } elseif (count($attributes) > 0 && - $this->attach($id, $attributes) - ) { - $changes['updated'][] = $id; - } - } - - return $changes; - - } - - /** - * 执行基础查询(进执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $table = $this->pivot->getTable(); - $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); - $this->baseQuery = true; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表模型对象 + protected $pivot; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + + if ('think\model\Pivot' == get_class($this->pivot)) { + $this->pivot->name($this->middle); + } + } + + /** + * 设置中间表模型 + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + $model->setRelation('pivot', $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + $pk = $this->parent->getPk(); + // 关联查询 + $condition['pivot.' . $localKey] = $this->parent->$pk; + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载select方法 + * @param null $data + * @return false|\PDOStatement|string|Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载paginate方法 + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + return $result; + } + + /** + * 重载find方法 + * @param null $data + * @return array|false|\PDOStatement|string|Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @param $field + * @param null $op + * @param null $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $field = 'pivot.' . $field; + $this->query->where($field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + 'pivot.' . $localKey => [ + 'in', + $range, + ], + ], $relation, $subRelation); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany(['pivot.' . $this->localKey => $pk], $relation, $subRelation); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + $pk = $result->$pk; + $count = $this->belongsToManyQuery($this->foreignKey, $this->localKey, ['pivot.' . $this->localKey => $pk])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + 'pivot.' . $this->localKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->count(); + } + + /** + * 多对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $set->setRelation('pivot', $this->newPivot($pivot, true)); + $data[$pivot[$this->localKey]][] = $set; + } + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access public + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return integer + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return integer + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = false; + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + $result = $this->attach($data, $pivotData); + } + return $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $model->save($data); + $id = $model->getLastInsID(); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->insert($pivot, true); + $result[] = $this->newPivot($pivot, true); + } + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + if (isset($id)) { + $pivot[$this->foreignKey] = is_array($id) ? ['in', $id] : $id; + } + $this->pivot->where($pivot)->delete(); + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + } + + /** + * 数据同步 + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + $pk = $this->parent->getPk(); + $current = $this->pivot->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && + $this->attach($id, $attributes) + ) { + $changes['updated'][] = $id; + } + } + + return $changes; + + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + $this->query->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk())->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php old mode 100755 new mode 100644 index 3ee6237..cfcf2a9 --- a/thinkphp/library/think/model/relation/HasMany.php +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -1,295 +1,295 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Loader; -use think\Model; -use think\model\Relation; - -class HasMany extends Relation -{ - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - */ - public function __construct(Model $parent, $model, $foreignKey, $localKey) - { - $this->parent = $parent; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->query = (new $model)->db(); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection - */ - public function getRelation($subRelation = '', $closure = null) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $list = $this->relation($subRelation)->select(); - $parent = clone $this->parent; - - foreach ($list as &$model) { - $model->setParent($parent); - } - - return $list; - } - - /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$localKey)) { - $range[] = $result->$localKey; - } - } - - if (!empty($range)) { - $data = $this->eagerlyOneToMany($this->query, [ - $this->foreignKey => [ - 'in', - $range, - ], - ], $relation, $subRelation, $closure); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - - foreach ($data[$result->$localKey] as &$relationModel) { - $relationModel->setParent(clone $result); - } - - $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); - } - } - } - - /** - * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - - if (isset($result->$localKey)) { - $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); - // 关联数据封装 - if (!isset($data[$result->$localKey])) { - $data[$result->$localKey] = []; - } - - foreach ($data[$result->$localKey] as &$relationModel) { - $relationModel->setParent(clone $result); - } - - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - $localKey = $this->localKey; - $count = 0; - if (isset($result->$localKey)) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); - } - return $count; - } - - /** - * 创建关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @return string - */ - public function getRelationCountQuery($closure) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $localKey = $this->localKey ?: $this->parent->getPk(); - return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); - } - - /** - * 一对多 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool $closure - * @return array - */ - protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) - { - $foreignKey = $this->foreignKey; - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - } - $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$foreignKey][] = $set; - } - return $data; - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return Model|false - */ - public function save($data) - { - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $model = new $this->model; - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - return $model->save($data) ? $model : false; - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @return integer - */ - public function saveAll(array $dataSet) - { - $result = false; - foreach ($dataSet as $key => $data) { - $result = $this->save($data); - } - return $result; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); - - return $this->parent->db() - ->alias($model) - ->field($model . '.*') - ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) - ->group($relation . '.' . $this->foreignKey) - ->having('count(' . $id . ')' . $operator . $count); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); - - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } - } - - $fields = $this->getRelationQueryFields($fields, $model); - - return $this->parent->db()->alias($model) - ->field($fields) - ->group($model . '.' . $this->localKey) - ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) - ->where($where); - } - - /** - * 执行基础查询(进执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery)) { - if (isset($this->parent->{$this->localKey})) { - // 关联查询带入关联条件 - $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); - } - $this->baseQuery = true; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany($this->query, [ + $this->foreignKey => [ + 'in', + $range, + ], + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$localKey])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $data = $this->eagerlyOneToMany($this->query, [$this->foreignKey => $result->$localKey], $relation, $subRelation, $closure); + // 关联数据封装 + if (!isset($data[$result->$localKey])) { + $data[$result->$localKey] = []; + } + + foreach ($data[$result->$localKey] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$localKey])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $localKey = $this->localKey; + $count = 0; + if (isset($result->$localKey)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where($this->foreignKey, $result->$localKey)->count(); + } + return $count; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $localKey = $this->localKey ?: $this->parent->getPk(); + return $this->query->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $localKey)->fetchSql()->count(); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool $closure + * @return array + */ + protected function eagerlyOneToMany($model, $where, $relation, $subRelation = '', $closure = false) + { + $foreignKey = $this->foreignKey; + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + } + $list = $model->removeWhereField($foreignKey)->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $model = new $this->model; + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->group($model . '.' . $this->localKey) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, $this->parent->{$this->localKey}); + } + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php old mode 100755 new mode 100644 index 441c4d0..b7b3333 --- a/thinkphp/library/think/model/relation/HasManyThrough.php +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -1,145 +1,145 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Relation; - -class HasManyThrough extends Relation -{ - // 中间关联表外键 - protected $throughKey; - // 中间表模型 - protected $through; - - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $through 中间模型名 - * @param string $foreignKey 关联外键 - * @param string $throughKey 关联外键 - * @param string $localKey 关联主键 - */ - public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) - { - $this->parent = $parent; - $this->model = $model; - $this->through = $through; - $this->foreignKey = $foreignKey; - $this->throughKey = $throughKey; - $this->localKey = $localKey; - $this->query = (new $model)->db(); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection - */ - public function getRelation($subRelation = '', $closure = null) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - - return $this->relation($subRelation)->select(); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - return $this->parent; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - throw new Exception('relation not support: hasWhere'); - } - - /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - {} - - /** - * 预载入关联查询 返回模型对象 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - {} - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - {} - - /** - * 执行基础查询(进执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery) && $this->parent->getData()) { - $through = $this->through; - $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); - $throughTable = $through::getTable(); - $pk = (new $through)->getPk(); - $throughKey = $this->throughKey; - $modelTable = $this->parent->getTable(); - $this->query->field($alias . '.*')->alias($alias) - ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) - ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) - ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); - $this->baseQuery = true; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 关联主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + {} + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $this->query->field($alias . '.*')->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php old mode 100755 new mode 100644 index 22104e9..db74e4a --- a/thinkphp/library/think/model/relation/HasOne.php +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -1,215 +1,215 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Loader; -use think\Model; - -class HasOne extends OneToOne -{ - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $foreignKey 关联外键 - * @param string $localKey 当前模型主键 - * @param string $joinType JOIN类型 - */ - public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') - { - $this->parent = $parent; - $this->model = $model; - $this->foreignKey = $foreignKey; - $this->localKey = $localKey; - $this->joinType = $joinType; - $this->query = (new $model)->db(); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return array|false|\PDOStatement|string|Model - */ - public function getRelation($subRelation = '', $closure = null) - { - // 执行关联定义方法 - $localKey = $this->localKey; - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - // 判断关联类型执行查询 - $relationModel = $this->query - ->removeWhereField($this->foreignKey) - ->where($this->foreignKey, $this->parent->$localKey) - ->relation($subRelation) - ->find(); - - if ($relationModel) { - $relationModel->setParent(clone $this->parent); - } - - return $relationModel; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @return Query - */ - public function has() - { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - return $this->parent->db() - ->alias($model) - ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { - $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); - }); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - $table = $this->query->getTable(); - $model = basename(str_replace('\\', '/', get_class($this->parent))); - $relation = basename(str_replace('\\', '/', $this->model)); - - if (is_array($where)) { - foreach ($where as $key => $val) { - if (false === strpos($key, '.')) { - $where[$relation . '.' . $key] = $val; - unset($where[$key]); - } - } - } - $fields = $this->getRelationQueryFields($fields, $model); - - return $this->parent->db()->alias($model) - ->field($fields) - ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) - ->where($where); - } - - /** - * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (isset($result->$localKey)) { - $range[] = $result->$localKey; - } - } - - if (!empty($range)) { - $this->query->removeWhereField($foreignKey); - $data = $this->eagerlyWhere($this->query, [ - $foreignKey => [ - 'in', - $range, - ], - ], $foreignKey, $relation, $subRelation, $closure); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - // 关联模型 - if (!isset($data[$result->$localKey])) { - $relationModel = null; - } else { - $relationModel = $data[$result->$localKey]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } else { - // 设置关联属性 - $result->setRelation($attr, $relationModel); - } - } - } - } - - /** - * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - protected function eagerlyOne(&$result, $relation, $subRelation, $closure) - { - $localKey = $this->localKey; - $foreignKey = $this->foreignKey; - $this->query->removeWhereField($foreignKey); - $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); - - // 关联模型 - if (!isset($data[$result->$localKey])) { - $relationModel = null; - } else { - $relationModel = $data[$result->$localKey]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - if (!empty($this->bindAttr)) { - // 绑定关联属性 - $this->bindAttr($relationModel, $result, $this->bindAttr); - } else { - $result->setRelation(Loader::parseName($relation), $relationModel); - } - } - - /** - * 执行基础查询(仅执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery)) { - if (isset($this->parent->{$this->localKey})) { - // 关联查询带入关联条件 - $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); - } - - $this->baseQuery = true; - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + * @param string $joinType JOIN类型 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $joinType = 'INNER') + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = $joinType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return array|false|\PDOStatement|string|Model + */ + public function getRelation($subRelation = '', $closure = null) + { + // 执行关联定义方法 + $localKey = $this->localKey; + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @return Query + */ + public function has() + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation])->field($relation . '.' . $foreignKey)->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + foreach ($where as $key => $val) { + if (false === strpos($key, '.')) { + $where[$relation . '.' . $key] = $val; + unset($where[$key]); + } + } + } + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db()->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [ + $foreignKey => [ + 'in', + $range, + ], + ], $foreignKey, $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $this->query->removeWhereField($foreignKey); + $data = $this->eagerlyWhere($this->query, [$foreignKey => $result->$localKey], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php old mode 100755 new mode 100644 index 8767f3c..8945042 --- a/thinkphp/library/think/model/relation/MorphMany.php +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -1,287 +1,287 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\Db; -use think\db\Query; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Relation; - -class MorphMany extends Relation -{ - // 多态字段 - protected $morphKey; - protected $morphType; - // 多态类型 - protected $type; - - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 - */ - public function __construct(Model $parent, $model, $morphKey, $morphType, $type) - { - $this->parent = $parent; - $this->model = $model; - $this->type = $type; - $this->morphKey = $morphKey; - $this->morphType = $morphType; - $this->query = (new $model)->db(); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection - */ - public function getRelation($subRelation = '', $closure = null) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $list = $this->relation($subRelation)->select(); - $parent = clone $this->parent; - - foreach ($list as &$model) { - $model->setParent($parent); - } - - return $list; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - throw new Exception('relation not support: has'); - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - throw new Exception('relation not support: hasWhere'); - } - - /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - $morphType = $this->morphType; - $morphKey = $this->morphKey; - $type = $this->type; - $range = []; - foreach ($resultSet as $result) { - $pk = $result->getPk(); - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - $data = $this->eagerlyMorphToMany([ - $morphKey => ['in', $range], - $morphType => $type, - ], $relation, $subRelation, $closure); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - foreach ($data[$result->$pk] as &$relationModel) { - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); - } - } - } - - /** - * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - $pk = $result->getPk(); - if (isset($result->$pk)) { - $data = $this->eagerlyMorphToMany([ - $this->morphKey => $result->$pk, - $this->morphType => $this->type, - ], $relation, $subRelation, $closure); - - if (!isset($data[$result->$pk])) { - $data[$result->$pk] = []; - } - - foreach ($data[$result->$pk] as &$relationModel) { - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - - $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); - } - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - $pk = $result->getPk(); - $count = 0; - if (isset($result->$pk)) { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); - } - return $count; - } - - /** - * 获取关联统计子查询 - * @access public - * @param \Closure $closure 闭包 - * @return string - */ - public function getRelationCountQuery($closure) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - - return $this->query->where([ - $this->morphKey => [ - 'exp', - Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), - ], - $this->morphType => $this->type, - ])->fetchSql()->count(); - } - - /** - * 多态一对多 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 - * @return array - */ - protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) - { - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $this]); - } - $list = $this->query->where($where)->with($subRelation)->select(); - $morphKey = $this->morphKey; - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$morphKey][] = $set; - } - return $data; - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return Model|false - */ - public function save($data) - { - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $pk = $this->parent->getPk(); - - $model = new $this->model; - $data[$this->morphKey] = $this->parent->$pk; - $data[$this->morphType] = $this->type; - return $model->save($data) ? $model : false; - } - - /** - * 批量保存当前关联数据对象 - * @access public - * @param array $dataSet 数据集 - * @return integer - */ - public function saveAll(array $dataSet) - { - $result = false; - foreach ($dataSet as $key => $data) { - $result = $this->save($data); - } - return $result; - } - - /** - * 执行基础查询(进执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); - $this->baseQuery = true; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Db; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $list = $this->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToMany([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $data = $this->eagerlyMorphToMany([ + $this->morphKey => $result->$pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$result->$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + $pk = $result->getPk(); + $count = 0; + if (isset($result->$pk)) { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $count = $this->query->where([$this->morphKey => $result->$pk, $this->morphType => $this->type])->count(); + } + return $count; + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @return string + */ + public function getRelationCountQuery($closure) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + + return $this->query->where([ + $this->morphKey => [ + 'exp', + Db::raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + $this->morphType => $this->type, + ])->fetchSql()->count(); + } + + /** + * 多态一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $model = new $this->model; + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + return $model->save($data) ? $model : false; + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return integer + */ + public function saveAll(array $dataSet) + { + $result = false; + foreach ($dataSet as $key => $data) { + $result = $this->save($data); + } + return $result; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php old mode 100755 new mode 100644 index f295939..2337599 --- a/thinkphp/library/think/model/relation/MorphOne.php +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -1,230 +1,230 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Relation; - -class MorphOne extends Relation -{ - // 多态字段 - protected $morphKey; - protected $morphType; - // 多态类型 - protected $type; - - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $model 模型名 - * @param string $morphKey 关联外键 - * @param string $morphType 多态字段名 - * @param string $type 多态类型 - */ - public function __construct(Model $parent, $model, $morphKey, $morphType, $type) - { - $this->parent = $parent; - $this->model = $model; - $this->type = $type; - $this->morphKey = $morphKey; - $this->morphType = $morphType; - $this->query = (new $model)->db(); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return false|\PDOStatement|string|\think\Collection - */ - public function getRelation($subRelation = '', $closure = null) - { - if ($closure) { - call_user_func_array($closure, [ & $this->query]); - } - $relationModel = $this->relation($subRelation)->find(); - - if ($relationModel) { - $relationModel->setParent(clone $this->parent); - } - - return $relationModel; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - return $this->parent; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - throw new Exception('relation not support: hasWhere'); - } - - /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - $morphType = $this->morphType; - $morphKey = $this->morphKey; - $type = $this->type; - $range = []; - foreach ($resultSet as $result) { - $pk = $result->getPk(); - // 获取关联外键列表 - if (isset($result->$pk)) { - $range[] = $result->$pk; - } - } - - if (!empty($range)) { - $data = $this->eagerlyMorphToOne([ - $morphKey => ['in', $range], - $morphType => $type, - ], $relation, $subRelation, $closure); - // 关联属性名 - $attr = Loader::parseName($relation); - // 关联数据封装 - foreach ($resultSet as $result) { - if (!isset($data[$result->$pk])) { - $relationModel = null; - } else { - $relationModel = $data[$result->$pk]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } - - $result->setRelation($attr, $relationModel); - } - } - } - - /** - * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - $pk = $result->getPk(); - if (isset($result->$pk)) { - $pk = $result->$pk; - $data = $this->eagerlyMorphToOne([ - $this->morphKey => $pk, - $this->morphType => $this->type, - ], $relation, $subRelation, $closure); - - if (isset($data[$pk])) { - $relationModel = $data[$pk]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - } else { - $relationModel = null; - } - - $result->setRelation(Loader::parseName($relation), $relationModel); - } - } - - /** - * 多态一对一 关联模型预查询 - * @access public - * @param array $where 关联预查询条件 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure 闭包 - * @return array - */ - protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) - { - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $this]); - } - $list = $this->query->where($where)->with($subRelation)->find(); - $morphKey = $this->morphKey; - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$morphKey][] = $set; - } - return $data; - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return Model|false - */ - public function save($data) - { - if ($data instanceof Model) { - $data = $data->getData(); - } - // 保存关联表数据 - $pk = $this->parent->getPk(); - - $model = new $this->model; - $data[$this->morphKey] = $this->parent->$pk; - $data[$this->morphType] = $this->type; - return $model->save($data) ? $model : false; - } - - /** - * 执行基础查询(进执行一次) - * @access protected - * @return void - */ - protected function baseQuery() - { - if (empty($this->baseQuery) && $this->parent->getData()) { - $pk = $this->parent->getPk(); - $map[$this->morphKey] = $this->parent->$pk; - $map[$this->morphType] = $this->type; - $this->query->where($map); - $this->baseQuery = true; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return false|\PDOStatement|string|\think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + call_user_func_array($closure, [ & $this->query]); + } + $relationModel = $this->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + $morphKey => ['in', $range], + $morphType => $type, + ], $relation, $subRelation, $closure); + // 关联属性名 + $attr = Loader::parseName($relation); + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + $this->morphKey => $pk, + $this->morphType => $this->type, + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = false) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $this]); + } + $list = $this->query->where($where)->with($subRelation)->find(); + $morphKey = $this->morphKey; + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $model = new $this->model; + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + return $model->save($data) ? $model : false; + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $map[$this->morphKey] = $this->parent->$pk; + $map[$this->morphType] = $this->type; + $this->query->where($map); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php old mode 100755 new mode 100644 index 0040ee4..a3a0293 --- a/thinkphp/library/think/model/relation/MorphTo.php +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -1,288 +1,288 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Relation; - -class MorphTo extends Relation -{ - // 多态字段 - protected $morphKey; - protected $morphType; - // 多态别名 - protected $alias; - protected $relation; - - /** - * 构造函数 - * @access public - * @param Model $parent 上级模型对象 - * @param string $morphType 多态字段名 - * @param string $morphKey 外键名 - * @param array $alias 多态别名定义 - * @param string $relation 关联名 - */ - public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) - { - $this->parent = $parent; - $this->morphType = $morphType; - $this->morphKey = $morphKey; - $this->alias = $alias; - $this->relation = $relation; - } - - /** - * 获取当前的关联模型类的实例 - * @access public - * @return Model - */ - public function getModel() - { - $morphType = $this->morphType; - $model = $this->parseModel($this->parent->$morphType); - return (new $model); - } - - /** - * 延迟获取关联数据 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包查询条件 - * @return mixed - */ - public function getRelation($subRelation = '', $closure = null) - { - $morphKey = $this->morphKey; - $morphType = $this->morphType; - // 多态模型 - $model = $this->parseModel($this->parent->$morphType); - // 主键数据 - $pk = $this->parent->$morphKey; - $relationModel = (new $model)->relation($subRelation)->find($pk); - - if ($relationModel) { - $relationModel->setParent(clone $this->parent); - } - return $relationModel; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param string $operator 比较操作符 - * @param integer $count 个数 - * @param string $id 关联表的统计字段 - * @param string $joinType JOIN类型 - * @return Query - */ - public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') - { - return $this->parent; - } - - /** - * 根据关联条件查询当前模型 - * @access public - * @param mixed $where 查询条件(数组或者闭包) - * @param mixed $fields 字段 - * @return Query - */ - public function hasWhere($where = [], $fields = null) - { - throw new Exception('relation not support: hasWhere'); - } - - /** - * 解析模型的完整命名空间 - * @access public - * @param string $model 模型名(或者完整类名) - * @return string - */ - protected function parseModel($model) - { - if (isset($this->alias[$model])) { - $model = $this->alias[$model]; - } - if (false === strpos($model, '\\')) { - $path = explode('\\', get_class($this->parent)); - array_pop($path); - array_push($path, Loader::parseName($model, 1)); - $model = implode('\\', $path); - } - return $model; - } - - /** - * 设置多态别名 - * @access public - * @param array $alias 别名定义 - * @return $this - */ - public function setAlias($alias) - { - $this->alias = $alias; - return $this; - } - - /** - * 移除关联查询参数 - * @access public - * @return $this - */ - public function removeOption() - { - return $this; - } - - /** - * 预载入关联查询 - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - * @throws Exception - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - $morphKey = $this->morphKey; - $morphType = $this->morphType; - $range = []; - foreach ($resultSet as $result) { - // 获取关联外键列表 - if (!empty($result->$morphKey)) { - $range[$result->$morphType][] = $result->$morphKey; - } - } - - if (!empty($range)) { - // 关联属性名 - $attr = Loader::parseName($relation); - foreach ($range as $key => $val) { - // 多态类型映射 - $model = $this->parseModel($key); - $obj = new $model; - $pk = $obj->getPk(); - $list = $obj->all($val, $subRelation); - $data = []; - foreach ($list as $k => $vo) { - $data[$vo->$pk] = $vo; - } - foreach ($resultSet as $result) { - if ($key == $result->$morphType) { - // 关联模型 - if (!isset($data[$result->$morphKey])) { - throw new Exception('relation data not exists :' . $this->model); - } else { - $relationModel = $data[$result->$morphKey]; - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - - $result->setRelation($attr, $relationModel); - } - } - } - } - } - } - - /** - * 预载入关联查询 - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - $morphKey = $this->morphKey; - $morphType = $this->morphType; - // 多态类型映射 - $model = $this->parseModel($result->{$this->morphType}); - $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - } - - /** - * 多态MorphTo 关联模型预查询 - * @access public - * @param object $model 关联模型对象 - * @param string $relation 关联名 - * @param $result - * @param string $subRelation 子关联 - * @return void - */ - protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') - { - // 预载入关联查询 支持嵌套预载入 - $pk = $this->parent->{$this->morphKey}; - $data = (new $model)->with($subRelation)->find($pk); - if ($data) { - $data->setParent(clone $result); - $data->isUpdate(true); - } - $result->setRelation(Loader::parseName($relation), $data ?: null); - } - - /** - * 添加关联数据 - * @access public - * @param Model $model 关联模型对象 - * @param string $type 多态类型 - * @return Model - */ - public function associate($model, $type = '') - { - $morphKey = $this->morphKey; - $morphType = $this->morphType; - $pk = $model->getPk(); - - $this->parent->setAttr($morphKey, $model->$pk); - $this->parent->setAttr($morphType, $type ?: get_class($model)); - $this->parent->save(); - - return $this->parent->setRelation($this->relation, $model); - } - - /** - * 注销关联数据 - * @access public - * @return Model - */ - public function dissociate() - { - $morphKey = $this->morphKey; - $morphType = $this->morphType; - - $this->parent->setAttr($morphKey, null); - $this->parent->setAttr($morphType, null); - $this->parent->save(); - - return $this->parent->setRelation($this->relation, null); - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + protected $relation; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + return (new $model); + } + + /** + * 延迟获取关联数据 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return mixed + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + // 主键数据 + $pk = $this->parent->$morphKey; + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + throw new Exception('relation data not exists :' . $this->model); + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 多态MorphTo 关联模型预查询 + * @access public + * @param object $model 关联模型对象 + * @param string $relation 关联名 + * @param $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + +} diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php old mode 100755 new mode 100644 index 21b0040..dd8595c --- a/thinkphp/library/think/model/relation/OneToOne.php +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -1,326 +1,326 @@ - -// +---------------------------------------------------------------------- - -namespace think\model\relation; - -use think\db\Query; -use think\Exception; -use think\Loader; -use think\Model; -use think\model\Relation; - -/** - * Class OneToOne - * @package think\model\relation - * - */ -abstract class OneToOne extends Relation -{ - // 预载入方式 0 -JOIN 1 -IN - protected $eagerlyType = 1; - // 当前关联的JOIN类型 - protected $joinType; - // 要绑定的属性 - protected $bindAttr = []; - // 关联方法名 - protected $relation; - - /** - * 设置join类型 - * @access public - * @param string $type JOIN类型 - * @return $this - */ - public function joinType($type) - { - $this->joinType = $type; - return $this; - } - - /** - * 预载入关联查询(JOIN方式) - * @access public - * @param Query $query 查询对象 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param \Closure $closure 闭包条件 - * @param bool $first - * @return void - */ - public function eagerly(Query $query, $relation, $subRelation, $closure, $first) - { - $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); - - if ($first) { - $table = $query->getTable(); - $query->table([$table => $name]); - if ($query->getOptions('field')) { - $field = $query->getOptions('field'); - $query->removeOption('field'); - } else { - $field = true; - } - $query->field($field, false, $table, $name); - $field = null; - } - - // 预载入封装 - $joinTable = $this->query->getTable(); - $joinAlias = $relation; - $query->via($joinAlias); - - if ($this instanceof BelongsTo) { - $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); - } else { - $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); - } - - if ($closure) { - // 执行闭包查询 - call_user_func_array($closure, [ & $query]); - // 使用withField指定获取关联的字段,如 - // $query->where(['id'=>1])->withField('id,name'); - if ($query->getOptions('with_field')) { - $field = $query->getOptions('with_field'); - $query->removeOption('with_field'); - } - } elseif (isset($this->option['field'])) { - $field = $this->option['field']; - } - $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); - } - - /** - * 预载入关联查询(数据集) - * @param array $resultSet - * @param string $relation - * @param string $subRelation - * @param \Closure $closure - * @return mixed - */ - abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); - - /** - * 预载入关联查询(数据) - * @param Model $result - * @param string $relation - * @param string $subRelation - * @param \Closure $closure - * @return mixed - */ - abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); - - /** - * 预载入关联查询(数据集) - * @access public - * @param array $resultSet 数据集 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) - { - if (1 == $this->eagerlyType) { - // IN查询 - $this->eagerlySet($resultSet, $relation, $subRelation, $closure); - } else { - // 模型关联组装 - foreach ($resultSet as $result) { - $this->match($this->model, $relation, $result); - } - } - } - - /** - * 预载入关联查询(数据) - * @access public - * @param Model $result 数据对象 - * @param string $relation 当前关联名 - * @param string $subRelation 子关联名 - * @param \Closure $closure 闭包 - * @return void - */ - public function eagerlyResult(&$result, $relation, $subRelation, $closure) - { - if (1 == $this->eagerlyType) { - // IN查询 - $this->eagerlyOne($result, $relation, $subRelation, $closure); - } else { - // 模型关联组装 - $this->match($this->model, $relation, $result); - } - } - - /** - * 保存(新增)当前关联数据对象 - * @access public - * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 - * @return Model|false - */ - public function save($data) - { - if ($data instanceof Model) { - $data = $data->getData(); - } - $model = new $this->model; - // 保存关联表数据 - $data[$this->foreignKey] = $this->parent->{$this->localKey}; - return $model->save($data) ? $model : false; - } - - /** - * 设置预载入方式 - * @access public - * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 - * @return $this - */ - public function setEagerlyType($type) - { - $this->eagerlyType = $type; - return $this; - } - - /** - * 获取预载入方式 - * @access public - * @return integer - */ - public function getEagerlyType() - { - return $this->eagerlyType; - } - - /** - * 绑定关联表的属性到父模型属性 - * @access public - * @param mixed $attr 要绑定的属性列表 - * @return $this - */ - public function bind($attr) - { - if (is_string($attr)) { - $attr = explode(',', $attr); - } - $this->bindAttr = $attr; - return $this; - } - - /** - * 获取绑定属性 - * @access public - * @return array - */ - public function getBindAttr() - { - return $this->bindAttr; - } - - /** - * 关联统计 - * @access public - * @param Model $result 数据对象 - * @param \Closure $closure 闭包 - * @return integer - */ - public function relationCount($result, $closure) - { - } - - /** - * 一对一 关联模型预查询拼装 - * @access public - * @param string $model 模型名称 - * @param string $relation 关联名 - * @param Model $result 模型对象实例 - * @return void - */ - protected function match($model, $relation, &$result) - { - // 重新组装模型数据 - foreach ($result->getData() as $key => $val) { - if (strpos($key, '__')) { - list($name, $attr) = explode('__', $key, 2); - if ($name == $relation) { - $list[$name][$attr] = $val; - unset($result->$key); - } - } - } - - if (isset($list[$relation])) { - $relationModel = new $model($list[$relation]); - $relationModel->setParent(clone $result); - $relationModel->isUpdate(true); - - if (!empty($this->bindAttr)) { - $this->bindAttr($relationModel, $result, $this->bindAttr); - } - } else { - $relationModel = null; - } - $result->setRelation(Loader::parseName($relation), $relationModel); - } - - /** - * 绑定关联属性到父模型 - * @access protected - * @param Model $model 关联模型对象 - * @param Model $result 父模型对象 - * @param array $bindAttr 绑定属性 - * @return void - * @throws Exception - */ - protected function bindAttr($model, &$result, $bindAttr) - { - foreach ($bindAttr as $key => $attr) { - $key = is_numeric($key) ? $attr : $key; - if (isset($result->$key)) { - throw new Exception('bind attr has exists:' . $key); - } else { - $result->setAttr($key, $model ? $model->$attr : null); - } - } - } - - /** - * 一对一 关联模型预查询(IN方式) - * @access public - * @param object $model 关联模型对象 - * @param array $where 关联预查询条件 - * @param string $key 关联键名 - * @param string $relation 关联名 - * @param string $subRelation 子关联 - * @param bool|\Closure $closure - * @return array - */ - protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) - { - $this->baseQuery = true; - - // 预载入关联查询 支持嵌套预载入 - if ($closure) { - call_user_func_array($closure, [ & $model]); - if ($field = $model->getOptions('with_field')) { - $model->field($field)->removeOption('with_field'); - } - } - $list = $model->where($where)->with($subRelation)->select(); - - // 组装模型数据 - $data = []; - foreach ($list as $set) { - $data[$set->$key] = $set; - } - return $data; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联方法名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $subRelation, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($query->getModel())))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + if ($query->getOptions('field')) { + $field = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $field = true; + } + $query->field($field, false, $table, $name); + $field = null; + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey, $this->joinType); + } else { + $query->join([$joinTable => $joinAlias], $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey, $this->joinType); + } + + if ($closure) { + // 执行闭包查询 + call_user_func_array($closure, [ & $query]); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } elseif (isset($this->option['field'])) { + $field = $this->option['field']; + } + $query->field(isset($field) ? $field : true, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + if (1 == $this->eagerlyType) { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } else { + // 模型关联组装 + $this->match($this->model, $relation, $result); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @return integer + */ + public function relationCount($result, $closure) + { + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @param array $bindAttr 绑定属性 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result, $bindAttr) + { + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param object $model 关联模型对象 + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param bool|\Closure $closure + * @return array + */ + protected function eagerlyWhere($model, $where, $key, $relation, $subRelation = '', $closure = false) + { + $this->baseQuery = true; + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + call_user_func_array($closure, [ & $model]); + if ($field = $model->getOptions('with_field')) { + $model->field($field)->removeOption('with_field'); + } + } + $list = $model->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$key] = $set; + } + return $data; + } + +} diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php old mode 100755 new mode 100644 index 6bfc6be..c5ac60d --- a/thinkphp/library/think/paginator/driver/Bootstrap.php +++ b/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -1,205 +1,205 @@ - -// +---------------------------------------------------------------------- - -namespace think\paginator\driver; - -use think\Paginator; - -class Bootstrap extends Paginator -{ - - /** - * 上一页按钮 - * @param string $text - * @return string - */ - protected function getPreviousButton($text = "«") - { - - if ($this->currentPage() <= 1) { - return $this->getDisabledTextWrapper($text); - } - - $url = $this->url( - $this->currentPage() - 1 - ); - - return $this->getPageLinkWrapper($url, $text); - } - - /** - * 下一页按钮 - * @param string $text - * @return string - */ - protected function getNextButton($text = '»') - { - if (!$this->hasMore) { - return $this->getDisabledTextWrapper($text); - } - - $url = $this->url($this->currentPage() + 1); - - return $this->getPageLinkWrapper($url, $text); - } - - /** - * 页码按钮 - * @return string - */ - protected function getLinks() - { - if ($this->simple) - return ''; - - $block = [ - 'first' => null, - 'slider' => null, - 'last' => null - ]; - - $side = 3; - $window = $side * 2; - - if ($this->lastPage < $window + 6) { - $block['first'] = $this->getUrlRange(1, $this->lastPage); - } elseif ($this->currentPage <= $window) { - $block['first'] = $this->getUrlRange(1, $window + 2); - $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); - } elseif ($this->currentPage > ($this->lastPage - $window)) { - $block['first'] = $this->getUrlRange(1, 2); - $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); - } else { - $block['first'] = $this->getUrlRange(1, 2); - $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); - $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); - } - - $html = ''; - - if (is_array($block['first'])) { - $html .= $this->getUrlLinks($block['first']); - } - - if (is_array($block['slider'])) { - $html .= $this->getDots(); - $html .= $this->getUrlLinks($block['slider']); - } - - if (is_array($block['last'])) { - $html .= $this->getDots(); - $html .= $this->getUrlLinks($block['last']); - } - - return $html; - } - - /** - * 渲染分页html - * @return mixed - */ - public function render() - { - if ($this->hasPages()) { - if ($this->simple) { - return sprintf( - '
      %s %s
    ', - $this->getPreviousButton(), - $this->getNextButton() - ); - } else { - return sprintf( - '
      %s %s %s
    ', - $this->getPreviousButton(), - $this->getLinks(), - $this->getNextButton() - ); - } - } - } - - /** - * 生成一个可点击的按钮 - * - * @param string $url - * @param int $page - * @return string - */ - protected function getAvailablePageWrapper($url, $page) - { - return '
  • ' . $page . '
  • '; - } - - /** - * 生成一个禁用的按钮 - * - * @param string $text - * @return string - */ - protected function getDisabledTextWrapper($text) - { - return '
  • ' . $text . '
  • '; - } - - /** - * 生成一个激活的按钮 - * - * @param string $text - * @return string - */ - protected function getActivePageWrapper($text) - { - return '
  • ' . $text . '
  • '; - } - - /** - * 生成省略号按钮 - * - * @return string - */ - protected function getDots() - { - return $this->getDisabledTextWrapper('...'); - } - - /** - * 批量生成页码按钮. - * - * @param array $urls - * @return string - */ - protected function getUrlLinks(array $urls) - { - $html = ''; - - foreach ($urls as $page => $url) { - $html .= $this->getPageLinkWrapper($url, $page); - } - - return $html; - } - - /** - * 生成普通页码按钮 - * - * @param string $url - * @param int $page - * @return string - */ - protected function getPageLinkWrapper($url, $page) - { - if ($page == $this->currentPage()) { - return $this->getActivePageWrapper($page); - } - - return $this->getAvailablePageWrapper($url, $page); - } -} + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + if ($this->simple) + return ''; + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
      %s %s
    ', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
      %s %s %s
    ', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($page == $this->currentPage()) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/thinkphp/library/think/process/Builder.php b/thinkphp/library/think/process/Builder.php old mode 100755 new mode 100644 index 8b62559..da56163 --- a/thinkphp/library/think/process/Builder.php +++ b/thinkphp/library/think/process/Builder.php @@ -1,233 +1,233 @@ - -// +---------------------------------------------------------------------- - -namespace think\process; - -use think\Process; - -class Builder -{ - private $arguments; - private $cwd; - private $env = null; - private $input; - private $timeout = 60; - private $options = []; - private $inheritEnv = true; - private $prefix = []; - private $outputDisabled = false; - - /** - * 构造方法 - * @param string[] $arguments 参数 - */ - public function __construct(array $arguments = []) - { - $this->arguments = $arguments; - } - - /** - * 创建一个实例 - * @param string[] $arguments 参数 - * @return self - */ - public static function create(array $arguments = []) - { - return new static($arguments); - } - - /** - * 添加一个参数 - * @param string $argument 参数 - * @return self - */ - public function add($argument) - { - $this->arguments[] = $argument; - - return $this; - } - - /** - * 添加一个前缀 - * @param string|array $prefix - * @return self - */ - public function setPrefix($prefix) - { - $this->prefix = is_array($prefix) ? $prefix : [$prefix]; - - return $this; - } - - /** - * 设置参数 - * @param string[] $arguments - * @return self - */ - public function setArguments(array $arguments) - { - $this->arguments = $arguments; - - return $this; - } - - /** - * 设置工作目录 - * @param null|string $cwd - * @return self - */ - public function setWorkingDirectory($cwd) - { - $this->cwd = $cwd; - - return $this; - } - - /** - * 是否初始化环境变量 - * @param bool $inheritEnv - * @return self - */ - public function inheritEnvironmentVariables($inheritEnv = true) - { - $this->inheritEnv = $inheritEnv; - - return $this; - } - - /** - * 设置环境变量 - * @param string $name - * @param null|string $value - * @return self - */ - public function setEnv($name, $value) - { - $this->env[$name] = $value; - - return $this; - } - - /** - * 添加环境变量 - * @param array $variables - * @return self - */ - public function addEnvironmentVariables(array $variables) - { - $this->env = array_replace($this->env, $variables); - - return $this; - } - - /** - * 设置输入 - * @param mixed $input - * @return self - */ - public function setInput($input) - { - $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); - - return $this; - } - - /** - * 设置超时时间 - * @param float|null $timeout - * @return self - */ - public function setTimeout($timeout) - { - if (null === $timeout) { - $this->timeout = null; - - return $this; - } - - $timeout = (float) $timeout; - - if ($timeout < 0) { - throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); - } - - $this->timeout = $timeout; - - return $this; - } - - /** - * 设置proc_open选项 - * @param string $name - * @param string $value - * @return self - */ - public function setOption($name, $value) - { - $this->options[$name] = $value; - - return $this; - } - - /** - * 禁止输出 - * @return self - */ - public function disableOutput() - { - $this->outputDisabled = true; - - return $this; - } - - /** - * 开启输出 - * @return self - */ - public function enableOutput() - { - $this->outputDisabled = false; - - return $this; - } - - /** - * 创建一个Process实例 - * @return Process - */ - public function getProcess() - { - if (0 === count($this->prefix) && 0 === count($this->arguments)) { - throw new \LogicException('You must add() command arguments before calling getProcess().'); - } - - $options = $this->options; - - $arguments = array_merge($this->prefix, $this->arguments); - $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); - - if ($this->inheritEnv) { - // include $_ENV for BC purposes - $env = array_replace($_ENV, $_SERVER, $this->env); - } else { - $env = $this->env; - } - - $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); - - if ($this->outputDisabled) { - $process->disableOutput(); - } - - return $process; - } -} + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/thinkphp/library/think/process/Utils.php b/thinkphp/library/think/process/Utils.php old mode 100755 new mode 100644 index 06c315b..f94c648 --- a/thinkphp/library/think/process/Utils.php +++ b/thinkphp/library/think/process/Utils.php @@ -1,75 +1,75 @@ - -// +---------------------------------------------------------------------- - -namespace think\process; - -class Utils -{ - - /** - * 转义字符串 - * @param string $argument - * @return string - */ - public static function escapeArgument($argument) - { - - if ('' === $argument) { - return escapeshellarg($argument); - } - $escapedArgument = ''; - $quote = false; - foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { - if ('"' === $part) { - $escapedArgument .= '\\"'; - } elseif (self::isSurroundedBy($part, '%')) { - // Avoid environment variable expansion - $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; - } else { - // escape trailing backslash - if ('\\' === substr($part, -1)) { - $part .= '\\'; - } - $quote = true; - $escapedArgument .= $part; - } - } - if ($quote) { - $escapedArgument = '"' . $escapedArgument . '"'; - } - return $escapedArgument; - } - - /** - * 验证并进行规范化Process输入。 - * @param string $caller - * @param mixed $input - * @return string - * @throws \InvalidArgumentException - */ - public static function validateInput($caller, $input) - { - if (null !== $input) { - if (is_resource($input)) { - return $input; - } - if (is_scalar($input)) { - return (string) $input; - } - throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); - } - return $input; - } - - private static function isSurroundedBy($arg, $char) - { - return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/thinkphp/library/think/process/exception/Failed.php b/thinkphp/library/think/process/exception/Failed.php old mode 100755 new mode 100644 index a392b4d..5295082 --- a/thinkphp/library/think/process/exception/Failed.php +++ b/thinkphp/library/think/process/exception/Failed.php @@ -1,42 +1,42 @@ - -// +---------------------------------------------------------------------- - -namespace think\process\exception; - -use think\Process; - -class Failed extends \RuntimeException -{ - - private $process; - - public function __construct(Process $process) - { - if ($process->isSuccessful()) { - throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); - } - - $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); - - if (!$process->isOutputDisabled()) { - $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); - } - - parent::__construct($error); - - $this->process = $process; - } - - public function getProcess() - { - return $this->process; - } -} + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/thinkphp/library/think/process/exception/Timeout.php b/thinkphp/library/think/process/exception/Timeout.php old mode 100755 new mode 100644 index bd518d2..d5f1162 --- a/thinkphp/library/think/process/exception/Timeout.php +++ b/thinkphp/library/think/process/exception/Timeout.php @@ -1,61 +1,61 @@ - -// +---------------------------------------------------------------------- - -namespace think\process\exception; - -use think\Process; - -class Timeout extends \RuntimeException -{ - - const TYPE_GENERAL = 1; - const TYPE_IDLE = 2; - - private $process; - private $timeoutType; - - public function __construct(Process $process, $timeoutType) - { - $this->process = $process; - $this->timeoutType = $timeoutType; - - parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); - } - - public function getProcess() - { - return $this->process; - } - - public function isGeneralTimeout() - { - return $this->timeoutType === self::TYPE_GENERAL; - } - - public function isIdleTimeout() - { - return $this->timeoutType === self::TYPE_IDLE; - } - - public function getExceededTimeout() - { - switch ($this->timeoutType) { - case self::TYPE_GENERAL: - return $this->process->getTimeout(); - - case self::TYPE_IDLE: - return $this->process->getIdleTimeout(); - - default: - throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/thinkphp/library/think/process/pipes/Pipes.php b/thinkphp/library/think/process/pipes/Pipes.php old mode 100755 new mode 100644 index 1b18ec3..82396b8 --- a/thinkphp/library/think/process/pipes/Pipes.php +++ b/thinkphp/library/think/process/pipes/Pipes.php @@ -1,93 +1,93 @@ - -// +---------------------------------------------------------------------- - -namespace think\process\pipes; - -abstract class Pipes -{ - - /** @var array */ - public $pipes = []; - - /** @var string */ - protected $inputBuffer = ''; - /** @var resource|null */ - protected $input; - - /** @var bool */ - private $blocked = true; - - const CHUNK_SIZE = 16384; - - /** - * 返回用于 proc_open 描述符的数组 - * @return array - */ - abstract public function getDescriptors(); - - /** - * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 - * @return string[] - */ - abstract public function getFiles(); - - /** - * 文件句柄和管道中读取数据。 - * @param bool $blocking 是否使用阻塞调用 - * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 - * @return string[] - */ - abstract public function readAndWrite($blocking, $close = false); - - /** - * 返回当前状态如果有打开的文件句柄或管道。 - * @return bool - */ - abstract public function areOpen(); - - /** - * {@inheritdoc} - */ - public function close() - { - foreach ($this->pipes as $pipe) { - fclose($pipe); - } - $this->pipes = []; - } - - /** - * 检查系统调用已被中断 - * @return bool - */ - protected function hasSystemCallBeenInterrupted() - { - $lastError = error_get_last(); - - return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); - } - - protected function unblock() - { - if (!$this->blocked) { - return; - } - - foreach ($this->pipes as $pipe) { - stream_set_blocking($pipe, 0); - } - if (null !== $this->input) { - stream_set_blocking($this->input, 0); - } - - $this->blocked = false; - } -} + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/thinkphp/library/think/process/pipes/Unix.php b/thinkphp/library/think/process/pipes/Unix.php old mode 100755 new mode 100644 index 3cb961b..fd99a5d --- a/thinkphp/library/think/process/pipes/Unix.php +++ b/thinkphp/library/think/process/pipes/Unix.php @@ -1,196 +1,196 @@ - -// +---------------------------------------------------------------------- - -namespace think\process\pipes; - -use think\Process; - -class Unix extends Pipes -{ - - /** @var bool */ - private $ttyMode; - /** @var bool */ - private $ptyMode; - /** @var bool */ - private $disableOutput; - - public function __construct($ttyMode, $ptyMode, $input, $disableOutput) - { - $this->ttyMode = (bool) $ttyMode; - $this->ptyMode = (bool) $ptyMode; - $this->disableOutput = (bool) $disableOutput; - - if (is_resource($input)) { - $this->input = $input; - } else { - $this->inputBuffer = (string) $input; - } - } - - public function __destruct() - { - $this->close(); - } - - /** - * {@inheritdoc} - */ - public function getDescriptors() - { - if ($this->disableOutput) { - $nullstream = fopen('/dev/null', 'c'); - - return [ - ['pipe', 'r'], - $nullstream, - $nullstream, - ]; - } - - if ($this->ttyMode) { - return [ - ['file', '/dev/tty', 'r'], - ['file', '/dev/tty', 'w'], - ['file', '/dev/tty', 'w'], - ]; - } - - if ($this->ptyMode && Process::isPtySupported()) { - return [ - ['pty'], - ['pty'], - ['pty'], - ]; - } - - return [ - ['pipe', 'r'], - ['pipe', 'w'], // stdout - ['pipe', 'w'], // stderr - ]; - } - - /** - * {@inheritdoc} - */ - public function getFiles() - { - return []; - } - - /** - * {@inheritdoc} - */ - public function readAndWrite($blocking, $close = false) - { - - if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { - fclose($this->pipes[0]); - unset($this->pipes[0]); - } - - if (empty($this->pipes)) { - return []; - } - - $this->unblock(); - - $read = []; - - if (null !== $this->input) { - $r = array_merge($this->pipes, ['input' => $this->input]); - } else { - $r = $this->pipes; - } - - unset($r[0]); - - $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; - $e = null; - - if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { - - if (!$this->hasSystemCallBeenInterrupted()) { - $this->pipes = []; - } - - return $read; - } - - if (0 === $n) { - return $read; - } - - foreach ($r as $pipe) { - - $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; - $data = ''; - while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { - $data .= $dataread; - } - - if ('' !== $data) { - if ('input' === $type) { - $this->inputBuffer .= $data; - } else { - $read[$type] = $data; - } - } - - if (false === $data || (true === $close && feof($pipe) && '' === $data)) { - if ('input' === $type) { - $this->input = null; - } else { - fclose($this->pipes[$type]); - unset($this->pipes[$type]); - } - } - } - - if (null !== $w && 0 < count($w)) { - while (strlen($this->inputBuffer)) { - $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k - if ($written > 0) { - $this->inputBuffer = (string) substr($this->inputBuffer, $written); - } else { - break; - } - } - } - - if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { - fclose($this->pipes[0]); - unset($this->pipes[0]); - } - - return $read; - } - - /** - * {@inheritdoc} - */ - public function areOpen() - { - return (bool) $this->pipes; - } - - /** - * 创建一个新的 UnixPipes 实例 - * @param Process $process - * @param string|resource $input - * @return self - */ - public static function create(Process $process, $input) - { - return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); - } -} + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php old mode 100755 new mode 100644 index b905ec1..bba7e9b --- a/thinkphp/library/think/process/pipes/Windows.php +++ b/thinkphp/library/think/process/pipes/Windows.php @@ -1,228 +1,228 @@ - -// +---------------------------------------------------------------------- - -namespace think\process\pipes; - -use think\Process; - -class Windows extends Pipes -{ - - /** @var array */ - private $files = []; - /** @var array */ - private $fileHandles = []; - /** @var array */ - private $readBytes = [ - Process::STDOUT => 0, - Process::STDERR => 0, - ]; - /** @var bool */ - private $disableOutput; - - public function __construct($disableOutput, $input) - { - $this->disableOutput = (bool) $disableOutput; - - if (!$this->disableOutput) { - - $this->files = [ - Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), - Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), - ]; - foreach ($this->files as $offset => $file) { - $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); - if (false === $this->fileHandles[$offset]) { - throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); - } - } - } - - if (is_resource($input)) { - $this->input = $input; - } else { - $this->inputBuffer = $input; - } - } - - public function __destruct() - { - $this->close(); - $this->removeFiles(); - } - - /** - * {@inheritdoc} - */ - public function getDescriptors() - { - if ($this->disableOutput) { - $nullstream = fopen('NUL', 'c'); - - return [ - ['pipe', 'r'], - $nullstream, - $nullstream, - ]; - } - - return [ - ['pipe', 'r'], - ['file', 'NUL', 'w'], - ['file', 'NUL', 'w'], - ]; - } - - /** - * {@inheritdoc} - */ - public function getFiles() - { - return $this->files; - } - - /** - * {@inheritdoc} - */ - public function readAndWrite($blocking, $close = false) - { - $this->write($blocking, $close); - - $read = []; - $fh = $this->fileHandles; - foreach ($fh as $type => $fileHandle) { - if (0 !== fseek($fileHandle, $this->readBytes[$type])) { - continue; - } - $data = ''; - $dataread = null; - while (!feof($fileHandle)) { - if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { - $data .= $dataread; - } - } - if (0 < $length = strlen($data)) { - $this->readBytes[$type] += $length; - $read[$type] = $data; - } - - if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { - fclose($this->fileHandles[$type]); - unset($this->fileHandles[$type]); - } - } - - return $read; - } - - /** - * {@inheritdoc} - */ - public function areOpen() - { - return (bool) $this->pipes && (bool) $this->fileHandles; - } - - /** - * {@inheritdoc} - */ - public function close() - { - parent::close(); - foreach ($this->fileHandles as $handle) { - fclose($handle); - } - $this->fileHandles = []; - } - - /** - * 创建一个新的 WindowsPipes 实例。 - * @param Process $process - * @param $input - * @return self - */ - public static function create(Process $process, $input) - { - return new static($process->isOutputDisabled(), $input); - } - - /** - * 删除临时文件 - */ - private function removeFiles() - { - foreach ($this->files as $filename) { - if (file_exists($filename)) { - @unlink($filename); - } - } - $this->files = []; - } - - /** - * 写入到 stdin 输入 - * @param bool $blocking - * @param bool $close - */ - private function write($blocking, $close) - { - if (empty($this->pipes)) { - return; - } - - $this->unblock(); - - $r = null !== $this->input ? ['input' => $this->input] : null; - $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; - $e = null; - - if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { - if (!$this->hasSystemCallBeenInterrupted()) { - $this->pipes = []; - } - - return; - } - - if (0 === $n) { - return; - } - - if (null !== $w && 0 < count($r)) { - $data = ''; - while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { - $data .= $dataread; - } - - $this->inputBuffer .= $data; - - if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { - $this->input = null; - } - } - - if (null !== $w && 0 < count($w)) { - while (strlen($this->inputBuffer)) { - $written = fwrite($w[0], $this->inputBuffer, 2 << 18); - if ($written > 0) { - $this->inputBuffer = (string) substr($this->inputBuffer, $written); - } else { - break; - } - } - } - - if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { - fclose($this->pipes[0]); - unset($this->pipes[0]); - } - } -} + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $w && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php old mode 100755 new mode 100644 index b81c92b..c906bfc --- a/thinkphp/library/think/response/Json.php +++ b/thinkphp/library/think/response/Json.php @@ -1,51 +1,51 @@ - -// +---------------------------------------------------------------------- - -namespace think\response; - -use think\Response; - -class Json extends Response -{ - // 输出参数 - protected $options = [ - 'json_encode_param' => JSON_UNESCAPED_UNICODE, - ]; - - protected $contentType = 'application/json'; - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - * @throws \Exception - */ - protected function output($data) - { - try { - // 返回JSON数据格式到客户端 包含状态信息 - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); - } - - return $data; - } catch (\Exception $e) { - if ($e->getPrevious()) { - throw $e->getPrevious(); - } - throw $e; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php old mode 100755 new mode 100644 index 484eb25..404bacb --- a/thinkphp/library/think/response/Jsonp.php +++ b/thinkphp/library/think/response/Jsonp.php @@ -1,58 +1,58 @@ - -// +---------------------------------------------------------------------- - -namespace think\response; - -use think\Request; -use think\Response; - -class Jsonp extends Response -{ - // 输出参数 - protected $options = [ - 'var_jsonp_handler' => 'callback', - 'default_jsonp_handler' => 'jsonpReturn', - 'json_encode_param' => JSON_UNESCAPED_UNICODE, - ]; - - protected $contentType = 'application/javascript'; - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - * @throws \Exception - */ - protected function output($data) - { - try { - // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] - $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); - $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; - - $data = json_encode($data, $this->options['json_encode_param']); - - if ($data === false) { - throw new \InvalidArgumentException(json_last_error_msg()); - } - - $data = $handler . '(' . $data . ');'; - return $data; - } catch (\Exception $e) { - if ($e->getPrevious()) { - throw $e->getPrevious(); - } - throw $e; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; + +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = Request::instance()->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if ($data === false) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php old mode 100755 new mode 100644 index 661c554..91694cb --- a/thinkphp/library/think/response/Redirect.php +++ b/thinkphp/library/think/response/Redirect.php @@ -1,105 +1,105 @@ - -// +---------------------------------------------------------------------- - -namespace think\response; - -use think\Request; -use think\Response; -use think\Session; -use think\Url; - -class Redirect extends Response -{ - - protected $options = []; - - // URL参数 - protected $params = []; - - public function __construct($data = '', $code = 302, array $header = [], array $options = []) - { - parent::__construct($data, $code, $header, $options); - $this->cacheControl('no-cache,must-revalidate'); - } - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - */ - protected function output($data) - { - $this->header['Location'] = $this->getTargetUrl(); - return; - } - - /** - * 重定向传值(通过Session) - * @access protected - * @param string|array $name 变量名或者数组 - * @param mixed $value 值 - * @return $this - */ - public function with($name, $value = null) - { - if (is_array($name)) { - foreach ($name as $key => $val) { - Session::flash($key, $val); - } - } else { - Session::flash($name, $value); - } - return $this; - } - - /** - * 获取跳转地址 - * @return string - */ - public function getTargetUrl() - { - if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { - return $this->data; - } else { - return Url::build($this->data, $this->params); - } - } - - public function params($params = []) - { - $this->params = $params; - return $this; - } - - /** - * 记住当前url后跳转 - * @return $this - */ - public function remember() - { - Session::set('redirect_url', Request::instance()->url()); - return $this; - } - - /** - * 跳转到上次记住的url - * @return $this - */ - public function restore() - { - if (Session::has('redirect_url')) { - $this->data = Session::get('redirect_url'); - Session::delete('redirect_url'); - } - return $this; - } -} + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Request; +use think\Response; +use think\Session; +use think\Url; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + return; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + Session::flash($key, $val); + } + } else { + Session::flash($name, $value); + } + return $this; + } + + /** + * 获取跳转地址 + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return Url::build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + return $this; + } + + /** + * 记住当前url后跳转 + * @return $this + */ + public function remember() + { + Session::set('redirect_url', Request::instance()->url()); + return $this; + } + + /** + * 跳转到上次记住的url + * @return $this + */ + public function restore() + { + if (Session::has('redirect_url')) { + $this->data = Session::get('redirect_url'); + Session::delete('redirect_url'); + } + return $this; + } +} diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php old mode 100755 new mode 100644 index 495cdb2..48f944a --- a/thinkphp/library/think/response/View.php +++ b/thinkphp/library/think/response/View.php @@ -1,89 +1,89 @@ - -// +---------------------------------------------------------------------- - -namespace think\response; - -use think\Config; -use think\Response; -use think\View as ViewTemplate; - -class View extends Response -{ - // 输出参数 - protected $options = []; - protected $vars = []; - protected $replace = []; - protected $contentType = 'text/html'; - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - */ - protected function output($data) - { - // 渲染模板输出 - return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) - ->fetch($data, $this->vars, $this->replace); - } - - /** - * 获取视图变量 - * @access public - * @param string $name 模板变量 - * @return mixed - */ - public function getVars($name = null) - { - if (is_null($name)) { - return $this->vars; - } else { - return isset($this->vars[$name]) ? $this->vars[$name] : null; - } - } - - /** - * 模板变量赋值 - * @access public - * @param mixed $name 变量名 - * @param mixed $value 变量值 - * @return $this - */ - public function assign($name, $value = '') - { - if (is_array($name)) { - $this->vars = array_merge($this->vars, $name); - return $this; - } else { - $this->vars[$name] = $value; - } - return $this; - } - - /** - * 视图内容替换 - * @access public - * @param string|array $content 被替换内容(支持批量替换) - * @param string $replace 替换内容 - * @return $this - */ - public function replace($content, $replace = '') - { - if (is_array($content)) { - $this->replace = array_merge($this->replace, $content); - } else { - $this->replace[$content] = $replace; - } - return $this; - } - -} + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Config; +use think\Response; +use think\View as ViewTemplate; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $replace = []; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return ViewTemplate::instance(Config::get('template'), Config::get('view_replace_str')) + ->fetch($data, $this->vars, $this->replace); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + return $this; + } else { + $this->vars[$name] = $value; + } + return $this; + } + + /** + * 视图内容替换 + * @access public + * @param string|array $content 被替换内容(支持批量替换) + * @param string $replace 替换内容 + * @return $this + */ + public function replace($content, $replace = '') + { + if (is_array($content)) { + $this->replace = array_merge($this->replace, $content); + } else { + $this->replace[$content] = $replace; + } + return $this; + } + +} diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php old mode 100755 new mode 100644 index 0f83b74..3bdc052 --- a/thinkphp/library/think/response/Xml.php +++ b/thinkphp/library/think/response/Xml.php @@ -1,102 +1,102 @@ - -// +---------------------------------------------------------------------- - -namespace think\response; - -use think\Collection; -use think\Model; -use think\Response; - -class Xml extends Response -{ - // 输出参数 - protected $options = [ - // 根节点名 - 'root_node' => 'think', - // 根节点属性 - 'root_attr' => '', - //数字索引的子节点名 - 'item_node' => 'item', - // 数字索引子节点key转换的属性名 - 'item_key' => 'id', - // 数据编码 - 'encoding' => 'utf-8', - ]; - - protected $contentType = 'text/xml'; - - /** - * 处理数据 - * @access protected - * @param mixed $data 要处理的数据 - * @return mixed - */ - protected function output($data) - { - // XML数据转换 - return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); - } - - /** - * XML编码 - * @param mixed $data 数据 - * @param string $root 根节点名 - * @param string $item 数字索引的子节点名 - * @param string $attr 根节点属性 - * @param string $id 数字索引子节点key转换的属性名 - * @param string $encoding 数据编码 - * @return string - */ - protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) - { - if (is_array($attr)) { - $array = []; - foreach ($attr as $key => $value) { - $array[] = "{$key}=\"{$value}\""; - } - $attr = implode(' ', $array); - } - $attr = trim($attr); - $attr = empty($attr) ? '' : " {$attr}"; - $xml = ""; - $xml .= "<{$root}{$attr}>"; - $xml .= $this->dataToXml($data, $item, $id); - $xml .= ""; - return $xml; - } - - /** - * 数据XML编码 - * @param mixed $data 数据 - * @param string $item 数字索引时的节点名称 - * @param string $id 数字索引key转换为的属性名 - * @return string - */ - protected function dataToXml($data, $item, $id) - { - $xml = $attr = ''; - - if ($data instanceof Collection || $data instanceof Model) { - $data = $data->toArray(); - } - - foreach ($data as $key => $val) { - if (is_numeric($key)) { - $id && $attr = " {$id}=\"{$key}\""; - $key = $item; - } - $xml .= "<{$key}{$attr}>"; - $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; - $xml .= ""; - } - return $xml; - } -} + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\Response; + +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + return $xml; + } + + /** + * 数据XML编码 + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + return $xml; + } +} diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php old mode 100755 new mode 100644 index d617d08..877d7bd --- a/thinkphp/library/think/session/driver/Memcache.php +++ b/thinkphp/library/think/session/driver/Memcache.php @@ -1,118 +1,118 @@ - -// +---------------------------------------------------------------------- - -namespace think\session\driver; - -use SessionHandler; -use think\Exception; - -class Memcache extends SessionHandler -{ - protected $handler = null; - protected $config = [ - 'host' => '127.0.0.1', // memcache主机 - 'port' => 11211, // memcache端口 - 'expire' => 3600, // session有效期 - 'timeout' => 0, // 连接超时时间(单位:毫秒) - 'persistent' => true, // 长连接 - 'session_name' => '', // memcache key前缀 - ]; - - public function __construct($config = []) - { - $this->config = array_merge($this->config, $config); - } - - /** - * 打开Session - * @access public - * @param string $savePath - * @param mixed $sessName - */ - public function open($savePath, $sessName) - { - // 检测php环境 - if (!extension_loaded('memcache')) { - throw new Exception('not support:memcache'); - } - $this->handler = new \Memcache; - // 支持集群 - $hosts = explode(',', $this->config['host']); - $ports = explode(',', $this->config['port']); - if (empty($ports[0])) { - $ports[0] = 11211; - } - // 建立连接 - foreach ((array) $hosts as $i => $host) { - $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; - $this->config['timeout'] > 0 ? - $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : - $this->handler->addServer($host, $port, $this->config['persistent'], 1); - } - return true; - } - - /** - * 关闭Session - * @access public - */ - public function close() - { - $this->gc(ini_get('session.gc_maxlifetime')); - $this->handler->close(); - $this->handler = null; - return true; - } - - /** - * 读取Session - * @access public - * @param string $sessID - */ - public function read($sessID) - { - return (string) $this->handler->get($this->config['session_name'] . $sessID); - } - - /** - * 写入Session - * @access public - * @param string $sessID - * @param String $sessData - * @return bool - */ - public function write($sessID, $sessData) - { - return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); - } - - /** - * 删除Session - * @access public - * @param string $sessID - * @return bool - */ - public function destroy($sessID) - { - return $this->handler->delete($this->config['session_name'] . $sessID); - } - - /** - * Session 垃圾回收 - * @access public - * @param string $sessMaxLifeTime - * @return true - */ - public function gc($sessMaxLifeTime) - { - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcache extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + $this->handler = new \Memcache; + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php old mode 100755 new mode 100644 index f2dc8fc..2994a07 --- a/thinkphp/library/think/session/driver/Memcached.php +++ b/thinkphp/library/think/session/driver/Memcached.php @@ -1,126 +1,126 @@ - -// +---------------------------------------------------------------------- - -namespace think\session\driver; - -use SessionHandler; -use think\Exception; - -class Memcached extends SessionHandler -{ - protected $handler = null; - protected $config = [ - 'host' => '127.0.0.1', // memcache主机 - 'port' => 11211, // memcache端口 - 'expire' => 3600, // session有效期 - 'timeout' => 0, // 连接超时时间(单位:毫秒) - 'session_name' => '', // memcache key前缀 - 'username' => '', //账号 - 'password' => '', //密码 - ]; - - public function __construct($config = []) - { - $this->config = array_merge($this->config, $config); - } - - /** - * 打开Session - * @access public - * @param string $savePath - * @param mixed $sessName - */ - public function open($savePath, $sessName) - { - // 检测php环境 - if (!extension_loaded('memcached')) { - throw new Exception('not support:memcached'); - } - $this->handler = new \Memcached; - // 设置连接超时时间(单位:毫秒) - if ($this->config['timeout'] > 0) { - $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); - } - // 支持集群 - $hosts = explode(',', $this->config['host']); - $ports = explode(',', $this->config['port']); - if (empty($ports[0])) { - $ports[0] = 11211; - } - // 建立连接 - $servers = []; - foreach ((array) $hosts as $i => $host) { - $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; - } - $this->handler->addServers($servers); - if ('' != $this->config['username']) { - $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); - $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); - } - return true; - } - - /** - * 关闭Session - * @access public - */ - public function close() - { - $this->gc(ini_get('session.gc_maxlifetime')); - $this->handler->quit(); - $this->handler = null; - return true; - } - - /** - * 读取Session - * @access public - * @param string $sessID - */ - public function read($sessID) - { - return (string) $this->handler->get($this->config['session_name'] . $sessID); - } - - /** - * 写入Session - * @access public - * @param string $sessID - * @param String $sessData - * @return bool - */ - public function write($sessID, $sessData) - { - return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); - } - - /** - * 删除Session - * @access public - * @param string $sessID - * @return bool - */ - public function destroy($sessID) - { - return $this->handler->delete($this->config['session_name'] . $sessID); - } - - /** - * Session 垃圾回收 - * @access public - * @param string $sessMaxLifeTime - * @return true - */ - public function gc($sessMaxLifeTime) - { - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Memcached extends SessionHandler +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + $this->handler = new \Memcached; + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + $this->handler->addServers($servers); + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php old mode 100755 new mode 100644 index ef1b263..8d05126 --- a/thinkphp/library/think/session/driver/Redis.php +++ b/thinkphp/library/think/session/driver/Redis.php @@ -1,128 +1,128 @@ - -// +---------------------------------------------------------------------- - -namespace think\session\driver; - -use SessionHandler; -use think\Exception; - -class Redis extends SessionHandler -{ - /** @var \Redis */ - protected $handler = null; - protected $config = [ - 'host' => '127.0.0.1', // redis主机 - 'port' => 6379, // redis端口 - 'password' => '', // 密码 - 'select' => 0, // 操作库 - 'expire' => 3600, // 有效期(秒) - 'timeout' => 0, // 超时时间(秒) - 'persistent' => true, // 是否长连接 - 'session_name' => '', // sessionkey前缀 - ]; - - public function __construct($config = []) - { - $this->config = array_merge($this->config, $config); - } - - /** - * 打开Session - * @access public - * @param string $savePath - * @param mixed $sessName - * @return bool - * @throws Exception - */ - public function open($savePath, $sessName) - { - // 检测php环境 - if (!extension_loaded('redis')) { - throw new Exception('not support:redis'); - } - $this->handler = new \Redis; - - // 建立连接 - $func = $this->config['persistent'] ? 'pconnect' : 'connect'; - $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); - - if ('' != $this->config['password']) { - $this->handler->auth($this->config['password']); - } - - if (0 != $this->config['select']) { - $this->handler->select($this->config['select']); - } - - return true; - } - - /** - * 关闭Session - * @access public - */ - public function close() - { - $this->gc(ini_get('session.gc_maxlifetime')); - $this->handler->close(); - $this->handler = null; - return true; - } - - /** - * 读取Session - * @access public - * @param string $sessID - * @return string - */ - public function read($sessID) - { - return (string) $this->handler->get($this->config['session_name'] . $sessID); - } - - /** - * 写入Session - * @access public - * @param string $sessID - * @param String $sessData - * @return bool - */ - public function write($sessID, $sessData) - { - if ($this->config['expire'] > 0) { - return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); - } else { - return $this->handler->set($this->config['session_name'] . $sessID, $sessData); - } - } - - /** - * 删除Session - * @access public - * @param string $sessID - * @return bool - */ - public function destroy($sessID) - { - return $this->handler->delete($this->config['session_name'] . $sessID) > 0; - } - - /** - * Session 垃圾回收 - * @access public - * @param string $sessMaxLifeTime - * @return bool - */ - public function gc($sessMaxLifeTime) - { - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandler; +use think\Exception; + +class Redis extends SessionHandler +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('redis')) { + throw new Exception('not support:redis'); + } + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param String $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + return $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php old mode 100755 new mode 100644 index dedaf75..c5b72f9 --- a/thinkphp/library/think/template/TagLib.php +++ b/thinkphp/library/think/template/TagLib.php @@ -1,334 +1,334 @@ - -// +---------------------------------------------------------------------- - -namespace think\template; - -use think\Exception; - -/** - * ThinkPHP标签库TagLib解析基类 - * @category Think - * @package Think - * @subpackage Template - * @author liu21st - */ -class TagLib -{ - - /** - * 标签库定义XML文件 - * @var string - * @access protected - */ - protected $xml = ''; - protected $tags = []; // 标签定义 - /** - * 标签库名称 - * @var string - * @access protected - */ - protected $tagLib = ''; - - /** - * 标签库标签列表 - * @var array - * @access protected - */ - protected $tagList = []; - - /** - * 标签库分析数组 - * @var array - * @access protected - */ - protected $parse = []; - - /** - * 标签库是否有效 - * @var bool - * @access protected - */ - protected $valid = false; - - /** - * 当前模板对象 - * @var object - * @access protected - */ - protected $tpl; - - protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; - - /** - * 构造函数 - * @access public - * @param \stdClass $template 模板引擎对象 - */ - public function __construct($template) - { - $this->tpl = $template; - } - - /** - * 按签标库替换页面中的标签 - * @access public - * @param string $content 模板内容 - * @param string $lib 标签库名 - * @return void - */ - public function parseTag(&$content, $lib = '') - { - $tags = []; - $lib = $lib ? strtolower($lib) . ':' : ''; - foreach ($this->tags as $name => $val) { - $close = !isset($val['close']) || $val['close'] ? 1 : 0; - $tags[$close][$lib . $name] = $name; - if (isset($val['alias'])) { - // 别名设置 - $array = (array) $val['alias']; - foreach (explode(',', $array[0]) as $v) { - $tags[$close][$lib . $v] = $name; - } - } - } - - // 闭合标签 - if (!empty($tags[1])) { - $nodes = []; - $regex = $this->getRegex(array_keys($tags[1]), 1); - if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { - $right = []; - foreach ($matches as $match) { - if ('' == $match[1][0]) { - $name = strtolower($match[2][0]); - // 如果有没闭合的标签头则取出最后一个 - if (!empty($right[$name])) { - // $match[0][1]为标签结束符在模板中的位置 - $nodes[$match[0][1]] = [ - 'name' => $name, - 'begin' => array_pop($right[$name]), // 标签开始符 - 'end' => $match[0], // 标签结束符 - ]; - } - } else { - // 标签头压入栈 - $right[strtolower($match[1][0])][] = $match[0]; - } - } - unset($right, $matches); - // 按标签在模板中的位置从后向前排序 - krsort($nodes); - } - - $break = ''; - if ($nodes) { - $beginArray = []; - // 标签替换 从后向前 - foreach ($nodes as $pos => $node) { - // 对应的标签名 - $name = $tags[1][$node['name']]; - $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; - // 解析标签属性 - $attrs = $this->parseAttr($node['begin'][0], $name, $alias); - $method = 'tag' . $name; - // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 - $replace = explode($break, $this->$method($attrs, $break)); - if (count($replace) > 1) { - while ($beginArray) { - $begin = end($beginArray); - // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 - if ($node['end'][1] > $begin['pos']) { - break; - } else { - // 不为子标签时,取出栈中最后一个标签头 - $begin = array_pop($beginArray); - // 替换标签头部 - $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); - } - } - // 替换标签尾部 - $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); - // 把标签头压入栈 - $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; - } - } - while ($beginArray) { - $begin = array_pop($beginArray); - // 替换标签头部 - $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); - } - } - } - // 自闭合标签 - if (!empty($tags[0])) { - $regex = $this->getRegex(array_keys($tags[0]), 0); - $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { - // 对应的标签名 - $name = $tags[0][strtolower($matches[1])]; - $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; - // 解析标签属性 - $attrs = $this->parseAttr($matches[0], $name, $alias); - $method = 'tag' . $name; - return $this->$method($attrs, ''); - }, $content); - } - return; - } - - /** - * 按标签生成正则 - * @access private - * @param array|string $tags 标签名 - * @param boolean $close 是否为闭合标签 - * @return string - */ - public function getRegex($tags, $close) - { - $begin = $this->tpl->config('taglib_begin'); - $end = $this->tpl->config('taglib_end'); - $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; - $tagName = is_array($tags) ? implode('|', $tags) : $tags; - if ($single) { - if ($close) { - // 如果是闭合标签 - $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; - } else { - $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; - } - } else { - if ($close) { - // 如果是闭合标签 - $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; - } else { - $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; - } - } - return '/' . $regex . '/is'; - } - - /** - * 分析标签属性 正则方式 - * @access public - * @param string $str 标签属性字符串 - * @param string $name 标签名 - * @param string $alias 别名 - * @return array - */ - public function parseAttr($str, $name, $alias = '') - { - $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; - $result = []; - if (preg_match_all($regex, $str, $matches)) { - foreach ($matches['name'] as $key => $val) { - $result[$val] = $matches['value'][$key]; - } - if (!isset($this->tags[$name])) { - // 检测是否存在别名定义 - foreach ($this->tags as $key => $val) { - if (isset($val['alias'])) { - $array = (array) $val['alias']; - if (in_array($name, explode(',', $array[0]))) { - $tag = $val; - $type = !empty($array[1]) ? $array[1] : 'type'; - $result[$type] = $name; - break; - } - } - } - } else { - $tag = $this->tags[$name]; - // 设置了标签别名 - if (!empty($alias) && isset($tag['alias'])) { - $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; - $result[$type] = $alias; - } - } - if (!empty($tag['must'])) { - $must = explode(',', $tag['must']); - foreach ($must as $name) { - if (!isset($result[$name])) { - throw new Exception('tag attr must:' . $name); - } - } - } - } else { - // 允许直接使用表达式的标签 - if (!empty($this->tags[$name]['expression'])) { - static $_taglibs; - if (!isset($_taglibs[$name])) { - $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); - $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); - } - $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); - // 清除自闭合标签尾部/ - $result['expression'] = rtrim($result['expression'], '/'); - $result['expression'] = trim($result['expression']); - } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { - throw new Exception('tag error:' . $name); - } - } - return $result; - } - - /** - * 解析条件表达式 - * @access public - * @param string $condition 表达式标签内容 - * @return string - */ - public function parseCondition($condition) - { - if (strpos($condition, ':')) { - $condition = ' ' . substr(strstr($condition, ':'), 1); - } - $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); - $this->tpl->parseVar($condition); - // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 - return $condition; - } - - /** - * 自动识别构建变量 - * @access public - * @param string $name 变量描述 - * @return string - */ - public function autoBuildVar(&$name) - { - $flag = substr($name, 0, 1); - if (':' == $flag) { - // 以:开头为函数调用,解析前去掉: - $name = substr($name, 1); - } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { - // XXX: 这句的写法可能还需要改进 - // 常量不需要解析 - if (defined($name)) { - return $name; - } - // 不以$开头并且也不是常量,自动补上$前缀 - $name = '$' . $name; - } - $this->tpl->parseVar($name); - $this->tpl->parseVarFunction($name); - return $name; - } - - /** - * 获取标签列表 - * @access public - * @return array - */ - // 获取标签定义 - public function getTags() - { - return $this->tags; - } -} + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 构造函数 + * @access public + * @param \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + return; + } + + /** + * 按标签生成正则 + * @access private + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition($condition) + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $flag = substr($name, 0, 1); + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name); + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + // 获取标签定义 + public function getTags() + { + return $this->tags; + } +} diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php old mode 100755 new mode 100644 index 18dba49..a9a86bf --- a/thinkphp/library/think/template/driver/File.php +++ b/thinkphp/library/think/template/driver/File.php @@ -1,74 +1,74 @@ - -// +---------------------------------------------------------------------- - -namespace think\template\driver; - -use think\Exception; - -class File -{ - protected $cacheFile; - - /** - * 写入编译缓存 - * @param string $cacheFile 缓存的文件名 - * @param string $content 缓存的内容 - * @return void|array - */ - public function write($cacheFile, $content) - { - // 检测模板目录 - $dir = dirname($cacheFile); - if (!is_dir($dir)) { - mkdir($dir, 0755, true); - } - // 生成模板缓存文件 - if (false === file_put_contents($cacheFile, $content)) { - throw new Exception('cache write error:' . $cacheFile, 11602); - } - } - - /** - * 读取编译编译 - * @param string $cacheFile 缓存的文件名 - * @param array $vars 变量数组 - * @return void - */ - public function read($cacheFile, $vars = []) - { - $this->cacheFile = $cacheFile; - if (!empty($vars) && is_array($vars)) { - // 模板阵列变量分解成为独立变量 - extract($vars, EXTR_OVERWRITE); - } - //载入模版缓存文件 - include $this->cacheFile; - } - - /** - * 检查编译缓存是否有效 - * @param string $cacheFile 缓存的文件名 - * @param int $cacheTime 缓存时间 - * @return boolean - */ - public function check($cacheFile, $cacheTime) - { - // 缓存文件不存在, 直接返回false - if (!file_exists($cacheFile)) { - return false; - } - if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { - // 缓存是否在有效期 - return false; - } - return true; - } -} + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $dir = dirname($cacheFile); + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + if (0 != $cacheTime && $_SERVER['REQUEST_TIME'] > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + return true; + } +} diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php old mode 100755 new mode 100644 index 9fbc373..31e0698 --- a/thinkphp/library/think/template/taglib/Cx.php +++ b/thinkphp/library/think/template/taglib/Cx.php @@ -1,673 +1,673 @@ - -// +---------------------------------------------------------------------- - -namespace think\template\taglib; - -use think\template\TagLib; - -/** - * CX标签库解析类 - * @category Think - * @package Think - * @subpackage Driver.Taglib - * @author liu21st - */ -class Cx extends Taglib -{ - - // 标签定义 - protected $tags = [ - // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 - 'php' => ['attr' => ''], - 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], - 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], - 'if' => ['attr' => 'condition', 'expression' => true], - 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], - 'else' => ['attr' => '', 'close' => 0], - 'switch' => ['attr' => 'name', 'expression' => true], - 'case' => ['attr' => 'value,break', 'expression' => true], - 'default' => ['attr' => '', 'close' => 0], - 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], - 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], - 'empty' => ['attr' => 'name'], - 'notempty' => ['attr' => 'name'], - 'present' => ['attr' => 'name'], - 'notpresent' => ['attr' => 'name'], - 'defined' => ['attr' => 'name'], - 'notdefined' => ['attr' => 'name'], - 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], - 'assign' => ['attr' => 'name,value', 'close' => 0], - 'define' => ['attr' => 'name,value', 'close' => 0], - 'for' => ['attr' => 'start,end,name,comparison,step'], - 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], - 'function' => ['attr' => 'name,vars,use,call'], - ]; - - /** - * php标签解析 - * 格式: - * {php}echo $name{/php} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagPhp($tag, $content) - { - $parseStr = ''; - return $parseStr; - } - - /** - * volist标签解析 循环输出数据集 - * 格式: - * {volist name="userList" id="user" empty=""} - * {user.username} - * {user.email} - * {/volist} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string|void - */ - public function tagVolist($tag, $content) - { - $name = $tag['name']; - $id = $tag['id']; - $empty = isset($tag['empty']) ? $tag['empty'] : ''; - $key = !empty($tag['key']) ? $tag['key'] : 'i'; - $mod = isset($tag['mod']) ? $tag['mod'] : '2'; - $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; - $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; - // 允许使用函数设定数据集 {$vo.name} - $parseStr = 'autoBuildVar($name); - $parseStr .= '$_result=' . $name . ';'; - $name = '$_result'; - } else { - $name = $this->autoBuildVar($name); - } - - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; - // 设置了输出数组长度 - if (0 != $offset || 'null' != $length) { - $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; - } else { - $parseStr .= ' $__LIST__ = ' . $name . ';'; - } - $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; - $parseStr .= 'else: '; - $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; - $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; - $parseStr .= '++$' . $key . ';?>'; - $parseStr .= $content; - $parseStr .= ''; - - if (!empty($parseStr)) { - return $parseStr; - } - return; - } - - /** - * foreach标签解析 循环输出数据集 - * 格式: - * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} - * {user.username} - * {/foreach} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string|void - */ - public function tagForeach($tag, $content) - { - // 直接使用表达式 - if (!empty($tag['expression'])) { - $expression = ltrim(rtrim($tag['expression'], ')'), '('); - $expression = $this->autoBuildVar($expression); - $parseStr = ''; - $parseStr .= $content; - $parseStr .= ''; - return $parseStr; - } - $name = $tag['name']; - $key = !empty($tag['key']) ? $tag['key'] : 'key'; - $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; - $empty = isset($tag['empty']) ? $tag['empty'] : ''; - $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; - $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; - - $parseStr = 'autoBuildVar($name); - $parseStr .= $var . '=' . $name . '; '; - $name = $var; - } else { - $name = $this->autoBuildVar($name); - } - $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; - // 设置了输出数组长度 - if (0 != $offset || 'null' != $length) { - if (!isset($var)) { - $var = '$_' . uniqid(); - } - $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; - } else { - $var = &$name; - } - - $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; - $parseStr .= 'else: '; - - // 设置了索引项 - if (isset($tag['index'])) { - $index = $tag['index']; - $parseStr .= '$' . $index . '=0; '; - } - $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; - // 设置了索引项 - if (isset($tag['index'])) { - $index = $tag['index']; - if (isset($tag['mod'])) { - $mod = (int) $tag['mod']; - $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; - } - $parseStr .= '++$' . $index . '; '; - } - $parseStr .= '?>'; - // 循环体中的内容 - $parseStr .= $content; - $parseStr .= ''; - - if (!empty($parseStr)) { - return $parseStr; - } - return; - } - - /** - * if标签解析 - * 格式: - * {if condition=" $a eq 1"} - * {elseif condition="$a eq 2" /} - * {else /} - * {/if} - * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagIf($tag, $content) - { - $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; - $condition = $this->parseCondition($condition); - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * elseif标签解析 - * 格式:见if标签 - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagElseif($tag, $content) - { - $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; - $condition = $this->parseCondition($condition); - $parseStr = ''; - return $parseStr; - } - - /** - * else标签解析 - * 格式:见if标签 - * @access public - * @param array $tag 标签属性 - * @return string - */ - public function tagElse($tag) - { - $parseStr = ''; - return $parseStr; - } - - /** - * switch标签解析 - * 格式: - * {switch name="a.name"} - * {case value="1" break="false"}1{/case} - * {case value="2" }2{/case} - * {default /}other - * {/switch} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagSwitch($tag, $content) - { - $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; - $name = $this->autoBuildVar($name); - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * case标签解析 需要配合switch才有效 - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagCase($tag, $content) - { - $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; - $flag = substr($value, 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($value); - $value = 'case ' . $value . ':'; - } elseif (strpos($value, '|')) { - $values = explode('|', $value); - $value = ''; - foreach ($values as $val) { - $value .= 'case "' . addslashes($val) . '":'; - } - } else { - $value = 'case "' . $value . '":'; - } - $parseStr = '' . $content; - $isBreak = isset($tag['break']) ? $tag['break'] : ''; - if ('' == $isBreak || $isBreak) { - $parseStr .= ''; - } - return $parseStr; - } - - /** - * default标签解析 需要配合switch才有效 - * 使用: {default /}ddfdf - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagDefault($tag) - { - $parseStr = ''; - return $parseStr; - } - - /** - * compare标签解析 - * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq - * 格式: {compare name="" type="eq" value="" }content{/compare} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagCompare($tag, $content) - { - $name = $tag['name']; - $value = $tag['value']; - $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 - $name = $this->autoBuildVar($name); - $flag = substr($value, 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($value); - } else { - $value = '\'' . $value . '\''; - } - switch ($type) { - case 'equal': - $type = 'eq'; - break; - case 'notequal': - $type = 'neq'; - break; - } - $type = $this->parseCondition(' ' . $type . ' '); - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * range标签解析 - * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 - * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} - * example: {range name="a" value="1,2,3" type='in' }content{/range} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagRange($tag, $content) - { - $name = $tag['name']; - $value = $tag['value']; - $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 - - $name = $this->autoBuildVar($name); - $flag = substr($value, 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($value); - $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; - } else { - $value = '"' . $value . '"'; - $str = 'explode(\',\',' . $value . ')'; - } - if ('between' == $type) { - $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; - } elseif ('notbetween' == $type) { - $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; - } else { - $fun = ('in' == $type) ? 'in_array' : '!in_array'; - $parseStr = '' . $content . ''; - } - return $parseStr; - } - - /** - * present标签解析 - * 如果某个变量已经设置 则输出内容 - * 格式: {present name="" }content{/present} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagPresent($tag, $content) - { - $name = $tag['name']; - $name = $this->autoBuildVar($name); - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * notpresent标签解析 - * 如果某个变量没有设置,则输出内容 - * 格式: {notpresent name="" }content{/notpresent} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagNotpresent($tag, $content) - { - $name = $tag['name']; - $name = $this->autoBuildVar($name); - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * empty标签解析 - * 如果某个变量为empty 则输出内容 - * 格式: {empty name="" }content{/empty} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagEmpty($tag, $content) - { - $name = $tag['name']; - $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty())): ?>' . $content . ''; - return $parseStr; - } - - /** - * notempty标签解析 - * 如果某个变量不为empty 则输出内容 - * 格式: {notempty name="" }content{/notempty} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagNotempty($tag, $content) - { - $name = $tag['name']; - $name = $this->autoBuildVar($name); - $parseStr = 'isEmpty()))): ?>' . $content . ''; - return $parseStr; - } - - /** - * 判断是否已经定义了该常量 - * {defined name='TXT'}已定义{/defined} - * @param array $tag - * @param string $content - * @return string - */ - public function tagDefined($tag, $content) - { - $name = $tag['name']; - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * 判断是否没有定义了该常量 - * {notdefined name='TXT'}已定义{/notdefined} - * @param array $tag - * @param string $content - * @return string - */ - public function tagNotdefined($tag, $content) - { - $name = $tag['name']; - $parseStr = '' . $content . ''; - return $parseStr; - } - - /** - * load 标签解析 {load file="/static/js/base.js" /} - * 格式:{load file="/static/css/base.css" /} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagLoad($tag, $content) - { - $file = isset($tag['file']) ? $tag['file'] : $tag['href']; - $type = isset($tag['type']) ? strtolower($tag['type']) : ''; - $parseStr = ''; - $endStr = ''; - // 判断是否存在加载条件 允许使用函数判断(默认为isset) - if (isset($tag['value'])) { - $name = $tag['value']; - $name = $this->autoBuildVar($name); - $name = 'isset(' . $name . ')'; - $parseStr .= ''; - $endStr = ''; - } - - // 文件方式导入 - $array = explode(',', $file); - foreach ($array as $val) { - $type = strtolower(substr(strrchr($val, '.'), 1)); - switch ($type) { - case 'js': - $parseStr .= ''; - break; - case 'css': - $parseStr .= ''; - break; - case 'php': - $parseStr .= ''; - break; - } - } - return $parseStr . $endStr; - } - - /** - * assign标签解析 - * 在模板中给某个变量赋值 支持变量赋值 - * 格式: {assign name="" value="" /} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagAssign($tag, $content) - { - $name = $this->autoBuildVar($tag['name']); - $flag = substr($tag['value'], 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($tag['value']); - } else { - $value = '\'' . $tag['value'] . '\''; - } - $parseStr = ''; - return $parseStr; - } - - /** - * define标签解析 - * 在模板中定义常量 支持变量赋值 - * 格式: {define name="" value="" /} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagDefine($tag, $content) - { - $name = '\'' . $tag['name'] . '\''; - $flag = substr($tag['value'], 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($tag['value']); - } else { - $value = '\'' . $tag['value'] . '\''; - } - $parseStr = ''; - return $parseStr; - } - - /** - * for标签解析 - * 格式: - * {for start="" end="" comparison="" step="" name=""} - * content - * {/for} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagFor($tag, $content) - { - //设置默认值 - $start = 0; - $end = 0; - $step = 1; - $comparison = 'lt'; - $name = 'i'; - $rand = rand(); //添加随机数,防止嵌套变量冲突 - //获取属性 - foreach ($tag as $key => $value) { - $value = trim($value); - $flag = substr($value, 0, 1); - if ('$' == $flag || ':' == $flag) { - $value = $this->autoBuildVar($value); - } - - switch ($key) { - case 'start': - $start = $value; - break; - case 'end': - $end = $value; - break; - case 'step': - $step = $value; - break; - case 'comparison': - $comparison = $value; - break; - case 'name': - $name = $value; - break; - } - } - - $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; - $parseStr .= $content; - $parseStr .= ''; - return $parseStr; - } - - /** - * url函数的tag标签 - * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagUrl($tag, $content) - { - $url = isset($tag['link']) ? $tag['link'] : ''; - $vars = isset($tag['vars']) ? $tag['vars'] : ''; - $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; - $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; - return ''; - } - - /** - * function标签解析 匿名函数,可实现递归 - * 使用: - * {function name="func" vars="$data" call="$list" use="&$a,&$b"} - * {if is_array($data)} - * {foreach $data as $val} - * {~func($val) /} - * {/foreach} - * {else /} - * {$data} - * {/if} - * {/function} - * @access public - * @param array $tag 标签属性 - * @param string $content 标签内容 - * @return string - */ - public function tagFunction($tag, $content) - { - $name = !empty($tag['name']) ? $tag['name'] : 'func'; - $vars = !empty($tag['vars']) ? $tag['vars'] : ''; - $call = !empty($tag['call']) ? $tag['call'] : ''; - $use = ['&$' . $name]; - if (!empty($tag['use'])) { - foreach (explode(',', $tag['use']) as $val) { - $use[] = '&' . ltrim(trim($val), '&'); - } - } - $parseStr = '' . $content . '' : '?>'; - return $parseStr; - } -} + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + return; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad($tag, $content) + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + $parseStr = ''; + $endStr = ''; + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + $parseStr = ''; + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl($tag, $content) + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction($tag, $content) + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + $parseStr = '' . $content . '' : '?>'; + return $parseStr; + } +} diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php old mode 100755 new mode 100644 index 6f3d771..f594a43 --- a/thinkphp/library/think/view/driver/Php.php +++ b/thinkphp/library/think/view/driver/Php.php @@ -1,160 +1,160 @@ - -// +---------------------------------------------------------------------- - -namespace think\view\driver; - -use think\App; -use think\exception\TemplateNotFoundException; -use think\Loader; -use think\Log; -use think\Request; - -class Php -{ - // 模板引擎参数 - protected $config = [ - // 视图基础目录(集中式) - 'view_base' => '', - // 模板起始路径 - 'view_path' => '', - // 模板文件后缀 - 'view_suffix' => 'php', - // 模板文件名分隔符 - 'view_depr' => DS, - // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 - 'auto_rule' => 1, - ]; - protected $template; - protected $content; - - public function __construct($config = []) - { - $this->config = array_merge($this->config, $config); - } - - /** - * 检测是否存在模板文件 - * @access public - * @param string $template 模板文件或者模板规则 - * @return bool - */ - public function exists($template) - { - if ('' == pathinfo($template, PATHINFO_EXTENSION)) { - // 获取模板文件名 - $template = $this->parseTemplate($template); - } - return is_file($template); - } - - /** - * 渲染模板文件 - * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 - * @return void - */ - public function fetch($template, $data = []) - { - if ('' == pathinfo($template, PATHINFO_EXTENSION)) { - // 获取模板文件名 - $template = $this->parseTemplate($template); - } - // 模板不存在 抛出异常 - if (!is_file($template)) { - throw new TemplateNotFoundException('template not exists:' . $template, $template); - } - $this->template = $template; - // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); - - extract($data, EXTR_OVERWRITE); - include $this->template; - } - - /** - * 渲染模板内容 - * @access public - * @param string $content 模板内容 - * @param array $data 模板变量 - * @return void - */ - public function display($content, $data = []) - { - $this->content = $content; - - extract($data, EXTR_OVERWRITE); - eval('?>' . $this->content); - } - - /** - * 自动定位模板文件 - * @access private - * @param string $template 模板文件规则 - * @return string - */ - private function parseTemplate($template) - { - if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; - } - - $request = Request::instance(); - // 获取视图根目录 - if (strpos($template, '@')) { - // 跨模块调用 - list($module, $template) = explode('@', $template); - } - if ($this->config['view_base']) { - // 基础视图目录 - $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); - } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; - } - - $depr = $this->config['view_depr']; - if (0 !== strpos($template, '/')) { - $template = str_replace(['/', ':'], $depr, $template); - $controller = Loader::parseName($request->controller()); - if ($controller) { - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; - } - } - } else { - $template = str_replace(['/', ':'], $depr, substr($template, 1)); - } - return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); - } - - /** - * 配置模板引擎 - * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @return void - */ - public function config($name, $value = null) - { - if (is_array($name)) { - $this->config = array_merge($this->config, $name); - } elseif (is_null($value)) { - return isset($this->config[$name]) ? $this->config[$name] : null; - } else { - $this->config[$name] = $value; - } - } - -} + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DS, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + protected $template; + protected $content; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + $this->template = $template; + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + +} diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php old mode 100755 new mode 100644 index eab219b..a314ad6 --- a/thinkphp/library/think/view/driver/Think.php +++ b/thinkphp/library/think/view/driver/Think.php @@ -1,167 +1,167 @@ - -// +---------------------------------------------------------------------- - -namespace think\view\driver; - -use think\App; -use think\exception\TemplateNotFoundException; -use think\Loader; -use think\Log; -use think\Request; -use think\Template; - -class Think -{ - // 模板引擎实例 - private $template; - // 模板引擎参数 - protected $config = [ - // 视图基础目录(集中式) - 'view_base' => '', - // 模板起始路径 - 'view_path' => '', - // 模板文件后缀 - 'view_suffix' => 'html', - // 模板文件名分隔符 - 'view_depr' => DS, - // 是否开启模板编译缓存,设为false则每次都会重新编译 - 'tpl_cache' => true, - // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 - 'auto_rule' => 1, - ]; - - public function __construct($config = []) - { - $this->config = array_merge($this->config, $config); - if (empty($this->config['view_path'])) { - $this->config['view_path'] = App::$modulePath . 'view' . DS; - } - - $this->template = new Template($this->config); - } - - /** - * 检测是否存在模板文件 - * @access public - * @param string $template 模板文件或者模板规则 - * @return bool - */ - public function exists($template) - { - if ('' == pathinfo($template, PATHINFO_EXTENSION)) { - // 获取模板文件名 - $template = $this->parseTemplate($template); - } - return is_file($template); - } - - /** - * 渲染模板文件 - * @access public - * @param string $template 模板文件 - * @param array $data 模板变量 - * @param array $config 模板参数 - * @return void - */ - public function fetch($template, $data = [], $config = []) - { - if ('' == pathinfo($template, PATHINFO_EXTENSION)) { - // 获取模板文件名 - $template = $this->parseTemplate($template); - } - // 模板不存在 抛出异常 - if (!is_file($template)) { - throw new TemplateNotFoundException('template not exists:' . $template, $template); - } - // 记录视图信息 - App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); - $this->template->fetch($template, $data, $config); - } - - /** - * 渲染模板内容 - * @access public - * @param string $template 模板内容 - * @param array $data 模板变量 - * @param array $config 模板参数 - * @return void - */ - public function display($template, $data = [], $config = []) - { - $this->template->display($template, $data, $config); - } - - /** - * 自动定位模板文件 - * @access private - * @param string $template 模板文件规则 - * @return string - */ - private function parseTemplate($template) - { - // 分析模板文件规则 - $request = Request::instance(); - // 获取视图根目录 - if (strpos($template, '@')) { - // 跨模块调用 - list($module, $template) = explode('@', $template); - } - if ($this->config['view_base']) { - // 基础视图目录 - $module = isset($module) ? $module : $request->module(); - $path = $this->config['view_base'] . ($module ? $module . DS : ''); - } else { - $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; - } - - $depr = $this->config['view_depr']; - if (0 !== strpos($template, '/')) { - $template = str_replace(['/', ':'], $depr, $template); - $controller = Loader::parseName($request->controller()); - if ($controller) { - if ('' == $template) { - // 如果模板文件名为空 按照默认规则定位 - $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); - } elseif (false === strpos($template, $depr)) { - $template = str_replace('.', DS, $controller) . $depr . $template; - } - } - } else { - $template = str_replace(['/', ':'], $depr, substr($template, 1)); - } - return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); - } - - /** - * 配置或者获取模板引擎参数 - * @access private - * @param string|array $name 参数名 - * @param mixed $value 参数值 - * @return mixed - */ - public function config($name, $value = null) - { - if (is_array($name)) { - $this->template->config($name); - $this->config = array_merge($this->config, $name); - } elseif (is_null($value)) { - return $this->template->config($name); - } else { - $this->template->$name = $value; - $this->config[$name] = $value; - } - } - - public function __call($method, $params) - { - return call_user_func_array([$this->template, $method], $params); - } -} + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Log; +use think\Request; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + // 模板引擎参数 + protected $config = [ + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DS, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + if (empty($this->config['view_path'])) { + $this->config['view_path'] = App::$modulePath . 'view' . DS; + } + + $this->template = new Template($this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + // 记录视图信息 + App::$debug && Log::record('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]', 'info'); + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = Request::instance(); + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DS : ''); + } else { + $path = isset($module) ? APP_PATH . $module . DS . 'view' . DS : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DS, $controller) . $depr . (1 == $this->config['auto_rule'] ? Loader::parseName($request->action(true)) : $request->action()); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DS, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php old mode 100755 new mode 100644 index 384bdc0..6a57224 --- a/thinkphp/library/traits/controller/Jump.php +++ b/thinkphp/library/traits/controller/Jump.php @@ -1,167 +1,167 @@ -error(); - * $this->redirect(); - * } - * } - */ -namespace traits\controller; - -use think\Config; -use think\exception\HttpResponseException; -use think\Request; -use think\Response; -use think\response\Redirect; -use think\Url; -use think\View as ViewTemplate; - -trait Jump -{ - /** - * 操作成功跳转的快捷方法 - * @access protected - * @param mixed $msg 提示信息 - * @param string $url 跳转的 URL 地址 - * @param mixed $data 返回的数据 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) - { - if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { - $url = Request::instance()->server('HTTP_REFERER'); - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); - } - - $type = $this->getResponseType(); - $result = [ - 'code' => 1, - 'msg' => $msg, - 'data' => $data, - 'url' => $url, - 'wait' => $wait, - ]; - - if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_success_tmpl'), $result); - } - - $response = Response::create($result, $type)->header($header); - - throw new HttpResponseException($response); - } - - /** - * 操作错误跳转的快捷方法 - * @access protected - * @param mixed $msg 提示信息 - * @param string $url 跳转的 URL 地址 - * @param mixed $data 返回的数据 - * @param int $wait 跳转等待时间 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) - { - if (is_null($url)) { - $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; - } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { - $url = Url::build($url); - } - - $type = $this->getResponseType(); - $result = [ - 'code' => 0, - 'msg' => $msg, - 'data' => $data, - 'url' => $url, - 'wait' => $wait, - ]; - - if ('html' == strtolower($type)) { - $template = Config::get('template'); - $view = Config::get('view_replace_str'); - - $result = ViewTemplate::instance($template, $view) - ->fetch(Config::get('dispatch_error_tmpl'), $result); - } - - $response = Response::create($result, $type)->header($header); - - throw new HttpResponseException($response); - } - - /** - * 返回封装后的 API 数据到客户端 - * @access protected - * @param mixed $data 要返回的数据 - * @param int $code 返回的 code - * @param mixed $msg 提示信息 - * @param string $type 返回数据格式 - * @param array $header 发送的 Header 信息 - * @return void - * @throws HttpResponseException - */ - protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) - { - $result = [ - 'code' => $code, - 'msg' => $msg, - 'time' => Request::instance()->server('REQUEST_TIME'), - 'data' => $data, - ]; - $type = $type ?: $this->getResponseType(); - $response = Response::create($result, $type)->header($header); - - throw new HttpResponseException($response); - } - - /** - * URL 重定向 - * @access protected - * @param string $url 跳转的 URL 表达式 - * @param array|int $params 其它 URL 参数 - * @param int $code http code - * @param array $with 隐式传参 - * @return void - * @throws HttpResponseException - */ - protected function redirect($url, $params = [], $code = 302, $with = []) - { - if (is_integer($params)) { - $code = $params; - $params = []; - } - - $response = new Redirect($url); - $response->code($code)->params($params)->with($with); - - throw new HttpResponseException($response); - } - - /** - * 获取当前的 response 输出类型 - * @access protected - * @return string - */ - protected function getResponseType() - { - return Request::instance()->isAjax() - ? Config::get('default_ajax_return') - : Config::get('default_return_type'); - } -} +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Config; +use think\exception\HttpResponseException; +use think\Request; +use think\Response; +use think\response\Redirect; +use think\Url; +use think\View as ViewTemplate; + +trait Jump +{ + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && !is_null(Request::instance()->server('HTTP_REFERER'))) { + $url = Request::instance()->server('HTTP_REFERER'); + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_success_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的 URL 地址 + * @param mixed $data 返回的数据 + * @param int $wait 跳转等待时间 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url)) { + $url = Request::instance()->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url && !strpos($url, '://') && 0 !== strpos($url, '/')) { + $url = Url::build($url); + } + + $type = $this->getResponseType(); + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $template = Config::get('template'); + $view = Config::get('view_replace_str'); + + $result = ViewTemplate::instance($template, $view) + ->fetch(Config::get('dispatch_error_tmpl'), $result); + } + + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的 API 数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param int $code 返回的 code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的 Header 信息 + * @return void + * @throws HttpResponseException + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => Request::instance()->server('REQUEST_TIME'), + 'data' => $data, + ]; + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL 重定向 + * @access protected + * @param string $url 跳转的 URL 表达式 + * @param array|int $params 其它 URL 参数 + * @param int $code http code + * @param array $with 隐式传参 + * @return void + * @throws HttpResponseException + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response = new Redirect($url); + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的 response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + return Request::instance()->isAjax() + ? Config::get('default_ajax_return') + : Config::get('default_return_type'); + } +} diff --git a/thinkphp/library/traits/model/SoftDelete.php b/thinkphp/library/traits/model/SoftDelete.php old mode 100755 new mode 100644 index c14adb5..4c8a6b8 --- a/thinkphp/library/traits/model/SoftDelete.php +++ b/thinkphp/library/traits/model/SoftDelete.php @@ -1,199 +1,199 @@ -getDeleteTimeField(); - - if ($field && !empty($this->data[$field])) { - return true; - } - return false; - } - - /** - * 查询包含软删除的数据 - * @access public - * @return Query - */ - public static function withTrashed() - { - return (new static )->getQuery(); - } - - /** - * 只查询软删除数据 - * @access public - * @return Query - */ - public static function onlyTrashed() - { - $model = new static(); - $field = $model->getDeleteTimeField(true); - - if ($field) { - return $model->getQuery()->useSoftDelete($field, ['not null', '']); - } else { - return $model->getQuery(); - } - } - - /** - * 删除当前的记录 - * @access public - * @param bool $force 是否强制删除 - * @return integer - */ - public function delete($force = false) - { - if (false === $this->trigger('before_delete', $this)) { - return false; - } - - $name = $this->getDeleteTimeField(); - if ($name && !$force) { - // 软删除 - $this->data[$name] = $this->autoWriteTimestamp($name); - $result = $this->isUpdate()->save(); - } else { - // 强制删除当前模型数据 - $result = $this->getQuery()->where($this->getWhere())->delete(); - } - - // 关联删除 - if (!empty($this->relationWrite)) { - foreach ($this->relationWrite as $key => $name) { - $name = is_numeric($key) ? $name : $key; - $result = $this->getRelation($name); - if ($result instanceof Model) { - $result->delete(); - } elseif ($result instanceof Collection || is_array($result)) { - foreach ($result as $model) { - $model->delete(); - } - } - } - } - - $this->trigger('after_delete', $this); - - // 清空原始数据 - $this->origin = []; - - return $result; - } - - /** - * 删除记录 - * @access public - * @param mixed $data 主键列表(支持闭包查询条件) - * @param bool $force 是否强制删除 - * @return integer 成功删除的记录数 - */ - public static function destroy($data, $force = false) - { - if (is_null($data)) { - return 0; - } - - // 包含软删除数据 - $query = self::withTrashed(); - if (is_array($data) && key($data) !== 0) { - $query->where($data); - $data = null; - } elseif ($data instanceof \Closure) { - call_user_func_array($data, [ & $query]); - $data = null; - } - - $count = 0; - if ($resultSet = $query->select($data)) { - foreach ($resultSet as $data) { - $result = $data->delete($force); - $count += $result; - } - } - - return $count; - } - - /** - * 恢复被软删除的记录 - * @access public - * @param array $where 更新条件 - * @return integer - */ - public function restore($where = []) - { - if (empty($where)) { - $pk = $this->getPk(); - $where[$pk] = $this->getData($pk); - } - - $name = $this->getDeleteTimeField(); - - if ($name) { - // 恢复删除 - return $this->getQuery() - ->useSoftDelete($name, ['not null', '']) - ->where($where) - ->update([$name => null]); - } else { - return 0; - } - } - - /** - * 查询默认不包含软删除数据 - * @access protected - * @param Query $query 查询对象 - * @return Query - */ - protected function base($query) - { - $field = $this->getDeleteTimeField(true); - return $field ? $query->useSoftDelete($field) : $query; - } - - /** - * 获取软删除字段 - * @access public - * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) - * @return string - */ - protected function getDeleteTimeField($read = false) - { - $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? - $this->deleteTime : - 'delete_time'; - - if (false === $field) { - return false; - } - - if (!strpos($field, '.')) { - $field = '__TABLE__.' . $field; - } - - if (!$read && strpos($field, '.')) { - $array = explode('.', $field); - $field = array_pop($array); - } - - return $field; - } -} +getDeleteTimeField(); + + if ($field && !empty($this->data[$field])) { + return true; + } + return false; + } + + /** + * 查询包含软删除的数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + return (new static )->getQuery(); + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model->getQuery()->useSoftDelete($field, ['not null', '']); + } else { + return $model->getQuery(); + } + } + + /** + * 删除当前的记录 + * @access public + * @param bool $force 是否强制删除 + * @return integer + */ + public function delete($force = false) + { + if (false === $this->trigger('before_delete', $this)) { + return false; + } + + $name = $this->getDeleteTimeField(); + if ($name && !$force) { + // 软删除 + $this->data[$name] = $this->autoWriteTimestamp($name); + $result = $this->isUpdate()->save(); + } else { + // 强制删除当前模型数据 + $result = $this->getQuery()->where($this->getWhere())->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection || is_array($result)) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + $this->trigger('after_delete', $this); + + // 清空原始数据 + $this->origin = []; + + return $result; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表(支持闭包查询条件) + * @param bool $force 是否强制删除 + * @return integer 成功删除的记录数 + */ + public static function destroy($data, $force = false) + { + if (is_null($data)) { + return 0; + } + + // 包含软删除数据 + $query = self::withTrashed(); + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } + + $count = 0; + if ($resultSet = $query->select($data)) { + foreach ($resultSet as $data) { + $result = $data->delete($force); + $count += $result; + } + } + + return $count; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return integer + */ + public function restore($where = []) + { + if (empty($where)) { + $pk = $this->getPk(); + $where[$pk] = $this->getData($pk); + } + + $name = $this->getDeleteTimeField(); + + if ($name) { + // 恢复删除 + return $this->getQuery() + ->useSoftDelete($name, ['not null', '']) + ->where($where) + ->update([$name => null]); + } else { + return 0; + } + } + + /** + * 查询默认不包含软删除数据 + * @access protected + * @param Query $query 查询对象 + * @return Query + */ + protected function base($query) + { + $field = $this->getDeleteTimeField(true); + return $field ? $query->useSoftDelete($field) : $query; + } + + /** + * 获取软删除字段 + * @access public + * @param bool $read 是否查询操作(写操作的时候会自动去掉表别名) + * @return string + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? + $this->deleteTime : + 'delete_time'; + + if (false === $field) { + return false; + } + + if (!strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } +} diff --git a/thinkphp/library/traits/think/Instance.php b/thinkphp/library/traits/think/Instance.php old mode 100755 new mode 100644 index 1e5a5f3..428c8fd --- a/thinkphp/library/traits/think/Instance.php +++ b/thinkphp/library/traits/think/Instance.php @@ -1,54 +1,54 @@ - -// +---------------------------------------------------------------------- - -namespace traits\think; - -use think\Exception; - -trait Instance -{ - /** - * @var null|static 实例对象 - */ - protected static $instance = null; - - /** - * 获取示例 - * @param array $options 实例配置 - * @return static - */ - public static function instance($options = []) - { - if (is_null(self::$instance)) self::$instance = new self($options); - - return self::$instance; - } - - /** - * 静态调用 - * @param string $method 调用方法 - * @param array $params 调用参数 - * @return mixed - * @throws Exception - */ - public static function __callStatic($method, array $params) - { - if (is_null(self::$instance)) self::$instance = new self(); - - $call = substr($method, 1); - - if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { - throw new Exception("method not exists:" . $method); - } - - return call_user_func_array([self::$instance, $call], $params); - } -} + +// +---------------------------------------------------------------------- + +namespace traits\think; + +use think\Exception; + +trait Instance +{ + /** + * @var null|static 实例对象 + */ + protected static $instance = null; + + /** + * 获取示例 + * @param array $options 实例配置 + * @return static + */ + public static function instance($options = []) + { + if (is_null(self::$instance)) self::$instance = new self($options); + + return self::$instance; + } + + /** + * 静态调用 + * @param string $method 调用方法 + * @param array $params 调用参数 + * @return mixed + * @throws Exception + */ + public static function __callStatic($method, array $params) + { + if (is_null(self::$instance)) self::$instance = new self(); + + $call = substr($method, 1); + + if (0 !== strpos($method, '_') || !is_callable([self::$instance, $call])) { + throw new Exception("method not exists:" . $method); + } + + return call_user_func_array([self::$instance, $call], $params); + } +} diff --git a/thinkphp/logo.png b/thinkphp/logo.png old mode 100755 new mode 100644 diff --git a/thinkphp/phpunit.xml b/thinkphp/phpunit.xml old mode 100755 new mode 100644 index 1d7c9ab..7c6ef03 --- a/thinkphp/phpunit.xml +++ b/thinkphp/phpunit.xml @@ -1,35 +1,35 @@ - - - - - ./tests/thinkphp/ - - - - - - - - ./ - - tests - vendor - - - - - - - - - - + + + + + ./tests/thinkphp/ + + + + + + + + ./ + + tests + vendor + + + + + + + + + + diff --git a/thinkphp/start.php b/thinkphp/start.php old mode 100755 new mode 100644 index 8b566a9..adb1bc6 --- a/thinkphp/start.php +++ b/thinkphp/start.php @@ -1,19 +1,19 @@ - -// +---------------------------------------------------------------------- - -namespace think; - -// ThinkPHP 引导文件 -// 1. 加载基础文件 -require __DIR__ . '/base.php'; - -// 2. 执行应用 -App::run()->send(); + +// +---------------------------------------------------------------------- + +namespace think; + +// ThinkPHP 引导文件 +// 1. 加载基础文件 +require __DIR__ . '/base.php'; + +// 2. 执行应用 +App::run()->send(); diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl old mode 100755 new mode 100644 index 77ec757..8538b4d --- a/thinkphp/tpl/default_index.tpl +++ b/thinkphp/tpl/default_index.tpl @@ -1,10 +1,10 @@ -*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; - } -} +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5
    十年磨一剑 - 为API开发设计的高性能框架

    [ V5.0 版本由 七牛云 独家赞助发布 ]
    '; + } +} diff --git a/thinkphp/tpl/dispatch_jump.tpl b/thinkphp/tpl/dispatch_jump.tpl old mode 100755 new mode 100644 index 5594532..583376b --- a/thinkphp/tpl/dispatch_jump.tpl +++ b/thinkphp/tpl/dispatch_jump.tpl @@ -1,49 +1,49 @@ -{__NOLAYOUT__} - - - - - 跳转提示 - - - -
    - - -

    :)

    -

    - - -

    :(

    -

    - - -

    -

    - 页面自动 跳转 等待时间: -

    -
    - - - +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
    + + +

    :)

    +

    + + +

    :(

    +

    + + +

    +

    + 页面自动 跳转 等待时间: +

    +
    + + + diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl old mode 100755 new mode 100644 index 378fd30..7c5df6f --- a/thinkphp/tpl/page_trace.tpl +++ b/thinkphp/tpl/page_trace.tpl @@ -1,71 +1,71 @@ -
    - - -
    -
    -
    - -
    - - +
    + + +
    +
    +
    + +
    + + diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl old mode 100755 new mode 100644 index 1f989c2..21bbafc --- a/thinkphp/tpl/think_exception.tpl +++ b/thinkphp/tpl/think_exception.tpl @@ -1,537 +1,537 @@ -'.end($names).''; - } - } - - if(!function_exists('parse_file')){ - function parse_file($file, $line) - { - return ''.basename($file)." line {$line}".''; - } - } - - if(!function_exists('parse_args')){ - function parse_args($args) - { - $result = []; - - foreach ($args as $key => $item) { - switch (true) { - case is_object($item): - $value = sprintf('object(%s)', parse_class(get_class($item))); - break; - case is_array($item): - if(count($item) > 3){ - $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); - } else { - $value = sprintf('[%s]', parse_args($item)); - } - break; - case is_string($item): - if(strlen($item) > 20){ - $value = sprintf( - '\'%s...\'', - htmlentities($item), - htmlentities(substr($item, 0, 20)) - ); - } else { - $value = sprintf("'%s'", htmlentities($item)); - } - break; - case is_int($item): - case is_float($item): - $value = $item; - break; - case is_null($item): - $value = 'null'; - break; - case is_bool($item): - $value = '' . ($item ? 'true' : 'false') . ''; - break; - case is_resource($item): - $value = 'resource'; - break; - default: - $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); - break; - } - - $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; - } - - return implode(', ', $result); - } - } -?> - - - - - <?php echo \think\Lang::get('System Error'); ?> - - - - - -
    - -
    - -
    -
    - -
    -
    -

    [

    -
    -

    -
    - -
    - -
    -
      $value) { ?>
    -
    - -
    -

    Call Stack

    -
      -
    1. - -
    2. - -
    3. - -
    -
    -
    - -
    - -

    - -
    - - - -
    -

    Exception Datas

    - $value) { ?> - - - - - - - $val) { ?> - - - - - - - -
    empty
    - -
    - -
    - - - -
    -

    Environment Variables

    - $value) { ?> -
    - -
    -
    -
    empty
    -
    - -

    -
    - $val) { ?> -
    -
    -
    - -
    -
    - -
    - -
    - -
    - - - - - - - - +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + <?php echo \think\Lang::get('System Error'); ?> + + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

    +
      +
    1. + +
    2. + +
    3. + +
    +
    +
    + +
    + +

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> +
    + +
    +
    +
    empty
    +
    + +

    +
    + $val) { ?> +
    +
    +
    + +
    +
    + +
    + +
    + +
    + + + + + + + + diff --git a/vendor/autoload.php b/vendor/autoload.php old mode 100755 new mode 100644 index 27e9bdb..b5b28cf --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -2,6 +2,6 @@ // autoload.php @generated by Composer -require_once __DIR__ . '/composer' . '/autoload_real.php'; +require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb::getLoader(); +return ComposerAutoloaderInitde23ff7bcf12cf4bb58fbe1d807f2677::getLoader(); diff --git a/vendor/bin/phpunit b/vendor/bin/phpunit deleted file mode 100755 index f562735..0000000 --- a/vendor/bin/phpunit +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env sh - -dir=$(d=${0%[/\\]*}; cd "$d"; cd "../phpunit/phpunit" && pwd) - -# See if we are running in Cygwin by checking for cygpath program -if command -v 'cygpath' >/dev/null 2>&1; then - # Cygwin paths start with /cygdrive/ which will break windows PHP, - # so we need to translate the dir path to windows format. However - # we could be using cygwin PHP which does not require this, so we - # test if the path to PHP starts with /cygdrive/ rather than /usr/bin - if [[ $(which php) == /cygdrive/* ]]; then - dir=$(cygpath -m "$dir"); - fi -fi - -dir=$(echo $dir | sed 's/ /\ /g') -"${dir}/phpunit" "$@" diff --git a/vendor/bin/phpunit.bat b/vendor/bin/phpunit.bat deleted file mode 100755 index b377b3e..0000000 --- a/vendor/bin/phpunit.bat +++ /dev/null @@ -1,4 +0,0 @@ -@ECHO OFF -setlocal DISABLEDELAYEDEXPANSION -SET BIN_TARGET=%~dp0/../phpunit/phpunit/phpunit -php "%BIN_TARGET%" %* diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php old mode 100755 new mode 100644 index ff6ecfb..dc02dfb --- a/vendor/composer/ClassLoader.php +++ b/vendor/composer/ClassLoader.php @@ -53,8 +53,9 @@ class ClassLoader private $useIncludePath = false; private $classMap = array(); - private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; public function getPrefixes() { @@ -271,6 +272,26 @@ class ClassLoader return $this->classMapAuthoritative; } + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && ini_get('apc.enabled') ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + /** * Registers this instance as an autoloader. * @@ -313,29 +334,34 @@ class ClassLoader */ public function findFile($class) { - // work around for PHP 5.3.0 - 5.3.2 https://bugs.php.net/50731 - if ('\\' == $class[0]) { - $class = substr($class, 1); - } - // class map lookup if (isset($this->classMap[$class])) { return $this->classMap[$class]; } - if ($this->classMapAuthoritative) { + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { return false; } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } $file = $this->findFileWithExtension($class, '.php'); // Search for Hack files if we are running on HHVM - if ($file === null && defined('HHVM_VERSION')) { + if (false === $file && defined('HHVM_VERSION')) { $file = $this->findFileWithExtension($class, '.hh'); } - if ($file === null) { + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { // Remember that this class does not exist. - return $this->classMap[$class] = false; + $this->missingClasses[$class] = true; } return $file; @@ -348,10 +374,14 @@ class ClassLoader $first = $class[0]; if (isset($this->prefixLengthsPsr4[$first])) { - foreach ($this->prefixLengthsPsr4[$first] as $prefix => $length) { - if (0 === strpos($class, $prefix)) { - foreach ($this->prefixDirsPsr4[$prefix] as $dir) { - if (file_exists($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath.'\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { return $file; } } @@ -399,6 +429,8 @@ class ClassLoader if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { return $file; } + + return false; } } diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE old mode 100755 new mode 100644 index 1a28124..f27399a --- a/vendor/composer/LICENSE +++ b/vendor/composer/LICENSE @@ -1,5 +1,5 @@ -Copyright (c) 2016 Nils Adermann, Jordi Boggiano +Copyright (c) Nils Adermann, Jordi Boggiano Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php old mode 100755 new mode 100644 index 95789d3..7a91153 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -6,447 +6,4 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'File_Iterator' => $vendorDir . '/phpunit/php-file-iterator/src/Iterator.php', - 'File_Iterator_Facade' => $vendorDir . '/phpunit/php-file-iterator/src/Facade.php', - 'File_Iterator_Factory' => $vendorDir . '/phpunit/php-file-iterator/src/Factory.php', - 'PHPUnit\\Framework\\Assert' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php', - 'PHPUnit\\Framework\\BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php', - 'PHPUnit\\Framework\\TestCase' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php', - 'PHPUnit\\Framework\\TestListener' => $vendorDir . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php', - 'PHPUnit_Exception' => $vendorDir . '/phpunit/phpunit/src/Exception.php', - 'PHPUnit_Extensions_GroupTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php', - 'PHPUnit_Extensions_PhptTestCase' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestCase.php', - 'PHPUnit_Extensions_PhptTestSuite' => $vendorDir . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php', - 'PHPUnit_Extensions_RepeatedTest' => $vendorDir . '/phpunit/phpunit/src/Extensions/RepeatedTest.php', - 'PHPUnit_Extensions_TestDecorator' => $vendorDir . '/phpunit/phpunit/src/Extensions/TestDecorator.php', - 'PHPUnit_Extensions_TicketListener' => $vendorDir . '/phpunit/phpunit/src/Extensions/TicketListener.php', - 'PHPUnit_Framework_Assert' => $vendorDir . '/phpunit/phpunit/src/Framework/Assert.php', - 'PHPUnit_Framework_AssertionFailedError' => $vendorDir . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', - 'PHPUnit_Framework_BaseTestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/BaseTestListener.php', - 'PHPUnit_Framework_CodeCoverageException' => $vendorDir . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', - 'PHPUnit_Framework_Constraint' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint.php', - 'PHPUnit_Framework_Constraint_And' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/And.php', - 'PHPUnit_Framework_Constraint_ArrayHasKey' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', - 'PHPUnit_Framework_Constraint_ArraySubset' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', - 'PHPUnit_Framework_Constraint_Attribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', - 'PHPUnit_Framework_Constraint_Callback' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', - 'PHPUnit_Framework_Constraint_ClassHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', - 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', - 'PHPUnit_Framework_Constraint_Composite' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', - 'PHPUnit_Framework_Constraint_Count' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Count.php', - 'PHPUnit_Framework_Constraint_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', - 'PHPUnit_Framework_Constraint_ExceptionCode' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', - 'PHPUnit_Framework_Constraint_ExceptionMessage' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', - 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php', - 'PHPUnit_Framework_Constraint_FileExists' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', - 'PHPUnit_Framework_Constraint_GreaterThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', - 'PHPUnit_Framework_Constraint_IsAnything' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', - 'PHPUnit_Framework_Constraint_IsEmpty' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', - 'PHPUnit_Framework_Constraint_IsEqual' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', - 'PHPUnit_Framework_Constraint_IsFalse' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', - 'PHPUnit_Framework_Constraint_IsIdentical' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', - 'PHPUnit_Framework_Constraint_IsInstanceOf' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', - 'PHPUnit_Framework_Constraint_IsJson' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', - 'PHPUnit_Framework_Constraint_IsNull' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', - 'PHPUnit_Framework_Constraint_IsTrue' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', - 'PHPUnit_Framework_Constraint_IsType' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', - 'PHPUnit_Framework_Constraint_JsonMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', - 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php', - 'PHPUnit_Framework_Constraint_LessThan' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', - 'PHPUnit_Framework_Constraint_Not' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Not.php', - 'PHPUnit_Framework_Constraint_ObjectHasAttribute' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', - 'PHPUnit_Framework_Constraint_Or' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Or.php', - 'PHPUnit_Framework_Constraint_PCREMatch' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php', - 'PHPUnit_Framework_Constraint_SameSize' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', - 'PHPUnit_Framework_Constraint_StringContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', - 'PHPUnit_Framework_Constraint_StringEndsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', - 'PHPUnit_Framework_Constraint_StringMatches' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php', - 'PHPUnit_Framework_Constraint_StringStartsWith' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', - 'PHPUnit_Framework_Constraint_TraversableContains' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', - 'PHPUnit_Framework_Constraint_TraversableContainsOnly' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', - 'PHPUnit_Framework_Constraint_Xor' => $vendorDir . '/phpunit/phpunit/src/Framework/Constraint/Xor.php', - 'PHPUnit_Framework_Error' => $vendorDir . '/phpunit/phpunit/src/Framework/Error.php', - 'PHPUnit_Framework_Error_Deprecated' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', - 'PHPUnit_Framework_Error_Notice' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Notice.php', - 'PHPUnit_Framework_Error_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Error/Warning.php', - 'PHPUnit_Framework_Exception' => $vendorDir . '/phpunit/phpunit/src/Framework/Exception.php', - 'PHPUnit_Framework_ExceptionWrapper' => $vendorDir . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', - 'PHPUnit_Framework_ExpectationFailedException' => $vendorDir . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', - 'PHPUnit_Framework_IncompleteTest' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTest.php', - 'PHPUnit_Framework_IncompleteTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', - 'PHPUnit_Framework_IncompleteTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', - 'PHPUnit_Framework_InvalidCoversTargetError' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidCoversTargetError.php', - 'PHPUnit_Framework_InvalidCoversTargetException' => $vendorDir . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', - 'PHPUnit_Framework_MockObject_BadMethodCallException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php', - 'PHPUnit_Framework_MockObject_Builder_Identity' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php', - 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php', - 'PHPUnit_Framework_MockObject_Builder_Match' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php', - 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php', - 'PHPUnit_Framework_MockObject_Builder_Namespace' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php', - 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php', - 'PHPUnit_Framework_MockObject_Builder_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php', - 'PHPUnit_Framework_MockObject_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php', - 'PHPUnit_Framework_MockObject_Generator' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php', - 'PHPUnit_Framework_MockObject_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php', - 'PHPUnit_Framework_MockObject_InvocationMocker' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php', - 'PHPUnit_Framework_MockObject_Invocation_Object' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php', - 'PHPUnit_Framework_MockObject_Invocation_Static' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php', - 'PHPUnit_Framework_MockObject_Invokable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php', - 'PHPUnit_Framework_MockObject_Matcher' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php', - 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php', - 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php', - 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php', - 'PHPUnit_Framework_MockObject_Matcher_Invocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php', - 'PHPUnit_Framework_MockObject_Matcher_MethodName' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php', - 'PHPUnit_Framework_MockObject_Matcher_Parameters' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php', - 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php', - 'PHPUnit_Framework_MockObject_MockBuilder' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php', - 'PHPUnit_Framework_MockObject_MockObject' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php', - 'PHPUnit_Framework_MockObject_RuntimeException' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php', - 'PHPUnit_Framework_MockObject_Stub' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php', - 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php', - 'PHPUnit_Framework_MockObject_Stub_Exception' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php', - 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php', - 'PHPUnit_Framework_MockObject_Stub_Return' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php', - 'PHPUnit_Framework_MockObject_Verifiable' => $vendorDir . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php', - 'PHPUnit_Framework_OutputError' => $vendorDir . '/phpunit/phpunit/src/Framework/OutputError.php', - 'PHPUnit_Framework_RiskyTest' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTest.php', - 'PHPUnit_Framework_RiskyTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/RiskyTestError.php', - 'PHPUnit_Framework_SelfDescribing' => $vendorDir . '/phpunit/phpunit/src/Framework/SelfDescribing.php', - 'PHPUnit_Framework_SkippedTest' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTest.php', - 'PHPUnit_Framework_SkippedTestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', - 'PHPUnit_Framework_SkippedTestError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestError.php', - 'PHPUnit_Framework_SkippedTestSuiteError' => $vendorDir . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', - 'PHPUnit_Framework_SyntheticError' => $vendorDir . '/phpunit/phpunit/src/Framework/SyntheticError.php', - 'PHPUnit_Framework_Test' => $vendorDir . '/phpunit/phpunit/src/Framework/Test.php', - 'PHPUnit_Framework_TestCase' => $vendorDir . '/phpunit/phpunit/src/Framework/TestCase.php', - 'PHPUnit_Framework_TestFailure' => $vendorDir . '/phpunit/phpunit/src/Framework/TestFailure.php', - 'PHPUnit_Framework_TestListener' => $vendorDir . '/phpunit/phpunit/src/Framework/TestListener.php', - 'PHPUnit_Framework_TestResult' => $vendorDir . '/phpunit/phpunit/src/Framework/TestResult.php', - 'PHPUnit_Framework_TestSuite' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite.php', - 'PHPUnit_Framework_TestSuite_DataProvider' => $vendorDir . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php', - 'PHPUnit_Framework_UnintentionallyCoveredCodeError' => $vendorDir . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', - 'PHPUnit_Framework_Warning' => $vendorDir . '/phpunit/phpunit/src/Framework/Warning.php', - 'PHPUnit_Runner_BaseTestRunner' => $vendorDir . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', - 'PHPUnit_Runner_Exception' => $vendorDir . '/phpunit/phpunit/src/Runner/Exception.php', - 'PHPUnit_Runner_Filter_Factory' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Factory.php', - 'PHPUnit_Runner_Filter_GroupFilterIterator' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group.php', - 'PHPUnit_Runner_Filter_Group_Exclude' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php', - 'PHPUnit_Runner_Filter_Group_Include' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php', - 'PHPUnit_Runner_Filter_Test' => $vendorDir . '/phpunit/phpunit/src/Runner/Filter/Test.php', - 'PHPUnit_Runner_StandardTestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', - 'PHPUnit_Runner_TestSuiteLoader' => $vendorDir . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', - 'PHPUnit_Runner_Version' => $vendorDir . '/phpunit/phpunit/src/Runner/Version.php', - 'PHPUnit_TextUI_Command' => $vendorDir . '/phpunit/phpunit/src/TextUI/Command.php', - 'PHPUnit_TextUI_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', - 'PHPUnit_TextUI_TestRunner' => $vendorDir . '/phpunit/phpunit/src/TextUI/TestRunner.php', - 'PHPUnit_Util_Blacklist' => $vendorDir . '/phpunit/phpunit/src/Util/Blacklist.php', - 'PHPUnit_Util_Configuration' => $vendorDir . '/phpunit/phpunit/src/Util/Configuration.php', - 'PHPUnit_Util_ErrorHandler' => $vendorDir . '/phpunit/phpunit/src/Util/ErrorHandler.php', - 'PHPUnit_Util_Fileloader' => $vendorDir . '/phpunit/phpunit/src/Util/Fileloader.php', - 'PHPUnit_Util_Filesystem' => $vendorDir . '/phpunit/phpunit/src/Util/Filesystem.php', - 'PHPUnit_Util_Filter' => $vendorDir . '/phpunit/phpunit/src/Util/Filter.php', - 'PHPUnit_Util_Getopt' => $vendorDir . '/phpunit/phpunit/src/Util/Getopt.php', - 'PHPUnit_Util_GlobalState' => $vendorDir . '/phpunit/phpunit/src/Util/GlobalState.php', - 'PHPUnit_Util_InvalidArgumentHelper' => $vendorDir . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', - 'PHPUnit_Util_Log_JSON' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JSON.php', - 'PHPUnit_Util_Log_JUnit' => $vendorDir . '/phpunit/phpunit/src/Util/Log/JUnit.php', - 'PHPUnit_Util_Log_TAP' => $vendorDir . '/phpunit/phpunit/src/Util/Log/TAP.php', - 'PHPUnit_Util_PHP' => $vendorDir . '/phpunit/phpunit/src/Util/PHP.php', - 'PHPUnit_Util_PHP_Default' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Default.php', - 'PHPUnit_Util_PHP_Windows' => $vendorDir . '/phpunit/phpunit/src/Util/PHP/Windows.php', - 'PHPUnit_Util_Printer' => $vendorDir . '/phpunit/phpunit/src/Util/Printer.php', - 'PHPUnit_Util_Regex' => $vendorDir . '/phpunit/phpunit/src/Util/Regex.php', - 'PHPUnit_Util_String' => $vendorDir . '/phpunit/phpunit/src/Util/String.php', - 'PHPUnit_Util_Test' => $vendorDir . '/phpunit/phpunit/src/Util/Test.php', - 'PHPUnit_Util_TestDox_NamePrettifier' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', - 'PHPUnit_Util_TestDox_ResultPrinter' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', - 'PHPUnit_Util_TestDox_ResultPrinter_HTML' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php', - 'PHPUnit_Util_TestDox_ResultPrinter_Text' => $vendorDir . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php', - 'PHPUnit_Util_TestSuiteIterator' => $vendorDir . '/phpunit/phpunit/src/Util/TestSuiteIterator.php', - 'PHPUnit_Util_Type' => $vendorDir . '/phpunit/phpunit/src/Util/Type.php', - 'PHPUnit_Util_XML' => $vendorDir . '/phpunit/phpunit/src/Util/XML.php', - 'PHP_CodeCoverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage.php', - 'PHP_CodeCoverage_Driver' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Driver.php', - 'PHP_CodeCoverage_Driver_HHVM' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/HHVM.php', - 'PHP_CodeCoverage_Driver_PHPDBG' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/PHPDBG.php', - 'PHP_CodeCoverage_Driver_Xdebug' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/Xdebug.php', - 'PHP_CodeCoverage_Exception' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Exception.php', - 'PHP_CodeCoverage_Exception_UnintentionallyCoveredCode' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Exception/UnintentionallyCoveredCode.php', - 'PHP_CodeCoverage_Filter' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Filter.php', - 'PHP_CodeCoverage_Report_Clover' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Clover.php', - 'PHP_CodeCoverage_Report_Crap4j' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Crap4j.php', - 'PHP_CodeCoverage_Report_Factory' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Factory.php', - 'PHP_CodeCoverage_Report_HTML' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML.php', - 'PHP_CodeCoverage_Report_HTML_Renderer' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_Dashboard' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Dashboard.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_Directory' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Directory.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_File' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/File.php', - 'PHP_CodeCoverage_Report_Node' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node.php', - 'PHP_CodeCoverage_Report_Node_Directory' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Directory.php', - 'PHP_CodeCoverage_Report_Node_File' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/File.php', - 'PHP_CodeCoverage_Report_Node_Iterator' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Iterator.php', - 'PHP_CodeCoverage_Report_PHP' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/PHP.php', - 'PHP_CodeCoverage_Report_Text' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Text.php', - 'PHP_CodeCoverage_Report_XML' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML.php', - 'PHP_CodeCoverage_Report_XML_Directory' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Directory.php', - 'PHP_CodeCoverage_Report_XML_File' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File.php', - 'PHP_CodeCoverage_Report_XML_File_Coverage' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Coverage.php', - 'PHP_CodeCoverage_Report_XML_File_Method' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Method.php', - 'PHP_CodeCoverage_Report_XML_File_Report' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Report.php', - 'PHP_CodeCoverage_Report_XML_File_Unit' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Unit.php', - 'PHP_CodeCoverage_Report_XML_Node' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Node.php', - 'PHP_CodeCoverage_Report_XML_Project' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Project.php', - 'PHP_CodeCoverage_Report_XML_Tests' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Tests.php', - 'PHP_CodeCoverage_Report_XML_Totals' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Totals.php', - 'PHP_CodeCoverage_Util' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Util.php', - 'PHP_CodeCoverage_Util_InvalidArgumentHelper' => $vendorDir . '/phpunit/php-code-coverage/src/CodeCoverage/Util/InvalidArgumentHelper.php', - 'PHP_Timer' => $vendorDir . '/phpunit/php-timer/src/Timer.php', - 'PHP_Token' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_TokenWithScope' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_TokenWithScopeAndVisibility' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ABSTRACT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AMPERSAND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AND_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ARRAY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ARRAY_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ASYNC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AWAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BACKTICK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BAD_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOLEAN_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOLEAN_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOL_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BREAK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CALLABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CARET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CASE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CATCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CHARACTER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS_NAME_CONSTANT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLONE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COALESCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMMA' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMPILER_HALT_OFFSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONCAT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONSTANT_ENCAPSED_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONTINUE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CURLY_OPEN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DEC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DEFAULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIV' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIV_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOC_COMMENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOLLAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_COLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_QUOTES' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELLIPSIS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELSE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELSEIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EMPTY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENCAPSED_AND_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDDECLARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDFOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDFOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDIF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDSWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDWHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_END_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENUM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EQUALS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EVAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXCLAMATION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXTENDS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FINAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FINALLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FOREACH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FUNC_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GLOBAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GOTO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_HALT_COMPILER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IMPLEMENTS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INCLUDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INCLUDE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INLINE_HTML' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INSTANCEOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INSTEADOF' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INTERFACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ISSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_GREATER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_NOT_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_NOT_IDENTICAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_SMALLER_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_Includes' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_JOIN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_ARROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_CP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_OP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LINE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LIST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LNUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_AND' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_OR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_XOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_METHOD_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MINUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MINUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MOD_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MULT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MUL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NAMESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NEW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NS_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NS_SEPARATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NUM_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OBJECT_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OBJECT_OPERATOR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ONUMBER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_BRACKET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_CURLY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_SQUARE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_TAG' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_TAG_WITH_ECHO' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PERCENT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PIPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PLUS' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PLUS_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_POW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_POW_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PRINT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PRIVATE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PROTECTED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PUBLIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_QUESTION_MARK' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_REQUIRE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_REQUIRE_ONCE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_RETURN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SEMICOLON' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SHAPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SL_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SPACESHIP' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_START_HEREDOC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STATIC' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING_VARNAME' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SUPER' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SWITCH' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_Stream' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream.php', - 'PHP_Token_Stream_CachingFactory' => $vendorDir . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', - 'PHP_Token_THROW' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TILDE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRAIT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRAIT_C' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPELIST_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPELIST_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_UNSET' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_UNSET_CAST' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_USE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_USE_FUNCTION' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_VAR' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_VARIABLE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHERE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHILE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHITESPACE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_ATTRIBUTE' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CATEGORY' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CATEGORY_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CHILDREN' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_LABEL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_REQUIRED' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TAG_GT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TAG_LT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TEXT' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XOR_EQUAL' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_YIELD' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_YIELD_FROM' => $vendorDir . '/phpunit/php-token-stream/src/Token.php', - 'SebastianBergmann\\Comparator\\ArrayComparator' => $vendorDir . '/sebastian/comparator/src/ArrayComparator.php', - 'SebastianBergmann\\Comparator\\Comparator' => $vendorDir . '/sebastian/comparator/src/Comparator.php', - 'SebastianBergmann\\Comparator\\ComparisonFailure' => $vendorDir . '/sebastian/comparator/src/ComparisonFailure.php', - 'SebastianBergmann\\Comparator\\DOMNodeComparator' => $vendorDir . '/sebastian/comparator/src/DOMNodeComparator.php', - 'SebastianBergmann\\Comparator\\DateTimeComparator' => $vendorDir . '/sebastian/comparator/src/DateTimeComparator.php', - 'SebastianBergmann\\Comparator\\DoubleComparator' => $vendorDir . '/sebastian/comparator/src/DoubleComparator.php', - 'SebastianBergmann\\Comparator\\ExceptionComparator' => $vendorDir . '/sebastian/comparator/src/ExceptionComparator.php', - 'SebastianBergmann\\Comparator\\Factory' => $vendorDir . '/sebastian/comparator/src/Factory.php', - 'SebastianBergmann\\Comparator\\MockObjectComparator' => $vendorDir . '/sebastian/comparator/src/MockObjectComparator.php', - 'SebastianBergmann\\Comparator\\NumericComparator' => $vendorDir . '/sebastian/comparator/src/NumericComparator.php', - 'SebastianBergmann\\Comparator\\ObjectComparator' => $vendorDir . '/sebastian/comparator/src/ObjectComparator.php', - 'SebastianBergmann\\Comparator\\ResourceComparator' => $vendorDir . '/sebastian/comparator/src/ResourceComparator.php', - 'SebastianBergmann\\Comparator\\ScalarComparator' => $vendorDir . '/sebastian/comparator/src/ScalarComparator.php', - 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => $vendorDir . '/sebastian/comparator/src/SplObjectStorageComparator.php', - 'SebastianBergmann\\Comparator\\TypeComparator' => $vendorDir . '/sebastian/comparator/src/TypeComparator.php', - 'SebastianBergmann\\Diff\\Chunk' => $vendorDir . '/sebastian/diff/src/Chunk.php', - 'SebastianBergmann\\Diff\\Diff' => $vendorDir . '/sebastian/diff/src/Diff.php', - 'SebastianBergmann\\Diff\\Differ' => $vendorDir . '/sebastian/diff/src/Differ.php', - 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => $vendorDir . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', - 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', - 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => $vendorDir . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', - 'SebastianBergmann\\Diff\\Line' => $vendorDir . '/sebastian/diff/src/Line.php', - 'SebastianBergmann\\Diff\\Parser' => $vendorDir . '/sebastian/diff/src/Parser.php', - 'SebastianBergmann\\Environment\\Console' => $vendorDir . '/sebastian/environment/src/Console.php', - 'SebastianBergmann\\Environment\\Runtime' => $vendorDir . '/sebastian/environment/src/Runtime.php', - 'SebastianBergmann\\Exporter\\Exporter' => $vendorDir . '/sebastian/exporter/src/Exporter.php', - 'SebastianBergmann\\GlobalState\\Blacklist' => $vendorDir . '/sebastian/global-state/src/Blacklist.php', - 'SebastianBergmann\\GlobalState\\CodeExporter' => $vendorDir . '/sebastian/global-state/src/CodeExporter.php', - 'SebastianBergmann\\GlobalState\\Exception' => $vendorDir . '/sebastian/global-state/src/Exception.php', - 'SebastianBergmann\\GlobalState\\Restorer' => $vendorDir . '/sebastian/global-state/src/Restorer.php', - 'SebastianBergmann\\GlobalState\\RuntimeException' => $vendorDir . '/sebastian/global-state/src/RuntimeException.php', - 'SebastianBergmann\\GlobalState\\Snapshot' => $vendorDir . '/sebastian/global-state/src/Snapshot.php', - 'SebastianBergmann\\RecursionContext\\Context' => $vendorDir . '/sebastian/recursion-context/src/Context.php', - 'SebastianBergmann\\RecursionContext\\Exception' => $vendorDir . '/sebastian/recursion-context/src/Exception.php', - 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => $vendorDir . '/sebastian/recursion-context/src/InvalidArgumentException.php', - 'SebastianBergmann\\Version' => $vendorDir . '/sebastian/version/src/Version.php', - 'Text_Template' => $vendorDir . '/phpunit/php-text-template/src/Template.php', - 'think\\view\\driver\\Angular' => $vendorDir . '/topthink/think-angular/drivers/thinkphp5/Angular.php', ); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php old mode 100755 new mode 100644 index 851cd2e..c676939 --- a/vendor/composer/autoload_files.php +++ b/vendor/composer/autoload_files.php @@ -6,10 +6,6 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php', - '72c97b53391125cae04082a81029f42d' => $vendorDir . '/topthink/think-testing/src/config.php', - 'ddc3cd2a04224f9638c5d0de6a69c7e3' => $vendorDir . '/topthink/think-migration/src/config.php', - 'cc56288302d9df745d97c934d6a6e5f0' => $vendorDir . '/topthink/think-queue/src/common.php', + '9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php', ); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php old mode 100755 new mode 100644 index b24b217..b7fc012 --- a/vendor/composer/autoload_namespaces.php +++ b/vendor/composer/autoload_namespaces.php @@ -6,5 +6,4 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'Prophecy\\' => array($vendorDir . '/phpspec/prophecy/src'), ); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php old mode 100755 new mode 100644 index 4486260..3a9ded1 --- a/vendor/composer/autoload_psr4.php +++ b/vendor/composer/autoload_psr4.php @@ -6,21 +6,9 @@ $vendorDir = dirname(dirname(__FILE__)); $baseDir = dirname($vendorDir); return array( - 'think\\worker\\' => array($vendorDir . '/topthink/think-worker/src'), - 'think\\testing\\' => array($vendorDir . '/topthink/think-testing/src'), - 'think\\mongo\\' => array($vendorDir . '/topthink/think-mongo/src'), - 'think\\migration\\' => array($vendorDir . '/topthink/think-migration/src'), 'think\\helper\\' => array($vendorDir . '/topthink/think-helper/src'), 'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'), 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), - 'think\\angular\\' => array($vendorDir . '/topthink/think-angular/src'), - 'think\\' => array($vendorDir . '/topthink/think-image/src', $baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src'), - 'phpDocumentor\\Reflection\\' => array($vendorDir . '/phpdocumentor/reflection-common/src', $vendorDir . '/phpdocumentor/reflection-docblock/src', $vendorDir . '/phpdocumentor/type-resolver/src'), - 'Workerman\\' => array($vendorDir . '/workerman/workerman'), - 'Webmozart\\Assert\\' => array($vendorDir . '/webmozart/assert/src'), - 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), - 'Symfony\\Component\\Yaml\\' => array($vendorDir . '/symfony/yaml'), - 'Symfony\\Component\\DomCrawler\\' => array($vendorDir . '/symfony/dom-crawler'), - 'Phinx\\' => array($vendorDir . '/topthink/think-migration/phinx/src/Phinx'), - 'Doctrine\\Instantiator\\' => array($vendorDir . '/doctrine/instantiator/src/Doctrine/Instantiator'), + 'think\\' => array($baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-image/src'), + 'app\\' => array($baseDir . '/application'), ); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php old mode 100755 new mode 100644 index 166ab0c..f50deb1 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb +class ComposerAutoloaderInitde23ff7bcf12cf4bb58fbe1d807f2677 { private static $loader; @@ -19,15 +19,15 @@ class ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInitde23ff7bcf12cf4bb58fbe1d807f2677', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInitde23ff7bcf12cf4bb58fbe1d807f2677', 'loadClassLoader')); - $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION'); + $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInitde23ff7bcf12cf4bb58fbe1d807f2677::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -48,19 +48,19 @@ class ComposerAutoloaderInit547f1ba8138a17d6b867624279d0c7eb $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::$files; + $includeFiles = Composer\Autoload\ComposerStaticInitde23ff7bcf12cf4bb58fbe1d807f2677::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire547f1ba8138a17d6b867624279d0c7eb($fileIdentifier, $file); + composerRequirede23ff7bcf12cf4bb58fbe1d807f2677($fileIdentifier, $file); } return $loader; } } -function composerRequire547f1ba8138a17d6b867624279d0c7eb($fileIdentifier, $file) +function composerRequirede23ff7bcf12cf4bb58fbe1d807f2677($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php old mode 100755 new mode 100644 index f9f9cb1..a929f75 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,72 +4,28 @@ namespace Composer\Autoload; -class ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb +class ComposerStaticInitde23ff7bcf12cf4bb58fbe1d807f2677 { public static $files = array ( - '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', - '72c97b53391125cae04082a81029f42d' => __DIR__ . '/..' . '/topthink/think-testing/src/config.php', - 'ddc3cd2a04224f9638c5d0de6a69c7e3' => __DIR__ . '/..' . '/topthink/think-migration/src/config.php', - 'cc56288302d9df745d97c934d6a6e5f0' => __DIR__ . '/..' . '/topthink/think-queue/src/common.php', + '9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', ); public static $prefixLengthsPsr4 = array ( 't' => array ( - 'think\\worker\\' => 13, - 'think\\testing\\' => 14, - 'think\\mongo\\' => 12, - 'think\\migration\\' => 16, 'think\\helper\\' => 13, 'think\\composer\\' => 15, 'think\\captcha\\' => 14, - 'think\\angular\\' => 14, 'think\\' => 6, ), - 'p' => - array ( - 'phpDocumentor\\Reflection\\' => 25, - ), - 'W' => - array ( - 'Workerman\\' => 10, - 'Webmozart\\Assert\\' => 17, - ), - 'S' => - array ( - 'Symfony\\Polyfill\\Mbstring\\' => 26, - 'Symfony\\Component\\Yaml\\' => 23, - 'Symfony\\Component\\DomCrawler\\' => 29, - ), - 'P' => - array ( - 'Phinx\\' => 6, - ), - 'D' => + 'a' => array ( - 'Doctrine\\Instantiator\\' => 22, + 'app\\' => 4, ), ); public static $prefixDirsPsr4 = array ( - 'think\\worker\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-worker/src', - ), - 'think\\testing\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-testing/src', - ), - 'think\\mongo\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-mongo/src', - ), - 'think\\migration\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-migration/src', - ), 'think\\helper\\' => array ( 0 => __DIR__ . '/..' . '/topthink/think-helper/src', @@ -82,515 +38,22 @@ class ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb array ( 0 => __DIR__ . '/..' . '/topthink/think-captcha/src', ), - 'think\\angular\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-angular/src', - ), 'think\\' => array ( - 0 => __DIR__ . '/..' . '/topthink/think-image/src', - 1 => __DIR__ . '/../..' . '/thinkphp/library/think', - 2 => __DIR__ . '/..' . '/topthink/think-queue/src', + 0 => __DIR__ . '/../..' . '/thinkphp/library/think', + 1 => __DIR__ . '/..' . '/topthink/think-image/src', ), - 'phpDocumentor\\Reflection\\' => + 'app\\' => array ( - 0 => __DIR__ . '/..' . '/phpdocumentor/reflection-common/src', - 1 => __DIR__ . '/..' . '/phpdocumentor/reflection-docblock/src', - 2 => __DIR__ . '/..' . '/phpdocumentor/type-resolver/src', + 0 => __DIR__ . '/../..' . '/application', ), - 'Workerman\\' => - array ( - 0 => __DIR__ . '/..' . '/workerman/workerman', - ), - 'Webmozart\\Assert\\' => - array ( - 0 => __DIR__ . '/..' . '/webmozart/assert/src', - ), - 'Symfony\\Polyfill\\Mbstring\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', - ), - 'Symfony\\Component\\Yaml\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/yaml', - ), - 'Symfony\\Component\\DomCrawler\\' => - array ( - 0 => __DIR__ . '/..' . '/symfony/dom-crawler', - ), - 'Phinx\\' => - array ( - 0 => __DIR__ . '/..' . '/topthink/think-migration/phinx/src/Phinx', - ), - 'Doctrine\\Instantiator\\' => - array ( - 0 => __DIR__ . '/..' . '/doctrine/instantiator/src/Doctrine/Instantiator', - ), - ); - - public static $prefixesPsr0 = array ( - 'P' => - array ( - 'Prophecy\\' => - array ( - 0 => __DIR__ . '/..' . '/phpspec/prophecy/src', - ), - ), - ); - - public static $classMap = array ( - 'File_Iterator' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Iterator.php', - 'File_Iterator_Facade' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Facade.php', - 'File_Iterator_Factory' => __DIR__ . '/..' . '/phpunit/php-file-iterator/src/Factory.php', - 'PHPUnit\\Framework\\Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/Assert.php', - 'PHPUnit\\Framework\\BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/BaseTestListener.php', - 'PHPUnit\\Framework\\TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestCase.php', - 'PHPUnit\\Framework\\TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/ForwardCompatibility/TestListener.php', - 'PHPUnit_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Exception.php', - 'PHPUnit_Extensions_GroupTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/GroupTestSuite.php', - 'PHPUnit_Extensions_PhptTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestCase.php', - 'PHPUnit_Extensions_PhptTestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/PhptTestSuite.php', - 'PHPUnit_Extensions_RepeatedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/RepeatedTest.php', - 'PHPUnit_Extensions_TestDecorator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TestDecorator.php', - 'PHPUnit_Extensions_TicketListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Extensions/TicketListener.php', - 'PHPUnit_Framework_Assert' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Assert.php', - 'PHPUnit_Framework_AssertionFailedError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/AssertionFailedError.php', - 'PHPUnit_Framework_BaseTestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/BaseTestListener.php', - 'PHPUnit_Framework_CodeCoverageException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/CodeCoverageException.php', - 'PHPUnit_Framework_Constraint' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint.php', - 'PHPUnit_Framework_Constraint_And' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/And.php', - 'PHPUnit_Framework_Constraint_ArrayHasKey' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArrayHasKey.php', - 'PHPUnit_Framework_Constraint_ArraySubset' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ArraySubset.php', - 'PHPUnit_Framework_Constraint_Attribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Attribute.php', - 'PHPUnit_Framework_Constraint_Callback' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Callback.php', - 'PHPUnit_Framework_Constraint_ClassHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasAttribute.php', - 'PHPUnit_Framework_Constraint_ClassHasStaticAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ClassHasStaticAttribute.php', - 'PHPUnit_Framework_Constraint_Composite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Composite.php', - 'PHPUnit_Framework_Constraint_Count' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Count.php', - 'PHPUnit_Framework_Constraint_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Exception.php', - 'PHPUnit_Framework_Constraint_ExceptionCode' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionCode.php', - 'PHPUnit_Framework_Constraint_ExceptionMessage' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessage.php', - 'PHPUnit_Framework_Constraint_ExceptionMessageRegExp' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ExceptionMessageRegExp.php', - 'PHPUnit_Framework_Constraint_FileExists' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/FileExists.php', - 'PHPUnit_Framework_Constraint_GreaterThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/GreaterThan.php', - 'PHPUnit_Framework_Constraint_IsAnything' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsAnything.php', - 'PHPUnit_Framework_Constraint_IsEmpty' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEmpty.php', - 'PHPUnit_Framework_Constraint_IsEqual' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsEqual.php', - 'PHPUnit_Framework_Constraint_IsFalse' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsFalse.php', - 'PHPUnit_Framework_Constraint_IsIdentical' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsIdentical.php', - 'PHPUnit_Framework_Constraint_IsInstanceOf' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsInstanceOf.php', - 'PHPUnit_Framework_Constraint_IsJson' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsJson.php', - 'PHPUnit_Framework_Constraint_IsNull' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsNull.php', - 'PHPUnit_Framework_Constraint_IsTrue' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsTrue.php', - 'PHPUnit_Framework_Constraint_IsType' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/IsType.php', - 'PHPUnit_Framework_Constraint_JsonMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches.php', - 'PHPUnit_Framework_Constraint_JsonMatches_ErrorMessageProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/JsonMatches/ErrorMessageProvider.php', - 'PHPUnit_Framework_Constraint_LessThan' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/LessThan.php', - 'PHPUnit_Framework_Constraint_Not' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Not.php', - 'PHPUnit_Framework_Constraint_ObjectHasAttribute' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/ObjectHasAttribute.php', - 'PHPUnit_Framework_Constraint_Or' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Or.php', - 'PHPUnit_Framework_Constraint_PCREMatch' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/PCREMatch.php', - 'PHPUnit_Framework_Constraint_SameSize' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/SameSize.php', - 'PHPUnit_Framework_Constraint_StringContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringContains.php', - 'PHPUnit_Framework_Constraint_StringEndsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringEndsWith.php', - 'PHPUnit_Framework_Constraint_StringMatches' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringMatches.php', - 'PHPUnit_Framework_Constraint_StringStartsWith' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/StringStartsWith.php', - 'PHPUnit_Framework_Constraint_TraversableContains' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContains.php', - 'PHPUnit_Framework_Constraint_TraversableContainsOnly' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/TraversableContainsOnly.php', - 'PHPUnit_Framework_Constraint_Xor' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Constraint/Xor.php', - 'PHPUnit_Framework_Error' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error.php', - 'PHPUnit_Framework_Error_Deprecated' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Deprecated.php', - 'PHPUnit_Framework_Error_Notice' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Notice.php', - 'PHPUnit_Framework_Error_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Error/Warning.php', - 'PHPUnit_Framework_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Exception.php', - 'PHPUnit_Framework_ExceptionWrapper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExceptionWrapper.php', - 'PHPUnit_Framework_ExpectationFailedException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/ExpectationFailedException.php', - 'PHPUnit_Framework_IncompleteTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTest.php', - 'PHPUnit_Framework_IncompleteTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestCase.php', - 'PHPUnit_Framework_IncompleteTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/IncompleteTestError.php', - 'PHPUnit_Framework_InvalidCoversTargetError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidCoversTargetError.php', - 'PHPUnit_Framework_InvalidCoversTargetException' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/InvalidCoversTargetException.php', - 'PHPUnit_Framework_MockObject_BadMethodCallException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/BadMethodCallException.php', - 'PHPUnit_Framework_MockObject_Builder_Identity' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Identity.php', - 'PHPUnit_Framework_MockObject_Builder_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/InvocationMocker.php', - 'PHPUnit_Framework_MockObject_Builder_Match' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Match.php', - 'PHPUnit_Framework_MockObject_Builder_MethodNameMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/MethodNameMatch.php', - 'PHPUnit_Framework_MockObject_Builder_Namespace' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Namespace.php', - 'PHPUnit_Framework_MockObject_Builder_ParametersMatch' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/ParametersMatch.php', - 'PHPUnit_Framework_MockObject_Builder_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Builder/Stub.php', - 'PHPUnit_Framework_MockObject_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/Exception.php', - 'PHPUnit_Framework_MockObject_Generator' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Generator.php', - 'PHPUnit_Framework_MockObject_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation.php', - 'PHPUnit_Framework_MockObject_InvocationMocker' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/InvocationMocker.php', - 'PHPUnit_Framework_MockObject_Invocation_Object' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Object.php', - 'PHPUnit_Framework_MockObject_Invocation_Static' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invocation/Static.php', - 'PHPUnit_Framework_MockObject_Invokable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Invokable.php', - 'PHPUnit_Framework_MockObject_Matcher' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher.php', - 'PHPUnit_Framework_MockObject_Matcher_AnyInvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyInvokedCount.php', - 'PHPUnit_Framework_MockObject_Matcher_AnyParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/AnyParameters.php', - 'PHPUnit_Framework_MockObject_Matcher_ConsecutiveParameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/ConsecutiveParameters.php', - 'PHPUnit_Framework_MockObject_Matcher_Invocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Invocation.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtIndex' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtIndex.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtLeastOnce' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtLeastOnce.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedAtMostCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedAtMostCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedCount' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedCount.php', - 'PHPUnit_Framework_MockObject_Matcher_InvokedRecorder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/InvokedRecorder.php', - 'PHPUnit_Framework_MockObject_Matcher_MethodName' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/MethodName.php', - 'PHPUnit_Framework_MockObject_Matcher_Parameters' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/Parameters.php', - 'PHPUnit_Framework_MockObject_Matcher_StatelessInvocation' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Matcher/StatelessInvocation.php', - 'PHPUnit_Framework_MockObject_MockBuilder' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockBuilder.php', - 'PHPUnit_Framework_MockObject_MockObject' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/MockObject.php', - 'PHPUnit_Framework_MockObject_RuntimeException' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Exception/RuntimeException.php', - 'PHPUnit_Framework_MockObject_Stub' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub.php', - 'PHPUnit_Framework_MockObject_Stub_ConsecutiveCalls' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ConsecutiveCalls.php', - 'PHPUnit_Framework_MockObject_Stub_Exception' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Exception.php', - 'PHPUnit_Framework_MockObject_Stub_MatcherCollection' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/MatcherCollection.php', - 'PHPUnit_Framework_MockObject_Stub_Return' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/Return.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnArgument' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnArgument.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnCallback' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnCallback.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnSelf' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnSelf.php', - 'PHPUnit_Framework_MockObject_Stub_ReturnValueMap' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Stub/ReturnValueMap.php', - 'PHPUnit_Framework_MockObject_Verifiable' => __DIR__ . '/..' . '/phpunit/phpunit-mock-objects/src/Framework/MockObject/Verifiable.php', - 'PHPUnit_Framework_OutputError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/OutputError.php', - 'PHPUnit_Framework_RiskyTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTest.php', - 'PHPUnit_Framework_RiskyTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/RiskyTestError.php', - 'PHPUnit_Framework_SelfDescribing' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SelfDescribing.php', - 'PHPUnit_Framework_SkippedTest' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTest.php', - 'PHPUnit_Framework_SkippedTestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestCase.php', - 'PHPUnit_Framework_SkippedTestError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestError.php', - 'PHPUnit_Framework_SkippedTestSuiteError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SkippedTestSuiteError.php', - 'PHPUnit_Framework_SyntheticError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/SyntheticError.php', - 'PHPUnit_Framework_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Test.php', - 'PHPUnit_Framework_TestCase' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestCase.php', - 'PHPUnit_Framework_TestFailure' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestFailure.php', - 'PHPUnit_Framework_TestListener' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestListener.php', - 'PHPUnit_Framework_TestResult' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestResult.php', - 'PHPUnit_Framework_TestSuite' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite.php', - 'PHPUnit_Framework_TestSuite_DataProvider' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/TestSuite/DataProvider.php', - 'PHPUnit_Framework_UnintentionallyCoveredCodeError' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/UnintentionallyCoveredCodeError.php', - 'PHPUnit_Framework_Warning' => __DIR__ . '/..' . '/phpunit/phpunit/src/Framework/Warning.php', - 'PHPUnit_Runner_BaseTestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/BaseTestRunner.php', - 'PHPUnit_Runner_Exception' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Exception.php', - 'PHPUnit_Runner_Filter_Factory' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Factory.php', - 'PHPUnit_Runner_Filter_GroupFilterIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group.php', - 'PHPUnit_Runner_Filter_Group_Exclude' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Exclude.php', - 'PHPUnit_Runner_Filter_Group_Include' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Group/Include.php', - 'PHPUnit_Runner_Filter_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Filter/Test.php', - 'PHPUnit_Runner_StandardTestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/StandardTestSuiteLoader.php', - 'PHPUnit_Runner_TestSuiteLoader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/TestSuiteLoader.php', - 'PHPUnit_Runner_Version' => __DIR__ . '/..' . '/phpunit/phpunit/src/Runner/Version.php', - 'PHPUnit_TextUI_Command' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/Command.php', - 'PHPUnit_TextUI_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/ResultPrinter.php', - 'PHPUnit_TextUI_TestRunner' => __DIR__ . '/..' . '/phpunit/phpunit/src/TextUI/TestRunner.php', - 'PHPUnit_Util_Blacklist' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Blacklist.php', - 'PHPUnit_Util_Configuration' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Configuration.php', - 'PHPUnit_Util_ErrorHandler' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/ErrorHandler.php', - 'PHPUnit_Util_Fileloader' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Fileloader.php', - 'PHPUnit_Util_Filesystem' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filesystem.php', - 'PHPUnit_Util_Filter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Filter.php', - 'PHPUnit_Util_Getopt' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Getopt.php', - 'PHPUnit_Util_GlobalState' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/GlobalState.php', - 'PHPUnit_Util_InvalidArgumentHelper' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/InvalidArgumentHelper.php', - 'PHPUnit_Util_Log_JSON' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JSON.php', - 'PHPUnit_Util_Log_JUnit' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/JUnit.php', - 'PHPUnit_Util_Log_TAP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Log/TAP.php', - 'PHPUnit_Util_PHP' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP.php', - 'PHPUnit_Util_PHP_Default' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Default.php', - 'PHPUnit_Util_PHP_Windows' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/PHP/Windows.php', - 'PHPUnit_Util_Printer' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Printer.php', - 'PHPUnit_Util_Regex' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Regex.php', - 'PHPUnit_Util_String' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/String.php', - 'PHPUnit_Util_Test' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Test.php', - 'PHPUnit_Util_TestDox_NamePrettifier' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/NamePrettifier.php', - 'PHPUnit_Util_TestDox_ResultPrinter' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter.php', - 'PHPUnit_Util_TestDox_ResultPrinter_HTML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/HTML.php', - 'PHPUnit_Util_TestDox_ResultPrinter_Text' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestDox/ResultPrinter/Text.php', - 'PHPUnit_Util_TestSuiteIterator' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/TestSuiteIterator.php', - 'PHPUnit_Util_Type' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/Type.php', - 'PHPUnit_Util_XML' => __DIR__ . '/..' . '/phpunit/phpunit/src/Util/XML.php', - 'PHP_CodeCoverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage.php', - 'PHP_CodeCoverage_Driver' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Driver.php', - 'PHP_CodeCoverage_Driver_HHVM' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/HHVM.php', - 'PHP_CodeCoverage_Driver_PHPDBG' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/PHPDBG.php', - 'PHP_CodeCoverage_Driver_Xdebug' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Driver/Xdebug.php', - 'PHP_CodeCoverage_Exception' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Exception.php', - 'PHP_CodeCoverage_Exception_UnintentionallyCoveredCode' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Exception/UnintentionallyCoveredCode.php', - 'PHP_CodeCoverage_Filter' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Filter.php', - 'PHP_CodeCoverage_Report_Clover' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Clover.php', - 'PHP_CodeCoverage_Report_Crap4j' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Crap4j.php', - 'PHP_CodeCoverage_Report_Factory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Factory.php', - 'PHP_CodeCoverage_Report_HTML' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML.php', - 'PHP_CodeCoverage_Report_HTML_Renderer' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_Dashboard' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Dashboard.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/Directory.php', - 'PHP_CodeCoverage_Report_HTML_Renderer_File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/HTML/Renderer/File.php', - 'PHP_CodeCoverage_Report_Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node.php', - 'PHP_CodeCoverage_Report_Node_Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Directory.php', - 'PHP_CodeCoverage_Report_Node_File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/File.php', - 'PHP_CodeCoverage_Report_Node_Iterator' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Node/Iterator.php', - 'PHP_CodeCoverage_Report_PHP' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/PHP.php', - 'PHP_CodeCoverage_Report_Text' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/Text.php', - 'PHP_CodeCoverage_Report_XML' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML.php', - 'PHP_CodeCoverage_Report_XML_Directory' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Directory.php', - 'PHP_CodeCoverage_Report_XML_File' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File.php', - 'PHP_CodeCoverage_Report_XML_File_Coverage' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Coverage.php', - 'PHP_CodeCoverage_Report_XML_File_Method' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Method.php', - 'PHP_CodeCoverage_Report_XML_File_Report' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Report.php', - 'PHP_CodeCoverage_Report_XML_File_Unit' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/File/Unit.php', - 'PHP_CodeCoverage_Report_XML_Node' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Node.php', - 'PHP_CodeCoverage_Report_XML_Project' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Project.php', - 'PHP_CodeCoverage_Report_XML_Tests' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Tests.php', - 'PHP_CodeCoverage_Report_XML_Totals' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Report/XML/Totals.php', - 'PHP_CodeCoverage_Util' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Util.php', - 'PHP_CodeCoverage_Util_InvalidArgumentHelper' => __DIR__ . '/..' . '/phpunit/php-code-coverage/src/CodeCoverage/Util/InvalidArgumentHelper.php', - 'PHP_Timer' => __DIR__ . '/..' . '/phpunit/php-timer/src/Timer.php', - 'PHP_Token' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_TokenWithScope' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_TokenWithScopeAndVisibility' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ABSTRACT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AMPERSAND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AND_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ARRAY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ARRAY_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ASYNC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_AWAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BACKTICK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BAD_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOLEAN_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOLEAN_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BOOL_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_BREAK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CALLABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CARET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CASE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CATCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CHARACTER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLASS_NAME_CONSTANT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLONE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CLOSE_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COALESCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMMA' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_COMPILER_HALT_OFFSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONCAT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONSTANT_ENCAPSED_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CONTINUE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_CURLY_OPEN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DEC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DEFAULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIV' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DIV_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOC_COMMENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOLLAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOLLAR_OPEN_CURLY_BRACES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_COLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_DOUBLE_QUOTES' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELLIPSIS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELSE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ELSEIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EMPTY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENCAPSED_AND_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDDECLARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDFOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDFOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDIF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDSWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENDWHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_END_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ENUM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EQUALS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EVAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXCLAMATION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_EXTENDS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FINAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FINALLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FOREACH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_FUNC_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GLOBAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GOTO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_HALT_COMPILER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IMPLEMENTS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INCLUDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INCLUDE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INLINE_HTML' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INSTANCEOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INSTEADOF' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INTERFACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_INT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ISSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_GREATER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_NOT_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_NOT_IDENTICAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_IS_SMALLER_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_Includes' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_JOIN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_ARROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_CP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LAMBDA_OP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LINE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LIST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LNUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_AND' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_OR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LOGICAL_XOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_METHOD_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MINUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MINUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MOD_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MULT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_MUL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NAMESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NEW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NS_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NS_SEPARATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NULLSAFE_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_NUM_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OBJECT_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OBJECT_OPERATOR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_ONUMBER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_BRACKET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_CURLY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_SQUARE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_TAG' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OPEN_TAG_WITH_ECHO' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_OR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PAAMAYIM_NEKUDOTAYIM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PERCENT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PIPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PLUS' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PLUS_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_POW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_POW_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PRINT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PRIVATE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PROTECTED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_PUBLIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_QUESTION_MARK' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_REQUIRE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_REQUIRE_ONCE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_RETURN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SEMICOLON' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SHAPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SL_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SPACESHIP' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_START_HEREDOC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STATIC' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_STRING_VARNAME' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SUPER' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_SWITCH' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_Stream' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream.php', - 'PHP_Token_Stream_CachingFactory' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token/Stream/CachingFactory.php', - 'PHP_Token_THROW' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TILDE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRAIT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRAIT_C' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TRY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPELIST_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_TYPELIST_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_UNSET' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_UNSET_CAST' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_USE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_USE_FUNCTION' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_VAR' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_VARIABLE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHERE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHILE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_WHITESPACE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_ATTRIBUTE' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CATEGORY' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CATEGORY_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_CHILDREN' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_LABEL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_REQUIRED' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TAG_GT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TAG_LT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XHP_TEXT' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_XOR_EQUAL' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_YIELD' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'PHP_Token_YIELD_FROM' => __DIR__ . '/..' . '/phpunit/php-token-stream/src/Token.php', - 'SebastianBergmann\\Comparator\\ArrayComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ArrayComparator.php', - 'SebastianBergmann\\Comparator\\Comparator' => __DIR__ . '/..' . '/sebastian/comparator/src/Comparator.php', - 'SebastianBergmann\\Comparator\\ComparisonFailure' => __DIR__ . '/..' . '/sebastian/comparator/src/ComparisonFailure.php', - 'SebastianBergmann\\Comparator\\DOMNodeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DOMNodeComparator.php', - 'SebastianBergmann\\Comparator\\DateTimeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DateTimeComparator.php', - 'SebastianBergmann\\Comparator\\DoubleComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/DoubleComparator.php', - 'SebastianBergmann\\Comparator\\ExceptionComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ExceptionComparator.php', - 'SebastianBergmann\\Comparator\\Factory' => __DIR__ . '/..' . '/sebastian/comparator/src/Factory.php', - 'SebastianBergmann\\Comparator\\MockObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/MockObjectComparator.php', - 'SebastianBergmann\\Comparator\\NumericComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/NumericComparator.php', - 'SebastianBergmann\\Comparator\\ObjectComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ObjectComparator.php', - 'SebastianBergmann\\Comparator\\ResourceComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ResourceComparator.php', - 'SebastianBergmann\\Comparator\\ScalarComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/ScalarComparator.php', - 'SebastianBergmann\\Comparator\\SplObjectStorageComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/SplObjectStorageComparator.php', - 'SebastianBergmann\\Comparator\\TypeComparator' => __DIR__ . '/..' . '/sebastian/comparator/src/TypeComparator.php', - 'SebastianBergmann\\Diff\\Chunk' => __DIR__ . '/..' . '/sebastian/diff/src/Chunk.php', - 'SebastianBergmann\\Diff\\Diff' => __DIR__ . '/..' . '/sebastian/diff/src/Diff.php', - 'SebastianBergmann\\Diff\\Differ' => __DIR__ . '/..' . '/sebastian/diff/src/Differ.php', - 'SebastianBergmann\\Diff\\LCS\\LongestCommonSubsequence' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/LongestCommonSubsequence.php', - 'SebastianBergmann\\Diff\\LCS\\MemoryEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/MemoryEfficientLongestCommonSubsequenceImplementation.php', - 'SebastianBergmann\\Diff\\LCS\\TimeEfficientImplementation' => __DIR__ . '/..' . '/sebastian/diff/src/LCS/TimeEfficientLongestCommonSubsequenceImplementation.php', - 'SebastianBergmann\\Diff\\Line' => __DIR__ . '/..' . '/sebastian/diff/src/Line.php', - 'SebastianBergmann\\Diff\\Parser' => __DIR__ . '/..' . '/sebastian/diff/src/Parser.php', - 'SebastianBergmann\\Environment\\Console' => __DIR__ . '/..' . '/sebastian/environment/src/Console.php', - 'SebastianBergmann\\Environment\\Runtime' => __DIR__ . '/..' . '/sebastian/environment/src/Runtime.php', - 'SebastianBergmann\\Exporter\\Exporter' => __DIR__ . '/..' . '/sebastian/exporter/src/Exporter.php', - 'SebastianBergmann\\GlobalState\\Blacklist' => __DIR__ . '/..' . '/sebastian/global-state/src/Blacklist.php', - 'SebastianBergmann\\GlobalState\\CodeExporter' => __DIR__ . '/..' . '/sebastian/global-state/src/CodeExporter.php', - 'SebastianBergmann\\GlobalState\\Exception' => __DIR__ . '/..' . '/sebastian/global-state/src/Exception.php', - 'SebastianBergmann\\GlobalState\\Restorer' => __DIR__ . '/..' . '/sebastian/global-state/src/Restorer.php', - 'SebastianBergmann\\GlobalState\\RuntimeException' => __DIR__ . '/..' . '/sebastian/global-state/src/RuntimeException.php', - 'SebastianBergmann\\GlobalState\\Snapshot' => __DIR__ . '/..' . '/sebastian/global-state/src/Snapshot.php', - 'SebastianBergmann\\RecursionContext\\Context' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Context.php', - 'SebastianBergmann\\RecursionContext\\Exception' => __DIR__ . '/..' . '/sebastian/recursion-context/src/Exception.php', - 'SebastianBergmann\\RecursionContext\\InvalidArgumentException' => __DIR__ . '/..' . '/sebastian/recursion-context/src/InvalidArgumentException.php', - 'SebastianBergmann\\Version' => __DIR__ . '/..' . '/sebastian/version/src/Version.php', - 'Text_Template' => __DIR__ . '/..' . '/phpunit/php-text-template/src/Template.php', - 'think\\view\\driver\\Angular' => __DIR__ . '/..' . '/topthink/think-angular/drivers/thinkphp5/Angular.php', ); public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::$prefixDirsPsr4; - $loader->prefixesPsr0 = ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::$prefixesPsr0; - $loader->classMap = ComposerStaticInit547f1ba8138a17d6b867624279d0c7eb::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInitde23ff7bcf12cf4bb58fbe1d807f2677::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInitde23ff7bcf12cf4bb58fbe1d807f2677::$prefixDirsPsr4; }, null, ClassLoader::class); } diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json old mode 100755 new mode 100644 index d2692c7..b2f2190 --- a/vendor/composer/installed.json +++ b/vendor/composer/installed.json @@ -1,32 +1,43 @@ [ { - "name": "topthink/think-image", - "version": "v1.0.7", - "version_normalized": "1.0.7.0", + "name": "topthink/framework", + "version": "v5.0.21", + "version_normalized": "5.0.21.0", "source": { "type": "git", - "url": "https://github.com/top-think/think-image.git", - "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" + "url": "https://github.com/top-think/framework.git", + "reference": "60e38a040b31885ade6887208d1429ac4052f623" }, "dist": { "type": "zip", - "url": "https://packagist.phpcomposer.com/files/top-think/think-image/8586cf47f117481c6d415b20f7dedf62e79d5512.zip", - "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", - "shasum": "" + "url": "https://api.github.com/repos/top-think/framework/zipball/60e38a040b31885ade6887208d1429ac4052f623", + "reference": "60e38a040b31885ade6887208d1429ac4052f623", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "ext-gd": "*" + "php": ">=5.4.0", + "topthink/think-installer": "~1.0" }, "require-dev": { + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsstream": "~1.6", + "phpdocumentor/reflection-docblock": "^2.0", + "phploc/phploc": "2.*", "phpunit/phpunit": "4.8.*", - "topthink/framework": "^5.0" + "sebastian/phpcpd": "2.*" }, - "time": "2016-09-29 06:05:43", - "type": "library", + "time": "2018-08-23T09:38:39+00:00", + "type": "think-framework", "installation-source": "dist", "autoload": { "psr-4": { - "think\\": "src" + "think\\": "library/think" } }, "notification-url": "https://packagist.org/downloads/", @@ -35,11 +46,17 @@ ], "authors": [ { - "name": "yunwuxin", - "email": "448901948@qq.com" + "name": "liu21st", + "email": "liu21st@gmail.com" } ], - "description": "The ThinkPHP5 Image Package" + "description": "the new thinkphp framework", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ] }, { "name": "topthink/think-captcha", @@ -52,11 +69,17 @@ }, "dist": { "type": "zip", - "url": "https://packagist.phpcomposer.com/files/top-think/think-captcha/0c55455df26a1626a60d0dc35d2d89002b741d44.zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/0c55455df26a1626a60d0dc35d2d89002b741d44", "reference": "0c55455df26a1626a60d0dc35d2d89002b741d44", - "shasum": "" + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] }, - "time": "2016-07-06 01:47:11", + "time": "2016-07-06T01:47:11+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -80,1710 +103,144 @@ "description": "captcha package for thinkphp5" }, { - "name": "sebastian/version", - "version": "1.0.6", + "name": "topthink/think-helper", + "version": "v1.0.6", "version_normalized": "1.0.6.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/version.git", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/version/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6.zip", - "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", - "shasum": "" - }, - "time": "2015-06-21 13:59:46", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Library that helps with managing the version number of Git-hosted PHP projects", - "homepage": "https://github.com/sebastianbergmann/version" - }, - { - "name": "sebastian/global-state", - "version": "1.1.1", - "version_normalized": "1.1.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" + "url": "https://github.com/top-think/think-helper.git", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f" }, "dist": { "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/global-state/bc37d50fea7d017d3d340f230811c9f1d7280af4.zip", - "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "suggest": { - "ext-uopz": "*" - }, - "time": "2015-10-12 03:26:01", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" + "url": "https://api.github.com/repos/top-think/think-helper/zipball/0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } ] }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", - "keywords": [ - "global state" - ] - }, - { - "name": "sebastian/exporter", - "version": "1.2.2", - "version_normalized": "1.2.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/exporter/42c4c2eec485ee3e159ec9884f95b431287edde4.zip", - "reference": "42c4c2eec485ee3e159ec9884f95b431287edde4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/recursion-context": "~1.0" - }, - "require-dev": { - "ext-mbstring": "*", - "phpunit/phpunit": "~4.4" - }, - "time": "2016-06-17 09:04:28", + "time": "2017-04-05T07:15:37+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, "installation-source": "dist", "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "psr-4": { + "think\\helper\\": "src" }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "http://www.github.com/sebastianbergmann/exporter", - "keywords": [ - "export", - "exporter" - ] - }, - { - "name": "sebastian/environment", - "version": "1.3.8", - "version_normalized": "1.3.8.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/environment/be2c607e43ce4c89ecd60e75c6a85c126e754aea.zip", - "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8 || ^5.0" - }, - "time": "2016-08-18 05:49:44", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" + "files": [ + "src/helper.php" ] }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" + "name": "yunwuxin", + "email": "448901948@qq.com" } ], - "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "http://www.github.com/sebastianbergmann/environment", - "keywords": [ - "Xdebug", - "environment", - "hhvm" - ] + "description": "The ThinkPHP5 Helper Package" }, { - "name": "doctrine/instantiator", - "version": "1.0.5", - "version_normalized": "1.0.5.0", + "name": "topthink/think-image", + "version": "v1.0.7", + "version_normalized": "1.0.7.0", "source": { "type": "git", - "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "url": "https://github.com/top-think/think-image.git", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512" }, "dist": { "type": "zip", - "url": "https://packagist.phpcomposer.com/files/doctrine/instantiator/8e884e78f9f0eb1329e445619e04456e64d8051d.zip", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", - "shasum": "" + "url": "https://api.github.com/repos/top-think/think-image/zipball/8586cf47f117481c6d415b20f7dedf62e79d5512", + "reference": "8586cf47f117481c6d415b20f7dedf62e79d5512", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } + ] }, "require": { - "php": ">=5.3,<8.0-DEV" + "ext-gd": "*" }, "require-dev": { - "athletic/athletic": "~0.1.8", - "ext-pdo": "*", - "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpunit/phpunit": "4.8.*", + "topthink/framework": "^5.0" }, - "time": "2015-06-14 21:17:01", + "time": "2016-09-29T06:05:43+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, "installation-source": "dist", "autoload": { "psr-4": { - "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com", - "homepage": "http://ocramius.github.com/" - } - ], - "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", - "keywords": [ - "constructor", - "instantiate" - ] - }, - { - "name": "phpunit/php-text-template", - "version": "1.2.1", - "version_normalized": "1.2.1.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/php-text-template/31f8b717e51d9a2afca6c9f046f5d69fc27c8686.zip", - "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "time": "2015-06-21 13:50:34", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "Simple template engine.", - "homepage": "https://github.com/sebastianbergmann/php-text-template/", - "keywords": [ - "template" - ] - }, - { - "name": "phpunit/phpunit-mock-objects", - "version": "2.3.8", - "version_normalized": "2.3.8.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/phpunit-mock-objects/ac8e7a3db35738d56ee9a76e78a4e03d97628983.zip", - "reference": "ac8e7a3db35738d56ee9a76e78a4e03d97628983", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": ">=5.3.3", - "phpunit/php-text-template": "~1.2", - "sebastian/exporter": "~1.2" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "suggest": { - "ext-soap": "*" - }, - "time": "2015-10-02 06:51:40", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.3.x-dev" + "think\\": "src" } }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-3-Clause" + "Apache-2.0" ], "authors": [ { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "name": "yunwuxin", + "email": "448901948@qq.com" } ], - "description": "Mock Object library for PHPUnit", - "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", - "keywords": [ - "mock", - "xunit" - ] + "description": "The ThinkPHP5 Image Package" }, { - "name": "phpunit/php-code-coverage", - "version": "2.2.4", - "version_normalized": "2.2.4.0", + "name": "topthink/think-installer", + "version": "v1.0.12", + "version_normalized": "1.0.12.0", "source": { "type": "git", - "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979" + "url": "https://github.com/top-think/think-installer.git", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d" }, "dist": { "type": "zip", - "url": "https://packagist.phpcomposer.com/files/sebastianbergmann/php-code-coverage/eabf68b476ac7d0f73793aada060f1c1a9bf8979.zip", - "reference": "eabf68b476ac7d0f73793aada060f1c1a9bf8979", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "phpunit/php-file-iterator": "~1.3", - "phpunit/php-text-template": "~1.2", - "phpunit/php-token-stream": "~1.3", - "sebastian/environment": "^1.3.2", - "sebastian/version": "~1.0" - }, - "require-dev": { - "ext-xdebug": ">=2.1.4", - "phpunit/phpunit": "~4" - }, - "suggest": { - "ext-dom": "*", - "ext-xdebug": ">=2.2.1", - "ext-xmlwriter": "*" - }, - "time": "2015-10-06 15:47:00", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" + "url": "https://api.github.com/repos/top-think/think-installer/zipball/1be326e68f63de4e95977ed50f46ae75f017556d", + "reference": "1be326e68f63de4e95977ed50f46ae75f017556d", + "shasum": "", + "mirrors": [ + { + "url": "https://dl.laravel-china.org/%package%/%reference%.%type%", + "preferred": true + } ] }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", - "homepage": "https://github.com/sebastianbergmann/php-code-coverage", - "keywords": [ - "coverage", - "testing", - "xunit" - ] - }, - { - "name": "phpdocumentor/reflection-common", - "version": "1.0", - "version_normalized": "1.0.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionCommon.git", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/phpDocumentor/ReflectionCommon/144c307535e82c8fdcaacbcfc1d6d8eeb896687c.zip", - "reference": "144c307535e82c8fdcaacbcfc1d6d8eeb896687c", - "shasum": "" - }, "require": { - "php": ">=5.5" + "composer-plugin-api": "^1.0" }, "require-dev": { - "phpunit/phpunit": "^4.6" + "composer/composer": "1.0.*@dev" }, - "time": "2015-12-27 11:43:31", - "type": "library", + "time": "2017-05-27T06:58:09+00:00", + "type": "composer-plugin", "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } + "class": "think\\composer\\Plugin" }, "installation-source": "dist", "autoload": { "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src" - ] + "think\\composer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "description": "Common reflection classes used by phpdocumentor to reflect the code structure", - "homepage": "http://www.phpdoc.org", - "keywords": [ - "FQSEN", - "phpDocumentor", - "phpdoc", - "reflection", - "static analysis" - ] - }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "3.1.1", - "version_normalized": "3.1.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/phpDocumentor/ReflectionDocBlock/8331b5efe816ae05461b7ca1e721c01b46bafb3e.zip", - "reference": "8331b5efe816ae05461b7ca1e721c01b46bafb3e", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0@dev", - "phpdocumentor/type-resolver": "^0.2.0", - "webmozart/assert": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^4.4" - }, - "time": "2016-09-30 07:12:33", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ], - "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock." - }, - { - "name": "topthink/think-testing", - "version": "v1.0.6", - "version_normalized": "1.0.6.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-testing.git", - "reference": "e89794e5c58aa5587f7b08038e9468150870b185" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/top-think/think-testing/e89794e5c58aa5587f7b08038e9468150870b185.zip", - "reference": "e89794e5c58aa5587f7b08038e9468150870b185", - "shasum": "" - }, - "require": { - "phpunit/phpunit": "^4.8.26", - "symfony/dom-crawler": "^2.8.8", - "topthink/think-helper": "~1.0", - "topthink/think-installer": "~1.0" - }, - "time": "2016-08-08 09:43:56", - "type": "think-testing", - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\testing\\": "src" - }, - "files": [ - "src/config.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ] - }, - { - "name": "topthink/think-worker", - "version": "v1.0.1", - "version_normalized": "1.0.1.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-worker.git", - "reference": "b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/top-think/think-worker/b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1.zip", - "reference": "b609ff5e38dbb7194aab027d2b2c6b31a7ed1bd1", - "shasum": "" - }, - "require": { - "workerman/workerman": "^3.3.0" - }, - "time": "2016-10-08 06:07:03", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\worker\\": "src" - }, - "files": [] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "description": "workerman extend for thinkphp5" - }, - { - "name": "topthink/think-installer", - "version": "v1.0.11", - "version_normalized": "1.0.11.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-installer.git", - "reference": "4c6e1ebecd1afce3f4ccc47e147d61bbe1bf641d" - }, - "dist": { - "type": "zip", - "url": "https://packagist.phpcomposer.com/files/top-think/think-installer/4c6e1ebecd1afce3f4ccc47e147d61bbe1bf641d.zip", - "reference": "4c6e1ebecd1afce3f4ccc47e147d61bbe1bf641d", - "shasum": "" - }, - "require": { - "composer-plugin-api": "^1.0" - }, - "require-dev": { - "composer/composer": "1.0.*@dev" - }, - "time": "2016-12-01 09:08:45", - "type": "composer-plugin", - "extra": { - "class": "think\\composer\\Plugin" - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\composer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ] - }, - { - "name": "topthink/framework", - "version": "v5.0.9", - "version_normalized": "5.0.9.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/framework.git", - "reference": "24ae4b9042bf9acfd041eb34602b14eb8669bc3f" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/framework/24ae4b9042bf9acfd041eb34602b14eb8669bc3f.zip", - "reference": "24ae4b9042bf9acfd041eb34602b14eb8669bc3f", - "shasum": "" - }, - "require": { - "php": ">=5.4.0", - "topthink/think-installer": "~1.0" - }, - "require-dev": { - "johnkary/phpunit-speedtrap": "^1.0", - "mikey179/vfsstream": "~1.6", - "phpdocumentor/reflection-docblock": "^2.0", - "phploc/phploc": "2.*", - "phpunit/phpunit": "4.8.*", - "sebastian/phpcpd": "2.*" - }, - "time": "2017-05-20 10:43:04", - "type": "think-framework", - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\": "library/think" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "description": "the new thinkphp framework", - "homepage": "http://thinkphp.cn/", - "keywords": [ - "framework", - "orm", - "thinkphp" - ] - }, - { - "name": "topthink/think-migration", - "version": "v1.1.1", - "version_normalized": "1.1.1.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-migration.git", - "reference": "8e489f8d38a39876690c0e00fcf9a54ae92c4d3d" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/think-migration/8e489f8d38a39876690c0e00fcf9a54ae92c4d3d.zip", - "reference": "8e489f8d38a39876690c0e00fcf9a54ae92c4d3d", - "shasum": "" - }, - "require-dev": { - "topthink/framework": "^5.0" - }, - "time": "2017-03-31 06:33:23", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Phinx\\": "phinx/src/Phinx", - "think\\migration\\": "src" - }, - "files": [ - "src/config.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ] - }, - { - "name": "topthink/think-mongo", - "version": "v1.7", - "version_normalized": "1.7.0.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-mongo.git", - "reference": "fc552c8fe5798679a52f4f0d7e60763f64aeb155" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/think-mongo/fc552c8fe5798679a52f4f0d7e60763f64aeb155.zip", - "reference": "fc552c8fe5798679a52f4f0d7e60763f64aeb155", - "shasum": "" - }, - "time": "2017-05-05 07:52:33", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\mongo\\": "src" - }, - "files": [] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "liu21st", - "email": "liu21st@gmail.com" - } - ], - "description": "mongodb driver for thinkphp5" - }, - { - "name": "topthink/think-helper", - "version": "v1.0.6", - "version_normalized": "1.0.6.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-helper.git", - "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/think-helper/0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f.zip", - "reference": "0c99dc625b0d2d4124e1b6ca15a3ad6f0125963f", - "shasum": "" - }, - "time": "2017-04-05 07:15:37", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\helper\\": "src" - }, - "files": [ - "src/helper.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" + "Apache-2.0" ], "authors": [ { "name": "yunwuxin", "email": "448901948@qq.com" } - ], - "description": "The ThinkPHP5 Helper Package" - }, - { - "name": "topthink/think-queue", - "version": "v1.1.3", - "version_normalized": "1.1.3.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-queue.git", - "reference": "07480c36381344bef9db9c9bbe3de28005a839d3" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/think-queue/07480c36381344bef9db9c9bbe3de28005a839d3.zip", - "reference": "07480c36381344bef9db9c9bbe3de28005a839d3", - "shasum": "" - }, - "require": { - "topthink/think-helper": ">=1.0.4", - "topthink/think-installer": ">=1.0.10" - }, - "time": "2017-02-28 08:07:37", - "type": "think-extend", - "extra": { - "think-config": { - "queue": "src/config.php" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "think\\": "src" - }, - "files": [ - "src/common.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache-2.0" - ], - "authors": [ - { - "name": "yunwuxin", - "email": "448901948@qq.com" - } - ], - "description": "The ThinkPHP5 Queue Package" - }, - { - "name": "topthink/think-angular", - "version": "1.0.10", - "version_normalized": "1.0.10.0", - "source": { - "type": "git", - "url": "https://github.com/top-think/think-angular.git", - "reference": "7fc77bc951a899428f0a4f910392938e9ee96e5c" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/top-think/think-angular/7fc77bc951a899428f0a4f910392938e9ee96e5c.zip", - "reference": "7fc77bc951a899428f0a4f910392938e9ee96e5c", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "time": "2017-05-16 10:56:18", - "type": "library", - "installation-source": "dist", - "autoload": { - "classmap": { - "think\\view\\driver\\Angular": "drivers/thinkphp5/Angular.php" - }, - "psr-4": { - "think\\angular\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "Apache2" - ], - "authors": [ - { - "name": "玩具机器人", - "email": "zhaishuaigan@qq.com" - } - ], - "description": "think angular view engine", - "homepage": "http://kancloud.cn/shuai/php-angular" - }, - { - "name": "symfony/yaml", - "version": "v3.2.8", - "version_normalized": "3.2.8.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/yaml.git", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/symfony/yaml/acec26fcf7f3031e094e910b94b002fa53d4e4d6.zip", - "reference": "acec26fcf7f3031e094e910b94b002fa53d4e4d6", - "shasum": "" - }, - "require": { - "php": ">=5.5.9" - }, - "require-dev": { - "symfony/console": "~2.8|~3.0" - }, - "suggest": { - "symfony/console": "For validating YAML files using the lint command" - }, - "time": "2017-05-01 14:55:58", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\Yaml\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony Yaml Component", - "homepage": "https://symfony.com" - }, - { - "name": "sebastian/recursion-context", - "version": "1.0.5", - "version_normalized": "1.0.5.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/recursion-context/b19cc3298482a335a95f3016d2f8a6950f0fbcd7.zip", - "reference": "b19cc3298482a335a95f3016d2f8a6950f0fbcd7", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "time": "2016-10-03 07:41:43", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Adam Harvey", - "email": "aharvey@php.net" - } - ], - "description": "Provides functionality to recursively process PHP variables", - "homepage": "http://www.github.com/sebastianbergmann/recursion-context" - }, - { - "name": "sebastian/diff", - "version": "1.4.2", - "version_normalized": "1.4.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "3c7d21999e815cdfac70c6c7d79d3a9cb1bc7bc2" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/diff/3c7d21999e815cdfac70c6c7d79d3a9cb1bc7bc2.zip", - "reference": "3c7d21999e815cdfac70c6c7d79d3a9cb1bc7bc2", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "time": "2017-05-18 13:44:30", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff" - ] - }, - { - "name": "sebastian/comparator", - "version": "1.2.4", - "version_normalized": "1.2.4.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/comparator/2b7424b55f5047b47ac6e5ccb20b2aea4011d9be.zip", - "reference": "2b7424b55f5047b47ac6e5ccb20b2aea4011d9be", - "shasum": "" - }, - "require": { - "php": ">=5.3.3", - "sebastian/diff": "~1.2", - "sebastian/exporter": "~1.2 || ~2.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.4" - }, - "time": "2017-01-29 09:50:25", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.2.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Jeff Welch", - "email": "whatthejeff@gmail.com" - }, - { - "name": "Volker Dusch", - "email": "github@wallbash.com" - }, - { - "name": "Bernhard Schussek", - "email": "bschussek@2bepublished.at" - }, - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Provides the functionality to compare PHP values for equality", - "homepage": "http://www.github.com/sebastianbergmann/comparator", - "keywords": [ - "comparator", - "compare", - "equality" - ] - }, - { - "name": "phpunit/php-timer", - "version": "1.0.9", - "version_normalized": "1.0.9.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/php-timer/3dcf38ca72b158baf0bc245e9184d3fdffa9c46f.zip", - "reference": "3dcf38ca72b158baf0bc245e9184d3fdffa9c46f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.0" - }, - "time": "2017-02-26 11:10:40", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "Utility class for timing", - "homepage": "https://github.com/sebastianbergmann/php-timer/", - "keywords": [ - "timer" - ] - }, - { - "name": "phpunit/php-file-iterator", - "version": "1.4.2", - "version_normalized": "1.4.2.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/php-file-iterator/3cc8f69b3028d0f96a9078e6295d86e9bf019be5.zip", - "reference": "3cc8f69b3028d0f96a9078e6295d86e9bf019be5", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "time": "2016-10-03 07:40:28", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "FilterIterator implementation that filters files based on a list of suffixes.", - "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", - "keywords": [ - "filesystem", - "iterator" - ] - }, - { - "name": "phpunit/php-token-stream", - "version": "1.4.11", - "version_normalized": "1.4.11.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/php-token-stream/e03f8f67534427a787e21a385a67ec3ca6978ea7.zip", - "reference": "e03f8f67534427a787e21a385a67ec3ca6978ea7", - "shasum": "" - }, - "require": { - "ext-tokenizer": "*", - "php": ">=5.3.3" - }, - "require-dev": { - "phpunit/phpunit": "~4.2" - }, - "time": "2017-02-27 10:12:30", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.4-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - } - ], - "description": "Wrapper around PHP's tokenizer extension.", - "homepage": "https://github.com/sebastianbergmann/php-token-stream/", - "keywords": [ - "tokenizer" - ] - }, - { - "name": "webmozart/assert", - "version": "1.2.0", - "version_normalized": "1.2.0.0", - "source": { - "type": "git", - "url": "https://github.com/webmozart/assert.git", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/webmozart/assert/2db61e59ff05fe5126d152bd0655c9ea113e550f.zip", - "reference": "2db61e59ff05fe5126d152bd0655c9ea113e550f", - "shasum": "" - }, - "require": { - "php": "^5.3.3 || ^7.0" - }, - "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" - }, - "time": "2016-11-23 20:04:58", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Webmozart\\Assert\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Bernhard Schussek", - "email": "bschussek@gmail.com" - } - ], - "description": "Assertions to validate method input/output with nice error messages.", - "keywords": [ - "assert", - "check", - "validate" - ] - }, - { - "name": "phpdocumentor/type-resolver", - "version": "0.2.1", - "version_normalized": "0.2.1.0", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/phpDocumentor/TypeResolver/e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb.zip", - "reference": "e224fb2ea2fba6d3ad6fdaef91cd09a172155ccb", - "shasum": "" - }, - "require": { - "php": ">=5.5", - "phpdocumentor/reflection-common": "^1.0" - }, - "require-dev": { - "mockery/mockery": "^0.9.4", - "phpunit/phpunit": "^5.2||^4.8.24" - }, - "time": "2016-11-25 06:54:22", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": [ - "src/" - ] - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - } - ] - }, - { - "name": "phpspec/prophecy", - "version": "v1.7.0", - "version_normalized": "1.7.0.0", - "source": { - "type": "git", - "url": "https://github.com/phpspec/prophecy.git", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/phpspec/prophecy/93d39f1f7f9326d746203c7c056f300f7f126073.zip", - "reference": "93d39f1f7f9326d746203c7c056f300f7f126073", - "shasum": "" - }, - "require": { - "doctrine/instantiator": "^1.0.2", - "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2", - "sebastian/comparator": "^1.1|^2.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" - }, - "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", - "phpunit/phpunit": "^4.8 || ^5.6.5" - }, - "time": "2017-03-02 20:05:34", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.6.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-0": { - "Prophecy\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Konstantin Kudryashov", - "email": "ever.zet@gmail.com", - "homepage": "http://everzet.com" - }, - { - "name": "Marcello Duarte", - "email": "marcello.duarte@gmail.com" - } - ], - "description": "Highly opinionated mocking framework for PHP 5.3+", - "homepage": "https://github.com/phpspec/prophecy", - "keywords": [ - "Double", - "Dummy", - "fake", - "mock", - "spy", - "stub" - ] - }, - { - "name": "phpunit/phpunit", - "version": "4.8.35", - "version_normalized": "4.8.35.0", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/sebastianbergmann/phpunit/791b1a67c25af50e230f841ee7a9c6eba507dc87.zip", - "reference": "791b1a67c25af50e230f841ee7a9c6eba507dc87", - "shasum": "" - }, - "require": { - "ext-dom": "*", - "ext-json": "*", - "ext-pcre": "*", - "ext-reflection": "*", - "ext-spl": "*", - "php": ">=5.3.3", - "phpspec/prophecy": "^1.3.1", - "phpunit/php-code-coverage": "~2.1", - "phpunit/php-file-iterator": "~1.4", - "phpunit/php-text-template": "~1.2", - "phpunit/php-timer": "^1.0.6", - "phpunit/phpunit-mock-objects": "~2.3", - "sebastian/comparator": "~1.2.2", - "sebastian/diff": "~1.2", - "sebastian/environment": "~1.3", - "sebastian/exporter": "~1.2", - "sebastian/global-state": "~1.0", - "sebastian/version": "~1.0", - "symfony/yaml": "~2.1|~3.0" - }, - "suggest": { - "phpunit/php-invoker": "~1.1" - }, - "time": "2017-02-06 05:18:07", - "bin": [ - "phpunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.8.x-dev" - } - }, - "installation-source": "dist", - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" - } - ], - "description": "The PHP Unit Testing framework.", - "homepage": "https://phpunit.de/", - "keywords": [ - "phpunit", - "testing", - "xunit" - ] - }, - { - "name": "symfony/polyfill-mbstring", - "version": "v1.3.0", - "version_normalized": "1.3.0.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/symfony/polyfill-mbstring/e79d363049d1c2128f133a2667e4f4190904f7f4.zip", - "reference": "e79d363049d1c2128f133a2667e4f4190904f7f4", - "shasum": "" - }, - "require": { - "php": ">=5.3.3" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, - "time": "2016-11-14 01:06:16", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.3-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - }, - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for the Mbstring extension", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "mbstring", - "polyfill", - "portable", - "shim" - ] - }, - { - "name": "symfony/dom-crawler", - "version": "v2.8.20", - "version_normalized": "2.8.20.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/dom-crawler.git", - "reference": "dd2fc76e011cb480b2d163c3b2deebd3de4471c8" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/symfony/dom-crawler/dd2fc76e011cb480b2d163c3b2deebd3de4471c8.zip", - "reference": "dd2fc76e011cb480b2d163c3b2deebd3de4471c8", - "shasum": "" - }, - "require": { - "php": ">=5.3.9", - "symfony/polyfill-mbstring": "~1.0" - }, - "require-dev": { - "symfony/css-selector": "~2.8|~3.0.0" - }, - "suggest": { - "symfony/css-selector": "" - }, - "time": "2017-04-12 14:07:15", - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.8-dev" - } - }, - "installation-source": "dist", - "autoload": { - "psr-4": { - "Symfony\\Component\\DomCrawler\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony DomCrawler Component", - "homepage": "https://symfony.com" - }, - { - "name": "workerman/workerman", - "version": "v3.4.2", - "version_normalized": "3.4.2.0", - "source": { - "type": "git", - "url": "https://github.com/walkor/Workerman.git", - "reference": "a10f58e546af755f3546a309afea7ac71f879aaf" - }, - "dist": { - "type": "zip", - "url": "https://files.phpcomposer.com/files/walkor/Workerman/a10f58e546af755f3546a309afea7ac71f879aaf.zip", - "reference": "a10f58e546af755f3546a309afea7ac71f879aaf", - "shasum": "" - }, - "require": { - "php": ">=5.3" - }, - "suggest": { - "ext-event": "For better performance." - }, - "time": "2017-05-04 15:08:38", - "type": "library", - "installation-source": "dist", - "autoload": { - "psr-4": { - "Workerman\\": "./" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "walkor", - "email": "walkor@workerman.net", - "homepage": "http://www.workerman.net", - "role": "Developer" - } - ], - "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", - "homepage": "http://www.workerman.net", - "keywords": [ - "asynchronous", - "event-loop" ] } ] diff --git a/vendor/symfony/dom-crawler/.gitignore b/vendor/symfony/dom-crawler/.gitignore deleted file mode 100755 index c49a5d8..0000000 --- a/vendor/symfony/dom-crawler/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -vendor/ -composer.lock -phpunit.xml diff --git a/vendor/symfony/dom-crawler/CHANGELOG.md b/vendor/symfony/dom-crawler/CHANGELOG.md deleted file mode 100755 index 48fd323..0000000 --- a/vendor/symfony/dom-crawler/CHANGELOG.md +++ /dev/null @@ -1,45 +0,0 @@ -CHANGELOG -========= - -2.5.0 ------ - -* [BC BREAK] The default value for checkbox and radio inputs without a value attribute have changed - from '1' to 'on' to match the HTML specification. -* [BC BREAK] The typehints on the `Link`, `Form` and `FormField` classes have been changed from - `\DOMNode` to `DOMElement`. Using any other type of `DOMNode` was triggering fatal errors in previous - versions. Code extending these classes will need to update the typehints when overwriting these methods. - -2.4.0 ------ - - * `Crawler::addXmlContent()` removes the default document namespace again if it's an only namespace. - * added support for automatic discovery and explicit registration of document - namespaces for `Crawler::filterXPath()` and `Crawler::filter()` - * improved content type guessing in `Crawler::addContent()` - * [BC BREAK] `Crawler::addXmlContent()` no longer removes the default document - namespace - -2.3.0 ------ - - * added Crawler::html() - * [BC BREAK] Crawler::each() and Crawler::reduce() now return Crawler instances instead of DomElement instances - * added schema relative URL support to links - * added support for HTML5 'form' attribute - -2.2.0 ------ - - * added a way to set raw path to the file in FileFormField - necessary for - simulating HTTP requests - -2.1.0 ------ - - * added support for the HTTP PATCH method - * refactored the Form class internals to support multi-dimensional fields - (the public API is backward compatible) - * added a way to get parsing errors for Crawler::addHtmlContent() and - Crawler::addXmlContent() via libxml functions - * added support for submitting a form without a submit button diff --git a/vendor/symfony/dom-crawler/Crawler.php b/vendor/symfony/dom-crawler/Crawler.php deleted file mode 100755 index bb429c2..0000000 --- a/vendor/symfony/dom-crawler/Crawler.php +++ /dev/null @@ -1,1209 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -use Symfony\Component\CssSelector\CssSelectorConverter; - -/** - * Crawler eases navigation of a list of \DOMNode objects. - * - * @author Fabien Potencier - */ -class Crawler extends \SplObjectStorage -{ - /** - * @var string The current URI - */ - protected $uri; - - /** - * @var string The default namespace prefix to be used with XPath and CSS expressions - */ - private $defaultNamespacePrefix = 'default'; - - /** - * @var array A map of manually registered namespaces - */ - private $namespaces = array(); - - /** - * @var string The base href value - */ - private $baseHref; - - /** - * @var \DOMDocument|null - */ - private $document; - - /** - * Whether the Crawler contains HTML or XML content (used when converting CSS to XPath). - * - * @var bool - */ - private $isHtml = true; - - /** - * Constructor. - * - * @param mixed $node A Node to use as the base for the crawling - * @param string $currentUri The current URI - * @param string $baseHref The base href value - */ - public function __construct($node = null, $currentUri = null, $baseHref = null) - { - $this->uri = $currentUri; - $this->baseHref = $baseHref ?: $currentUri; - - $this->add($node); - } - - /** - * Removes all the nodes. - */ - public function clear() - { - parent::removeAll($this); - $this->document = null; - } - - /** - * Adds a node to the current list of nodes. - * - * This method uses the appropriate specialized add*() method based - * on the type of the argument. - * - * @param \DOMNodeList|\DOMNode|array|string|null $node A node - * - * @throws \InvalidArgumentException When node is not the expected type. - */ - public function add($node) - { - if ($node instanceof \DOMNodeList) { - $this->addNodeList($node); - } elseif ($node instanceof \DOMNode) { - $this->addNode($node); - } elseif (is_array($node)) { - $this->addNodes($node); - } elseif (is_string($node)) { - $this->addContent($node); - } elseif (null !== $node) { - throw new \InvalidArgumentException(sprintf('Expecting a DOMNodeList or DOMNode instance, an array, a string, or null, but got "%s".', is_object($node) ? get_class($node) : gettype($node))); - } - } - - /** - * Adds HTML/XML content. - * - * If the charset is not set via the content type, it is assumed - * to be ISO-8859-1, which is the default charset defined by the - * HTTP 1.1 specification. - * - * @param string $content A string to parse as HTML/XML - * @param null|string $type The content type of the string - */ - public function addContent($content, $type = null) - { - if (empty($type)) { - $type = 0 === strpos($content, ']+charset *= *["\']?([a-zA-Z\-0-9_:.]+)/i', $content, $matches)) { - $charset = $matches[1]; - } - - if (null === $charset) { - $charset = 'ISO-8859-1'; - } - - if ('x' === $xmlMatches[1]) { - $this->addXmlContent($content, $charset); - } else { - $this->addHtmlContent($content, $charset); - } - } - - /** - * Adds an HTML content to the list of nodes. - * - * The libxml errors are disabled when the content is parsed. - * - * If you want to get parsing errors, be sure to enable - * internal errors via libxml_use_internal_errors(true) - * and then, get the errors via libxml_get_errors(). Be - * sure to clear errors with libxml_clear_errors() afterward. - * - * @param string $content The HTML content - * @param string $charset The charset - */ - public function addHtmlContent($content, $charset = 'UTF-8') - { - $internalErrors = libxml_use_internal_errors(true); - $disableEntities = libxml_disable_entity_loader(true); - - $dom = new \DOMDocument('1.0', $charset); - $dom->validateOnParse = true; - - set_error_handler(function () { throw new \Exception(); }); - - try { - // Convert charset to HTML-entities to work around bugs in DOMDocument::loadHTML() - $content = mb_convert_encoding($content, 'HTML-ENTITIES', $charset); - } catch (\Exception $e) { - } - - restore_error_handler(); - - if ('' !== trim($content)) { - @$dom->loadHTML($content); - } - - libxml_use_internal_errors($internalErrors); - libxml_disable_entity_loader($disableEntities); - - $this->addDocument($dom); - - $base = $this->filterRelativeXPath('descendant-or-self::base')->extract(array('href')); - - $baseHref = current($base); - if (count($base) && !empty($baseHref)) { - if ($this->baseHref) { - $linkNode = $dom->createElement('a'); - $linkNode->setAttribute('href', $baseHref); - $link = new Link($linkNode, $this->baseHref); - $this->baseHref = $link->getUri(); - } else { - $this->baseHref = $baseHref; - } - } - } - - /** - * Adds an XML content to the list of nodes. - * - * The libxml errors are disabled when the content is parsed. - * - * If you want to get parsing errors, be sure to enable - * internal errors via libxml_use_internal_errors(true) - * and then, get the errors via libxml_get_errors(). Be - * sure to clear errors with libxml_clear_errors() afterward. - * - * @param string $content The XML content - * @param string $charset The charset - * @param int $options Bitwise OR of the libxml option constants - * LIBXML_PARSEHUGE is dangerous, see - * http://symfony.com/blog/security-release-symfony-2-0-17-released - */ - public function addXmlContent($content, $charset = 'UTF-8', $options = LIBXML_NONET) - { - // remove the default namespace if it's the only namespace to make XPath expressions simpler - if (!preg_match('/xmlns:/', $content)) { - $content = str_replace('xmlns', 'ns', $content); - } - - $internalErrors = libxml_use_internal_errors(true); - $disableEntities = libxml_disable_entity_loader(true); - - $dom = new \DOMDocument('1.0', $charset); - $dom->validateOnParse = true; - - if ('' !== trim($content)) { - @$dom->loadXML($content, $options); - } - - libxml_use_internal_errors($internalErrors); - libxml_disable_entity_loader($disableEntities); - - $this->addDocument($dom); - - $this->isHtml = false; - } - - /** - * Adds a \DOMDocument to the list of nodes. - * - * @param \DOMDocument $dom A \DOMDocument instance - */ - public function addDocument(\DOMDocument $dom) - { - if ($dom->documentElement) { - $this->addNode($dom->documentElement); - } - } - - /** - * Adds a \DOMNodeList to the list of nodes. - * - * @param \DOMNodeList $nodes A \DOMNodeList instance - */ - public function addNodeList(\DOMNodeList $nodes) - { - foreach ($nodes as $node) { - if ($node instanceof \DOMNode) { - $this->addNode($node); - } - } - } - - /** - * Adds an array of \DOMNode instances to the list of nodes. - * - * @param \DOMNode[] $nodes An array of \DOMNode instances - */ - public function addNodes(array $nodes) - { - foreach ($nodes as $node) { - $this->add($node); - } - } - - /** - * Adds a \DOMNode instance to the list of nodes. - * - * @param \DOMNode $node A \DOMNode instance - */ - public function addNode(\DOMNode $node) - { - if ($node instanceof \DOMDocument) { - $node = $node->documentElement; - } - - if (null !== $this->document && $this->document !== $node->ownerDocument) { - @trigger_error('Attaching DOM nodes from multiple documents in a Crawler is deprecated as of 2.8 and will be forbidden in 3.0.', E_USER_DEPRECATED); - } - - if (null === $this->document) { - $this->document = $node->ownerDocument; - } - - parent::attach($node); - } - - // Serializing and unserializing a crawler creates DOM objects in a corrupted state. DOM elements are not properly serializable. - public function unserialize($serialized) - { - throw new \BadMethodCallException('A Crawler cannot be serialized.'); - } - - public function serialize() - { - throw new \BadMethodCallException('A Crawler cannot be serialized.'); - } - - /** - * Returns a node given its position in the node list. - * - * @param int $position The position - * - * @return self - */ - public function eq($position) - { - foreach ($this as $i => $node) { - if ($i == $position) { - return $this->createSubCrawler($node); - } - } - - return $this->createSubCrawler(null); - } - - /** - * Calls an anonymous function on each node of the list. - * - * The anonymous function receives the position and the node wrapped - * in a Crawler instance as arguments. - * - * Example: - * - * $crawler->filter('h1')->each(function ($node, $i) { - * return $node->text(); - * }); - * - * @param \Closure $closure An anonymous function - * - * @return array An array of values returned by the anonymous function - */ - public function each(\Closure $closure) - { - $data = array(); - foreach ($this as $i => $node) { - $data[] = $closure($this->createSubCrawler($node), $i); - } - - return $data; - } - - /** - * Slices the list of nodes by $offset and $length. - * - * @param int $offset - * @param int $length - * - * @return self - */ - public function slice($offset = 0, $length = -1) - { - return $this->createSubCrawler(iterator_to_array(new \LimitIterator($this, $offset, $length))); - } - - /** - * Reduces the list of nodes by calling an anonymous function. - * - * To remove a node from the list, the anonymous function must return false. - * - * @param \Closure $closure An anonymous function - * - * @return self - */ - public function reduce(\Closure $closure) - { - $nodes = array(); - foreach ($this as $i => $node) { - if (false !== $closure($this->createSubCrawler($node), $i)) { - $nodes[] = $node; - } - } - - return $this->createSubCrawler($nodes); - } - - /** - * Returns the first node of the current selection. - * - * @return self - */ - public function first() - { - return $this->eq(0); - } - - /** - * Returns the last node of the current selection. - * - * @return self - */ - public function last() - { - return $this->eq(count($this) - 1); - } - - /** - * Returns the siblings nodes of the current selection. - * - * @return self - * - * @throws \InvalidArgumentException When current node is empty - */ - public function siblings() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->createSubCrawler($this->sibling($this->getNode(0)->parentNode->firstChild)); - } - - /** - * Returns the next siblings nodes of the current selection. - * - * @return self - * - * @throws \InvalidArgumentException When current node is empty - */ - public function nextAll() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->createSubCrawler($this->sibling($this->getNode(0))); - } - - /** - * Returns the previous sibling nodes of the current selection. - * - * @return self - * - * @throws \InvalidArgumentException - */ - public function previousAll() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->createSubCrawler($this->sibling($this->getNode(0), 'previousSibling')); - } - - /** - * Returns the parents nodes of the current selection. - * - * @return self - * - * @throws \InvalidArgumentException When current node is empty - */ - public function parents() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - $nodes = array(); - - while ($node = $node->parentNode) { - if (XML_ELEMENT_NODE === $node->nodeType) { - $nodes[] = $node; - } - } - - return $this->createSubCrawler($nodes); - } - - /** - * Returns the children nodes of the current selection. - * - * @return self - * - * @throws \InvalidArgumentException When current node is empty - */ - public function children() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0)->firstChild; - - return $this->createSubCrawler($node ? $this->sibling($node) : array()); - } - - /** - * Returns the attribute value of the first node of the list. - * - * @param string $attribute The attribute name - * - * @return string|null The attribute value or null if the attribute does not exist - * - * @throws \InvalidArgumentException When current node is empty - */ - public function attr($attribute) - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - - return $node->hasAttribute($attribute) ? $node->getAttribute($attribute) : null; - } - - /** - * Returns the node name of the first node of the list. - * - * @return string The node name - * - * @throws \InvalidArgumentException When current node is empty - */ - public function nodeName() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->getNode(0)->nodeName; - } - - /** - * Returns the node value of the first node of the list. - * - * @return string The node value - * - * @throws \InvalidArgumentException When current node is empty - */ - public function text() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - return $this->getNode(0)->nodeValue; - } - - /** - * Returns the first node of the list as HTML. - * - * @return string The node html - * - * @throws \InvalidArgumentException When current node is empty - */ - public function html() - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $html = ''; - foreach ($this->getNode(0)->childNodes as $child) { - $html .= $child->ownerDocument->saveHTML($child); - } - - return $html; - } - - /** - * Extracts information from the list of nodes. - * - * You can extract attributes or/and the node value (_text). - * - * Example: - * - * $crawler->filter('h1 a')->extract(array('_text', 'href')); - * - * @param array $attributes An array of attributes - * - * @return array An array of extracted values - */ - public function extract($attributes) - { - $attributes = (array) $attributes; - $count = count($attributes); - - $data = array(); - foreach ($this as $node) { - $elements = array(); - foreach ($attributes as $attribute) { - if ('_text' === $attribute) { - $elements[] = $node->nodeValue; - } else { - $elements[] = $node->getAttribute($attribute); - } - } - - $data[] = $count > 1 ? $elements : $elements[0]; - } - - return $data; - } - - /** - * Filters the list of nodes with an XPath expression. - * - * The XPath expression is evaluated in the context of the crawler, which - * is considered as a fake parent of the elements inside it. - * This means that a child selector "div" or "./div" will match only - * the div elements of the current crawler, not their children. - * - * @param string $xpath An XPath expression - * - * @return self - */ - public function filterXPath($xpath) - { - $xpath = $this->relativize($xpath); - - // If we dropped all expressions in the XPath while preparing it, there would be no match - if ('' === $xpath) { - return $this->createSubCrawler(null); - } - - return $this->filterRelativeXPath($xpath); - } - - /** - * Filters the list of nodes with a CSS selector. - * - * This method only works if you have installed the CssSelector Symfony Component. - * - * @param string $selector A CSS selector - * - * @return self - * - * @throws \RuntimeException if the CssSelector Component is not available - */ - public function filter($selector) - { - if (!class_exists('Symfony\\Component\\CssSelector\\CssSelectorConverter')) { - throw new \RuntimeException('Unable to filter with a CSS selector as the Symfony CssSelector 2.8+ is not installed (you can use filterXPath instead).'); - } - - $converter = new CssSelectorConverter($this->isHtml); - - // The CssSelector already prefixes the selector with descendant-or-self:: - return $this->filterRelativeXPath($converter->toXPath($selector)); - } - - /** - * Selects links by name or alt value for clickable images. - * - * @param string $value The link text - * - * @return self - */ - public function selectLink($value) - { - $xpath = sprintf('descendant-or-self::a[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) ', static::xpathLiteral(' '.$value.' ')). - sprintf('or ./img[contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)]]', static::xpathLiteral(' '.$value.' ')); - - return $this->filterRelativeXPath($xpath); - } - - /** - * Selects a button by name or alt value for images. - * - * @param string $value The button text - * - * @return self - */ - public function selectButton($value) - { - $translate = 'translate(@type, "ABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyz")'; - $xpath = sprintf('descendant-or-self::input[((contains(%s, "submit") or contains(%s, "button")) and contains(concat(\' \', normalize-space(string(@value)), \' \'), %s)) ', $translate, $translate, static::xpathLiteral(' '.$value.' ')). - sprintf('or (contains(%s, "image") and contains(concat(\' \', normalize-space(string(@alt)), \' \'), %s)) or @id=%s or @name=%s] ', $translate, static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)). - sprintf('| descendant-or-self::button[contains(concat(\' \', normalize-space(string(.)), \' \'), %s) or @id=%s or @name=%s]', static::xpathLiteral(' '.$value.' '), static::xpathLiteral($value), static::xpathLiteral($value)); - - return $this->filterRelativeXPath($xpath); - } - - /** - * Returns a Link object for the first node in the list. - * - * @param string $method The method for the link (get by default) - * - * @return Link A Link instance - * - * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement - */ - public function link($method = 'get') - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - - if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_class($node))); - } - - return new Link($node, $this->baseHref, $method); - } - - /** - * Returns an array of Link objects for the nodes in the list. - * - * @return Link[] An array of Link instances - * - * @throws \InvalidArgumentException If the current node list contains non-DOMElement instances - */ - public function links() - { - $links = array(); - foreach ($this as $node) { - if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The current node list should contain only DOMElement instances, "%s" found.', get_class($node))); - } - - $links[] = new Link($node, $this->baseHref, 'get'); - } - - return $links; - } - - /** - * Returns a Form object for the first node in the list. - * - * @param array $values An array of values for the form fields - * @param string $method The method for the form - * - * @return Form A Form instance - * - * @throws \InvalidArgumentException If the current node list is empty or the selected node is not instance of DOMElement - */ - public function form(array $values = null, $method = null) - { - if (!count($this)) { - throw new \InvalidArgumentException('The current node list is empty.'); - } - - $node = $this->getNode(0); - - if (!$node instanceof \DOMElement) { - throw new \InvalidArgumentException(sprintf('The selected node should be instance of DOMElement, got "%s".', get_class($node))); - } - - $form = new Form($node, $this->uri, $method, $this->baseHref); - - if (null !== $values) { - $form->setValues($values); - } - - return $form; - } - - /** - * Overloads a default namespace prefix to be used with XPath and CSS expressions. - * - * @param string $prefix - */ - public function setDefaultNamespacePrefix($prefix) - { - $this->defaultNamespacePrefix = $prefix; - } - - /** - * @param string $prefix - * @param string $namespace - */ - public function registerNamespace($prefix, $namespace) - { - $this->namespaces[$prefix] = $namespace; - } - - /** - * Converts string for XPath expressions. - * - * Escaped characters are: quotes (") and apostrophe ('). - * - * Examples: - * - * echo Crawler::xpathLiteral('foo " bar'); - * //prints 'foo " bar' - * - * echo Crawler::xpathLiteral("foo ' bar"); - * //prints "foo ' bar" - * - * echo Crawler::xpathLiteral('a\'b"c'); - * //prints concat('a', "'", 'b"c') - * - * - * @param string $s String to be escaped - * - * @return string Converted string - */ - public static function xpathLiteral($s) - { - if (false === strpos($s, "'")) { - return sprintf("'%s'", $s); - } - - if (false === strpos($s, '"')) { - return sprintf('"%s"', $s); - } - - $string = $s; - $parts = array(); - while (true) { - if (false !== $pos = strpos($string, "'")) { - $parts[] = sprintf("'%s'", substr($string, 0, $pos)); - $parts[] = "\"'\""; - $string = substr($string, $pos + 1); - } else { - $parts[] = "'$string'"; - break; - } - } - - return sprintf('concat(%s)', implode(', ', $parts)); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function attach($object, $data = null) - { - $this->triggerDeprecation(__METHOD__); - - parent::attach($object, $data); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function detach($object) - { - $this->triggerDeprecation(__METHOD__); - - parent::detach($object); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function contains($object) - { - $this->triggerDeprecation(__METHOD__); - - return parent::contains($object); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function addAll($storage) - { - $this->triggerDeprecation(__METHOD__); - - parent::addAll($storage); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function removeAll($storage) - { - $this->triggerDeprecation(__METHOD__); - - parent::removeAll($storage); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function removeAllExcept($storage) - { - $this->triggerDeprecation(__METHOD__); - - parent::removeAllExcept($storage); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function getInfo() - { - $this->triggerDeprecation(__METHOD__); - - return parent::getInfo(); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function setInfo($data) - { - $this->triggerDeprecation(__METHOD__); - - parent::setInfo($data); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function offsetExists($object) - { - $this->triggerDeprecation(__METHOD__); - - return parent::offsetExists($object); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function offsetSet($object, $data = null) - { - $this->triggerDeprecation(__METHOD__); - - parent::offsetSet($object, $data); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function offsetUnset($object) - { - $this->triggerDeprecation(__METHOD__); - - parent::offsetUnset($object); - } - - /** - * @deprecated Using the SplObjectStorage API on the Crawler is deprecated as of 2.8 and will be removed in 3.0. - */ - public function offsetGet($object) - { - $this->triggerDeprecation(__METHOD__); - - return parent::offsetGet($object); - } - - /** - * Filters the list of nodes with an XPath expression. - * - * The XPath expression should already be processed to apply it in the context of each node. - * - * @param string $xpath - * - * @return self - */ - private function filterRelativeXPath($xpath) - { - $prefixes = $this->findNamespacePrefixes($xpath); - - $crawler = $this->createSubCrawler(null); - - foreach ($this as $node) { - $domxpath = $this->createDOMXPath($node->ownerDocument, $prefixes); - $crawler->add($domxpath->query($xpath, $node)); - } - - return $crawler; - } - - /** - * Make the XPath relative to the current context. - * - * The returned XPath will match elements matching the XPath inside the current crawler - * when running in the context of a node of the crawler. - * - * @param string $xpath - * - * @return string - */ - private function relativize($xpath) - { - $expressions = array(); - - // An expression which will never match to replace expressions which cannot match in the crawler - // We cannot simply drop - $nonMatchingExpression = 'a[name() = "b"]'; - - $xpathLen = strlen($xpath); - $openedBrackets = 0; - $startPosition = strspn($xpath, " \t\n\r\0\x0B"); - - for ($i = $startPosition; $i <= $xpathLen; ++$i) { - $i += strcspn($xpath, '"\'[]|', $i); - - if ($i < $xpathLen) { - switch ($xpath[$i]) { - case '"': - case "'": - if (false === $i = strpos($xpath, $xpath[$i], $i + 1)) { - return $xpath; // The XPath expression is invalid - } - continue 2; - case '[': - ++$openedBrackets; - continue 2; - case ']': - --$openedBrackets; - continue 2; - } - } - if ($openedBrackets) { - continue; - } - - if ($startPosition < $xpathLen && '(' === $xpath[$startPosition]) { - // If the union is inside some braces, we need to preserve the opening braces and apply - // the change only inside it. - $j = 1 + strspn($xpath, "( \t\n\r\0\x0B", $startPosition + 1); - $parenthesis = substr($xpath, $startPosition, $j); - $startPosition += $j; - } else { - $parenthesis = ''; - } - $expression = rtrim(substr($xpath, $startPosition, $i - $startPosition)); - - // BC for Symfony 2.4 and lower were elements were adding in a fake _root parent - if (0 === strpos($expression, '/_root/')) { - @trigger_error('XPath expressions referencing the fake root node are deprecated since version 2.8 and will be unsupported in 3.0. Please use "./" instead of "/_root/".', E_USER_DEPRECATED); - - $expression = './'.substr($expression, 7); - } elseif (0 === strpos($expression, 'self::*/')) { - $expression = './'.substr($expression, 8); - } - - // add prefix before absolute element selector - if ('' === $expression) { - $expression = $nonMatchingExpression; - } elseif (0 === strpos($expression, '//')) { - $expression = 'descendant-or-self::'.substr($expression, 2); - } elseif (0 === strpos($expression, './/')) { - $expression = 'descendant-or-self::'.substr($expression, 3); - } elseif (0 === strpos($expression, './')) { - $expression = 'self::'.substr($expression, 2); - } elseif (0 === strpos($expression, 'child::')) { - $expression = 'self::'.substr($expression, 7); - } elseif ('/' === $expression[0] || 0 === strpos($expression, 'self::')) { - // the only direct child in Symfony 2.4 and lower is _root, which is already handled previously - // so let's drop the expression entirely - $expression = $nonMatchingExpression; - } elseif ('.' === $expression[0]) { - // '.' is the fake root element in Symfony 2.4 and lower, which is excluded from results - $expression = $nonMatchingExpression; - } elseif (0 === strpos($expression, 'descendant::')) { - $expression = 'descendant-or-self::'.substr($expression, 12); - } elseif (preg_match('/^(ancestor|ancestor-or-self|attribute|following|following-sibling|namespace|parent|preceding|preceding-sibling)::/', $expression)) { - // the fake root has no parent, preceding or following nodes and also no attributes (even no namespace attributes) - $expression = $nonMatchingExpression; - } elseif (0 !== strpos($expression, 'descendant-or-self::')) { - $expression = 'self::'.$expression; - } - $expressions[] = $parenthesis.$expression; - - if ($i === $xpathLen) { - return implode(' | ', $expressions); - } - - $i += strspn($xpath, " \t\n\r\0\x0B", $i + 1); - $startPosition = $i + 1; - } - - return $xpath; // The XPath expression is invalid - } - - /** - * @param int $position - * - * @return \DOMElement|null - */ - public function getNode($position) - { - foreach ($this as $i => $node) { - if ($i == $position) { - return $node; - } - } - } - - /** - * @param \DOMElement $node - * @param string $siblingDir - * - * @return array - */ - protected function sibling($node, $siblingDir = 'nextSibling') - { - $nodes = array(); - - do { - if ($node !== $this->getNode(0) && $node->nodeType === 1) { - $nodes[] = $node; - } - } while ($node = $node->$siblingDir); - - return $nodes; - } - - /** - * @param \DOMDocument $document - * @param array $prefixes - * - * @return \DOMXPath - * - * @throws \InvalidArgumentException - */ - private function createDOMXPath(\DOMDocument $document, array $prefixes = array()) - { - $domxpath = new \DOMXPath($document); - - foreach ($prefixes as $prefix) { - $namespace = $this->discoverNamespace($domxpath, $prefix); - if (null !== $namespace) { - $domxpath->registerNamespace($prefix, $namespace); - } - } - - return $domxpath; - } - - /** - * @param \DOMXPath $domxpath - * @param string $prefix - * - * @return string - * - * @throws \InvalidArgumentException - */ - private function discoverNamespace(\DOMXPath $domxpath, $prefix) - { - if (isset($this->namespaces[$prefix])) { - return $this->namespaces[$prefix]; - } - - // ask for one namespace, otherwise we'd get a collection with an item for each node - $namespaces = $domxpath->query(sprintf('(//namespace::*[name()="%s"])[last()]', $this->defaultNamespacePrefix === $prefix ? '' : $prefix)); - - if ($node = $namespaces->item(0)) { - return $node->nodeValue; - } - } - - /** - * @param string $xpath - * - * @return array - */ - private function findNamespacePrefixes($xpath) - { - if (preg_match_all('/(?P[a-z_][a-z_0-9\-\.]*+):[^"\/:]/i', $xpath, $matches)) { - return array_unique($matches['prefix']); - } - - return array(); - } - - /** - * Creates a crawler for some subnodes. - * - * @param \DOMElement|\DOMElement[]|\DOMNodeList|null $nodes - * - * @return static - */ - private function createSubCrawler($nodes) - { - $crawler = new static($nodes, $this->uri, $this->baseHref); - $crawler->isHtml = $this->isHtml; - $crawler->document = $this->document; - $crawler->namespaces = $this->namespaces; - - return $crawler; - } - - private function triggerDeprecation($methodName, $useTrace = false) - { - if ($useTrace || defined('HHVM_VERSION')) { - if (PHP_VERSION_ID >= 50400) { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 3); - } else { - $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); - } - - // The SplObjectStorage class performs calls to its own methods. These - // method calls must not lead to triggered deprecation notices. - if (isset($trace[2]['class']) && 'SplObjectStorage' === $trace[2]['class']) { - return; - } - } - - @trigger_error('The '.$methodName.' method is deprecated since version 2.8 and will be removed in 3.0.', E_USER_DEPRECATED); - } -} diff --git a/vendor/symfony/dom-crawler/Field/ChoiceFormField.php b/vendor/symfony/dom-crawler/Field/ChoiceFormField.php deleted file mode 100755 index c479daa..0000000 --- a/vendor/symfony/dom-crawler/Field/ChoiceFormField.php +++ /dev/null @@ -1,324 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Field; - -/** - * ChoiceFormField represents a choice form field. - * - * It is constructed from a HTML select tag, or a HTML checkbox, or radio inputs. - * - * @author Fabien Potencier - */ -class ChoiceFormField extends FormField -{ - /** - * @var string - */ - private $type; - /** - * @var bool - */ - private $multiple; - /** - * @var array - */ - private $options; - /** - * @var bool - */ - private $validationDisabled = false; - - /** - * Returns true if the field should be included in the submitted values. - * - * @return bool true if the field should be included in the submitted values, false otherwise - */ - public function hasValue() - { - // don't send a value for unchecked checkboxes - if (in_array($this->type, array('checkbox', 'radio')) && null === $this->value) { - return false; - } - - return true; - } - - /** - * Check if the current selected option is disabled. - * - * @return bool - */ - public function isDisabled() - { - if (parent::isDisabled() && 'select' === $this->type) { - return true; - } - - foreach ($this->options as $option) { - if ($option['value'] == $this->value && $option['disabled']) { - return true; - } - } - - return false; - } - - /** - * Sets the value of the field. - * - * @param string $value The value of the field - */ - public function select($value) - { - $this->setValue($value); - } - - /** - * Ticks a checkbox. - * - * @throws \LogicException When the type provided is not correct - */ - public function tick() - { - if ('checkbox' !== $this->type) { - throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); - } - - $this->setValue(true); - } - - /** - * Ticks a checkbox. - * - * @throws \LogicException When the type provided is not correct - */ - public function untick() - { - if ('checkbox' !== $this->type) { - throw new \LogicException(sprintf('You cannot tick "%s" as it is not a checkbox (%s).', $this->name, $this->type)); - } - - $this->setValue(false); - } - - /** - * Sets the value of the field. - * - * @param string $value The value of the field - * - * @throws \InvalidArgumentException When value type provided is not correct - */ - public function setValue($value) - { - if ('checkbox' === $this->type && false === $value) { - // uncheck - $this->value = null; - } elseif ('checkbox' === $this->type && true === $value) { - // check - $this->value = $this->options[0]['value']; - } else { - if (is_array($value)) { - if (!$this->multiple) { - throw new \InvalidArgumentException(sprintf('The value for "%s" cannot be an array.', $this->name)); - } - - foreach ($value as $v) { - if (!$this->containsOption($v, $this->options)) { - throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $v, implode(', ', $this->availableOptionValues()))); - } - } - } elseif (!$this->containsOption($value, $this->options)) { - throw new \InvalidArgumentException(sprintf('Input "%s" cannot take "%s" as a value (possible values: %s).', $this->name, $value, implode(', ', $this->availableOptionValues()))); - } - - if ($this->multiple) { - $value = (array) $value; - } - - if (is_array($value)) { - $this->value = $value; - } else { - parent::setValue($value); - } - } - } - - /** - * Adds a choice to the current ones. - * - * @param \DOMElement $node - * - * @throws \LogicException When choice provided is not multiple nor radio - * - * @internal - */ - public function addChoice(\DOMElement $node) - { - if (!$this->multiple && 'radio' !== $this->type) { - throw new \LogicException(sprintf('Unable to add a choice for "%s" as it is not multiple or is not a radio button.', $this->name)); - } - - $option = $this->buildOptionValue($node); - $this->options[] = $option; - - if ($node->hasAttribute('checked')) { - $this->value = $option['value']; - } - } - - /** - * Returns the type of the choice field (radio, select, or checkbox). - * - * @return string The type - */ - public function getType() - { - return $this->type; - } - - /** - * Returns true if the field accepts multiple values. - * - * @return bool true if the field accepts multiple values, false otherwise - */ - public function isMultiple() - { - return $this->multiple; - } - - /** - * Initializes the form field. - * - * @throws \LogicException When node type is incorrect - */ - protected function initialize() - { - if ('input' !== $this->node->nodeName && 'select' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input or select tag (%s given).', $this->node->nodeName)); - } - - if ('input' === $this->node->nodeName && 'checkbox' !== strtolower($this->node->getAttribute('type')) && 'radio' !== strtolower($this->node->getAttribute('type'))) { - throw new \LogicException(sprintf('A ChoiceFormField can only be created from an input tag with a type of checkbox or radio (given type is %s).', $this->node->getAttribute('type'))); - } - - $this->value = null; - $this->options = array(); - $this->multiple = false; - - if ('input' == $this->node->nodeName) { - $this->type = strtolower($this->node->getAttribute('type')); - $optionValue = $this->buildOptionValue($this->node); - $this->options[] = $optionValue; - - if ($this->node->hasAttribute('checked')) { - $this->value = $optionValue['value']; - } - } else { - $this->type = 'select'; - if ($this->node->hasAttribute('multiple')) { - $this->multiple = true; - $this->value = array(); - $this->name = str_replace('[]', '', $this->name); - } - - $found = false; - foreach ($this->xpath->query('descendant::option', $this->node) as $option) { - $optionValue = $this->buildOptionValue($option); - $this->options[] = $optionValue; - - if ($option->hasAttribute('selected')) { - $found = true; - if ($this->multiple) { - $this->value[] = $optionValue['value']; - } else { - $this->value = $optionValue['value']; - } - } - } - - // if no option is selected and if it is a simple select box, take the first option as the value - if (!$found && !$this->multiple && !empty($this->options)) { - $this->value = $this->options[0]['value']; - } - } - } - - /** - * Returns option value with associated disabled flag. - * - * @param \DOMElement $node - * - * @return array - */ - private function buildOptionValue(\DOMElement $node) - { - $option = array(); - - $defaultDefaultValue = 'select' === $this->node->nodeName ? '' : 'on'; - $defaultValue = (isset($node->nodeValue) && !empty($node->nodeValue)) ? $node->nodeValue : $defaultDefaultValue; - $option['value'] = $node->hasAttribute('value') ? $node->getAttribute('value') : $defaultValue; - $option['disabled'] = $node->hasAttribute('disabled'); - - return $option; - } - - /** - * Checks whether given value is in the existing options. - * - * @param string $optionValue - * @param array $options - * - * @return bool - */ - public function containsOption($optionValue, $options) - { - if ($this->validationDisabled) { - return true; - } - - foreach ($options as $option) { - if ($option['value'] == $optionValue) { - return true; - } - } - - return false; - } - - /** - * Returns list of available field options. - * - * @return array - */ - public function availableOptionValues() - { - $values = array(); - - foreach ($this->options as $option) { - $values[] = $option['value']; - } - - return $values; - } - - /** - * Disables the internal validation of the field. - * - * @return self - */ - public function disableValidation() - { - $this->validationDisabled = true; - - return $this; - } -} diff --git a/vendor/symfony/dom-crawler/Field/FileFormField.php b/vendor/symfony/dom-crawler/Field/FileFormField.php deleted file mode 100755 index 0e0f943..0000000 --- a/vendor/symfony/dom-crawler/Field/FileFormField.php +++ /dev/null @@ -1,108 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Field; - -/** - * FileFormField represents a file form field (an HTML file input tag). - * - * @author Fabien Potencier - */ -class FileFormField extends FormField -{ - /** - * Sets the PHP error code associated with the field. - * - * @param int $error The error code (one of UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, or UPLOAD_ERR_EXTENSION) - * - * @throws \InvalidArgumentException When error code doesn't exist - */ - public function setErrorCode($error) - { - $codes = array(UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, UPLOAD_ERR_PARTIAL, UPLOAD_ERR_NO_FILE, UPLOAD_ERR_NO_TMP_DIR, UPLOAD_ERR_CANT_WRITE, UPLOAD_ERR_EXTENSION); - if (!in_array($error, $codes)) { - throw new \InvalidArgumentException(sprintf('The error code %s is not valid.', $error)); - } - - $this->value = array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => $error, 'size' => 0); - } - - /** - * Sets the value of the field. - * - * @param string $value The value of the field - */ - public function upload($value) - { - $this->setValue($value); - } - - /** - * Sets the value of the field. - * - * @param string $value The value of the field - */ - public function setValue($value) - { - if (null !== $value && is_readable($value)) { - $error = UPLOAD_ERR_OK; - $size = filesize($value); - $info = pathinfo($value); - $name = $info['basename']; - - // copy to a tmp location - $tmp = sys_get_temp_dir().'/'.sha1(uniqid(mt_rand(), true)); - if (array_key_exists('extension', $info)) { - $tmp .= '.'.$info['extension']; - } - if (is_file($tmp)) { - unlink($tmp); - } - copy($value, $tmp); - $value = $tmp; - } else { - $error = UPLOAD_ERR_NO_FILE; - $size = 0; - $name = ''; - $value = ''; - } - - $this->value = array('name' => $name, 'type' => '', 'tmp_name' => $value, 'error' => $error, 'size' => $size); - } - - /** - * Sets path to the file as string for simulating HTTP request. - * - * @param string $path The path to the file - */ - public function setFilePath($path) - { - parent::setValue($path); - } - - /** - * Initializes the form field. - * - * @throws \LogicException When node type is incorrect - */ - protected function initialize() - { - if ('input' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A FileFormField can only be created from an input tag (%s given).', $this->node->nodeName)); - } - - if ('file' !== strtolower($this->node->getAttribute('type'))) { - throw new \LogicException(sprintf('A FileFormField can only be created from an input tag with a type of file (given type is %s).', $this->node->getAttribute('type'))); - } - - $this->setValue(null); - } -} diff --git a/vendor/symfony/dom-crawler/Field/FormField.php b/vendor/symfony/dom-crawler/Field/FormField.php deleted file mode 100755 index a6b33de..0000000 --- a/vendor/symfony/dom-crawler/Field/FormField.php +++ /dev/null @@ -1,114 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Field; - -/** - * FormField is the abstract class for all form fields. - * - * @author Fabien Potencier - */ -abstract class FormField -{ - /** - * @var \DOMElement - */ - protected $node; - /** - * @var string - */ - protected $name; - /** - * @var string - */ - protected $value; - /** - * @var \DOMDocument - */ - protected $document; - /** - * @var \DOMXPath - */ - protected $xpath; - /** - * @var bool - */ - protected $disabled; - - /** - * Constructor. - * - * @param \DOMElement $node The node associated with this field - */ - public function __construct(\DOMElement $node) - { - $this->node = $node; - $this->name = $node->getAttribute('name'); - $this->xpath = new \DOMXPath($node->ownerDocument); - - $this->initialize(); - } - - /** - * Returns the name of the field. - * - * @return string The name of the field - */ - public function getName() - { - return $this->name; - } - - /** - * Gets the value of the field. - * - * @return string|array The value of the field - */ - public function getValue() - { - return $this->value; - } - - /** - * Sets the value of the field. - * - * @param string $value The value of the field - */ - public function setValue($value) - { - $this->value = (string) $value; - } - - /** - * Returns true if the field should be included in the submitted values. - * - * @return bool true if the field should be included in the submitted values, false otherwise - */ - public function hasValue() - { - return true; - } - - /** - * Check if the current field is disabled. - * - * @return bool - */ - public function isDisabled() - { - return $this->node->hasAttribute('disabled'); - } - - /** - * Initializes the form field. - */ - abstract protected function initialize(); -} diff --git a/vendor/symfony/dom-crawler/Field/InputFormField.php b/vendor/symfony/dom-crawler/Field/InputFormField.php deleted file mode 100755 index 090913e..0000000 --- a/vendor/symfony/dom-crawler/Field/InputFormField.php +++ /dev/null @@ -1,45 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Field; - -/** - * InputFormField represents an input form field (an HTML input tag). - * - * For inputs with type of file, checkbox, or radio, there are other more - * specialized classes (cf. FileFormField and ChoiceFormField). - * - * @author Fabien Potencier - */ -class InputFormField extends FormField -{ - /** - * Initializes the form field. - * - * @throws \LogicException When node type is incorrect - */ - protected function initialize() - { - if ('input' !== $this->node->nodeName && 'button' !== $this->node->nodeName) { - throw new \LogicException(sprintf('An InputFormField can only be created from an input or button tag (%s given).', $this->node->nodeName)); - } - - if ('checkbox' === strtolower($this->node->getAttribute('type'))) { - throw new \LogicException('Checkboxes should be instances of ChoiceFormField.'); - } - - if ('file' === strtolower($this->node->getAttribute('type'))) { - throw new \LogicException('File inputs should be instances of FileFormField.'); - } - - $this->value = $this->node->getAttribute('value'); - } -} diff --git a/vendor/symfony/dom-crawler/Field/TextareaFormField.php b/vendor/symfony/dom-crawler/Field/TextareaFormField.php deleted file mode 100755 index 15526e1..0000000 --- a/vendor/symfony/dom-crawler/Field/TextareaFormField.php +++ /dev/null @@ -1,37 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Field; - -/** - * TextareaFormField represents a textarea form field (an HTML textarea tag). - * - * @author Fabien Potencier - */ -class TextareaFormField extends FormField -{ - /** - * Initializes the form field. - * - * @throws \LogicException When node type is incorrect - */ - protected function initialize() - { - if ('textarea' !== $this->node->nodeName) { - throw new \LogicException(sprintf('A TextareaFormField can only be created from a textarea tag (%s given).', $this->node->nodeName)); - } - - $this->value = ''; - foreach ($this->node->childNodes as $node) { - $this->value .= $node->wholeText; - } - } -} diff --git a/vendor/symfony/dom-crawler/Form.php b/vendor/symfony/dom-crawler/Form.php deleted file mode 100755 index bad1b34..0000000 --- a/vendor/symfony/dom-crawler/Form.php +++ /dev/null @@ -1,473 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -use Symfony\Component\DomCrawler\Field\ChoiceFormField; -use Symfony\Component\DomCrawler\Field\FormField; - -/** - * Form represents an HTML form. - * - * @author Fabien Potencier - */ -class Form extends Link implements \ArrayAccess -{ - /** - * @var \DOMElement - */ - private $button; - - /** - * @var FormFieldRegistry - */ - private $fields; - - /** - * @var string - */ - private $baseHref; - - /** - * Constructor. - * - * @param \DOMElement $node A \DOMElement instance - * @param string $currentUri The URI of the page where the form is embedded - * @param string $method The method to use for the link (if null, it defaults to the method defined by the form) - * @param string $baseHref The URI of the used for relative links, but not for empty action - * - * @throws \LogicException if the node is not a button inside a form tag - */ - public function __construct(\DOMElement $node, $currentUri, $method = null, $baseHref = null) - { - parent::__construct($node, $currentUri, $method); - $this->baseHref = $baseHref; - - $this->initialize(); - } - - /** - * Gets the form node associated with this form. - * - * @return \DOMElement A \DOMElement instance - */ - public function getFormNode() - { - return $this->node; - } - - /** - * Sets the value of the fields. - * - * @param array $values An array of field values - * - * @return $this - */ - public function setValues(array $values) - { - foreach ($values as $name => $value) { - $this->fields->set($name, $value); - } - - return $this; - } - - /** - * Gets the field values. - * - * The returned array does not include file fields (@see getFiles). - * - * @return array An array of field values - */ - public function getValues() - { - $values = array(); - foreach ($this->fields->all() as $name => $field) { - if ($field->isDisabled()) { - continue; - } - - if (!$field instanceof Field\FileFormField && $field->hasValue()) { - $values[$name] = $field->getValue(); - } - } - - return $values; - } - - /** - * Gets the file field values. - * - * @return array An array of file field values - */ - public function getFiles() - { - if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { - return array(); - } - - $files = array(); - - foreach ($this->fields->all() as $name => $field) { - if ($field->isDisabled()) { - continue; - } - - if ($field instanceof Field\FileFormField) { - $files[$name] = $field->getValue(); - } - } - - return $files; - } - - /** - * Gets the field values as PHP. - * - * This method converts fields with the array notation - * (like foo[bar] to arrays) like PHP does. - * - * @return array An array of field values - */ - public function getPhpValues() - { - $values = array(); - foreach ($this->getValues() as $name => $value) { - $qs = http_build_query(array($name => $value), '', '&'); - if (!empty($qs)) { - parse_str($qs, $expandedValue); - $varName = substr($name, 0, strlen(key($expandedValue))); - $values = array_replace_recursive($values, array($varName => current($expandedValue))); - } - } - - return $values; - } - - /** - * Gets the file field values as PHP. - * - * This method converts fields with the array notation - * (like foo[bar] to arrays) like PHP does. - * The returned array is consistent with the array for field values - * (@see getPhpValues), rather than uploaded files found in $_FILES. - * For a compound file field foo[bar] it will create foo[bar][name], - * instead of foo[name][bar] which would be found in $_FILES. - * - * @return array An array of file field values - */ - public function getPhpFiles() - { - $values = array(); - foreach ($this->getFiles() as $name => $value) { - $qs = http_build_query(array($name => $value), '', '&'); - if (!empty($qs)) { - parse_str($qs, $expandedValue); - $varName = substr($name, 0, strlen(key($expandedValue))); - $values = array_replace_recursive($values, array($varName => current($expandedValue))); - } - } - - return $values; - } - - /** - * Gets the URI of the form. - * - * The returned URI is not the same as the form "action" attribute. - * This method merges the value if the method is GET to mimics - * browser behavior. - * - * @return string The URI - */ - public function getUri() - { - $uri = parent::getUri(); - - if (!in_array($this->getMethod(), array('POST', 'PUT', 'DELETE', 'PATCH'))) { - $query = parse_url($uri, PHP_URL_QUERY); - $currentParameters = array(); - if ($query) { - parse_str($query, $currentParameters); - } - - $queryString = http_build_query(array_merge($currentParameters, $this->getValues()), null, '&'); - - $pos = strpos($uri, '?'); - $base = false === $pos ? $uri : substr($uri, 0, $pos); - $uri = rtrim($base.'?'.$queryString, '?'); - } - - return $uri; - } - - protected function getRawUri() - { - return $this->node->getAttribute('action'); - } - - /** - * Gets the form method. - * - * If no method is defined in the form, GET is returned. - * - * @return string The method - */ - public function getMethod() - { - if (null !== $this->method) { - return $this->method; - } - - return $this->node->getAttribute('method') ? strtoupper($this->node->getAttribute('method')) : 'GET'; - } - - /** - * Returns true if the named field exists. - * - * @param string $name The field name - * - * @return bool true if the field exists, false otherwise - */ - public function has($name) - { - return $this->fields->has($name); - } - - /** - * Removes a field from the form. - * - * @param string $name The field name - */ - public function remove($name) - { - $this->fields->remove($name); - } - - /** - * Gets a named field. - * - * @param string $name The field name - * - * @return FormField The field instance - * - * @throws \InvalidArgumentException When field is not present in this form - */ - public function get($name) - { - return $this->fields->get($name); - } - - /** - * Sets a named field. - * - * @param FormField $field The field - */ - public function set(FormField $field) - { - $this->fields->add($field); - } - - /** - * Gets all fields. - * - * @return FormField[] - */ - public function all() - { - return $this->fields->all(); - } - - /** - * Returns true if the named field exists. - * - * @param string $name The field name - * - * @return bool true if the field exists, false otherwise - */ - public function offsetExists($name) - { - return $this->has($name); - } - - /** - * Gets the value of a field. - * - * @param string $name The field name - * - * @return FormField The associated Field instance - * - * @throws \InvalidArgumentException if the field does not exist - */ - public function offsetGet($name) - { - return $this->fields->get($name); - } - - /** - * Sets the value of a field. - * - * @param string $name The field name - * @param string|array $value The value of the field - * - * @throws \InvalidArgumentException if the field does not exist - */ - public function offsetSet($name, $value) - { - $this->fields->set($name, $value); - } - - /** - * Removes a field from the form. - * - * @param string $name The field name - */ - public function offsetUnset($name) - { - $this->fields->remove($name); - } - - /** - * Disables validation. - * - * @return self - */ - public function disableValidation() - { - foreach ($this->fields->all() as $field) { - if ($field instanceof Field\ChoiceFormField) { - $field->disableValidation(); - } - } - - return $this; - } - - /** - * Sets the node for the form. - * - * Expects a 'submit' button \DOMElement and finds the corresponding form element, or the form element itself. - * - * @param \DOMElement $node A \DOMElement instance - * - * @throws \LogicException If given node is not a button or input or does not have a form ancestor - */ - protected function setNode(\DOMElement $node) - { - $this->button = $node; - if ('button' === $node->nodeName || ('input' === $node->nodeName && in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image')))) { - if ($node->hasAttribute('form')) { - // if the node has the HTML5-compliant 'form' attribute, use it - $formId = $node->getAttribute('form'); - $form = $node->ownerDocument->getElementById($formId); - if (null === $form) { - throw new \LogicException(sprintf('The selected node has an invalid form attribute (%s).', $formId)); - } - $this->node = $form; - - return; - } - // we loop until we find a form ancestor - do { - if (null === $node = $node->parentNode) { - throw new \LogicException('The selected node does not have a form ancestor.'); - } - } while ('form' !== $node->nodeName); - } elseif ('form' !== $node->nodeName) { - throw new \LogicException(sprintf('Unable to submit on a "%s" tag.', $node->nodeName)); - } - - $this->node = $node; - } - - /** - * Adds form elements related to this form. - * - * Creates an internal copy of the submitted 'button' element and - * the form node or the entire document depending on whether we need - * to find non-descendant elements through HTML5 'form' attribute. - */ - private function initialize() - { - $this->fields = new FormFieldRegistry(); - - $xpath = new \DOMXPath($this->node->ownerDocument); - - // add submitted button if it has a valid name - if ('form' !== $this->button->nodeName && $this->button->hasAttribute('name') && $this->button->getAttribute('name')) { - if ('input' == $this->button->nodeName && 'image' == strtolower($this->button->getAttribute('type'))) { - $name = $this->button->getAttribute('name'); - $this->button->setAttribute('value', '0'); - - // temporarily change the name of the input node for the x coordinate - $this->button->setAttribute('name', $name.'.x'); - $this->set(new Field\InputFormField($this->button)); - - // temporarily change the name of the input node for the y coordinate - $this->button->setAttribute('name', $name.'.y'); - $this->set(new Field\InputFormField($this->button)); - - // restore the original name of the input node - $this->button->setAttribute('name', $name); - } else { - $this->set(new Field\InputFormField($this->button)); - } - } - - // find form elements corresponding to the current form - if ($this->node->hasAttribute('id')) { - // corresponding elements are either descendants or have a matching HTML5 form attribute - $formId = Crawler::xpathLiteral($this->node->getAttribute('id')); - - $fieldNodes = $xpath->query(sprintf('descendant::input[@form=%s] | descendant::button[@form=%s] | descendant::textarea[@form=%s] | descendant::select[@form=%s] | //form[@id=%s]//input[not(@form)] | //form[@id=%s]//button[not(@form)] | //form[@id=%s]//textarea[not(@form)] | //form[@id=%s]//select[not(@form)]', $formId, $formId, $formId, $formId, $formId, $formId, $formId, $formId)); - foreach ($fieldNodes as $node) { - $this->addField($node); - } - } else { - // do the xpath query with $this->node as the context node, to only find descendant elements - // however, descendant elements with form attribute are not part of this form - $fieldNodes = $xpath->query('descendant::input[not(@form)] | descendant::button[not(@form)] | descendant::textarea[not(@form)] | descendant::select[not(@form)]', $this->node); - foreach ($fieldNodes as $node) { - $this->addField($node); - } - } - - if ($this->baseHref && '' !== $this->node->getAttribute('action')) { - $this->currentUri = $this->baseHref; - } - } - - private function addField(\DOMElement $node) - { - if (!$node->hasAttribute('name') || !$node->getAttribute('name')) { - return; - } - - $nodeName = $node->nodeName; - if ('select' == $nodeName || 'input' == $nodeName && 'checkbox' == strtolower($node->getAttribute('type'))) { - $this->set(new Field\ChoiceFormField($node)); - } elseif ('input' == $nodeName && 'radio' == strtolower($node->getAttribute('type'))) { - // there may be other fields with the same name that are no choice - // fields already registered (see https://github.com/symfony/symfony/issues/11689) - if ($this->has($node->getAttribute('name')) && $this->get($node->getAttribute('name')) instanceof ChoiceFormField) { - $this->get($node->getAttribute('name'))->addChoice($node); - } else { - $this->set(new Field\ChoiceFormField($node)); - } - } elseif ('input' == $nodeName && 'file' == strtolower($node->getAttribute('type'))) { - $this->set(new Field\FileFormField($node)); - } elseif ('input' == $nodeName && !in_array(strtolower($node->getAttribute('type')), array('submit', 'button', 'image'))) { - $this->set(new Field\InputFormField($node)); - } elseif ('textarea' == $nodeName) { - $this->set(new Field\TextareaFormField($node)); - } - } -} diff --git a/vendor/symfony/dom-crawler/FormFieldRegistry.php b/vendor/symfony/dom-crawler/FormFieldRegistry.php deleted file mode 100755 index 9168dd3..0000000 --- a/vendor/symfony/dom-crawler/FormFieldRegistry.php +++ /dev/null @@ -1,217 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -use Symfony\Component\DomCrawler\Field\FormField; - -/** - * This is an internal class that must not be used directly. - * - * @internal - */ -class FormFieldRegistry -{ - private $fields = array(); - - private $base; - - /** - * Adds a field to the registry. - * - * @param FormField $field The field - */ - public function add(FormField $field) - { - $segments = $this->getSegments($field->getName()); - - $target = &$this->fields; - while ($segments) { - if (!is_array($target)) { - $target = array(); - } - $path = array_shift($segments); - if ('' === $path) { - $target = &$target[]; - } else { - $target = &$target[$path]; - } - } - $target = $field; - } - - /** - * Removes a field and its children from the registry. - * - * @param string $name The fully qualified name of the base field - */ - public function remove($name) - { - $segments = $this->getSegments($name); - $target = &$this->fields; - while (count($segments) > 1) { - $path = array_shift($segments); - if (!array_key_exists($path, $target)) { - return; - } - $target = &$target[$path]; - } - unset($target[array_shift($segments)]); - } - - /** - * Returns the value of the field and its children. - * - * @param string $name The fully qualified name of the field - * - * @return mixed The value of the field - * - * @throws \InvalidArgumentException if the field does not exist - */ - public function &get($name) - { - $segments = $this->getSegments($name); - $target = &$this->fields; - while ($segments) { - $path = array_shift($segments); - if (!array_key_exists($path, $target)) { - throw new \InvalidArgumentException(sprintf('Unreachable field "%s"', $path)); - } - $target = &$target[$path]; - } - - return $target; - } - - /** - * Tests whether the form has the given field. - * - * @param string $name The fully qualified name of the field - * - * @return bool Whether the form has the given field - */ - public function has($name) - { - try { - $this->get($name); - - return true; - } catch (\InvalidArgumentException $e) { - return false; - } - } - - /** - * Set the value of a field and its children. - * - * @param string $name The fully qualified name of the field - * @param mixed $value The value - * - * @throws \InvalidArgumentException if the field does not exist - */ - public function set($name, $value) - { - $target = &$this->get($name); - if ((!is_array($value) && $target instanceof Field\FormField) || $target instanceof Field\ChoiceFormField) { - $target->setValue($value); - } elseif (is_array($value)) { - $fields = self::create($name, $value); - foreach ($fields->all() as $k => $v) { - $this->set($k, $v); - } - } else { - throw new \InvalidArgumentException(sprintf('Cannot set value on a compound field "%s".', $name)); - } - } - - /** - * Returns the list of field with their value. - * - * @return FormField[] The list of fields as array((string) Fully qualified name => (mixed) value) - */ - public function all() - { - return $this->walk($this->fields, $this->base); - } - - /** - * Creates an instance of the class. - * - * This function is made private because it allows overriding the $base and - * the $values properties without any type checking. - * - * @param string $base The fully qualified name of the base field - * @param array $values The values of the fields - * - * @return static - */ - private static function create($base, array $values) - { - $registry = new static(); - $registry->base = $base; - $registry->fields = $values; - - return $registry; - } - - /** - * Transforms a PHP array in a list of fully qualified name / value. - * - * @param array $array The PHP array - * @param string $base The name of the base field - * @param array $output The initial values - * - * @return array The list of fields as array((string) Fully qualified name => (mixed) value) - */ - private function walk(array $array, $base = '', array &$output = array()) - { - foreach ($array as $k => $v) { - $path = empty($base) ? $k : sprintf('%s[%s]', $base, $k); - if (is_array($v)) { - $this->walk($v, $path, $output); - } else { - $output[$path] = $v; - } - } - - return $output; - } - - /** - * Splits a field name into segments as a web browser would do. - * - * - * getSegments('base[foo][3][]') = array('base', 'foo, '3', ''); - * - * - * @param string $name The name of the field - * - * @return string[] The list of segments - */ - private function getSegments($name) - { - if (preg_match('/^(?P[^[]+)(?P(\[.*)|$)/', $name, $m)) { - $segments = array($m['base']); - while (!empty($m['extra'])) { - $extra = $m['extra']; - if (preg_match('/^\[(?P.*?)\](?P.*)$/', $extra, $m)) { - $segments[] = $m['segment']; - } else { - $segments[] = $extra; - } - } - - return $segments; - } - - return array($name); - } -} diff --git a/vendor/symfony/dom-crawler/LICENSE b/vendor/symfony/dom-crawler/LICENSE deleted file mode 100755 index 17d16a1..0000000 --- a/vendor/symfony/dom-crawler/LICENSE +++ /dev/null @@ -1,19 +0,0 @@ -Copyright (c) 2004-2017 Fabien Potencier - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is furnished -to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/vendor/symfony/dom-crawler/Link.php b/vendor/symfony/dom-crawler/Link.php deleted file mode 100755 index ede0991..0000000 --- a/vendor/symfony/dom-crawler/Link.php +++ /dev/null @@ -1,224 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler; - -/** - * Link represents an HTML link (an HTML a, area or link tag). - * - * @author Fabien Potencier - */ -class Link -{ - /** - * @var \DOMElement - */ - protected $node; - - /** - * @var string The method to use for the link - */ - protected $method; - - /** - * @var string The URI of the page where the link is embedded (or the base href) - */ - protected $currentUri; - - /** - * Constructor. - * - * @param \DOMElement $node A \DOMElement instance - * @param string $currentUri The URI of the page where the link is embedded (or the base href) - * @param string $method The method to use for the link (get by default) - * - * @throws \InvalidArgumentException if the node is not a link - */ - public function __construct(\DOMElement $node, $currentUri, $method = 'GET') - { - if (!in_array(strtolower(substr($currentUri, 0, 4)), array('http', 'file'))) { - throw new \InvalidArgumentException(sprintf('Current URI must be an absolute URL ("%s").', $currentUri)); - } - - $this->setNode($node); - $this->method = $method ? strtoupper($method) : null; - $this->currentUri = $currentUri; - } - - /** - * Gets the node associated with this link. - * - * @return \DOMElement A \DOMElement instance - */ - public function getNode() - { - return $this->node; - } - - /** - * Gets the method associated with this link. - * - * @return string The method - */ - public function getMethod() - { - return $this->method; - } - - /** - * Gets the URI associated with this link. - * - * @return string The URI - */ - public function getUri() - { - $uri = trim($this->getRawUri()); - - // absolute URL? - if (null !== parse_url($uri, PHP_URL_SCHEME)) { - return $uri; - } - - // empty URI - if (!$uri) { - return $this->currentUri; - } - - // an anchor - if ('#' === $uri[0]) { - return $this->cleanupAnchor($this->currentUri).$uri; - } - - $baseUri = $this->cleanupUri($this->currentUri); - - if ('?' === $uri[0]) { - return $baseUri.$uri; - } - - // absolute URL with relative schema - if (0 === strpos($uri, '//')) { - return preg_replace('#^([^/]*)//.*$#', '$1', $baseUri).$uri; - } - - $baseUri = preg_replace('#^(.*?//[^/]*)(?:\/.*)?$#', '$1', $baseUri); - - // absolute path - if ('/' === $uri[0]) { - return $baseUri.$uri; - } - - // relative path - $path = parse_url(substr($this->currentUri, strlen($baseUri)), PHP_URL_PATH); - $path = $this->canonicalizePath(substr($path, 0, strrpos($path, '/')).'/'.$uri); - - return $baseUri.('' === $path || '/' !== $path[0] ? '/' : '').$path; - } - - /** - * Returns raw URI data. - * - * @return string - */ - protected function getRawUri() - { - return $this->node->getAttribute('href'); - } - - /** - * Returns the canonicalized URI path (see RFC 3986, section 5.2.4). - * - * @param string $path URI path - * - * @return string - */ - protected function canonicalizePath($path) - { - if ('' === $path || '/' === $path) { - return $path; - } - - if ('.' === substr($path, -1)) { - $path .= '/'; - } - - $output = array(); - - foreach (explode('/', $path) as $segment) { - if ('..' === $segment) { - array_pop($output); - } elseif ('.' !== $segment) { - $output[] = $segment; - } - } - - return implode('/', $output); - } - - /** - * Sets current \DOMElement instance. - * - * @param \DOMElement $node A \DOMElement instance - * - * @throws \LogicException If given node is not an anchor - */ - protected function setNode(\DOMElement $node) - { - if ('a' !== $node->nodeName && 'area' !== $node->nodeName && 'link' !== $node->nodeName) { - throw new \LogicException(sprintf('Unable to navigate from a "%s" tag.', $node->nodeName)); - } - - $this->node = $node; - } - - /** - * Removes the query string and the anchor from the given uri. - * - * @param string $uri The uri to clean - * - * @return string - */ - private function cleanupUri($uri) - { - return $this->cleanupQuery($this->cleanupAnchor($uri)); - } - - /** - * Remove the query string from the uri. - * - * @param string $uri - * - * @return string - */ - private function cleanupQuery($uri) - { - if (false !== $pos = strpos($uri, '?')) { - return substr($uri, 0, $pos); - } - - return $uri; - } - - /** - * Remove the anchor from the uri. - * - * @param string $uri - * - * @return string - */ - private function cleanupAnchor($uri) - { - if (false !== $pos = strpos($uri, '#')) { - return substr($uri, 0, $pos); - } - - return $uri; - } -} diff --git a/vendor/symfony/dom-crawler/README.md b/vendor/symfony/dom-crawler/README.md deleted file mode 100755 index 5fad2e2..0000000 --- a/vendor/symfony/dom-crawler/README.md +++ /dev/null @@ -1,13 +0,0 @@ -DomCrawler Component -==================== - -The DomCrawler component eases DOM navigation for HTML and XML documents. - -Resources ---------- - - * [Documentation](https://symfony.com/doc/current/components/dom_crawler.html) - * [Contributing](https://symfony.com/doc/current/contributing/index.html) - * [Report issues](https://github.com/symfony/symfony/issues) and - [send Pull Requests](https://github.com/symfony/symfony/pulls) - in the [main Symfony repository](https://github.com/symfony/symfony) diff --git a/vendor/symfony/dom-crawler/Tests/CrawlerTest.php b/vendor/symfony/dom-crawler/Tests/CrawlerTest.php deleted file mode 100755 index 827570f..0000000 --- a/vendor/symfony/dom-crawler/Tests/CrawlerTest.php +++ /dev/null @@ -1,1109 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -namespace Symfony\Component\DomCrawler\Tests; - -use PHPUnit\Framework\TestCase; -use Symfony\Component\DomCrawler\Crawler; - -class CrawlerTest extends TestCase -{ - public function testConstructor() - { - $crawler = new Crawler(); - $this->assertCount(0, $crawler, '__construct() returns an empty crawler'); - - $doc = new \DOMDocument(); - $node = $doc->createElement('test'); - - $crawler = new Crawler($node); - $this->assertCount(1, $crawler, '__construct() takes a node as a first argument'); - } - - public function testAdd() - { - $crawler = new Crawler(); - $crawler->add($this->createDomDocument()); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMDocument'); - - $crawler = new Crawler(); - $crawler->add($this->createNodeList()); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNodeList'); - - $list = array(); - foreach ($this->createNodeList() as $node) { - $list[] = $node; - } - $crawler = new Crawler(); - $crawler->add($list); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from an array of nodes'); - - $crawler = new Crawler(); - $crawler->add($this->createNodeList()->item(0)); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->add() adds nodes from a \DOMNode'); - - $crawler = new Crawler(); - $crawler->add('Foo'); - $this->assertEquals('Foo', $crawler->filterXPath('//body')->text(), '->add() adds nodes from a string'); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testAddInvalidType() - { - $crawler = new Crawler(); - $crawler->add(1); - } - - public function testAddHtmlContent() - { - $crawler = new Crawler(); - $crawler->addHtmlContent('
    ', 'UTF-8'); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addHtmlContent() adds nodes from an HTML string'); - } - - public function testAddHtmlContentWithBaseTag() - { - $crawler = new Crawler(); - - $crawler->addHtmlContent('', 'UTF-8'); - - $this->assertEquals('http://symfony.com', $crawler->filterXPath('//base')->attr('href'), '->addHtmlContent() adds nodes from an HTML string'); - $this->assertEquals('http://symfony.com/contact', $crawler->filterXPath('//a')->link()->getUri(), '->addHtmlContent() adds nodes from an HTML string'); - } - - /** - * @requires extension mbstring - */ - public function testAddHtmlContentCharset() - { - $crawler = new Crawler(); - $crawler->addHtmlContent('
    Tiếng Việt', 'UTF-8'); - - $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); - } - - public function testAddHtmlContentInvalidBaseTag() - { - $crawler = new Crawler(null, 'http://symfony.com'); - - $crawler->addHtmlContent('', 'UTF-8'); - - $this->assertEquals('http://symfony.com/contact', current($crawler->filterXPath('//a')->links())->getUri(), '->addHtmlContent() correctly handles a non-existent base tag href attribute'); - } - - public function testAddHtmlContentUnsupportedCharset() - { - $crawler = new Crawler(); - $crawler->addHtmlContent(file_get_contents(__DIR__.'/Fixtures/windows-1250.html'), 'Windows-1250'); - - $this->assertEquals('Žťčýů', $crawler->filterXPath('//p')->text()); - } - - /** - * @requires extension mbstring - */ - public function testAddHtmlContentCharsetGbk() - { - $crawler = new Crawler(); - //gbk encode of

    中文

    - $crawler->addHtmlContent(base64_decode('PGh0bWw+PHA+1tDOxDwvcD48L2h0bWw+'), 'gbk'); - - $this->assertEquals('中文', $crawler->filterXPath('//p')->text()); - } - - public function testAddHtmlContentWithErrors() - { - $internalErrors = libxml_use_internal_errors(true); - - $crawler = new Crawler(); - $crawler->addHtmlContent(<<<'EOF' - - - - - - - - -EOF - , 'UTF-8'); - - $errors = libxml_get_errors(); - $this->assertCount(1, $errors); - $this->assertEquals("Tag nav invalid\n", $errors[0]->message); - - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - } - - public function testAddXmlContent() - { - $crawler = new Crawler(); - $crawler->addXmlContent('
    ', 'UTF-8'); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addXmlContent() adds nodes from an XML string'); - } - - public function testAddXmlContentCharset() - { - $crawler = new Crawler(); - $crawler->addXmlContent('
    Tiếng Việt
    ', 'UTF-8'); - - $this->assertEquals('Tiếng Việt', $crawler->filterXPath('//div')->text()); - } - - public function testAddXmlContentWithErrors() - { - $internalErrors = libxml_use_internal_errors(true); - - $crawler = new Crawler(); - $crawler->addXmlContent(<<<'EOF' - - - - - -
    - - -EOF - , 'UTF-8'); - - $this->assertTrue(count(libxml_get_errors()) > 1); - - libxml_clear_errors(); - libxml_use_internal_errors($internalErrors); - } - - public function testAddContent() - { - $crawler = new Crawler(); - $crawler->addContent('
    ', 'text/html; charset=UTF-8'); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string'); - - $crawler = new Crawler(); - $crawler->addContent('
    ', 'text/html; charset=UTF-8; dir=RTL'); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an HTML string with extended content type'); - - $crawler = new Crawler(); - $crawler->addContent('
    '); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() uses text/html as the default type'); - - $crawler = new Crawler(); - $crawler->addContent('
    ', 'text/xml; charset=UTF-8'); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); - - $crawler = new Crawler(); - $crawler->addContent('
    ', 'text/xml'); - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addContent() adds nodes from an XML string'); - - $crawler = new Crawler(); - $crawler->addContent('foo bar', 'text/plain'); - $this->assertCount(0, $crawler, '->addContent() does nothing if the type is not (x|ht)ml'); - - $crawler = new Crawler(); - $crawler->addContent('中文'); - $this->assertEquals('中文', $crawler->filterXPath('//span')->text(), '->addContent() guess wrong charset'); - } - - /** - * @requires extension iconv - */ - public function testAddContentNonUtf8() - { - $crawler = new Crawler(); - $crawler->addContent(iconv('UTF-8', 'SJIS', '日本語')); - $this->assertEquals('日本語', $crawler->filterXPath('//body')->text(), '->addContent() can recognize "Shift_JIS" in html5 meta charset tag'); - } - - public function testAddDocument() - { - $crawler = new Crawler(); - $crawler->addDocument($this->createDomDocument()); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addDocument() adds nodes from a \DOMDocument'); - } - - public function testAddNodeList() - { - $crawler = new Crawler(); - $crawler->addNodeList($this->createNodeList()); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodeList() adds nodes from a \DOMNodeList'); - } - - public function testAddNodes() - { - $list = array(); - foreach ($this->createNodeList() as $node) { - $list[] = $node; - } - - $crawler = new Crawler(); - $crawler->addNodes($list); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNodes() adds nodes from an array of nodes'); - } - - public function testAddNode() - { - $crawler = new Crawler(); - $crawler->addNode($this->createNodeList()->item(0)); - - $this->assertEquals('foo', $crawler->filterXPath('//div')->attr('class'), '->addNode() adds nodes from a \DOMNode'); - } - - public function testClear() - { - $doc = new \DOMDocument(); - $node = $doc->createElement('test'); - - $crawler = new Crawler($node); - $crawler->clear(); - $this->assertCount(0, $crawler, '->clear() removes all the nodes from the crawler'); - } - - public function testEq() - { - $crawler = $this->createTestCrawler()->filterXPath('//li'); - $this->assertNotSame($crawler, $crawler->eq(0), '->eq() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->eq() returns a new instance of a crawler'); - - $this->assertEquals('Two', $crawler->eq(1)->text(), '->eq() returns the nth node of the list'); - $this->assertCount(0, $crawler->eq(100), '->eq() returns an empty crawler if the nth node does not exist'); - } - - public function testEach() - { - $data = $this->createTestCrawler()->filterXPath('//ul[1]/li')->each(function ($node, $i) { - return $i.'-'.$node->text(); - }); - - $this->assertEquals(array('0-One', '1-Two', '2-Three'), $data, '->each() executes an anonymous function on each node of the list'); - } - - public function testIteration() - { - $crawler = $this->createTestCrawler()->filterXPath('//li'); - - $this->assertInstanceOf('Traversable', $crawler); - $this->assertContainsOnlyInstancesOf('DOMElement', iterator_to_array($crawler), 'Iterating a Crawler gives DOMElement instances'); - } - - public function testSlice() - { - $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); - $this->assertNotSame($crawler->slice(), $crawler, '->slice() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler->slice(), '->slice() returns a new instance of a crawler'); - - $this->assertCount(3, $crawler->slice(), '->slice() does not slice the nodes in the list if any param is entered'); - $this->assertCount(1, $crawler->slice(1, 1), '->slice() slices the nodes in the list'); - } - - public function testReduce() - { - $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); - $nodes = $crawler->reduce(function ($node, $i) { - return $i !== 1; - }); - $this->assertNotSame($nodes, $crawler, '->reduce() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $nodes, '->reduce() returns a new instance of a crawler'); - - $this->assertCount(2, $nodes, '->reduce() filters the nodes in the list'); - } - - public function testAttr() - { - $this->assertEquals('first', $this->createTestCrawler()->filterXPath('//li')->attr('class'), '->attr() returns the attribute of the first element of the node list'); - - try { - $this->createTestCrawler()->filterXPath('//ol')->attr('class'); - $this->fail('->attr() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->attr() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testMissingAttrValueIsNull() - { - $crawler = new Crawler(); - $crawler->addContent('
    ', 'text/html; charset=UTF-8'); - $div = $crawler->filterXPath('//div'); - - $this->assertEquals('sample value', $div->attr('non-empty-attr'), '->attr() reads non-empty attributes correctly'); - $this->assertEquals('', $div->attr('empty-attr'), '->attr() reads empty attributes correctly'); - $this->assertNull($div->attr('missing-attr'), '->attr() reads missing attributes correctly'); - } - - public function testNodeName() - { - $this->assertEquals('li', $this->createTestCrawler()->filterXPath('//li')->nodeName(), '->nodeName() returns the node name of the first element of the node list'); - - try { - $this->createTestCrawler()->filterXPath('//ol')->nodeName(); - $this->fail('->nodeName() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->nodeName() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testText() - { - $this->assertEquals('One', $this->createTestCrawler()->filterXPath('//li')->text(), '->text() returns the node value of the first element of the node list'); - - try { - $this->createTestCrawler()->filterXPath('//ol')->text(); - $this->fail('->text() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->text() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testHtml() - { - $this->assertEquals('Bar', $this->createTestCrawler()->filterXPath('//a[5]')->html()); - $this->assertEquals('', trim($this->createTestCrawler()->filterXPath('//form[@id="FooFormId"]')->html())); - - try { - $this->createTestCrawler()->filterXPath('//ol')->html(); - $this->fail('->html() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->html() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testExtract() - { - $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); - - $this->assertEquals(array('One', 'Two', 'Three'), $crawler->extract('_text'), '->extract() returns an array of extracted data from the node list'); - $this->assertEquals(array(array('One', 'first'), array('Two', ''), array('Three', '')), $crawler->extract(array('_text', 'class')), '->extract() returns an array of extracted data from the node list'); - - $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->extract('_text'), '->extract() returns an empty array if the node list is empty'); - } - - public function testFilterXpathComplexQueries() - { - $crawler = $this->createTestCrawler()->filterXPath('//body'); - - $this->assertCount(0, $crawler->filterXPath('/input')); - $this->assertCount(0, $crawler->filterXPath('/body')); - $this->assertCount(1, $crawler->filterXPath('./body')); - $this->assertCount(1, $crawler->filterXPath('.//body')); - $this->assertCount(5, $crawler->filterXPath('.//input')); - $this->assertCount(4, $crawler->filterXPath('//form')->filterXPath('//button | //input')); - $this->assertCount(1, $crawler->filterXPath('body')); - $this->assertCount(6, $crawler->filterXPath('//button | //input')); - $this->assertCount(1, $crawler->filterXPath('//body')); - $this->assertCount(1, $crawler->filterXPath('descendant-or-self::body')); - $this->assertCount(1, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('./div'), 'A child selection finds only the current div'); - $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('descendant::div'), 'A descendant selector matches the current div and its child'); - $this->assertCount(3, $crawler->filterXPath('//div[@id="parent"]')->filterXPath('//div'), 'A descendant selector matches the current div and its child'); - $this->assertCount(5, $crawler->filterXPath('(//a | //div)//img')); - $this->assertCount(7, $crawler->filterXPath('((//a | //div)//img | //ul)')); - $this->assertCount(7, $crawler->filterXPath('( ( //a | //div )//img | //ul )')); - $this->assertCount(1, $crawler->filterXPath("//a[./@href][((./@id = 'Klausi|Claudiu' or normalize-space(string(.)) = 'Klausi|Claudiu' or ./@title = 'Klausi|Claudiu' or ./@rel = 'Klausi|Claudiu') or .//img[./@alt = 'Klausi|Claudiu'])]")); - } - - public function testFilterXPath() - { - $crawler = $this->createTestCrawler(); - $this->assertNotSame($crawler, $crawler->filterXPath('//li'), '->filterXPath() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filterXPath() returns a new instance of a crawler'); - - $crawler = $this->createTestCrawler()->filterXPath('//ul'); - $this->assertCount(6, $crawler->filterXPath('//li'), '->filterXPath() filters the node list with the XPath expression'); - - $crawler = $this->createTestCrawler(); - $this->assertCount(3, $crawler->filterXPath('//body')->filterXPath('//button')->parents(), '->filterXpath() preserves parents when chained'); - } - - public function testFilterRemovesDuplicates() - { - $crawler = $this->createTestCrawler()->filter('html, body')->filter('li'); - $this->assertCount(6, $crawler, 'The crawler removes duplicates when filtering.'); - } - - public function testFilterXPathWithDefaultNamespace() - { - $crawler = $this->createTestXmlCrawler()->filterXPath('//default:entry/default:id'); - $this->assertCount(1, $crawler, '->filterXPath() automatically registers a namespace'); - $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); - } - - public function testFilterXPathWithCustomDefaultNamespace() - { - $crawler = $this->createTestXmlCrawler(); - $crawler->setDefaultNamespacePrefix('x'); - $crawler = $crawler->filterXPath('//x:entry/x:id'); - - $this->assertCount(1, $crawler, '->filterXPath() lets to override the default namespace prefix'); - $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); - } - - public function testFilterXPathWithNamespace() - { - $crawler = $this->createTestXmlCrawler()->filterXPath('//yt:accessControl'); - $this->assertCount(2, $crawler, '->filterXPath() automatically registers a namespace'); - } - - public function testFilterXPathWithMultipleNamespaces() - { - $crawler = $this->createTestXmlCrawler()->filterXPath('//media:group/yt:aspectRatio'); - $this->assertCount(1, $crawler, '->filterXPath() automatically registers multiple namespaces'); - $this->assertSame('widescreen', $crawler->text()); - } - - public function testFilterXPathWithManuallyRegisteredNamespace() - { - $crawler = $this->createTestXmlCrawler(); - $crawler->registerNamespace('m', 'http://search.yahoo.com/mrss/'); - - $crawler = $crawler->filterXPath('//m:group/yt:aspectRatio'); - $this->assertCount(1, $crawler, '->filterXPath() uses manually registered namespace'); - $this->assertSame('widescreen', $crawler->text()); - } - - public function testFilterXPathWithAnUrl() - { - $crawler = $this->createTestXmlCrawler(); - - $crawler = $crawler->filterXPath('//media:category[@scheme="http://gdata.youtube.com/schemas/2007/categories.cat"]'); - $this->assertCount(1, $crawler); - $this->assertSame('Music', $crawler->text()); - } - - public function testFilterXPathWithFakeRoot() - { - $crawler = $this->createTestCrawler(); - $this->assertCount(0, $crawler->filterXPath('.'), '->filterXPath() returns an empty result if the XPath references the fake root node'); - $this->assertCount(0, $crawler->filterXPath('self::*'), '->filterXPath() returns an empty result if the XPath references the fake root node'); - $this->assertCount(0, $crawler->filterXPath('self::_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); - } - - /** @group legacy */ - public function testLegacyFilterXPathWithFakeRoot() - { - $crawler = $this->createTestCrawler(); - $this->assertCount(0, $crawler->filterXPath('/_root'), '->filterXPath() returns an empty result if the XPath references the fake root node'); - - $crawler = $this->createTestCrawler()->filterXPath('//body'); - $this->assertCount(1, $crawler->filterXPath('/_root/body')); - } - - public function testFilterXPathWithAncestorAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//form'); - - $this->assertCount(0, $crawler->filterXPath('ancestor::*'), 'The fake root node has no ancestor nodes'); - } - - public function testFilterXPathWithAncestorOrSelfAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//form'); - - $this->assertCount(0, $crawler->filterXPath('ancestor-or-self::*'), 'The fake root node has no ancestor nodes'); - } - - public function testFilterXPathWithAttributeAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//form'); - - $this->assertCount(0, $crawler->filterXPath('attribute::*'), 'The fake root node has no attribute nodes'); - } - - public function testFilterXPathWithAttributeAxisAfterElementAxis() - { - $this->assertCount(3, $this->createTestCrawler()->filterXPath('//form/button/attribute::*'), '->filterXPath() handles attribute axes properly when they are preceded by an element filtering axis'); - } - - public function testFilterXPathWithChildAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]'); - - $this->assertCount(1, $crawler->filterXPath('child::div'), 'A child selection finds only the current div'); - } - - public function testFilterXPathWithFollowingAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//a'); - - $this->assertCount(0, $crawler->filterXPath('following::div'), 'The fake root node has no following nodes'); - } - - public function testFilterXPathWithFollowingSiblingAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//a'); - - $this->assertCount(0, $crawler->filterXPath('following-sibling::div'), 'The fake root node has no following nodes'); - } - - public function testFilterXPathWithNamespaceAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//button'); - - $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'The fake root node has no namespace nodes'); - } - - public function testFilterXPathWithNamespaceAxisAfterElementAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//div[@id="parent"]/namespace::*'); - - $this->assertCount(0, $crawler->filterXPath('namespace::*'), 'Namespace axes cannot be requested'); - } - - public function testFilterXPathWithParentAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//button'); - - $this->assertCount(0, $crawler->filterXPath('parent::*'), 'The fake root node has no parent nodes'); - } - - public function testFilterXPathWithPrecedingAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//form'); - - $this->assertCount(0, $crawler->filterXPath('preceding::*'), 'The fake root node has no preceding nodes'); - } - - public function testFilterXPathWithPrecedingSiblingAxis() - { - $crawler = $this->createTestCrawler()->filterXPath('//form'); - - $this->assertCount(0, $crawler->filterXPath('preceding-sibling::*'), 'The fake root node has no preceding nodes'); - } - - public function testFilterXPathWithSelfAxes() - { - $crawler = $this->createTestCrawler()->filterXPath('//a'); - - $this->assertCount(0, $crawler->filterXPath('self::a'), 'The fake root node has no "real" element name'); - $this->assertCount(0, $crawler->filterXPath('self::a/img'), 'The fake root node has no "real" element name'); - $this->assertCount(10, $crawler->filterXPath('self::*/a')); - } - - public function testFilter() - { - $crawler = $this->createTestCrawler(); - $this->assertNotSame($crawler, $crawler->filter('li'), '->filter() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->filter() returns a new instance of a crawler'); - - $crawler = $this->createTestCrawler()->filter('ul'); - - $this->assertCount(6, $crawler->filter('li'), '->filter() filters the node list with the CSS selector'); - } - - public function testFilterWithDefaultNamespace() - { - $crawler = $this->createTestXmlCrawler()->filter('default|entry default|id'); - $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); - $this->assertSame('tag:youtube.com,2008:video:kgZRZmEc9j4', $crawler->text()); - } - - public function testFilterWithNamespace() - { - $crawler = $this->createTestXmlCrawler()->filter('yt|accessControl'); - $this->assertCount(2, $crawler, '->filter() automatically registers namespaces'); - } - - public function testFilterWithMultipleNamespaces() - { - $crawler = $this->createTestXmlCrawler()->filter('media|group yt|aspectRatio'); - $this->assertCount(1, $crawler, '->filter() automatically registers namespaces'); - $this->assertSame('widescreen', $crawler->text()); - } - - public function testFilterWithDefaultNamespaceOnly() - { - $crawler = new Crawler(' - - - http://localhost/foo - weekly - 0.5 - 2012-11-16 - - - http://localhost/bar - weekly - 0.5 - 2012-11-16 - - - '); - - $this->assertEquals(2, $crawler->filter('url')->count()); - } - - public function testSelectLink() - { - $crawler = $this->createTestCrawler(); - $this->assertNotSame($crawler, $crawler->selectLink('Foo'), '->selectLink() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectLink() returns a new instance of a crawler'); - - $this->assertCount(1, $crawler->selectLink('Fabien\'s Foo'), '->selectLink() selects links by the node values'); - $this->assertCount(1, $crawler->selectLink('Fabien\'s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); - - $this->assertCount(2, $crawler->selectLink('Fabien"s Foo'), '->selectLink() selects links by the node values'); - $this->assertCount(2, $crawler->selectLink('Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); - - $this->assertCount(1, $crawler->selectLink('\' Fabien"s Foo'), '->selectLink() selects links by the node values'); - $this->assertCount(1, $crawler->selectLink('\' Fabien"s Bar'), '->selectLink() selects links by the alt attribute of a clickable image'); - - $this->assertCount(4, $crawler->selectLink('Foo'), '->selectLink() selects links by the node values'); - $this->assertCount(4, $crawler->selectLink('Bar'), '->selectLink() selects links by the node values'); - } - - public function testSelectButton() - { - $crawler = $this->createTestCrawler(); - $this->assertNotSame($crawler, $crawler->selectButton('FooValue'), '->selectButton() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->selectButton() returns a new instance of a crawler'); - - $this->assertEquals(1, $crawler->selectButton('FooValue')->count(), '->selectButton() selects buttons'); - $this->assertEquals(1, $crawler->selectButton('FooName')->count(), '->selectButton() selects buttons'); - $this->assertEquals(1, $crawler->selectButton('FooId')->count(), '->selectButton() selects buttons'); - - $this->assertEquals(1, $crawler->selectButton('BarValue')->count(), '->selectButton() selects buttons'); - $this->assertEquals(1, $crawler->selectButton('BarName')->count(), '->selectButton() selects buttons'); - $this->assertEquals(1, $crawler->selectButton('BarId')->count(), '->selectButton() selects buttons'); - - $this->assertEquals(1, $crawler->selectButton('FooBarValue')->count(), '->selectButton() selects buttons with form attribute too'); - $this->assertEquals(1, $crawler->selectButton('FooBarName')->count(), '->selectButton() selects buttons with form attribute too'); - } - - public function testSelectButtonWithSingleQuotesInNameAttribute() - { - $html = <<<'HTML' - - - -
    -
    - -
    - - -HTML; - - $crawler = new Crawler($html); - - $this->assertCount(1, $crawler->selectButton('Click \'Here\'')); - } - - public function testSelectButtonWithDoubleQuotesInNameAttribute() - { - $html = <<<'HTML' - - - -
    - Login -
    -
    - -
    - - -HTML; - - $crawler = new Crawler($html); - - $this->assertCount(1, $crawler->selectButton('Click "Here"')); - } - - public function testLink() - { - $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $crawler->link(), '->link() returns a Link instance'); - - $this->assertEquals('POST', $crawler->link('post')->getMethod(), '->link() takes a method as its argument'); - - $crawler = $this->createTestCrawler('http://example.com/bar')->selectLink('GetLink'); - $this->assertEquals('http://example.com/bar?get=param', $crawler->link()->getUri(), '->link() returns a Link instance'); - - try { - $this->createTestCrawler()->filterXPath('//ol')->link(); - $this->fail('->link() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->link() throws an \InvalidArgumentException if the node list is empty'); - } - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The selected node should be instance of DOMElement - */ - public function testInvalidLink() - { - $crawler = $this->createTestCrawler('http://example.com/bar/'); - $crawler->filterXPath('//li/text()')->link(); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The selected node should be instance of DOMElement - */ - public function testInvalidLinks() - { - $crawler = $this->createTestCrawler('http://example.com/bar/'); - $crawler->filterXPath('//li/text()')->link(); - } - - public function testSelectLinkAndLinkFiltered() - { - $html = <<<'HTML' - - - -
    - Login -
    -
    - -
    - - -HTML; - - $crawler = new Crawler($html); - $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'login-form']"); - - $this->assertCount(0, $filtered->selectLink('Login')); - $this->assertCount(1, $filtered->selectButton('Submit')); - - $filtered = $crawler->filterXPath("descendant-or-self::*[@id = 'action']"); - - $this->assertCount(1, $filtered->selectLink('Login')); - $this->assertCount(0, $filtered->selectButton('Submit')); - - $this->assertCount(1, $crawler->selectLink('Login')->selectLink('Login')); - $this->assertCount(1, $crawler->selectButton('Submit')->selectButton('Submit')); - } - - public function testChaining() - { - $crawler = new Crawler('
    '); - - $this->assertEquals('a', $crawler->filterXPath('//div')->filterXPath('div')->filterXPath('div')->attr('name')); - } - - public function testLinks() - { - $crawler = $this->createTestCrawler('http://example.com/bar/')->selectLink('Foo'); - $this->assertInternalType('array', $crawler->links(), '->links() returns an array'); - - $this->assertCount(4, $crawler->links(), '->links() returns an array'); - $links = $crawler->links(); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Link', $links[0], '->links() returns an array of Link instances'); - - $this->assertEquals(array(), $this->createTestCrawler()->filterXPath('//ol')->links(), '->links() returns an empty array if the node selection is empty'); - } - - public function testForm() - { - $testCrawler = $this->createTestCrawler('http://example.com/bar/'); - $crawler = $testCrawler->selectButton('FooValue'); - $crawler2 = $testCrawler->selectButton('FooBarValue'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler->form(), '->form() returns a Form instance'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Form', $crawler2->form(), '->form() returns a Form instance'); - - $this->assertEquals($crawler->form()->getFormNode()->getAttribute('id'), $crawler2->form()->getFormNode()->getAttribute('id'), '->form() works on elements with form attribute'); - - $this->assertEquals(array('FooName' => 'FooBar', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form(array('FooName' => 'FooBar'))->getValues(), '->form() takes an array of values to submit as its first argument'); - $this->assertEquals(array('FooName' => 'FooValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler->form()->getValues(), '->getValues() returns correct form values'); - $this->assertEquals(array('FooBarName' => 'FooBarValue', 'TextName' => 'TextValue', 'FooTextName' => 'FooTextValue'), $crawler2->form()->getValues(), '->getValues() returns correct form values'); - - try { - $this->createTestCrawler()->filterXPath('//ol')->form(); - $this->fail('->form() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->form() throws an \InvalidArgumentException if the node list is empty'); - } - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage The selected node should be instance of DOMElement - */ - public function testInvalidForm() - { - $crawler = $this->createTestCrawler('http://example.com/bar/'); - $crawler->filterXPath('//li/text()')->form(); - } - - public function testLast() - { - $crawler = $this->createTestCrawler()->filterXPath('//ul[1]/li'); - $this->assertNotSame($crawler, $crawler->last(), '->last() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->last() returns a new instance of a crawler'); - - $this->assertEquals('Three', $crawler->last()->text()); - } - - public function testFirst() - { - $crawler = $this->createTestCrawler()->filterXPath('//li'); - $this->assertNotSame($crawler, $crawler->first(), '->first() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->first() returns a new instance of a crawler'); - - $this->assertEquals('One', $crawler->first()->text()); - } - - public function testSiblings() - { - $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); - $this->assertNotSame($crawler, $crawler->siblings(), '->siblings() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->siblings() returns a new instance of a crawler'); - - $nodes = $crawler->siblings(); - $this->assertEquals(2, $nodes->count()); - $this->assertEquals('One', $nodes->eq(0)->text()); - $this->assertEquals('Three', $nodes->eq(1)->text()); - - $nodes = $this->createTestCrawler()->filterXPath('//li')->eq(0)->siblings(); - $this->assertEquals(2, $nodes->count()); - $this->assertEquals('Two', $nodes->eq(0)->text()); - $this->assertEquals('Three', $nodes->eq(1)->text()); - - try { - $this->createTestCrawler()->filterXPath('//ol')->siblings(); - $this->fail('->siblings() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->siblings() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testNextAll() - { - $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(1); - $this->assertNotSame($crawler, $crawler->nextAll(), '->nextAll() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->nextAll() returns a new instance of a crawler'); - - $nodes = $crawler->nextAll(); - $this->assertEquals(1, $nodes->count()); - $this->assertEquals('Three', $nodes->eq(0)->text()); - - try { - $this->createTestCrawler()->filterXPath('//ol')->nextAll(); - $this->fail('->nextAll() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->nextAll() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testPreviousAll() - { - $crawler = $this->createTestCrawler()->filterXPath('//li')->eq(2); - $this->assertNotSame($crawler, $crawler->previousAll(), '->previousAll() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->previousAll() returns a new instance of a crawler'); - - $nodes = $crawler->previousAll(); - $this->assertEquals(2, $nodes->count()); - $this->assertEquals('Two', $nodes->eq(0)->text()); - - try { - $this->createTestCrawler()->filterXPath('//ol')->previousAll(); - $this->fail('->previousAll() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->previousAll() throws an \InvalidArgumentException if the node list is empty'); - } - } - - public function testChildren() - { - $crawler = $this->createTestCrawler()->filterXPath('//ul'); - $this->assertNotSame($crawler, $crawler->children(), '->children() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->children() returns a new instance of a crawler'); - - $nodes = $crawler->children(); - $this->assertEquals(3, $nodes->count()); - $this->assertEquals('One', $nodes->eq(0)->text()); - $this->assertEquals('Two', $nodes->eq(1)->text()); - $this->assertEquals('Three', $nodes->eq(2)->text()); - - try { - $this->createTestCrawler()->filterXPath('//ol')->children(); - $this->fail('->children() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->children() throws an \InvalidArgumentException if the node list is empty'); - } - - try { - $crawler = new Crawler('

    '); - $crawler->filter('p')->children(); - $this->assertTrue(true, '->children() does not trigger a notice if the node has no children'); - } catch (\PHPUnit\Framework\Error\Notice $e) { - $this->fail('->children() does not trigger a notice if the node has no children'); - } catch (\PHPUnit_Framework_Error_Notice $e) { - $this->fail('->children() does not trigger a notice if the node has no children'); - } - } - - public function testParents() - { - $crawler = $this->createTestCrawler()->filterXPath('//li[1]'); - $this->assertNotSame($crawler, $crawler->parents(), '->parents() returns a new instance of a crawler'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Crawler', $crawler, '->parents() returns a new instance of a crawler'); - - $nodes = $crawler->parents(); - $this->assertEquals(3, $nodes->count()); - - $nodes = $this->createTestCrawler()->filterXPath('//html')->parents(); - $this->assertEquals(0, $nodes->count()); - - try { - $this->createTestCrawler()->filterXPath('//ol')->parents(); - $this->fail('->parents() throws an \InvalidArgumentException if the node list is empty'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->parents() throws an \InvalidArgumentException if the node list is empty'); - } - } - - /** - * @dataProvider getBaseTagData - */ - public function testBaseTag($baseValue, $linkValue, $expectedUri, $currentUri = null, $description = null) - { - $crawler = new Crawler('', $currentUri); - $this->assertEquals($expectedUri, $crawler->filterXPath('//a')->link()->getUri(), $description); - } - - public function getBaseTagData() - { - return array( - array('http://base.com', 'link', 'http://base.com/link'), - array('//base.com', 'link', 'https://base.com/link', 'https://domain.com', ' tag can use a schema-less URL'), - array('path/', 'link', 'https://domain.com/path/link', 'https://domain.com', ' tag can set a path'), - array('http://base.com', '#', 'http://base.com#', 'http://domain.com/path/link', ' tag does work with links to an anchor'), - array('http://base.com', '', 'http://base.com', 'http://domain.com/path/link', ' tag does work with empty links'), - ); - } - - /** - * @dataProvider getBaseTagWithFormData - */ - public function testBaseTagWithForm($baseValue, $actionValue, $expectedUri, $currentUri = null, $description = null) - { - $crawler = new Crawler('
    ', - array('bar' => array('InputFormField', 'bar')), - ), - array( - 'appends the submitted button value but not other submit buttons', - ' - ', - array('foobar' => array('InputFormField', 'foobar')), - ), - array( - 'turns an image input into x and y fields', - '', - array('bar.x' => array('InputFormField', '0'), 'bar.y' => array('InputFormField', '0')), - ), - array( - 'returns textareas', - ' - ', - array('foo' => array('TextareaFormField', 'foo')), - ), - array( - 'returns inputs', - ' - ', - array('foo' => array('InputFormField', 'foo')), - ), - array( - 'returns checkboxes', - ' - ', - array('foo' => array('ChoiceFormField', 'foo')), - ), - array( - 'returns not-checked checkboxes', - ' - ', - array('foo' => array('ChoiceFormField', false)), - ), - array( - 'returns radio buttons', - ' - - ', - array('foo' => array('ChoiceFormField', 'bar')), - ), - array( - 'returns file inputs', - ' - ', - array('foo' => array('FileFormField', array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), - ), - ); - } - - public function testGetFormNode() - { - $dom = new \DOMDocument(); - $dom->loadHTML('
    '); - - $form = new Form($dom->getElementsByTagName('input')->item(0), 'http://example.com'); - - $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); - } - - public function testGetFormNodeFromNamedForm() - { - $dom = new \DOMDocument(); - $dom->loadHTML('
    '); - - $form = new Form($dom->getElementsByTagName('form')->item(0), 'http://example.com'); - - $this->assertSame($dom->getElementsByTagName('form')->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); - } - - public function testGetMethod() - { - $form = $this->createForm('
    '); - $this->assertEquals('GET', $form->getMethod(), '->getMethod() returns get if no method is defined'); - - $form = $this->createForm('
    '); - $this->assertEquals('POST', $form->getMethod(), '->getMethod() returns the method attribute value of the form'); - - $form = $this->createForm('
    ', 'put'); - $this->assertEquals('PUT', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); - - $form = $this->createForm('
    ', 'delete'); - $this->assertEquals('DELETE', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); - - $form = $this->createForm('
    ', 'patch'); - $this->assertEquals('PATCH', $form->getMethod(), '->getMethod() returns the method defined in the constructor if provided'); - } - - public function testGetSetValue() - { - $form = $this->createForm('
    '); - - $this->assertEquals('foo', $form['foo']->getValue(), '->offsetGet() returns the value of a form field'); - - $form['foo'] = 'bar'; - - $this->assertEquals('bar', $form['foo']->getValue(), '->offsetSet() changes the value of a form field'); - - try { - $form['foobar'] = 'bar'; - $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); - } - - try { - $form['foobar']; - $this->fail('->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->offsetSet() throws an \InvalidArgumentException exception if the field does not exist'); - } - } - - public function testDisableValidation() - { - $form = $this->createForm('
    - - - -
    '); - - $form->disableValidation(); - - $form['foo[bar]']->select('foo'); - $form['foo[baz]']->select('bar'); - $this->assertEquals('foo', $form['foo[bar]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); - $this->assertEquals('bar', $form['foo[baz]']->getValue(), '->disableValidation() disables validation of all ChoiceFormField.'); - } - - public function testOffsetUnset() - { - $form = $this->createForm('
    '); - unset($form['foo']); - $this->assertFalse(isset($form['foo']), '->offsetUnset() removes a field'); - } - - public function testOffsetExists() - { - $form = $this->createForm('
    '); - - $this->assertTrue(isset($form['foo']), '->offsetExists() return true if the field exists'); - $this->assertFalse(isset($form['bar']), '->offsetExists() return false if the field does not exist'); - } - - public function testGetValues() - { - $form = $this->createForm('
    '); - $this->assertEquals(array('foo[bar]' => 'foo', 'bar' => 'bar', 'baz' => array()), $form->getValues(), '->getValues() returns all form field values'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include not-checked checkboxes'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include file input fields'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('bar' => 'bar'), $form->getValues(), '->getValues() does not include disabled fields'); - } - - public function testSetValues() - { - $form = $this->createForm('
    '); - $form->setValues(array('foo' => false, 'bar' => 'foo')); - $this->assertEquals(array('bar' => 'foo'), $form->getValues(), '->setValues() sets the values of fields'); - } - - public function testMultiselectSetValues() - { - $form = $this->createForm('
    '); - $form->setValues(array('multi' => array('foo', 'bar'))); - $this->assertEquals(array('multi' => array('foo', 'bar')), $form->getValues(), '->setValue() sets the values of select'); - } - - public function testGetPhpValues() - { - $form = $this->createForm('
    '); - $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), '->getPhpValues() converts keys with [] to arrays'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('fo.o' => array('ba.r' => 'foo'), 'ba r' => 'bar'), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('fo.o' => array('ba.r' => array('foo', 'ba.z' => 'bar'))), $form->getPhpValues(), '->getPhpValues() preserves periods and spaces in names recursively'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('foo' => array('bar' => 'foo'), 'bar' => 'bar'), $form->getPhpValues(), "->getPhpValues() doesn't return empty values"); - } - - public function testGetFiles() - { - $form = $this->createForm('
    '); - $this->assertEquals(array(), $form->getFiles(), '->getFiles() returns an empty array if method is get'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for POST'); - - $form = $this->createForm('
    ', 'put'); - $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PUT'); - - $form = $this->createForm('
    ', 'delete'); - $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for DELETE'); - - $form = $this->createForm('
    ', 'patch'); - $this->assertEquals(array('foo[bar]' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)), $form->getFiles(), '->getFiles() only returns file fields for PATCH'); - - $form = $this->createForm('
    '); - $this->assertEquals(array(), $form->getFiles(), '->getFiles() does not include disabled file fields'); - } - - public function testGetPhpFiles() - { - $form = $this->createForm('
    '); - $this->assertEquals(array('foo' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() converts keys with [] to arrays'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('f.o o' => array('bar' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names'); - - $form = $this->createForm('
    '); - $this->assertEquals(array('f.o o' => array('bar' => array('ba.z' => array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0), array('name' => '', 'type' => '', 'tmp_name' => '', 'error' => 4, 'size' => 0)))), $form->getPhpFiles(), '->getPhpFiles() preserves periods and spaces in names recursively'); - } - - /** - * @dataProvider provideGetUriValues - */ - public function testGetUri($message, $form, $values, $uri, $method = null) - { - $form = $this->createForm($form, $method); - $form->setValues($values); - - $this->assertEquals('http://example.com'.$uri, $form->getUri(), '->getUri() '.$message); - } - - public function testGetBaseUri() - { - $dom = new \DOMDocument(); - $dom->loadHTML('
    '); - - $nodes = $dom->getElementsByTagName('input'); - $form = new Form($nodes->item($nodes->length - 1), 'http://www.foo.com/'); - $this->assertEquals('http://www.foo.com/foo.php', $form->getUri()); - } - - public function testGetUriWithAnchor() - { - $form = $this->createForm('
    ', null, 'http://example.com/id/123'); - - $this->assertEquals('http://example.com/id/123#foo', $form->getUri()); - } - - public function testGetUriActionAbsolute() - { - $formHtml = '
    '; - - $form = $this->createForm($formHtml); - $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); - - $form = $this->createForm($formHtml, null, 'https://login.foo.com'); - $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); - - $form = $this->createForm($formHtml, null, 'https://login.foo.com/bar/'); - $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); - - // The action URI haven't the same domain Host have an another domain as Host - $form = $this->createForm($formHtml, null, 'https://www.foo.com'); - $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); - - $form = $this->createForm($formHtml, null, 'https://www.foo.com/bar/'); - $this->assertEquals('https://login.foo.com/login.php?login_attempt=1', $form->getUri(), '->getUri() returns absolute URIs set in the action form'); - } - - public function testGetUriAbsolute() - { - $form = $this->createForm('
    ', null, 'http://localhost/foo/'); - $this->assertEquals('http://localhost/foo/foo', $form->getUri(), '->getUri() returns absolute URIs'); - - $form = $this->createForm('
    ', null, 'http://localhost/foo/'); - $this->assertEquals('http://localhost/foo', $form->getUri(), '->getUri() returns absolute URIs'); - } - - public function testGetUriWithOnlyQueryString() - { - $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); - $this->assertEquals('http://localhost/foo/bar?get=param', $form->getUri(), '->getUri() returns absolute URIs only if the host has been defined in the constructor'); - } - - public function testGetUriWithoutAction() - { - $form = $this->createForm('
    ', null, 'http://localhost/foo/bar'); - $this->assertEquals('http://localhost/foo/bar', $form->getUri(), '->getUri() returns path if no action defined'); - } - - public function provideGetUriValues() - { - return array( - array( - 'returns the URI of the form', - '
    ', - array(), - '/foo', - ), - array( - 'appends the form values if the method is get', - '
    ', - array(), - '/foo?foo=foo', - ), - array( - 'appends the form values and merges the submitted values', - '
    ', - array('foo' => 'bar'), - '/foo?foo=bar', - ), - array( - 'does not append values if the method is post', - '
    ', - array(), - '/foo', - ), - array( - 'does not append values if the method is patch', - '
    ', - array(), - '/foo', - 'PUT', - ), - array( - 'does not append values if the method is delete', - '
    ', - array(), - '/foo', - 'DELETE', - ), - array( - 'does not append values if the method is put', - '
    ', - array(), - '/foo', - 'PATCH', - ), - array( - 'appends the form values to an existing query string', - '
    ', - array(), - '/foo?bar=bar&foo=foo', - ), - array( - 'replaces query values with the form values', - '
    ', - array(), - '/foo?bar=foo', - ), - array( - 'returns an empty URI if the action is empty', - '
    ', - array(), - '/', - ), - array( - 'appends the form values even if the action is empty', - '
    ', - array(), - '/?foo=foo', - ), - array( - 'chooses the path if the action attribute value is a sharp (#)', - '
    ', - array(), - '/#', - ), - ); - } - - public function testHas() - { - $form = $this->createForm('
    '); - - $this->assertFalse($form->has('foo'), '->has() returns false if a field is not in the form'); - $this->assertTrue($form->has('bar'), '->has() returns true if a field is in the form'); - } - - public function testRemove() - { - $form = $this->createForm('
    '); - $form->remove('bar'); - $this->assertFalse($form->has('bar'), '->remove() removes a field'); - } - - public function testGet() - { - $form = $this->createForm('
    '); - - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $form->get('bar'), '->get() returns the field object associated with the given name'); - - try { - $form->get('foo'); - $this->fail('->get() throws an \InvalidArgumentException if the field does not exist'); - } catch (\InvalidArgumentException $e) { - $this->assertTrue(true, '->get() throws an \InvalidArgumentException if the field does not exist'); - } - } - - public function testAll() - { - $form = $this->createForm('
    '); - - $fields = $form->all(); - $this->assertCount(1, $fields, '->all() return an array of form field objects'); - $this->assertInstanceOf('Symfony\\Component\\DomCrawler\\Field\\InputFormField', $fields['bar'], '->all() return an array of form field objects'); - } - - public function testSubmitWithoutAFormButton() - { - $dom = new \DOMDocument(); - $dom->loadHTML(' - -
    - -
    - - '); - - $nodes = $dom->getElementsByTagName('form'); - $form = new Form($nodes->item(0), 'http://example.com'); - $this->assertSame($nodes->item(0), $form->getFormNode(), '->getFormNode() returns the form node associated with this form'); - } - - public function testTypeAttributeIsCaseInsensitive() - { - $form = $this->createForm('
    '); - $this->assertTrue($form->has('example.x'), '->has() returns true if the image input was correctly turned into an x and a y fields'); - $this->assertTrue($form->has('example.y'), '->has() returns true if the image input was correctly turned into an x and a y fields'); - } - - public function testFormFieldRegistryAcceptAnyNames() - { - $field = $this->getFormFieldMock('[t:dbt%3adate;]data_daterange_enddate_value'); - - $registry = new FormFieldRegistry(); - $registry->add($field); - $this->assertEquals($field, $registry->get('[t:dbt%3adate;]data_daterange_enddate_value')); - $registry->set('[t:dbt%3adate;]data_daterange_enddate_value', null); - - $form = $this->createForm('
    '); - $form['[t:dbt%3adate;]data_daterange_enddate_value'] = 'bar'; - - $registry->remove('[t:dbt%3adate;]data_daterange_enddate_value'); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFormFieldRegistryGetThrowAnExceptionWhenTheFieldDoesNotExist() - { - $registry = new FormFieldRegistry(); - $registry->get('foo'); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFormFieldRegistrySetThrowAnExceptionWhenTheFieldDoesNotExist() - { - $registry = new FormFieldRegistry(); - $registry->set('foo', null); - } - - public function testFormFieldRegistryHasReturnsTrueWhenTheFQNExists() - { - $registry = new FormFieldRegistry(); - $registry->add($this->getFormFieldMock('foo[bar]')); - - $this->assertTrue($registry->has('foo')); - $this->assertTrue($registry->has('foo[bar]')); - $this->assertFalse($registry->has('bar')); - $this->assertFalse($registry->has('foo[foo]')); - } - - public function testFormRegistryFieldsCanBeRemoved() - { - $registry = new FormFieldRegistry(); - $registry->add($this->getFormFieldMock('foo')); - $registry->remove('foo'); - $this->assertFalse($registry->has('foo')); - } - - public function testFormRegistrySupportsMultivaluedFields() - { - $registry = new FormFieldRegistry(); - $registry->add($this->getFormFieldMock('foo[]')); - $registry->add($this->getFormFieldMock('foo[]')); - $registry->add($this->getFormFieldMock('bar[5]')); - $registry->add($this->getFormFieldMock('bar[]')); - $registry->add($this->getFormFieldMock('bar[baz]')); - - $this->assertEquals( - array('foo[0]', 'foo[1]', 'bar[5]', 'bar[6]', 'bar[baz]'), - array_keys($registry->all()) - ); - } - - public function testFormRegistrySetValues() - { - $registry = new FormFieldRegistry(); - $registry->add($f2 = $this->getFormFieldMock('foo[2]')); - $registry->add($f3 = $this->getFormFieldMock('foo[3]')); - $registry->add($fbb = $this->getFormFieldMock('foo[bar][baz]')); - - $f2 - ->expects($this->exactly(2)) - ->method('setValue') - ->with(2) - ; - - $f3 - ->expects($this->exactly(2)) - ->method('setValue') - ->with(3) - ; - - $fbb - ->expects($this->exactly(2)) - ->method('setValue') - ->with('fbb') - ; - - $registry->set('foo[2]', 2); - $registry->set('foo[3]', 3); - $registry->set('foo[bar][baz]', 'fbb'); - - $registry->set('foo', array( - 2 => 2, - 3 => 3, - 'bar' => array( - 'baz' => 'fbb', - ), - )); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Cannot set value on a compound field "foo[bar]". - */ - public function testFormRegistrySetValueOnCompoundField() - { - $registry = new FormFieldRegistry(); - $registry->add($this->getFormFieldMock('foo[bar][baz]')); - - $registry->set('foo[bar]', 'fbb'); - } - - /** - * @expectedException \InvalidArgumentException - * @expectedExceptionMessage Unreachable field "0" - */ - public function testFormRegistrySetArrayOnNotCompoundField() - { - $registry = new FormFieldRegistry(); - $registry->add($this->getFormFieldMock('bar')); - - $registry->set('bar', array('baz')); - } - - public function testDifferentFieldTypesWithSameName() - { - $dom = new \DOMDocument(); - $dom->loadHTML(' - - -
    - - - - - - -
    - - - '); - $form = new Form($dom->getElementsByTagName('form')->item(0), 'http://example.com'); - - $this->assertInstanceOf('Symfony\Component\DomCrawler\Field\ChoiceFormField', $form->get('option')); - } - - protected function getFormFieldMock($name, $value = null) - { - $field = $this - ->getMockBuilder('Symfony\\Component\\DomCrawler\\Field\\FormField') - ->setMethods(array('getName', 'getValue', 'setValue', 'initialize')) - ->disableOriginalConstructor() - ->getMock() - ; - - $field - ->expects($this->any()) - ->method('getName') - ->will($this->returnValue($name)) - ; - - $field - ->expects($this->any()) - ->method('getValue') - ->will($this->returnValue($value)) - ; - - return $field; - } - - protected function createForm($form, $method = null, $currentUri = null) - { - $dom = new \DOMDocument(); - $dom->loadHTML(''.$form.''); - - $xPath = new \DOMXPath($dom); - $nodes = $xPath->query('//input | //button'); - - if (null === $currentUri) { - $currentUri = 'http://example.com/'; - } - - return new Form($nodes->item($nodes->length - 1), $currentUri, $method); - } - - protected function createTestHtml5Form() - { - $dom = new \DOMDocument(); - $dom->loadHTML(' - -

    Hello form

    -
    -
    - -
    - - -
    - -
    -
    -
    - - - -
    - -
    - -
    -
    -
    - - -
    -
    - - -
    -
    -
    - - - -
    - ",'"].join(""));n.elem.attr("lay-arrow",n.arrow),n.elem.find("."+u)[0]&&n.elem.find("."+u).remove(),n.elem.append(t),t.on("click",function(){var n=i(this),t=n.attr("lay-type");e.slide(t)})},m.prototype.indicator=function(){var e=this,n=e.config,t=e.elemInd=i(['
      ',function(){var i=[];return layui.each(e.elemItem,function(e){i.push("")}),i.join("")}(),"
    "].join(""));n.elem.attr("lay-indicator",n.indicator),n.elem.find("."+c)[0]&&n.elem.find("."+c).remove(),n.elem.append(t),"updown"===n.anim&&t.css("margin-top",-(t.height()/2)),t.find("li").on("hover"===n.trigger?"mouseover":n.trigger,function(){var t=i(this),a=t.index();a>n.index?e.slide("add",a-n.index):a/g,">").replace(/'/g,"'").replace(/"/g,""")),c.html('
    1. '+o.replace(/[\r\t\n]+/g,"
    2. ")+"
    "),c.find(">.layui-code-h3")[0]||c.prepend('

    '+(c.attr("lay-title")||e.title||"code")+(e.about?'layui.code':"")+"

    ");var d=c.find(">.layui-code-ol");c.addClass("layui-box layui-code-view"),(c.attr("lay-skin")||e.skin)&&c.addClass("layui-code-"+(c.attr("lay-skin")||e.skin)),(d.find("li").length/100|0)>0&&d.css("margin-left",(d.find("li").length/100|0)+"px"),(c.attr("lay-height")||e.height)&&d.css("max-height",c.attr("lay-height")||e.height)})})}).addcss("modules/code.css","skincodecss"); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/colorpicker.js b/static/admin/js/layui/lay/modules/colorpicker.js index 1ec2902..fd99bf8 100755 --- a/static/admin/js/layui/lay/modules/colorpicker.js +++ b/static/admin/js/layui/lay/modules/colorpicker.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,o={config:{},index:layui.colorpicker?layui.colorpicker.index+1e4:0,set:function(e){var o=this;return o.config=i.extend({},o.config,e),o},on:function(e,i){return layui.onevent.call(this,"colorpicker",e,i)}},r=function(){var e=this,i=e.config;return{config:i}},t="colorpicker",n="layui-show",l="layui-colorpicker",c=".layui-colorpicker-main",a="layui-icon-down",s="layui-icon-close",f="layui-colorpicker-trigger-span",d="layui-colorpicker-trigger-i",u="layui-colorpicker-side",p="layui-colorpicker-side-slider",g="layui-colorpicker-basis",v="layui-colorpicker-alpha-bgcolor",h="layui-colorpicker-alpha-slider",m="layui-colorpicker-basis-cursor",b="layui-colorpicker-main-input",k=function(e){var i={h:0,s:0,b:0},o=Math.min(e.r,e.g,e.b),r=Math.max(e.r,e.g,e.b),t=r-o;return i.b=r,i.s=0!=r?255*t/r:0,0!=i.s?e.r==r?i.h=(e.g-e.b)/t:e.g==r?i.h=2+(e.b-e.r)/t:i.h=4+(e.r-e.g)/t:i.h=-1,r==o&&(i.h=0),i.h*=60,i.h<0&&(i.h+=360),i.s*=100/255,i.b*=100/255,i},y=function(e){var e=e.indexOf("#")>-1?e.substring(1):e;if(3==e.length){var i=e.split("");e=i[0]+i[0]+i[1]+i[1]+i[2]+i[2]}e=parseInt(e,16);var o={r:e>>16,g:(65280&e)>>8,b:255&e};return k(o)},x=function(e){var i={},o=e.h,r=255*e.s/100,t=255*e.b/100;if(0==r)i.r=i.g=i.b=t;else{var n=t,l=(255-r)*t/255,c=(n-l)*(o%60)/60;360==o&&(o=0),o<60?(i.r=n,i.b=l,i.g=l+c):o<120?(i.g=n,i.b=l,i.r=n-c):o<180?(i.g=n,i.r=l,i.b=l+c):o<240?(i.b=n,i.r=l,i.g=n-c):o<300?(i.b=n,i.g=l,i.r=l+c):o<360?(i.r=n,i.g=l,i.b=n-c):(i.r=0,i.g=0,i.b=0)}return{r:Math.round(i.r),g:Math.round(i.g),b:Math.round(i.b)}},C=function(e){var o=x(e),r=[o.r.toString(16),o.g.toString(16),o.b.toString(16)];return i.each(r,function(e,i){1==i.length&&(r[e]="0"+i)}),r.join("")},P=function(e){var i=/[0-9]{1,3}/g,o=e.match(i)||[];return{r:o[0],g:o[1],b:o[2]}},B=i(window),w=i(document),D=function(e){var r=this;r.index=++o.index,r.config=i.extend({},r.config,o.config,e),r.render()};D.prototype.config={color:"",size:null,alpha:!1,format:"hex",predefine:!1,colors:["#009688","#5FB878","#1E9FFF","#FF5722","#FFB800","#01AAED","#999","#c00","#ff8c00","#ffd700","#90ee90","#00ced1","#1e90ff","#c71585","rgb(0, 186, 189)","rgb(255, 120, 0)","rgb(250, 212, 0)","#393D49","rgba(0,0,0,.5)","rgba(255, 69, 0, 0.68)","rgba(144, 240, 144, 0.5)","rgba(31, 147, 255, 0.73)"]},D.prototype.render=function(){var e=this,o=e.config,r=i(['
    ',"",'3&&(o.alpha&&"rgb"==o.format||(e="#"+C(k(P(o.color))))),"background: "+e):e}()+'">','',"","","
    "].join("")),t=i(o.elem);o.size&&r.addClass("layui-colorpicker-"+o.size),t.addClass("layui-inline").html(e.elemColorBox=r),e.color=e.elemColorBox.find("."+f)[0].style.background,e.events()},D.prototype.renderPicker=function(){var e=this,o=e.config,r=e.elemColorBox[0],t=e.elemPicker=i(['
    ','
    ','
    ','
    ','
    ','
    ',"
    ",'
    ','
    ',"
    ","
    ",'
    ','
    ','
    ',"
    ","
    ",function(){if(o.predefine){var e=['
    '];return layui.each(o.colors,function(i,o){e.push(['
    ','
    ',"
    "].join(""))}),e.push("
    "),e.join("")}return""}(),'
    ','
    ','',"
    ",'
    ','','',"","
    "].join(""));e.elemColorBox.find("."+f)[0];i(c)[0]&&i(c).data("index")==e.index?e.removePicker(D.thisElemInd):(e.removePicker(D.thisElemInd),i("body").append(t)),D.thisElemInd=e.index,D.thisColor=r.style.background,e.position(),e.pickerEvents()},D.prototype.removePicker=function(e){var o=this;o.config;return i("#layui-colorpicker"+(e||o.index)).remove(),o},D.prototype.position=function(){var e=this,i=e.config,o=e.bindElem||e.elemColorBox[0],r=e.elemPicker[0],t=o.getBoundingClientRect(),n=r.offsetWidth,l=r.offsetHeight,c=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},a=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},s=5,f=t.left,d=t.bottom;f-=(n-o.offsetWidth)/2,d+=s,f+n+s>a("width")?f=a("width")-n-s:fa()&&(d=t.top>l?t.top-l:a()-l,d-=2*s),i.position&&(r.style.position=i.position),r.style.left=f+("fixed"===i.position?0:c(1))+"px",r.style.top=d+("fixed"===i.position?0:c())+"px"},D.prototype.val=function(){var e=this,i=(e.config,e.elemColorBox.find("."+f)),o=e.elemPicker.find("."+b),r=i[0],t=r.style.backgroundColor;if(t){var n=k(P(t)),l=i.attr("lay-type");if(e.select(n.h,n.s,n.b),"torgb"===l&&o.find("input").val(t),"rgba"===l){var c=P(t);if(3==(t.match(/[0-9]{1,3}/g)||[]).length)o.find("input").val("rgba("+c.r+", "+c.g+", "+c.b+", 1)"),e.elemPicker.find("."+h).css("left",280);else{o.find("input").val(t);var a=280*t.slice(t.lastIndexOf(",")+1,t.length-1);e.elemPicker.find("."+h).css("left",a)}e.elemPicker.find("."+v)[0].style.background="linear-gradient(to right, rgba("+c.r+", "+c.g+", "+c.b+", 0), rgb("+c.r+", "+c.g+", "+c.b+"))"}}else e.select(0,100,100),o.find("input").val(""),e.elemPicker.find("."+v)[0].style.background="",e.elemPicker.find("."+h).css("left",280)},D.prototype.side=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f),t=r.attr("lay-type"),n=e.elemPicker.find("."+u),l=e.elemPicker.find("."+p),c=e.elemPicker.find("."+g),y=e.elemPicker.find("."+m),C=e.elemPicker.find("."+v),w=e.elemPicker.find("."+h),D=l[0].offsetTop/180*360,E=100-(y[0].offsetTop+3)/180*100,H=(y[0].offsetLeft+3)/260*100,W=Math.round(w[0].offsetLeft/280*100)/100,j=e.elemColorBox.find("."+d),F=e.elemPicker.find(".layui-colorpicker-pre").children("div"),L=function(i,n,l,c){e.select(i,n,l);var f=x({h:i,s:n,b:l});if(j.addClass(a).removeClass(s),r[0].style.background="rgb("+f.r+", "+f.g+", "+f.b+")","torgb"===t&&e.elemPicker.find("."+b).find("input").val("rgb("+f.r+", "+f.g+", "+f.b+")"),"rgba"===t){var d=0;d=280*c,w.css("left",d),e.elemPicker.find("."+b).find("input").val("rgba("+f.r+", "+f.g+", "+f.b+", "+c+")"),r[0].style.background="rgba("+f.r+", "+f.g+", "+f.b+", "+c+")",C[0].style.background="linear-gradient(to right, rgba("+f.r+", "+f.g+", "+f.b+", 0), rgb("+f.r+", "+f.g+", "+f.b+"))"}o.change&&o.change(e.elemPicker.find("."+b).find("input").val())},M=i(['
    t&&(r=t);var l=r/180*360;D=l,L(l,H,E,W),e.preventDefault()};Y(r),e.preventDefault()}),n.on("click",function(e){var o=e.clientY-i(this).offset().top;o<0&&(o=0),o>this.offsetHeight&&(o=this.offsetHeight);var r=o/180*360;D=r,L(r,H,E,W),e.preventDefault()}),y.on("mousedown",function(e){var i=this.offsetTop,o=this.offsetLeft,r=e.clientY,t=e.clientX,n=function(e){var n=i+(e.clientY-r),l=o+(e.clientX-t),a=c[0].offsetHeight-3,s=c[0].offsetWidth-3;n<-3&&(n=-3),n>a&&(n=a),l<-3&&(l=-3),l>s&&(l=s);var f=(l+3)/260*100,d=100-(n+3)/180*100;E=d,H=f,L(D,f,d,W),e.preventDefault()};layui.stope(e),Y(n),e.preventDefault()}),c.on("mousedown",function(e){var o=e.clientY-i(this).offset().top-3+B.scrollTop(),r=e.clientX-i(this).offset().left-3+B.scrollLeft();o<-3&&(o=-3),o>this.offsetHeight-3&&(o=this.offsetHeight-3),r<-3&&(r=-3),r>this.offsetWidth-3&&(r=this.offsetWidth-3);var t=(r+3)/260*100,n=100-(o+3)/180*100;E=n,H=t,L(D,t,n,W),e.preventDefault(),y.trigger(e,"mousedown")}),w.on("mousedown",function(e){var i=this.offsetLeft,o=e.clientX,r=function(e){var r=i+(e.clientX-o),t=C[0].offsetWidth;r<0&&(r=0),r>t&&(r=t);var n=Math.round(r/280*100)/100;W=n,L(D,H,E,n),e.preventDefault()};Y(r),e.preventDefault()}),C.on("click",function(e){var o=e.clientX-i(this).offset().left;o<0&&(o=0),o>this.offsetWidth&&(o=this.offsetWidth);var r=Math.round(o/280*100)/100;W=r,L(D,H,E,r),e.preventDefault()}),F.each(function(){i(this).on("click",function(){i(this).parent(".layui-colorpicker-pre").addClass("selected").siblings().removeClass("selected");var e,o=this.style.backgroundColor,r=k(P(o)),t=o.slice(o.lastIndexOf(",")+1,o.length-1);D=r.h,H=r.s,E=r.b,3==(o.match(/[0-9]{1,3}/g)||[]).length&&(t=1),W=t,e=280*t,L(r.h,r.s,r.b,t)})})},D.prototype.select=function(e,i,o,r){var t=this,n=(t.config,C({h:e,s:100,b:100})),l=C({h:e,s:i,b:o}),c=e/360*180,a=180-o/100*180-3,s=i/100*260-3;t.elemPicker.find("."+p).css("top",c),t.elemPicker.find("."+g)[0].style.background="#"+n,t.elemPicker.find("."+m).css({top:a,left:s}),"change"!==r&&t.elemPicker.find("."+b).find("input").val("#"+l)},D.prototype.pickerEvents=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f),t=e.elemPicker.find("."+b+" input"),n={clear:function(i){r[0].style.background="",e.elemColorBox.find("."+d).removeClass(a).addClass(s),e.color="",o.done&&o.done(""),e.removePicker()},confirm:function(i,n){var l=t.val(),c=l,f={};if(l.indexOf(",")>-1){if(f=k(P(l)),e.select(f.h,f.s,f.b),r[0].style.background=c="#"+C(f),(l.match(/[0-9]{1,3}/g)||[]).length>3&&"rgba"===r.attr("lay-type")){var u=280*l.slice(l.lastIndexOf(",")+1,l.length-1);e.elemPicker.find("."+h).css("left",u),r[0].style.background=l,c=l}}else f=y(l),r[0].style.background=c="#"+C(f),e.elemColorBox.find("."+d).removeClass(s).addClass(a);return"change"===n?(e.select(f.h,f.s,f.b,n),void(o.change&&o.change(c))):(e.color=l,o.done&&o.done(l),void e.removePicker())}};e.elemPicker.on("click","*[colorpicker-events]",function(){var e=i(this),o=e.attr("colorpicker-events");n[o]&&n[o].call(this,e)}),t.on("keyup",function(e){var o=i(this);n.confirm.call(this,o,13===e.keyCode?null:"change")})},D.prototype.events=function(){var e=this,o=e.config,r=e.elemColorBox.find("."+f);e.elemColorBox.on("click",function(){e.renderPicker(),i(c)[0]&&(e.val(),e.side())}),o.elem[0]&&!e.elemColorBox[0].eventHandler&&(w.on("click",function(o){if(!i(o.target).hasClass(l)&&!i(o.target).parents("."+l)[0]&&!i(o.target).hasClass(c.replace(/\./g,""))&&!i(o.target).parents(c)[0]&&e.elemPicker){if(e.color){var t=k(P(e.color));e.select(t.h,t.s,t.b)}else e.elemColorBox.find("."+d).removeClass(a).addClass(s);r[0].style.background=e.color||"",e.removePicker()}}),B.on("resize",function(){return!(!e.elemPicker||!i(c)[0])&&void e.position()}),e.elemColorBox[0].eventHandler=!0)},o.render=function(e){var i=new D(e);return r.call(i)},e(t,o)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/element.js b/static/admin/js/layui/lay/modules/element.js index 687038d..4e1254e 100755 --- a/static/admin/js/layui/lay/modules/element.js +++ b/static/admin/js/layui/lay/modules/element.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(t){"use strict";var a=layui.$,i=(layui.hint(),layui.device()),e="element",l="layui-this",n="layui-show",s=function(){this.config={}};s.prototype.set=function(t){var i=this;return a.extend(!0,i.config,t),i},s.prototype.on=function(t,a){return layui.onevent.call(this,e,t,a)},s.prototype.tabAdd=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.children(".layui-tab-bar"),o=l.children(".layui-tab-content"),r='
  • "+(i.title||"unnaming")+"
  • ";return s[0]?s.before(r):n.append(r),o.append('
    '+(i.content||"")+"
    "),f.hideTabMore(!0),f.tabAuto(),this},s.prototype.tabDelete=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabDelete(null,s),this},s.prototype.tabChange=function(t,i){var e=".layui-tab-title",l=a(".layui-tab[lay-filter="+t+"]"),n=l.children(e),s=n.find('>li[lay-id="'+i+'"]');return f.tabClick.call(s[0],null,null,s),this},s.prototype.tab=function(t){t=t||{},b.on("click",t.headerElem,function(i){var e=a(this).index();f.tabClick.call(this,i,e,null,t)})},s.prototype.progress=function(t,i){var e="layui-progress",l=a("."+e+"[lay-filter="+t+"]"),n=l.find("."+e+"-bar"),s=n.find("."+e+"-text");return n.css("width",i),s.text(i),this};var o=".layui-nav",r="layui-nav-item",c="layui-nav-bar",u="layui-nav-tree",d="layui-nav-child",y="layui-nav-more",h="layui-anim layui-anim-upbit",f={tabClick:function(t,i,s,o){o=o||{};var r=s||a(this),i=i||r.parent().children("li").index(r),c=o.headerElem?r.parent():r.parents(".layui-tab").eq(0),u=o.bodyElem?a(o.bodyElem):c.children(".layui-tab-content").find(".layui-tab-item"),d=r.find("a"),y=c.attr("lay-filter");"javascript:;"!==d.attr("href")&&"_blank"===d.attr("target")||(r.addClass(l).siblings().removeClass(l),u.eq(i).addClass(n).siblings().removeClass(n)),layui.event.call(this,e,"tab("+y+")",{elem:c,index:i})},tabDelete:function(t,i){var n=i||a(this).parent(),s=n.index(),o=n.parents(".layui-tab").eq(0),r=o.children(".layui-tab-content").find(".layui-tab-item"),c=o.attr("lay-filter");n.hasClass(l)&&(n.next()[0]?f.tabClick.call(n.next()[0],null,s+1):n.prev()[0]&&f.tabClick.call(n.prev()[0],null,s-1)),n.remove(),r.eq(s).remove(),setTimeout(function(){f.tabAuto()},50),layui.event.call(this,e,"tabDelete("+c+")",{elem:o,index:s})},tabAuto:function(){var t="layui-tab-more",e="layui-tab-bar",l="layui-tab-close",n=this;a(".layui-tab").each(function(){var s=a(this),o=s.children(".layui-tab-title"),r=(s.children(".layui-tab-content").find(".layui-tab-item"),'lay-stope="tabmore"'),c=a('');if(n===window&&8!=i.ie&&f.hideTabMore(!0),s.attr("lay-allowClose")&&o.find("li").each(function(){var t=a(this);if(!t.find("."+l)[0]){var i=a('');i.on("click",f.tabDelete),t.append(i)}}),"string"!=typeof s.attr("lay-unauto"))if(o.prop("scrollWidth")>o.outerWidth()+1){if(o.find("."+e)[0])return;o.append(c),s.attr("overflow",""),c.on("click",function(a){o[this.title?"removeClass":"addClass"](t),this.title=this.title?"":"收缩"})}else o.find("."+e).remove(),s.removeAttr("overflow")})},hideTabMore:function(t){var i=a(".layui-tab-title");t!==!0&&"tabmore"===a(t.target).attr("lay-stope")||(i.removeClass("layui-tab-more"),i.find(".layui-tab-bar").attr("title",""))},clickThis:function(){var t=a(this),i=t.parents(o),n=i.attr("lay-filter"),s=t.parent(),c=t.siblings("."+d),y="string"==typeof s.attr("lay-unselect");"javascript:;"!==t.attr("href")&&"_blank"===t.attr("target")||y||c[0]||(i.find("."+l).removeClass(l),s.addClass(l)),i.hasClass(u)&&(c.removeClass(h),c[0]&&(s["none"===c.css("display")?"addClass":"removeClass"](r+"ed"),"all"===i.attr("lay-shrink")&&s.siblings().removeClass(r+"ed"))),layui.event.call(this,e,"nav("+n+")",t)},collapse:function(){var t=a(this),i=t.find(".layui-colla-icon"),l=t.siblings(".layui-colla-content"),s=t.parents(".layui-collapse").eq(0),o=s.attr("lay-filter"),r="none"===l.css("display");if("string"==typeof s.attr("lay-accordion")){var c=s.children(".layui-colla-item").children("."+n);c.siblings(".layui-colla-title").children(".layui-colla-icon").html(""),c.removeClass(n)}l[r?"addClass":"removeClass"](n),i.html(r?"":""),layui.event.call(this,e,"collapse("+o+")",{title:t,content:l,show:r})}};s.prototype.init=function(t,e){var l=function(){return e?'[lay-filter="'+e+'"]':""}(),s={tab:function(){f.tabAuto.call({})},nav:function(){var t=200,e={},s={},p={},b=function(l,o,r){var c=a(this),f=c.find("."+d);o.hasClass(u)?l.css({top:c.position().top,height:c.children("a").outerHeight(),opacity:1}):(f.addClass(h),l.css({left:c.position().left+parseFloat(c.css("marginLeft")),top:c.position().top+c.height()-l.height()}),e[r]=setTimeout(function(){l.css({width:c.width(),opacity:1})},i.ie&&i.ie<10?0:t),clearTimeout(p[r]),"block"===f.css("display")&&clearTimeout(s[r]),s[r]=setTimeout(function(){f.addClass(n),c.find("."+y).addClass(y+"d")},300))};a(o+l).each(function(i){var l=a(this),o=a(''),h=l.find("."+r);l.find("."+c)[0]||(l.append(o),h.on("mouseenter",function(){b.call(this,o,l,i)}).on("mouseleave",function(){l.hasClass(u)||(clearTimeout(s[i]),s[i]=setTimeout(function(){l.find("."+d).removeClass(n),l.find("."+y).removeClass(y+"d")},300))}),l.on("mouseleave",function(){clearTimeout(e[i]),p[i]=setTimeout(function(){l.hasClass(u)?o.css({height:0,top:o.position().top+o.height()/2,opacity:0}):o.css({width:0,left:o.position().left+o.width()/2,opacity:0})},t)})),h.find("a").each(function(){var t=a(this),i=(t.parent(),t.siblings("."+d));i[0]&&!t.children("."+y)[0]&&t.append(''),t.off("click",f.clickThis).on("click",f.clickThis)})})},breadcrumb:function(){var t=".layui-breadcrumb";a(t+l).each(function(){var t=a(this),i="lay-separator",e=t.attr(i)||"/",l=t.find("a");l.next("span["+i+"]")[0]||(l.each(function(t){t!==l.length-1&&a(this).after(""+e+"")}),t.css("visibility","visible"))})},progress:function(){var t="layui-progress";a("."+t+l).each(function(){var i=a(this),e=i.find(".layui-progress-bar"),l=e.attr("lay-percent");e.css("width",function(){return/^.+\/.+$/.test(l)?100*new Function("return "+l)()+"%":l}()),i.attr("lay-showPercent")&&setTimeout(function(){e.html(''+l+"")},350)})},collapse:function(){var t="layui-collapse";a("."+t+l).each(function(){var t=a(this).find(".layui-colla-item");t.each(function(){var t=a(this),i=t.find(".layui-colla-title"),e=t.find(".layui-colla-content"),l="none"===e.css("display");i.find(".layui-colla-icon").remove(),i.append(''+(l?"":"")+""),i.off("click",f.collapse).on("click",f.collapse)})})}};return s[t]?s[t]():layui.each(s,function(t,a){a()})},s.prototype.render=s.prototype.init;var p=new s,b=a(document);p.render();var v=".layui-tab-title li";b.on("click",v,f.tabClick),b.on("click",f.hideTabMore),a(window).on("resize",f.tabAuto),t(e,p)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/flow.js b/static/admin/js/layui/lay/modules/flow.js index eca5c7b..8a80c05 100755 --- a/static/admin/js/layui/lay/modules/flow.js +++ b/static/admin/js/layui/lay/modules/flow.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(e){"use strict";var l=layui.$,o=function(e){},t='';o.prototype.load=function(e){var o,i,n,r,a=this,c=0;e=e||{};var f=l(e.elem);if(f[0]){var m=l(e.scrollElem||document),u=e.mb||50,s=!("isAuto"in e)||e.isAuto,v=e.end||"没有更多了",y=e.scrollElem&&e.scrollElem!==document,d="加载更多",h=l('");f.find(".layui-flow-more")[0]||f.append(h);var p=function(e,t){e=l(e),h.before(e),t=0==t||null,t?h.html(v):h.find("a").html(d),i=t,o=null,n&&n()},g=function(){o=!0,h.find("a").html(t),"function"==typeof e.done&&e.done(++c,p)};if(g(),h.find("a").on("click",function(){l(this);i||o||g()}),e.isLazyimg)var n=a.lazyimg({elem:e.elem+" img",scrollElem:e.scrollElem});return s?(m.on("scroll",function(){var e=l(this),t=e.scrollTop();r&&clearTimeout(r),i||(r=setTimeout(function(){var i=y?e.height():l(window).height(),n=y?e.prop("scrollHeight"):document.documentElement.scrollHeight;n-t-i<=u&&(o||g())},100))}),a):a}},o.prototype.lazyimg=function(e){var o,t=this,i=0;e=e||{};var n=l(e.scrollElem||document),r=e.elem||"img",a=e.scrollElem&&e.scrollElem!==document,c=function(e,l){var o=n.scrollTop(),r=o+l,c=a?function(){return e.offset().top-n.offset().top+o}():e.offset().top;if(c>=o&&c<=r&&!e.attr("src")){var m=e.attr("lay-src");layui.img(m,function(){var l=t.lazyimg.elem.eq(i);e.attr("src",m).removeAttr("lay-src"),l[0]&&f(l),i++})}},f=function(e,o){var f=a?(o||n).height():l(window).height(),m=n.scrollTop(),u=m+f;if(t.lazyimg.elem=l(r),e)c(e,f);else for(var s=0;su)break}};if(f(),!o){var m;n.on("scroll",function(){var e=l(this);m&&clearTimeout(m),m=setTimeout(function(){f(null,e)},50)}),o=!0}return f},e("flow",new o)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/form.js b/static/admin/js/layui/lay/modules/form.js index 37c6acd..daa8ce5 100755 --- a/static/admin/js/layui/lay/modules/form.js +++ b/static/admin/js/layui/lay/modules/form.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ - ;layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,a=layui.hint(),n=layui.device(),l="form",r=".layui-form",s="layui-this",o="layui-hide",c="layui-disabled",u=function(){this.config={verify:{required:[/[\S]+/,"必填项不能为空"],phone:[/^1\d{10}$/,"请输入正确的手机号"],email:[/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,"邮箱格式不正确"],url:[/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/,"链接格式不正确"],number:function(e){if(!e||isNaN(e))return"只能填写数字"},date:[/^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/,"日期格式不正确"],identity:[/(^\d{15}$)|(^\d{17}(x|X|\d)$)/,"请输入正确的身份证号"]}}};u.prototype.set=function(e){var t=this;return i.extend(!0,t.config,e),t},u.prototype.verify=function(e){var t=this;return i.extend(!0,t.config.verify,e),t},u.prototype.on=function(e,i){return layui.onevent.call(this,l,e,i)},u.prototype.val=function(e,t){var a=i(r+'[lay-filter="'+e+'"]');a.each(function(e,a){var n=i(this);layui.each(t,function(e,i){var t,a=n.find('[name="'+e+'"]');a[0]&&(t=a[0].type,"checkbox"===t?a[0].checked=i:"radio"===t?a.each(function(){this.value===i&&(this.checked=!0)}):a.val(i))})}),f.render(null,e)},u.prototype.render=function(e,t){var n=this,u=i(r+function(){return t?'[lay-filter="'+t+'"]':""}()),d={select:function(){var e,t="请选择",a="layui-form-select",n="layui-select-title",r="layui-select-none",d="",f=u.find("select"),v=function(t,l){i(t.target).parent().hasClass(n)&&!l||(i("."+a).removeClass(a+"ed "+a+"up"),e&&d&&e.val(d)),e=null},y=function(t,u,f){var y,p=i(this),m=t.find("."+n),k=m.find("input"),x=t.find("dl"),g=x.children("dd"),b=this.selectedIndex;if(!u){var C=function(){var e=t.offset().top+t.outerHeight()+5-h.scrollTop(),i=x.outerHeight();b=p[0].selectedIndex,t.addClass(a+"ed"),g.removeClass(o),y=null,g.eq(b).addClass(s).siblings().removeClass(s),e+i>h.height()&&e>=i&&t.addClass(a+"up"),$()},w=function(e){t.removeClass(a+"ed "+a+"up"),k.blur(),y=null,e||T(k.val(),function(e){e&&(d=x.find("."+s).html(),k&&k.val(d))})},$=function(){var e=x.children("dd."+s);if(e[0]){var i=e.position().top,t=x.height(),a=e.height();i>t&&x.scrollTop(i+x.scrollTop()-t+a-5),i<0&&x.scrollTop(i+x.scrollTop()-5)}};m.on("click",function(e){t.hasClass(a+"ed")?w():(v(e,!0),C()),x.find("."+r).remove()}),m.find(".layui-edge").on("click",function(){k.focus()}),k.on("keyup",function(e){var i=e.keyCode;9===i&&C()}).on("keydown",function(e){var i=e.keyCode;9===i&&w();var t=function(i,a){var n,l;e.preventDefault();var r=function(){var e=x.children("dd."+s);if(x.children("dd."+o)[0]&&"next"===i){var t=x.children("dd:not(."+o+",."+c+")"),n=t.eq(0).index();if(n>=0&&n无匹配项

    '):x.find("."+r).remove()},"keyup"),""===i&&x.find("."+r).remove(),void $())};f&&k.on("keyup",j).on("blur",function(t){var a=p[0].selectedIndex;e=k,d=i(p[0].options[a]).html(),setTimeout(function(){T(k.val(),function(e){d||k.val("")},"blur")},200)}),g.on("click",function(){var e=i(this),a=e.attr("lay-value"),n=p.attr("lay-filter");return!e.hasClass(c)&&(e.hasClass("layui-select-tips")?k.val(""):(k.val(e.text()),e.addClass(s)),e.siblings().removeClass(s),p.val(a).removeClass("layui-form-danger"),layui.event.call(this,l,"select("+n+")",{elem:p[0],value:a,othis:t}),w(!0),!1)}),t.find("dl>dt").on("click",function(e){return!1}),i(document).off("click",v).on("click",v)}};f.each(function(e,l){var r=i(this),o=r.next("."+a),u=this.disabled,d=l.value,f=i(l.options[l.selectedIndex]),v=l.options[0];if("string"==typeof r.attr("lay-ignore"))return r.show();var h="string"==typeof r.attr("lay-search"),p=v?v.value?t:v.innerHTML||t:t,m=i(['
    ','
    ','','
    ','
    ',function(e){var i=[];return layui.each(e,function(e,a){0!==e||a.value?"optgroup"===a.tagName.toLowerCase()?i.push("
    "+a.label+"
    "):i.push('
    '+a.innerHTML+"
    "):i.push('
    '+(a.innerHTML||t)+"
    ")}),0===i.length&&i.push('
    没有选项
    '),i.join("")}(r.find("*"))+"
    ","
    "].join(""));o[0]&&o.remove(),r.after(m),y.call(this,m,u,h)})},checkbox:function(){var e={checkbox:["layui-form-checkbox","layui-form-checked","checkbox"],_switch:["layui-form-switch","layui-form-onswitch","switch"]},t=u.find("input[type=checkbox]"),a=function(e,t){var a=i(this);e.on("click",function(){var i=a.attr("lay-filter"),n=(a.attr("lay-text")||"").split("|");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(t[1]).find("em").text(n[1])):(a[0].checked=!0,e.addClass(t[1]).find("em").text(n[0])),layui.event.call(a[0],l,t[2]+"("+i+")",{elem:a[0],value:a[0].value,othis:e}))})};t.each(function(t,n){var l=i(this),r=l.attr("lay-skin"),s=(l.attr("lay-text")||"").split("|"),o=this.disabled;"switch"===r&&(r="_"+r);var u=e[r]||e.checkbox;if("string"==typeof l.attr("lay-ignore"))return l.show();var d=l.next("."+u[0]),f=i(['
    ",function(){var e=n.title.replace(/\s/g,""),i={checkbox:[e?""+n.title+"":"",''].join(""),_switch:""+((n.checked?s[0]:s[1])||"")+""};return i[r]||i.checkbox}(),"
    "].join(""));d[0]&&d.remove(),l.after(f),a.call(this,f,u)})},radio:function(){var e="layui-form-radio",t=["",""],a=u.find("input[type=radio]"),n=function(a){var n=i(this),s="layui-anim-scaleSpring";a.on("click",function(){var o=n[0].name,c=n.parents(r),u=n.attr("lay-filter"),d=c.find("input[name="+o.replace(/(\.|#|\[|\])/g,"\\$1")+"]");n[0].disabled||(layui.each(d,function(){var a=i(this).next("."+e);this.checked=!1,a.removeClass(e+"ed"),a.find(".layui-icon").removeClass(s).html(t[1])}),n[0].checked=!0,a.addClass(e+"ed"),a.find(".layui-icon").addClass(s).html(t[0]),layui.event.call(n[0],l,"radio("+u+")",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var r=i(this),s=r.next("."+e),o=this.disabled;if("string"==typeof r.attr("lay-ignore"))return r.show();s[0]&&s.remove();var u=i(['
    ',''+t[l.checked?0:1]+"","
    "+function(){var e=l.title||"";return"string"==typeof r.next().attr("lay-radio")&&(e=r.next().html(),r.next().remove()),e}()+"
    ","
    "].join(""));r.after(u),n.call(this,u)})}};return e?d[e]?d[e]():a.error("不支持的"+e+"表单渲染"):layui.each(d,function(e,i){i()}),n};var d=function(){var e=i(this),a=f.config.verify,s=null,o="layui-form-danger",c={},u=e.parents(r),d=u.find("*[lay-verify]"),v=e.parents("form")[0],h=u.find("input,select,textarea"),y=e.attr("lay-filter");if(layui.each(d,function(e,l){var r=i(this),c=r.attr("lay-verify").split("|"),u=r.attr("lay-verType"),d=r.val();if(r.removeClass(o),layui.each(c,function(e,i){var c,f="",v="function"==typeof a[i];if(a[i]){var c=v?f=a[i](d,l):!a[i][0].test(d);if(f=f||a[i][1],c)return"tips"===u?t.tips(f,function(){return"string"==typeof r.attr("lay-ignore")||"select"!==l.tagName.toLowerCase()&&!/^checkbox|radio$/.test(l.type)?r:r.next()}(),{tips:1}):"alert"===u?t.alert(f,{title:"提示",shadeClose:!0}):t.msg(f,{icon:5,shift:6}),n.android||n.ios||l.focus(),r.addClass(o),s=!0}}),s)return s}),s)return!1;var p={};return layui.each(h,function(e,i){if(i.name=(i.name||"").replace(/^\s*|\s*&/,""),i.name){if(/^.*\[\]$/.test(i.name)){var t=i.name.match(/^(.*)\[\]$/g)[0];p[t]=0|p[t],i.name=i.name.replace(/^(.*)\[\]$/,"$1["+p[t]++ +"]")}/^checkbox|radio$/.test(i.type)&&!i.checked||(c[i.name]=i.value)}}),layui.event.call(this,l,"submit("+y+")",{elem:this,form:v,field:c})},f=new u,v=i(document),h=i(window);f.render(),v.on("reset",r,function(){var e=i(this).attr("lay-filter");setTimeout(function(){f.render(null,e)},50)}),v.on("submit",r,d).on("click","*[lay-submit]",d),e(l,f)}); \ No newline at end of file +/** layui-v2.4.5 MIT License By https://www.layui.com */ + ;layui.define("layer",function(e){"use strict";var t=layui.$,i=layui.layer,a=layui.hint(),n=layui.device(),l="form",r=".layui-form",s="layui-this",o="layui-hide",c="layui-disabled",u=function(){this.config={verify:{required:[/[\S]+/,"必填项不能为空"],phone:[/^1\d{10}$/,"请输入正确的手机号"],email:[/^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/,"邮箱格式不正确"],url:[/(^#)|(^http(s*):\/\/[^\s]+\.[^\s]+)/,"链接格式不正确"],number:function(e){if(!e||isNaN(e))return"只能填写数字"},date:[/^(\d{4})[-\/](\d{1}|0\d{1}|1[0-2])([-\/](\d{1}|0\d{1}|[1-2][0-9]|3[0-1]))*$/,"日期格式不正确"],identity:[/(^\d{15}$)|(^\d{17}(x|X|\d)$)/,"请输入正确的身份证号"]}}};u.prototype.set=function(e){var i=this;return t.extend(!0,i.config,e),i},u.prototype.verify=function(e){var i=this;return t.extend(!0,i.config.verify,e),i},u.prototype.on=function(e,t){return layui.onevent.call(this,l,e,t)},u.prototype.val=function(e,i){var a=t(r+'[lay-filter="'+e+'"]');a.each(function(e,a){var n=t(this);layui.each(i,function(e,t){var i,a=n.find('[name="'+e+'"]');a[0]&&(i=a[0].type,"checkbox"===i?a[0].checked=t:"radio"===i?a.each(function(){this.value===t&&(this.checked=!0)}):a.val(t))})}),f.render(null,e)},u.prototype.render=function(e,i){var n=this,u=t(r+function(){return i?'[lay-filter="'+i+'"]':""}()),d={select:function(){var e,i="请选择",a="layui-form-select",n="layui-select-title",r="layui-select-none",d="",f=u.find("select"),v=function(i,l){t(i.target).parent().hasClass(n)&&!l||(t("."+a).removeClass(a+"ed "+a+"up"),e&&d&&e.val(d)),e=null},y=function(i,u,f){var y,p=t(this),m=i.find("."+n),k=m.find("input"),x=i.find("dl"),g=x.children("dd"),b=this.selectedIndex;if(!u){var C=function(){var e=i.offset().top+i.outerHeight()+5-h.scrollTop(),t=x.outerHeight();b=p[0].selectedIndex,i.addClass(a+"ed"),g.removeClass(o),y=null,g.eq(b).addClass(s).siblings().removeClass(s),e+t>h.height()&&e>=t&&i.addClass(a+"up"),$()},w=function(e){i.removeClass(a+"ed "+a+"up"),k.blur(),y=null,e||T(k.val(),function(e){var i=p[0].selectedIndex;e&&(d=t(p[0].options[i]).html(),0===i&&d===k.attr("placeholder")&&(d=""),k.val(d||""))})},$=function(){var e=x.children("dd."+s);if(e[0]){var t=e.position().top,i=x.height(),a=e.height();t>i&&x.scrollTop(t+x.scrollTop()-i+a-5),t<0&&x.scrollTop(t+x.scrollTop()-5)}};m.on("click",function(e){i.hasClass(a+"ed")?w():(v(e,!0),C()),x.find("."+r).remove()}),m.find(".layui-edge").on("click",function(){k.focus()}),k.on("keyup",function(e){var t=e.keyCode;9===t&&C()}).on("keydown",function(e){var t=e.keyCode;9===t&&w();var i=function(t,a){var n,l;e.preventDefault();var r=function(){var e=x.children("dd."+s);if(x.children("dd."+o)[0]&&"next"===t){var i=x.children("dd:not(."+o+",."+c+")"),n=i.eq(0).index();if(n>=0&&n无匹配项

    '):x.find("."+r).remove()},"keyup"),""===t&&x.find("."+r).remove(),void $())};f&&k.on("keyup",j).on("blur",function(i){var a=p[0].selectedIndex;e=k,d=t(p[0].options[a]).html(),0===a&&d===k.attr("placeholder")&&(d=""),setTimeout(function(){T(k.val(),function(e){d||k.val("")},"blur")},200)}),g.on("click",function(){var e=t(this),a=e.attr("lay-value"),n=p.attr("lay-filter");return!e.hasClass(c)&&(e.hasClass("layui-select-tips")?k.val(""):(k.val(e.text()),e.addClass(s)),e.siblings().removeClass(s),p.val(a).removeClass("layui-form-danger"),layui.event.call(this,l,"select("+n+")",{elem:p[0],value:a,othis:i}),w(!0),!1)}),i.find("dl>dt").on("click",function(e){return!1}),t(document).off("click",v).on("click",v)}};f.each(function(e,l){var r=t(this),o=r.next("."+a),u=this.disabled,d=l.value,f=t(l.options[l.selectedIndex]),v=l.options[0];if("string"==typeof r.attr("lay-ignore"))return r.show();var h="string"==typeof r.attr("lay-search"),p=v?v.value?i:v.innerHTML||i:i,m=t(['
    ','
    ','','
    ','
    ',function(e){var t=[];return layui.each(e,function(e,a){0!==e||a.value?"optgroup"===a.tagName.toLowerCase()?t.push("
    "+a.label+"
    "):t.push('
    '+a.innerHTML+"
    "):t.push('
    '+(a.innerHTML||i)+"
    ")}),0===t.length&&t.push('
    没有选项
    '),t.join("")}(r.find("*"))+"
    ","
    "].join(""));o[0]&&o.remove(),r.after(m),y.call(this,m,u,h)})},checkbox:function(){var e={checkbox:["layui-form-checkbox","layui-form-checked","checkbox"],_switch:["layui-form-switch","layui-form-onswitch","switch"]},i=u.find("input[type=checkbox]"),a=function(e,i){var a=t(this);e.on("click",function(){var t=a.attr("lay-filter"),n=(a.attr("lay-text")||"").split("|");a[0].disabled||(a[0].checked?(a[0].checked=!1,e.removeClass(i[1]).find("em").text(n[1])):(a[0].checked=!0,e.addClass(i[1]).find("em").text(n[0])),layui.event.call(a[0],l,i[2]+"("+t+")",{elem:a[0],value:a[0].value,othis:e}))})};i.each(function(i,n){var l=t(this),r=l.attr("lay-skin"),s=(l.attr("lay-text")||"").split("|"),o=this.disabled;"switch"===r&&(r="_"+r);var u=e[r]||e.checkbox;if("string"==typeof l.attr("lay-ignore"))return l.show();var d=l.next("."+u[0]),f=t(['
    ",function(){var e=n.title.replace(/\s/g,""),t={checkbox:[e?""+n.title+"":"",''].join(""),_switch:""+((n.checked?s[0]:s[1])||"")+""};return t[r]||t.checkbox}(),"
    "].join(""));d[0]&&d.remove(),l.after(f),a.call(this,f,u)})},radio:function(){var e="layui-form-radio",i=["",""],a=u.find("input[type=radio]"),n=function(a){var n=t(this),s="layui-anim-scaleSpring";a.on("click",function(){var o=n[0].name,c=n.parents(r),u=n.attr("lay-filter"),d=c.find("input[name="+o.replace(/(\.|#|\[|\])/g,"\\$1")+"]");n[0].disabled||(layui.each(d,function(){var a=t(this).next("."+e);this.checked=!1,a.removeClass(e+"ed"),a.find(".layui-icon").removeClass(s).html(i[1])}),n[0].checked=!0,a.addClass(e+"ed"),a.find(".layui-icon").addClass(s).html(i[0]),layui.event.call(n[0],l,"radio("+u+")",{elem:n[0],value:n[0].value,othis:a}))})};a.each(function(a,l){var r=t(this),s=r.next("."+e),o=this.disabled;if("string"==typeof r.attr("lay-ignore"))return r.show();s[0]&&s.remove();var u=t(['
    ',''+i[l.checked?0:1]+"","
    "+function(){var e=l.title||"";return"string"==typeof r.next().attr("lay-radio")&&(e=r.next().html(),r.next().remove()),e}()+"
    ","
    "].join(""));r.after(u),n.call(this,u)})}};return e?d[e]?d[e]():a.error("不支持的"+e+"表单渲染"):layui.each(d,function(e,t){t()}),n};var d=function(){var e=t(this),a=f.config.verify,s=null,o="layui-form-danger",c={},u=e.parents(r),d=u.find("*[lay-verify]"),v=e.parents("form")[0],h=u.find("input,select,textarea"),y=e.attr("lay-filter");if(layui.each(d,function(e,l){var r=t(this),c=r.attr("lay-verify").split("|"),u=r.attr("lay-verType"),d=r.val();if(r.removeClass(o),layui.each(c,function(e,t){var c,f="",v="function"==typeof a[t];if(a[t]){var c=v?f=a[t](d,l):!a[t][0].test(d);if(f=f||a[t][1],c)return"tips"===u?i.tips(f,function(){return"string"==typeof r.attr("lay-ignore")||"select"!==l.tagName.toLowerCase()&&!/^checkbox|radio$/.test(l.type)?r:r.next()}(),{tips:1}):"alert"===u?i.alert(f,{title:"提示",shadeClose:!0}):i.msg(f,{icon:5,shift:6}),n.android||n.ios||l.focus(),r.addClass(o),s=!0}}),s)return s}),s)return!1;var p={};return layui.each(h,function(e,t){if(t.name=(t.name||"").replace(/^\s*|\s*&/,""),t.name){if(/^.*\[\]$/.test(t.name)){var i=t.name.match(/^(.*)\[\]$/g)[0];p[i]=0|p[i],t.name=t.name.replace(/^(.*)\[\]$/,"$1["+p[i]++ +"]")}/^checkbox|radio$/.test(t.type)&&!t.checked||(c[t.name]=t.value)}}),layui.event.call(this,l,"submit("+y+")",{elem:this,form:v,field:c})},f=new u,v=t(document),h=t(window);f.render(),v.on("reset",r,function(){var e=t(this).attr("lay-filter");setTimeout(function(){f.render(null,e)},50)}),v.on("submit",r,d).on("click","*[lay-submit]",d),e(l,f)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/jquery.js b/static/admin/js/layui/lay/modules/jquery.js index f8c2b58..242696a 100755 --- a/static/admin/js/layui/lay/modules/jquery.js +++ b/static/admin/js/layui/lay/modules/jquery.js @@ -1,4 +1,4 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;!function(e,t){"object"==typeof module&&"object"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error("jQuery requires a window with a document");return t(e)}:t(e)}("undefined"!=typeof window?window:this,function(e,t){function n(e){var t=!!e&&"length"in e&&e.length,n=pe.type(e);return"function"!==n&&!pe.isWindow(e)&&("array"===n||0===t||"number"==typeof t&&t>0&&t-1 in e)}function r(e,t,n){if(pe.isFunction(t))return pe.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return pe.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(Ce.test(t))return pe.filter(t,e,n);t=pe.filter(t,e)}return pe.grep(e,function(e){return pe.inArray(e,t)>-1!==n})}function i(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}function o(e){var t={};return pe.each(e.match(De)||[],function(e,n){t[n]=!0}),t}function a(){re.addEventListener?(re.removeEventListener("DOMContentLoaded",s),e.removeEventListener("load",s)):(re.detachEvent("onreadystatechange",s),e.detachEvent("onload",s))}function s(){(re.addEventListener||"load"===e.event.type||"complete"===re.readyState)&&(a(),pe.ready())}function u(e,t,n){if(void 0===n&&1===e.nodeType){var r="data-"+t.replace(_e,"-$1").toLowerCase();if(n=e.getAttribute(r),"string"==typeof n){try{n="true"===n||"false"!==n&&("null"===n?null:+n+""===n?+n:qe.test(n)?pe.parseJSON(n):n)}catch(i){}pe.data(e,t,n)}else n=void 0}return n}function l(e){var t;for(t in e)if(("data"!==t||!pe.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}function c(e,t,n,r){if(He(e)){var i,o,a=pe.expando,s=e.nodeType,u=s?pe.cache:e,l=s?e[a]:e[a]&&a;if(l&&u[l]&&(r||u[l].data)||void 0!==n||"string"!=typeof t)return l||(l=s?e[a]=ne.pop()||pe.guid++:a),u[l]||(u[l]=s?{}:{toJSON:pe.noop}),"object"!=typeof t&&"function"!=typeof t||(r?u[l]=pe.extend(u[l],t):u[l].data=pe.extend(u[l].data,t)),o=u[l],r||(o.data||(o.data={}),o=o.data),void 0!==n&&(o[pe.camelCase(t)]=n),"string"==typeof t?(i=o[t],null==i&&(i=o[pe.camelCase(t)])):i=o,i}}function f(e,t,n){if(He(e)){var r,i,o=e.nodeType,a=o?pe.cache:e,s=o?e[pe.expando]:pe.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){pe.isArray(t)?t=t.concat(pe.map(t,pe.camelCase)):t in r?t=[t]:(t=pe.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;for(;i--;)delete r[t[i]];if(n?!l(r):!pe.isEmptyObject(r))return}(n||(delete a[s].data,l(a[s])))&&(o?pe.cleanData([e],!0):fe.deleteExpando||a!=a.window?delete a[s]:a[s]=void 0)}}}function d(e,t,n,r){var i,o=1,a=20,s=r?function(){return r.cur()}:function(){return pe.css(e,t,"")},u=s(),l=n&&n[3]||(pe.cssNumber[t]?"":"px"),c=(pe.cssNumber[t]||"px"!==l&&+u)&&Me.exec(pe.css(e,t));if(c&&c[3]!==l){l=l||c[3],n=n||[],c=+u||1;do o=o||".5",c/=o,pe.style(e,t,c+l);while(o!==(o=s()/u)&&1!==o&&--a)}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function p(e){var t=ze.split("|"),n=e.createDocumentFragment();if(n.createElement)for(;t.length;)n.createElement(t.pop());return n}function h(e,t){var n,r,i=0,o="undefined"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||"*"):"undefined"!=typeof e.querySelectorAll?e.querySelectorAll(t||"*"):void 0;if(!o)for(o=[],n=e.childNodes||e;null!=(r=n[i]);i++)!t||pe.nodeName(r,t)?o.push(r):pe.merge(o,h(r,t));return void 0===t||t&&pe.nodeName(e,t)?pe.merge([e],o):o}function g(e,t){for(var n,r=0;null!=(n=e[r]);r++)pe._data(n,"globalEval",!t||pe._data(t[r],"globalEval"))}function m(e){Be.test(e.type)&&(e.defaultChecked=e.checked)}function y(e,t,n,r,i){for(var o,a,s,u,l,c,f,d=e.length,y=p(t),v=[],x=0;x"!==f[1]||Ve.test(a)?0:u:u.firstChild,o=a&&a.childNodes.length;o--;)pe.nodeName(c=a.childNodes[o],"tbody")&&!c.childNodes.length&&a.removeChild(c);for(pe.merge(v,u.childNodes),u.textContent="";u.firstChild;)u.removeChild(u.firstChild);u=y.lastChild}else v.push(t.createTextNode(a));for(u&&y.removeChild(u),fe.appendChecked||pe.grep(h(v,"input"),m),x=0;a=v[x++];)if(r&&pe.inArray(a,r)>-1)i&&i.push(a);else if(s=pe.contains(a.ownerDocument,a),u=h(y.appendChild(a),"script"),s&&g(u),n)for(o=0;a=u[o++];)Ie.test(a.type||"")&&n.push(a);return u=null,y}function v(){return!0}function x(){return!1}function b(){try{return re.activeElement}catch(e){}}function w(e,t,n,r,i,o){var a,s;if("object"==typeof t){"string"!=typeof n&&(r=r||n,n=void 0);for(s in t)w(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),i===!1)i=x;else if(!i)return e;return 1===o&&(a=i,i=function(e){return pe().off(e),a.apply(this,arguments)},i.guid=a.guid||(a.guid=pe.guid++)),e.each(function(){pe.event.add(this,t,i,r,n)})}function T(e,t){return pe.nodeName(e,"table")&&pe.nodeName(11!==t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function C(e){return e.type=(null!==pe.find.attr(e,"type"))+"/"+e.type,e}function E(e){var t=it.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function N(e,t){if(1===t.nodeType&&pe.hasData(e)){var n,r,i,o=pe._data(e),a=pe._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;r1&&"string"==typeof p&&!fe.checkClone&&rt.test(p))return e.each(function(i){var o=e.eq(i);g&&(t[0]=p.call(this,i,o.html())),S(o,t,n,r)});if(f&&(l=y(t,e[0].ownerDocument,!1,e,r),i=l.firstChild,1===l.childNodes.length&&(l=i),i||r)){for(s=pe.map(h(l,"script"),C),a=s.length;c")).appendTo(t.documentElement),t=(ut[0].contentWindow||ut[0].contentDocument).document,t.write(),t.close(),n=D(e,t),ut.detach()),lt[e]=n),n}function L(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function H(e){if(e in Et)return e;for(var t=e.charAt(0).toUpperCase()+e.slice(1),n=Ct.length;n--;)if(e=Ct[n]+t,e in Et)return e}function q(e,t){for(var n,r,i,o=[],a=0,s=e.length;a=0&&n=0},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},isPlainObject:function(e){var t;if(!e||"object"!==pe.type(e)||e.nodeType||pe.isWindow(e))return!1;try{if(e.constructor&&!ce.call(e,"constructor")&&!ce.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}if(!fe.ownFirst)for(t in e)return ce.call(e,t);for(t in e);return void 0===t||ce.call(e,t)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?ue[le.call(e)]||"object":typeof e},globalEval:function(t){t&&pe.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(ge,"ms-").replace(me,ye)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t){var r,i=0;if(n(e))for(r=e.length;iT.cacheLength&&delete e[t.shift()],e[n+" "]=r}var t=[];return e}function r(e){return e[P]=!0,e}function i(e){var t=H.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function o(e,t){for(var n=e.split("|"),r=n.length;r--;)T.attrHandle[n[r]]=t}function a(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||V)-(~e.sourceIndex||V);if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function s(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function u(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function l(e){return r(function(t){return t=+t,r(function(n,r){for(var i,o=e([],n.length,t),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function c(e){return e&&"undefined"!=typeof e.getElementsByTagName&&e}function f(){}function d(e){for(var t=0,n=e.length,r="";t1?function(t,n,r){for(var i=e.length;i--;)if(!e[i](t,n,r))return!1;return!0}:e[0]}function g(e,n,r){for(var i=0,o=n.length;i-1&&(r[l]=!(a[l]=f))}}else x=m(x===a?x.splice(h,x.length):x),o?o(null,a,x,u):Q.apply(a,x)})}function v(e){for(var t,n,r,i=e.length,o=T.relative[e[0].type],a=o||T.relative[" "],s=o?1:0,u=p(function(e){return e===t},a,!0),l=p(function(e){return ee(t,e)>-1},a,!0),c=[function(e,n,r){var i=!o&&(r||n!==A)||((t=n).nodeType?u(e,n,r):l(e,n,r));return t=null,i}];s1&&h(c),s>1&&d(e.slice(0,s-1).concat({value:" "===e[s-2].type?"*":""})).replace(se,"$1"),n,s0,o=e.length>0,a=function(r,a,s,u,l){var c,f,d,p=0,h="0",g=r&&[],y=[],v=A,x=r||o&&T.find.TAG("*",l),b=W+=null==v?1:Math.random()||.1,w=x.length;for(l&&(A=a===H||a||l);h!==w&&null!=(c=x[h]);h++){if(o&&c){for(f=0,a||c.ownerDocument===H||(L(c),s=!_);d=e[f++];)if(d(c,a||H,s)){u.push(c);break}l&&(W=b)}i&&((c=!d&&c)&&p--,r&&g.push(c))}if(p+=h,i&&h!==p){for(f=0;d=n[f++];)d(g,y,a,s);if(r){if(p>0)for(;h--;)g[h]||y[h]||(y[h]=G.call(u));y=m(y)}Q.apply(u,y),l&&!r&&y.length>0&&p+n.length>1&&t.uniqueSort(u)}return l&&(W=b,A=v),g};return i?r(a):a}var b,w,T,C,E,N,k,S,A,D,j,L,H,q,_,F,M,O,R,P="sizzle"+1*new Date,B=e.document,W=0,I=0,$=n(),z=n(),X=n(),U=function(e,t){return e===t&&(j=!0),0},V=1<<31,Y={}.hasOwnProperty,J=[],G=J.pop,K=J.push,Q=J.push,Z=J.slice,ee=function(e,t){for(var n=0,r=e.length;n+~]|"+ne+")"+ne+"*"),ce=new RegExp("="+ne+"*([^\\]'\"]*?)"+ne+"*\\]","g"),fe=new RegExp(oe),de=new RegExp("^"+re+"$"),pe={ID:new RegExp("^#("+re+")"),CLASS:new RegExp("^\\.("+re+")"),TAG:new RegExp("^("+re+"|[*])"),ATTR:new RegExp("^"+ie),PSEUDO:new RegExp("^"+oe),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+ne+"*(even|odd|(([+-]|)(\\d*)n|)"+ne+"*(?:([+-]|)"+ne+"*(\\d+)|))"+ne+"*\\)|)","i"),bool:new RegExp("^(?:"+te+")$","i"),needsContext:new RegExp("^"+ne+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+ne+"*((?:-\\d)?\\d*)"+ne+"*\\)|)(?=[^-]|$)","i")},he=/^(?:input|select|textarea|button)$/i,ge=/^h\d$/i,me=/^[^{]+\{\s*\[native \w/,ye=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,ve=/[+~]/,xe=/'|\\/g,be=new RegExp("\\\\([\\da-f]{1,6}"+ne+"?|("+ne+")|.)","ig"),we=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},Te=function(){L()};try{Q.apply(J=Z.call(B.childNodes),B.childNodes),J[B.childNodes.length].nodeType}catch(Ce){Q={apply:J.length?function(e,t){K.apply(e,Z.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}w=t.support={},E=t.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return!!t&&"HTML"!==t.nodeName},L=t.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:B;return r!==H&&9===r.nodeType&&r.documentElement?(H=r,q=H.documentElement,_=!E(H),(n=H.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener("unload",Te,!1):n.attachEvent&&n.attachEvent("onunload",Te)),w.attributes=i(function(e){return e.className="i",!e.getAttribute("className")}),w.getElementsByTagName=i(function(e){return e.appendChild(H.createComment("")),!e.getElementsByTagName("*").length}),w.getElementsByClassName=me.test(H.getElementsByClassName),w.getById=i(function(e){return q.appendChild(e).id=P,!H.getElementsByName||!H.getElementsByName(P).length}),w.getById?(T.find.ID=function(e,t){if("undefined"!=typeof t.getElementById&&_){var n=t.getElementById(e);return n?[n]:[]}},T.filter.ID=function(e){var t=e.replace(be,we);return function(e){return e.getAttribute("id")===t}}):(delete T.find.ID,T.filter.ID=function(e){var t=e.replace(be,we);return function(e){var n="undefined"!=typeof e.getAttributeNode&&e.getAttributeNode("id");return n&&n.value===t}}),T.find.TAG=w.getElementsByTagName?function(e,t){return"undefined"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):w.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},T.find.CLASS=w.getElementsByClassName&&function(e,t){if("undefined"!=typeof t.getElementsByClassName&&_)return t.getElementsByClassName(e)},M=[],F=[],(w.qsa=me.test(H.querySelectorAll))&&(i(function(e){q.appendChild(e).innerHTML="",e.querySelectorAll("[msallowcapture^='']").length&&F.push("[*^$]="+ne+"*(?:''|\"\")"),e.querySelectorAll("[selected]").length||F.push("\\["+ne+"*(?:value|"+te+")"),e.querySelectorAll("[id~="+P+"-]").length||F.push("~="),e.querySelectorAll(":checked").length||F.push(":checked"),e.querySelectorAll("a#"+P+"+*").length||F.push(".#.+[+~]")}),i(function(e){var t=H.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("name","D"),e.querySelectorAll("[name=d]").length&&F.push("name"+ne+"*[*^$|!~]?="),e.querySelectorAll(":enabled").length||F.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),F.push(",.*:")})),(w.matchesSelector=me.test(O=q.matches||q.webkitMatchesSelector||q.mozMatchesSelector||q.oMatchesSelector||q.msMatchesSelector))&&i(function(e){w.disconnectedMatch=O.call(e,"div"),O.call(e,"[s!='']:x"),M.push("!=",oe)}),F=F.length&&new RegExp(F.join("|")),M=M.length&&new RegExp(M.join("|")),t=me.test(q.compareDocumentPosition),R=t||me.test(q.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},U=t?function(e,t){if(e===t)return j=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n?n:(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1,1&n||!w.sortDetached&&t.compareDocumentPosition(e)===n?e===H||e.ownerDocument===B&&R(B,e)?-1:t===H||t.ownerDocument===B&&R(B,t)?1:D?ee(D,e)-ee(D,t):0:4&n?-1:1)}:function(e,t){if(e===t)return j=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,s=[e],u=[t];if(!i||!o)return e===H?-1:t===H?1:i?-1:o?1:D?ee(D,e)-ee(D,t):0;if(i===o)return a(e,t);for(n=e;n=n.parentNode;)s.unshift(n);for(n=t;n=n.parentNode;)u.unshift(n);for(;s[r]===u[r];)r++;return r?a(s[r],u[r]):s[r]===B?-1:u[r]===B?1:0},H):H},t.matches=function(e,n){return t(e,null,null,n)},t.matchesSelector=function(e,n){if((e.ownerDocument||e)!==H&&L(e),n=n.replace(ce,"='$1']"),w.matchesSelector&&_&&!X[n+" "]&&(!M||!M.test(n))&&(!F||!F.test(n)))try{var r=O.call(e,n);if(r||w.disconnectedMatch||e.document&&11!==e.document.nodeType)return r}catch(i){}return t(n,H,null,[e]).length>0},t.contains=function(e,t){return(e.ownerDocument||e)!==H&&L(e),R(e,t)},t.attr=function(e,t){(e.ownerDocument||e)!==H&&L(e);var n=T.attrHandle[t.toLowerCase()],r=n&&Y.call(T.attrHandle,t.toLowerCase())?n(e,t,!_):void 0;return void 0!==r?r:w.attributes||!_?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},t.error=function(e){throw new Error("Syntax error, unrecognized expression: "+e)},t.uniqueSort=function(e){var t,n=[],r=0,i=0;if(j=!w.detectDuplicates,D=!w.sortStable&&e.slice(0),e.sort(U),j){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return D=null,e},C=t.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=C(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=C(t);return n},T=t.selectors={cacheLength:50,createPseudo:r,match:pe,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(be,we),e[3]=(e[3]||e[4]||e[5]||"").replace(be,we),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||t.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&t.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return pe.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||"":n&&fe.test(n)&&(t=N(n,!0))&&(t=n.indexOf(")",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(be,we).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=$[e+" "];return t||(t=new RegExp("(^|"+ne+")"+e+"("+ne+"|$)"))&&$(e,function(e){return t.test("string"==typeof e.className&&e.className||"undefined"!=typeof e.getAttribute&&e.getAttribute("class")||"")})},ATTR:function(e,n,r){return function(i){var o=t.attr(i,e);return null==o?"!="===n:!n||(o+="","="===n?o===r:"!="===n?o!==r:"^="===n?r&&0===o.indexOf(r):"*="===n?r&&o.indexOf(r)>-1:"$="===n?r&&o.slice(-r.length)===r:"~="===n?(" "+o.replace(ae," ")+" ").indexOf(r)>-1:"|="===n&&(o===r||o.slice(0,r.length+1)===r+"-"))}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,u){var l,c,f,d,p,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!u&&!s,x=!1;if(m){if(o){for(;g;){for(d=t;d=d[g];)if(s?d.nodeName.toLowerCase()===y:1===d.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){for(d=m,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}), l=c[e]||[],p=l[0]===W&&l[1],x=p&&l[2],d=p&&m.childNodes[p];d=++p&&d&&d[g]||(x=p=0)||h.pop();)if(1===d.nodeType&&++x&&d===t){c[e]=[W,p,x];break}}else if(v&&(d=t,f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),l=c[e]||[],p=l[0]===W&&l[1],x=p),x===!1)for(;(d=++p&&d&&d[g]||(x=p=0)||h.pop())&&((s?d.nodeName.toLowerCase()!==y:1!==d.nodeType)||!++x||(v&&(f=d[P]||(d[P]={}),c=f[d.uniqueID]||(f[d.uniqueID]={}),c[e]=[W,x]),d!==t)););return x-=i,x===r||x%r===0&&x/r>=0}}},PSEUDO:function(e,n){var i,o=T.pseudos[e]||T.setFilters[e.toLowerCase()]||t.error("unsupported pseudo: "+e);return o[P]?o(n):o.length>1?(i=[e,e,"",n],T.setFilters.hasOwnProperty(e.toLowerCase())?r(function(e,t){for(var r,i=o(e,n),a=i.length;a--;)r=ee(e,i[a]),e[r]=!(t[r]=i[a])}):function(e){return o(e,0,i)}):o}},pseudos:{not:r(function(e){var t=[],n=[],i=k(e.replace(se,"$1"));return i[P]?r(function(e,t,n,r){for(var o,a=i(e,null,r,[]),s=e.length;s--;)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,r,o){return t[0]=e,i(t,null,o,n),t[0]=null,!n.pop()}}),has:r(function(e){return function(n){return t(e,n).length>0}}),contains:r(function(e){return e=e.replace(be,we),function(t){return(t.textContent||t.innerText||C(t)).indexOf(e)>-1}}),lang:r(function(e){return de.test(e||"")||t.error("unsupported lang: "+e),e=e.replace(be,we).toLowerCase(),function(t){var n;do if(n=_?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===q},focus:function(e){return e===H.activeElement&&(!H.hasFocus||H.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!T.pseudos.empty(e)},header:function(e){return ge.test(e.nodeName)},input:function(e){return he.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||"text"===t.toLowerCase())},first:l(function(){return[0]}),last:l(function(e,t){return[t-1]}),eq:l(function(e,t,n){return[n<0?n+t:n]}),even:l(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:l(function(e,t,n){for(var r=n<0?n+t:n;++r2&&"ID"===(a=o[0]).type&&w.getById&&9===t.nodeType&&_&&T.relative[o[1].type]){if(t=(T.find.ID(a.matches[0].replace(be,we),t)||[])[0],!t)return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=pe.needsContext.test(e)?0:o.length;i--&&(a=o[i],!T.relative[s=a.type]);)if((u=T.find[s])&&(r=u(a.matches[0].replace(be,we),ve.test(o[0].type)&&c(t.parentNode)||t))){if(o.splice(i,1),e=r.length&&d(o),!e)return Q.apply(n,r),n;break}}return(l||k(e,f))(r,t,!_,n,!t||ve.test(e)&&c(t.parentNode)||t),n},w.sortStable=P.split("").sort(U).join("")===P,w.detectDuplicates=!!j,L(),w.sortDetached=i(function(e){return 1&e.compareDocumentPosition(H.createElement("div"))}),i(function(e){return e.innerHTML="","#"===e.firstChild.getAttribute("href")})||o("type|href|height|width",function(e,t,n){if(!n)return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}),w.attributes&&i(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")})||o("value",function(e,t,n){if(!n&&"input"===e.nodeName.toLowerCase())return e.defaultValue}),i(function(e){return null==e.getAttribute("disabled")})||o(te,function(e,t,n){var r;if(!n)return e[t]===!0?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),t}(e);pe.find=ve,pe.expr=ve.selectors,pe.expr[":"]=pe.expr.pseudos,pe.uniqueSort=pe.unique=ve.uniqueSort,pe.text=ve.getText,pe.isXMLDoc=ve.isXML,pe.contains=ve.contains;var xe=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&pe(e).is(n))break;r.push(e)}return r},be=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},we=pe.expr.match.needsContext,Te=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Ce=/^.[^:#\[\.,]*$/;pe.filter=function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?pe.find.matchesSelector(r,e)?[r]:[]:pe.find.matches(e,pe.grep(t,function(e){return 1===e.nodeType}))},pe.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(pe(e).filter(function(){for(t=0;t1?pe.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},filter:function(e){return this.pushStack(r(this,e||[],!1))},not:function(e){return this.pushStack(r(this,e||[],!0))},is:function(e){return!!r(this,"string"==typeof e&&we.test(e)?pe(e):e||[],!1).length}});var Ee,Ne=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,ke=pe.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||Ee,"string"==typeof e){if(r="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:Ne.exec(e),!r||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof pe?t[0]:t,pe.merge(this,pe.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:re,!0)),Te.test(r[1])&&pe.isPlainObject(t))for(r in t)pe.isFunction(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}if(i=re.getElementById(r[2]),i&&i.parentNode){if(i.id!==r[2])return Ee.find(e);this.length=1,this[0]=i}return this.context=re,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):pe.isFunction(e)?"undefined"!=typeof n.ready?n.ready(e):e(pe):(void 0!==e.selector&&(this.selector=e.selector,this.context=e.context),pe.makeArray(e,this))};ke.prototype=pe.fn,Ee=pe(re);var Se=/^(?:parents|prev(?:Until|All))/,Ae={children:!0,contents:!0,next:!0,prev:!0};pe.fn.extend({has:function(e){var t,n=pe(e,this),r=n.length;return this.filter(function(){for(t=0;t-1:1===n.nodeType&&pe.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(o.length>1?pe.uniqueSort(o):o)},index:function(e){return e?"string"==typeof e?pe.inArray(this[0],pe(e)):pe.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(pe.uniqueSort(pe.merge(this.get(),pe(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),pe.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return xe(e,"parentNode")},parentsUntil:function(e,t,n){return xe(e,"parentNode",n)},next:function(e){return i(e,"nextSibling")},prev:function(e){return i(e,"previousSibling")},nextAll:function(e){return xe(e,"nextSibling")},prevAll:function(e){return xe(e,"previousSibling")},nextUntil:function(e,t,n){return xe(e,"nextSibling",n)},prevUntil:function(e,t,n){return xe(e,"previousSibling",n)},siblings:function(e){return be((e.parentNode||{}).firstChild,e)},children:function(e){return be(e.firstChild)},contents:function(e){return pe.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:pe.merge([],e.childNodes)}},function(e,t){pe.fn[e]=function(n,r){var i=pe.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=pe.filter(r,i)),this.length>1&&(Ae[e]||(i=pe.uniqueSort(i)),Se.test(e)&&(i=i.reverse())),this.pushStack(i)}});var De=/\S+/g;pe.Callbacks=function(e){e="string"==typeof e?o(e):pe.extend({},e);var t,n,r,i,a=[],s=[],u=-1,l=function(){for(i=e.once,r=t=!0;s.length;u=-1)for(n=s.shift();++u-1;)a.splice(n,1),n<=u&&u--}),this},has:function(e){return e?pe.inArray(e,a)>-1:a.length>0},empty:function(){return a&&(a=[]),this},disable:function(){return i=s=[],a=n="",this},disabled:function(){return!a},lock:function(){return i=!0,n||c.disable(),this},locked:function(){return!!i},fireWith:function(e,n){return i||(n=n||[],n=[e,n.slice?n.slice():n],s.push(n),t||l()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},pe.extend({Deferred:function(e){var t=[["resolve","done",pe.Callbacks("once memory"),"resolved"],["reject","fail",pe.Callbacks("once memory"),"rejected"],["notify","progress",pe.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return pe.Deferred(function(n){pe.each(t,function(t,o){var a=pe.isFunction(e[t])&&e[t];i[o[1]](function(){var e=a&&a.apply(this,arguments);e&&pe.isFunction(e.promise)?e.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[o[0]+"With"](this===r?n.promise():this,a?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?pe.extend(e,r):r}},i={};return r.pipe=r.then,pe.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t,n,r,i=0,o=ie.call(arguments),a=o.length,s=1!==a||e&&pe.isFunction(e.promise)?a:0,u=1===s?e:pe.Deferred(),l=function(e,n,r){return function(i){n[e]=this,r[e]=arguments.length>1?ie.call(arguments):i,r===t?u.notifyWith(n,r):--s||u.resolveWith(n,r)}};if(a>1)for(t=new Array(a),n=new Array(a),r=new Array(a);i0||(je.resolveWith(re,[pe]),pe.fn.triggerHandler&&(pe(re).triggerHandler("ready"),pe(re).off("ready"))))}}),pe.ready.promise=function(t){if(!je)if(je=pe.Deferred(),"complete"===re.readyState||"loading"!==re.readyState&&!re.documentElement.doScroll)e.setTimeout(pe.ready);else if(re.addEventListener)re.addEventListener("DOMContentLoaded",s),e.addEventListener("load",s);else{re.attachEvent("onreadystatechange",s),e.attachEvent("onload",s);var n=!1;try{n=null==e.frameElement&&re.documentElement}catch(r){}n&&n.doScroll&&!function i(){if(!pe.isReady){try{n.doScroll("left")}catch(t){return e.setTimeout(i,50)}a(),pe.ready()}}()}return je.promise(t)},pe.ready.promise();var Le;for(Le in pe(fe))break;fe.ownFirst="0"===Le,fe.inlineBlockNeedsLayout=!1,pe(function(){var e,t,n,r;n=re.getElementsByTagName("body")[0],n&&n.style&&(t=re.createElement("div"),r=re.createElement("div"),r.style.cssText="position:absolute;border:0;width:0;height:0;top:0;left:-9999px",n.appendChild(r).appendChild(t),"undefined"!=typeof t.style.zoom&&(t.style.cssText="display:inline;margin:0;border:0;padding:1px;width:1px;zoom:1",fe.inlineBlockNeedsLayout=e=3===t.offsetWidth,e&&(n.style.zoom=1)),n.removeChild(r))}),function(){var e=re.createElement("div");fe.deleteExpando=!0;try{delete e.test}catch(t){fe.deleteExpando=!1}e=null}();var He=function(e){var t=pe.noData[(e.nodeName+" ").toLowerCase()],n=+e.nodeType||1;return(1===n||9===n)&&(!t||t!==!0&&e.getAttribute("classid")===t)},qe=/^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,_e=/([A-Z])/g;pe.extend({cache:{},noData:{"applet ":!0,"embed ":!0,"object ":"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?pe.cache[e[pe.expando]]:e[pe.expando],!!e&&!l(e)},data:function(e,t,n){return c(e,t,n)},removeData:function(e,t){return f(e,t)},_data:function(e,t,n){return c(e,t,n,!0)},_removeData:function(e,t){return f(e,t,!0)}}),pe.fn.extend({data:function(e,t){var n,r,i,o=this[0],a=o&&o.attributes;if(void 0===e){if(this.length&&(i=pe.data(o),1===o.nodeType&&!pe._data(o,"parsedAttrs"))){for(n=a.length;n--;)a[n]&&(r=a[n].name,0===r.indexOf("data-")&&(r=pe.camelCase(r.slice(5)),u(o,r,i[r])));pe._data(o,"parsedAttrs",!0)}return i}return"object"==typeof e?this.each(function(){pe.data(this,e)}):arguments.length>1?this.each(function(){pe.data(this,e,t)}):o?u(o,e,pe.data(o,e)):void 0},removeData:function(e){return this.each(function(){pe.removeData(this,e)})}}),pe.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=pe._data(e,t),n&&(!r||pe.isArray(n)?r=pe._data(e,t,pe.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=pe.queue(e,t),r=n.length,i=n.shift(),o=pe._queueHooks(e,t),a=function(){pe.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return pe._data(e,n)||pe._data(e,n,{empty:pe.Callbacks("once memory").add(function(){pe._removeData(e,t+"queue"),pe._removeData(e,n)})})}}),pe.fn.extend({queue:function(e,t){var n=2;return"string"!=typeof e&&(t=e,e="fx",n--),arguments.length
    a",fe.leadingWhitespace=3===e.firstChild.nodeType,fe.tbody=!e.getElementsByTagName("tbody").length,fe.htmlSerialize=!!e.getElementsByTagName("link").length,fe.html5Clone="<:nav>"!==re.createElement("nav").cloneNode(!0).outerHTML,n.type="checkbox",n.checked=!0,t.appendChild(n),fe.appendChecked=n.checked,e.innerHTML="",fe.noCloneChecked=!!e.cloneNode(!0).lastChild.defaultValue,t.appendChild(e),n=re.createElement("input"),n.setAttribute("type","radio"),n.setAttribute("checked","checked"),n.setAttribute("name","t"),e.appendChild(n),fe.checkClone=e.cloneNode(!0).cloneNode(!0).lastChild.checked,fe.noCloneEvent=!!e.addEventListener,e[pe.expando]=1,fe.attributes=!e.getAttribute(pe.expando)}();var Xe={option:[1,""],legend:[1,"
    ","
    "],area:[1,"",""],param:[1,"",""],thead:[1,"","
    "],tr:[2,"","
    "],col:[2,"","
    "],td:[3,"","
    "],_default:fe.htmlSerialize?[0,"",""]:[1,"X
    ","
    "]};Xe.optgroup=Xe.option,Xe.tbody=Xe.tfoot=Xe.colgroup=Xe.caption=Xe.thead,Xe.th=Xe.td;var Ue=/<|&#?\w+;/,Ve=/-1&&(h=p.split("."),p=h.shift(),h.sort()),a=p.indexOf(":")<0&&"on"+p,t=t[pe.expando]?t:new pe.Event(p,"object"==typeof t&&t),t.isTrigger=i?2:3,t.namespace=h.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),n=null==n?[t]:pe.makeArray(n,[t]),l=pe.event.special[p]||{},i||!l.trigger||l.trigger.apply(r,n)!==!1)){if(!i&&!l.noBubble&&!pe.isWindow(r)){for(u=l.delegateType||p,Ke.test(u+p)||(s=s.parentNode);s;s=s.parentNode)d.push(s),c=s;c===(r.ownerDocument||re)&&d.push(c.defaultView||c.parentWindow||e)}for(f=0;(s=d[f++])&&!t.isPropagationStopped();)t.type=f>1?u:l.bindType||p,o=(pe._data(s,"events")||{})[t.type]&&pe._data(s,"handle"),o&&o.apply(s,n),o=a&&s[a],o&&o.apply&&He(s)&&(t.result=o.apply(s,n),t.result===!1&&t.preventDefault());if(t.type=p,!i&&!t.isDefaultPrevented()&&(!l._default||l._default.apply(d.pop(),n)===!1)&&He(r)&&a&&r[p]&&!pe.isWindow(r)){c=r[a],c&&(r[a]=null),pe.event.triggered=p;try{r[p]()}catch(g){}pe.event.triggered=void 0,c&&(r[a]=c)}return t.result}},dispatch:function(e){e=pe.event.fix(e);var t,n,r,i,o,a=[],s=ie.call(arguments),u=(pe._data(this,"events")||{})[e.type]||[],l=pe.event.special[e.type]||{};if(s[0]=e,e.delegateTarget=this,!l.preDispatch||l.preDispatch.call(this,e)!==!1){for(a=pe.event.handlers.call(this,e,u),t=0;(i=a[t++])&&!e.isPropagationStopped();)for(e.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!e.isImmediatePropagationStopped();)e.rnamespace&&!e.rnamespace.test(o.namespace)||(e.handleObj=o,e.data=o.data,r=((pe.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,s),void 0!==r&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()));return l.postDispatch&&l.postDispatch.call(this,e),e.result}},handlers:function(e,t){var n,r,i,o,a=[],s=t.delegateCount,u=e.target;if(s&&u.nodeType&&("click"!==e.type||isNaN(e.button)||e.button<1))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(r=[],n=0;n-1:pe.find(i,this,null,[u]).length),r[i]&&r.push(o);r.length&&a.push({elem:u,handlers:r})}return s]","i"),tt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,nt=/\s*$/g,at=p(re),st=at.appendChild(re.createElement("div"));pe.extend({htmlPrefilter:function(e){return e.replace(tt,"<$1>")},clone:function(e,t,n){var r,i,o,a,s,u=pe.contains(e.ownerDocument,e);if(fe.html5Clone||pe.isXMLDoc(e)||!et.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(st.innerHTML=e.outerHTML,st.removeChild(o=st.firstChild)),!(fe.noCloneEvent&&fe.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||pe.isXMLDoc(e)))for(r=h(o),s=h(e),a=0;null!=(i=s[a]);++a)r[a]&&k(i,r[a]);if(t)if(n)for(s=s||h(e),r=r||h(o),a=0;null!=(i=s[a]);a++)N(i,r[a]);else N(e,o);return r=h(o,"script"),r.length>0&&g(r,!u&&h(e,"script")),r=s=i=null,o},cleanData:function(e,t){for(var n,r,i,o,a=0,s=pe.expando,u=pe.cache,l=fe.attributes,c=pe.event.special;null!=(n=e[a]);a++)if((t||He(n))&&(i=n[s],o=i&&u[i])){if(o.events)for(r in o.events)c[r]?pe.event.remove(n,r):pe.removeEvent(n,r,o.handle);u[i]&&(delete u[i],l||"undefined"==typeof n.removeAttribute?n[s]=void 0:n.removeAttribute(s),ne.push(i))}}}),pe.fn.extend({domManip:S,detach:function(e){return A(this,e,!0)},remove:function(e){return A(this,e)},text:function(e){return Pe(this,function(e){return void 0===e?pe.text(this):this.empty().append((this[0]&&this[0].ownerDocument||re).createTextNode(e))},null,e,arguments.length)},append:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.appendChild(e)}})},prepend:function(){return S(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=T(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return S(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++){for(1===e.nodeType&&pe.cleanData(h(e,!1));e.firstChild;)e.removeChild(e.firstChild);e.options&&pe.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return pe.clone(this,e,t)})},html:function(e){return Pe(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e)return 1===t.nodeType?t.innerHTML.replace(Ze,""):void 0;if("string"==typeof e&&!nt.test(e)&&(fe.htmlSerialize||!et.test(e))&&(fe.leadingWhitespace||!$e.test(e))&&!Xe[(We.exec(e)||["",""])[1].toLowerCase()]){e=pe.htmlPrefilter(e);try{for(;nt",t=l.getElementsByTagName("td"),t[0].style.cssText="margin:0;border:0;padding:0;display:none",o=0===t[0].offsetHeight,o&&(t[0].style.display="",t[1].style.display="none",o=0===t[0].offsetHeight)),f.removeChild(u)}var n,r,i,o,a,s,u=re.createElement("div"),l=re.createElement("div");l.style&&(l.style.cssText="float:left;opacity:.5",fe.opacity="0.5"===l.style.opacity,fe.cssFloat=!!l.style.cssFloat,l.style.backgroundClip="content-box",l.cloneNode(!0).style.backgroundClip="",fe.clearCloneStyle="content-box"===l.style.backgroundClip,u=re.createElement("div"),u.style.cssText="border:0;width:8px;height:0;top:0;left:-9999px;padding:0;margin-top:1px;position:absolute",l.innerHTML="",u.appendChild(l),fe.boxSizing=""===l.style.boxSizing||""===l.style.MozBoxSizing||""===l.style.WebkitBoxSizing,pe.extend(fe,{reliableHiddenOffsets:function(){return null==n&&t(),o},boxSizingReliable:function(){return null==n&&t(),i},pixelMarginRight:function(){return null==n&&t(),r},pixelPosition:function(){return null==n&&t(),n},reliableMarginRight:function(){return null==n&&t(),a},reliableMarginLeft:function(){return null==n&&t(),s}}))}();var ht,gt,mt=/^(top|right|bottom|left)$/;e.getComputedStyle?(ht=function(t){var n=t.ownerDocument.defaultView;return n&&n.opener||(n=e),n.getComputedStyle(t)},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n.getPropertyValue(t)||n[t]:void 0,""!==a&&void 0!==a||pe.contains(e.ownerDocument,e)||(a=pe.style(e,t)),n&&!fe.pixelMarginRight()&&ft.test(a)&&ct.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o),void 0===a?a:a+""}):pt.currentStyle&&(ht=function(e){return e.currentStyle},gt=function(e,t,n){var r,i,o,a,s=e.style;return n=n||ht(e),a=n?n[t]:void 0,null==a&&s&&s[t]&&(a=s[t]),ft.test(a)&&!mt.test(t)&&(r=s.left,i=e.runtimeStyle,o=i&&i.left,o&&(i.left=e.currentStyle.left),s.left="fontSize"===t?"1em":a,a=s.pixelLeft+"px",s.left=r,o&&(i.left=o)),void 0===a?a:a+""||"auto"});var yt=/alpha\([^)]*\)/i,vt=/opacity\s*=\s*([^)]*)/i,xt=/^(none|table(?!-c[ea]).+)/,bt=new RegExp("^("+Fe+")(.*)$","i"),wt={position:"absolute",visibility:"hidden",display:"block"},Tt={letterSpacing:"0",fontWeight:"400"},Ct=["Webkit","O","Moz","ms"],Et=re.createElement("div").style;pe.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=gt(e,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":fe.cssFloat?"cssFloat":"styleFloat"},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=pe.camelCase(t),u=e.style;if(t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(e,!1,r))?i:u[t];if(o=typeof n,"string"===o&&(i=Me.exec(n))&&i[1]&&(n=d(e,t,i),o="number"),null!=n&&n===n&&("number"===o&&(n+=i&&i[3]||(pe.cssNumber[s]?"":"px")),fe.clearCloneStyle||""!==n||0!==t.indexOf("background")||(u[t]="inherit"),!(a&&"set"in a&&void 0===(n=a.set(e,n,r)))))try{u[t]=n}catch(l){}}},css:function(e,t,n,r){var i,o,a,s=pe.camelCase(t);return t=pe.cssProps[s]||(pe.cssProps[s]=H(s)||s),a=pe.cssHooks[t]||pe.cssHooks[s],a&&"get"in a&&(o=a.get(e,!0,n)),void 0===o&&(o=gt(e,t,r)),"normal"===o&&t in Tt&&(o=Tt[t]),""===n||n?(i=parseFloat(o),n===!0||isFinite(i)?i||0:o):o}}),pe.each(["height","width"],function(e,t){pe.cssHooks[t]={get:function(e,n,r){if(n)return xt.test(pe.css(e,"display"))&&0===e.offsetWidth?dt(e,wt,function(){return M(e,t,r)}):M(e,t,r)},set:function(e,n,r){var i=r&&ht(e);return _(e,n,r?F(e,t,r,fe.boxSizing&&"border-box"===pe.css(e,"boxSizing",!1,i),i):0)}}}),fe.opacity||(pe.cssHooks.opacity={get:function(e,t){return vt.test((t&&e.currentStyle?e.currentStyle.filter:e.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":t?"1":""},set:function(e,t){var n=e.style,r=e.currentStyle,i=pe.isNumeric(t)?"alpha(opacity="+100*t+")":"",o=r&&r.filter||n.filter||"";n.zoom=1,(t>=1||""===t)&&""===pe.trim(o.replace(yt,""))&&n.removeAttribute&&(n.removeAttribute("filter"),""===t||r&&!r.filter)||(n.filter=yt.test(o)?o.replace(yt,i):o+" "+i)}}),pe.cssHooks.marginRight=L(fe.reliableMarginRight,function(e,t){if(t)return dt(e,{display:"inline-block"},gt,[e,"marginRight"])}),pe.cssHooks.marginLeft=L(fe.reliableMarginLeft,function(e,t){if(t)return(parseFloat(gt(e,"marginLeft"))||(pe.contains(e.ownerDocument,e)?e.getBoundingClientRect().left-dt(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}):0))+"px"}),pe.each({margin:"",padding:"",border:"Width"},function(e,t){pe.cssHooks[e+t]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[e+Oe[r]+t]=o[r]||o[r-2]||o[0];return i}},ct.test(e)||(pe.cssHooks[e+t].set=_)}),pe.fn.extend({css:function(e,t){return Pe(this,function(e,t,n){var r,i,o={},a=0;if(pe.isArray(t)){for(r=ht(e),i=t.length;a1)},show:function(){return q(this,!0)},hide:function(){return q(this)},toggle:function(e){return"boolean"==typeof e?e?this.show():this.hide():this.each(function(){Re(this)?pe(this).show():pe(this).hide()})}}),pe.Tween=O,O.prototype={constructor:O,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||pe.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(pe.cssNumber[n]?"":"px")},cur:function(){var e=O.propHooks[this.prop];return e&&e.get?e.get(this):O.propHooks._default.get(this)},run:function(e){var t,n=O.propHooks[this.prop];return this.options.duration?this.pos=t=pe.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):O.propHooks._default.set(this),this}},O.prototype.init.prototype=O.prototype,O.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=pe.css(e.elem,e.prop,""),t&&"auto"!==t?t:0)},set:function(e){pe.fx.step[e.prop]?pe.fx.step[e.prop](e):1!==e.elem.nodeType||null==e.elem.style[pe.cssProps[e.prop]]&&!pe.cssHooks[e.prop]?e.elem[e.prop]=e.now:pe.style(e.elem,e.prop,e.now+e.unit)}}},O.propHooks.scrollTop=O.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},pe.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:"swing"},pe.fx=O.prototype.init,pe.fx.step={};var Nt,kt,St=/^(?:toggle|show|hide)$/,At=/queueHooks$/;pe.Animation=pe.extend($,{tweeners:{"*":[function(e,t){var n=this.createTween(e,t);return d(n.elem,e,Me.exec(t),n),n}]},tweener:function(e,t){pe.isFunction(e)?(t=e,e=["*"]):e=e.match(De);for(var n,r=0,i=e.length;r
    a",e=n.getElementsByTagName("a")[0],t.setAttribute("type","checkbox"),n.appendChild(t),e=n.getElementsByTagName("a")[0],e.style.cssText="top:1px",fe.getSetAttribute="t"!==n.className,fe.style=/top/.test(e.getAttribute("style")),fe.hrefNormalized="/a"===e.getAttribute("href"),fe.checkOn=!!t.value,fe.optSelected=i.selected,fe.enctype=!!re.createElement("form").enctype,r.disabled=!0,fe.optDisabled=!i.disabled,t=re.createElement("input"),t.setAttribute("value",""),fe.input=""===t.getAttribute("value"),t.value="t",t.setAttribute("type","radio"),fe.radioValue="t"===t.value}();var Dt=/\r/g,jt=/[\x20\t\r\n\f]+/g;pe.fn.extend({val:function(e){var t,n,r,i=this[0];{if(arguments.length)return r=pe.isFunction(e),this.each(function(n){var i;1===this.nodeType&&(i=r?e.call(this,n,pe(this).val()):e,null==i?i="":"number"==typeof i?i+="":pe.isArray(i)&&(i=pe.map(i,function(e){return null==e?"":e+""})),t=pe.valHooks[this.type]||pe.valHooks[this.nodeName.toLowerCase()],t&&"set"in t&&void 0!==t.set(this,i,"value")||(this.value=i))});if(i)return t=pe.valHooks[i.type]||pe.valHooks[i.nodeName.toLowerCase()],t&&"get"in t&&void 0!==(n=t.get(i,"value"))?n:(n=i.value,"string"==typeof n?n.replace(Dt,""):null==n?"":n)}}}),pe.extend({valHooks:{option:{get:function(e){var t=pe.find.attr(e,"value");return null!=t?t:pe.trim(pe.text(e)).replace(jt," ")}},select:{get:function(e){for(var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||i<0,a=o?null:[],s=o?i+1:r.length,u=i<0?s:o?i:0;u-1)try{r.selected=n=!0}catch(s){r.scrollHeight}else r.selected=!1;return n||(e.selectedIndex=-1),i}}}}),pe.each(["radio","checkbox"],function(){pe.valHooks[this]={set:function(e,t){if(pe.isArray(t))return e.checked=pe.inArray(pe(e).val(),t)>-1}},fe.checkOn||(pe.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Lt,Ht,qt=pe.expr.attrHandle,_t=/^(?:checked|selected)$/i,Ft=fe.getSetAttribute,Mt=fe.input;pe.fn.extend({attr:function(e,t){return Pe(this,pe.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){pe.removeAttr(this,e)})}}),pe.extend({attr:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return"undefined"==typeof e.getAttribute?pe.prop(e,t,n):(1===o&&pe.isXMLDoc(e)||(t=t.toLowerCase(),i=pe.attrHooks[t]||(pe.expr.match.bool.test(t)?Ht:Lt)),void 0!==n?null===n?void pe.removeAttr(e,t):i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+""),n):i&&"get"in i&&null!==(r=i.get(e,t))?r:(r=pe.find.attr(e,t),null==r?void 0:r))},attrHooks:{type:{set:function(e,t){if(!fe.radioValue&&"radio"===t&&pe.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(De);if(o&&1===e.nodeType)for(;n=o[i++];)r=pe.propFix[n]||n,pe.expr.match.bool.test(n)?Mt&&Ft||!_t.test(n)?e[r]=!1:e[pe.camelCase("default-"+n)]=e[r]=!1:pe.attr(e,n,""),e.removeAttribute(Ft?n:r)}}),Ht={set:function(e,t,n){return t===!1?pe.removeAttr(e,n):Mt&&Ft||!_t.test(n)?e.setAttribute(!Ft&&pe.propFix[n]||n,n):e[pe.camelCase("default-"+n)]=e[n]=!0,n}},pe.each(pe.expr.match.bool.source.match(/\w+/g),function(e,t){var n=qt[t]||pe.find.attr;Mt&&Ft||!_t.test(t)?qt[t]=function(e,t,r){var i,o;return r||(o=qt[t],qt[t]=i,i=null!=n(e,t,r)?t.toLowerCase():null,qt[t]=o),i}:qt[t]=function(e,t,n){if(!n)return e[pe.camelCase("default-"+t)]?t.toLowerCase():null}}),Mt&&Ft||(pe.attrHooks.value={set:function(e,t,n){return pe.nodeName(e,"input")?void(e.defaultValue=t):Lt&&Lt.set(e,t,n)}}),Ft||(Lt={set:function(e,t,n){var r=e.getAttributeNode(n);if(r||e.setAttributeNode(r=e.ownerDocument.createAttribute(n)),r.value=t+="","value"===n||t===e.getAttribute(n))return t}},qt.id=qt.name=qt.coords=function(e,t,n){var r;if(!n)return(r=e.getAttributeNode(t))&&""!==r.value?r.value:null},pe.valHooks.button={get:function(e,t){var n=e.getAttributeNode(t);if(n&&n.specified)return n.value},set:Lt.set},pe.attrHooks.contenteditable={set:function(e,t,n){Lt.set(e,""!==t&&t,n)}},pe.each(["width","height"],function(e,t){pe.attrHooks[t]={set:function(e,n){if(""===n)return e.setAttribute(t,"auto"),n}}})),fe.style||(pe.attrHooks.style={get:function(e){return e.style.cssText||void 0},set:function(e,t){return e.style.cssText=t+""}});var Ot=/^(?:input|select|textarea|button|object)$/i,Rt=/^(?:a|area)$/i;pe.fn.extend({prop:function(e,t){return Pe(this,pe.prop,e,t,arguments.length>1)},removeProp:function(e){return e=pe.propFix[e]||e,this.each(function(){try{this[e]=void 0,delete this[e]}catch(t){}})}}),pe.extend({prop:function(e,t,n){var r,i,o=e.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&pe.isXMLDoc(e)||(t=pe.propFix[t]||t,i=pe.propHooks[t]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&"get"in i&&null!==(r=i.get(e,t))?r:e[t]},propHooks:{tabIndex:{get:function(e){var t=pe.find.attr(e,"tabindex");return t?parseInt(t,10):Ot.test(e.nodeName)||Rt.test(e.nodeName)&&e.href?0:-1}}},propFix:{"for":"htmlFor","class":"className"}}),fe.hrefNormalized||pe.each(["href","src"],function(e,t){pe.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),fe.optSelected||(pe.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),pe.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){pe.propFix[this.toLowerCase()]=this}),fe.enctype||(pe.propFix.enctype="encoding");var Pt=/[\t\r\n\f]/g;pe.fn.extend({addClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).addClass(e.call(this,t,z(this)))});if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},removeClass:function(e){var t,n,r,i,o,a,s,u=0;if(pe.isFunction(e))return this.each(function(t){pe(this).removeClass(e.call(this,t,z(this)))});if(!arguments.length)return this.attr("class","");if("string"==typeof e&&e)for(t=e.match(De)||[];n=this[u++];)if(i=z(n),r=1===n.nodeType&&(" "+i+" ").replace(Pt," ")){for(a=0;o=t[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");s=pe.trim(r),i!==s&&pe.attr(n,"class",s)}return this},toggleClass:function(e,t){var n=typeof e;return"boolean"==typeof t&&"string"===n?t?this.addClass(e):this.removeClass(e):pe.isFunction(e)?this.each(function(n){pe(this).toggleClass(e.call(this,n,z(this),t),t)}):this.each(function(){var t,r,i,o;if("string"===n)for(r=0,i=pe(this),o=e.match(De)||[];t=o[r++];)i.hasClass(t)?i.removeClass(t):i.addClass(t);else void 0!==e&&"boolean"!==n||(t=z(this),t&&pe._data(this,"__className__",t),pe.attr(this,"class",t||e===!1?"":pe._data(this,"__className__")||""))})},hasClass:function(e){var t,n,r=0;for(t=" "+e+" ";n=this[r++];)if(1===n.nodeType&&(" "+z(n)+" ").replace(Pt," ").indexOf(t)>-1)return!0;return!1}}),pe.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(e,t){pe.fn[t]=function(e,n){return arguments.length>0?this.on(t,null,e,n):this.trigger(t)}}),pe.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}});var Bt=e.location,Wt=pe.now(),It=/\?/,$t=/(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;pe.parseJSON=function(t){if(e.JSON&&e.JSON.parse)return e.JSON.parse(t+"");var n,r=null,i=pe.trim(t+"");return i&&!pe.trim(i.replace($t,function(e,t,i,o){return n&&t&&(r=0),0===r?e:(n=i||t,r+=!o-!i,"")}))?Function("return "+i)():pe.error("Invalid JSON: "+t)},pe.parseXML=function(t){var n,r;if(!t||"string"!=typeof t)return null;try{e.DOMParser?(r=new e.DOMParser,n=r.parseFromString(t,"text/xml")):(n=new e.ActiveXObject("Microsoft.XMLDOM"),n.async="false",n.loadXML(t))}catch(i){n=void 0}return n&&n.documentElement&&!n.getElementsByTagName("parsererror").length||pe.error("Invalid XML: "+t),n};var zt=/#.*$/,Xt=/([?&])_=[^&]*/,Ut=/^(.*?):[ \t]*([^\r\n]*)\r?$/gm,Vt=/^(?:about|app|app-storage|.+-extension|file|res|widget):$/,Yt=/^(?:GET|HEAD)$/,Jt=/^\/\//,Gt=/^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,Kt={},Qt={},Zt="*/".concat("*"),en=Bt.href,tn=Gt.exec(en.toLowerCase())||[];pe.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:en,type:"GET",isLocal:Vt.test(tn[1]),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Zt,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":pe.parseJSON,"text xml":pe.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?V(V(e,pe.ajaxSettings),t):V(pe.ajaxSettings,e)},ajaxPrefilter:X(Kt),ajaxTransport:X(Qt),ajax:function(t,n){function r(t,n,r,i){var o,f,v,x,w,C=n;2!==b&&(b=2,u&&e.clearTimeout(u),c=void 0,s=i||"",T.readyState=t>0?4:0,o=t>=200&&t<300||304===t,r&&(x=Y(d,T,r)),x=J(d,x,T,o),o?(d.ifModified&&(w=T.getResponseHeader("Last-Modified"),w&&(pe.lastModified[a]=w),w=T.getResponseHeader("etag"),w&&(pe.etag[a]=w)),204===t||"HEAD"===d.type?C="nocontent":304===t?C="notmodified":(C=x.state,f=x.data,v=x.error,o=!v)):(v=C,!t&&C||(C="error",t<0&&(t=0))),T.status=t,T.statusText=(n||C)+"",o?g.resolveWith(p,[f,C,T]):g.rejectWith(p,[T,C,v]),T.statusCode(y),y=void 0,l&&h.trigger(o?"ajaxSuccess":"ajaxError",[T,d,o?f:v]),m.fireWith(p,[T,C]),l&&(h.trigger("ajaxComplete",[T,d]),--pe.active||pe.event.trigger("ajaxStop")))}"object"==typeof t&&(n=t,t=void 0),n=n||{};var i,o,a,s,u,l,c,f,d=pe.ajaxSetup({},n),p=d.context||d,h=d.context&&(p.nodeType||p.jquery)?pe(p):pe.event,g=pe.Deferred(),m=pe.Callbacks("once memory"),y=d.statusCode||{},v={},x={},b=0,w="canceled",T={readyState:0,getResponseHeader:function(e){var t;if(2===b){if(!f)for(f={};t=Ut.exec(s);)f[t[1].toLowerCase()]=t[2];t=f[e.toLowerCase()]}return null==t?null:t},getAllResponseHeaders:function(){return 2===b?s:null},setRequestHeader:function(e,t){var n=e.toLowerCase();return b||(e=x[n]=x[n]||e,v[e]=t),this},overrideMimeType:function(e){return b||(d.mimeType=e),this},statusCode:function(e){var t;if(e)if(b<2)for(t in e)y[t]=[y[t],e[t]];else T.always(e[T.status]);return this},abort:function(e){var t=e||w;return c&&c.abort(t),r(0,t),this}};if(g.promise(T).complete=m.add,T.success=T.done,T.error=T.fail,d.url=((t||d.url||en)+"").replace(zt,"").replace(Jt,tn[1]+"//"),d.type=n.method||n.type||d.method||d.type,d.dataTypes=pe.trim(d.dataType||"*").toLowerCase().match(De)||[""],null==d.crossDomain&&(i=Gt.exec(d.url.toLowerCase()),d.crossDomain=!(!i||i[1]===tn[1]&&i[2]===tn[2]&&(i[3]||("http:"===i[1]?"80":"443"))===(tn[3]||("http:"===tn[1]?"80":"443")))),d.data&&d.processData&&"string"!=typeof d.data&&(d.data=pe.param(d.data,d.traditional)),U(Kt,d,n,T),2===b)return T;l=pe.event&&d.global,l&&0===pe.active++&&pe.event.trigger("ajaxStart"),d.type=d.type.toUpperCase(),d.hasContent=!Yt.test(d.type),a=d.url,d.hasContent||(d.data&&(a=d.url+=(It.test(a)?"&":"?")+d.data,delete d.data),d.cache===!1&&(d.url=Xt.test(a)?a.replace(Xt,"$1_="+Wt++):a+(It.test(a)?"&":"?")+"_="+Wt++)),d.ifModified&&(pe.lastModified[a]&&T.setRequestHeader("If-Modified-Since",pe.lastModified[a]),pe.etag[a]&&T.setRequestHeader("If-None-Match",pe.etag[a])),(d.data&&d.hasContent&&d.contentType!==!1||n.contentType)&&T.setRequestHeader("Content-Type",d.contentType),T.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+("*"!==d.dataTypes[0]?", "+Zt+"; q=0.01":""):d.accepts["*"]);for(o in d.headers)T.setRequestHeader(o,d.headers[o]);if(d.beforeSend&&(d.beforeSend.call(p,T,d)===!1||2===b))return T.abort();w="abort";for(o in{success:1,error:1,complete:1})T[o](d[o]);if(c=U(Qt,d,n,T)){if(T.readyState=1,l&&h.trigger("ajaxSend",[T,d]),2===b)return T;d.async&&d.timeout>0&&(u=e.setTimeout(function(){T.abort("timeout")},d.timeout));try{b=1,c.send(v,r)}catch(C){if(!(b<2))throw C;r(-1,C)}}else r(-1,"No Transport");return T},getJSON:function(e,t,n){return pe.get(e,t,n,"json")},getScript:function(e,t){return pe.get(e,void 0,t,"script")}}),pe.each(["get","post"],function(e,t){pe[t]=function(e,n,r,i){return pe.isFunction(n)&&(i=i||r,r=n,n=void 0),pe.ajax(pe.extend({url:e,type:t,dataType:i,data:n,success:r},pe.isPlainObject(e)&&e))}}),pe._evalUrl=function(e){return pe.ajax({url:e,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,"throws":!0})},pe.fn.extend({wrapAll:function(e){if(pe.isFunction(e))return this.each(function(t){pe(this).wrapAll(e.call(this,t))});if(this[0]){var t=pe(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstChild&&1===e.firstChild.nodeType;)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return pe.isFunction(e)?this.each(function(t){pe(this).wrapInner(e.call(this,t))}):this.each(function(){var t=pe(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=pe.isFunction(e);return this.each(function(n){pe(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){pe.nodeName(this,"body")||pe(this).replaceWith(this.childNodes)}).end()}}),pe.expr.filters.hidden=function(e){return fe.reliableHiddenOffsets()?e.offsetWidth<=0&&e.offsetHeight<=0&&!e.getClientRects().length:K(e)},pe.expr.filters.visible=function(e){return!pe.expr.filters.hidden(e)};var nn=/%20/g,rn=/\[\]$/,on=/\r?\n/g,an=/^(?:submit|button|image|reset|file)$/i,sn=/^(?:input|select|textarea|keygen)/i;pe.param=function(e,t){var n,r=[],i=function(e,t){t=pe.isFunction(t)?t():null==t?"":t,r[r.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};if(void 0===t&&(t=pe.ajaxSettings&&pe.ajaxSettings.traditional),pe.isArray(e)||e.jquery&&!pe.isPlainObject(e))pe.each(e,function(){i(this.name,this.value)});else for(n in e)Q(n,e[n],t,i);return r.join("&").replace(nn,"+")},pe.fn.extend({serialize:function(){return pe.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=pe.prop(this,"elements");return e?pe.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!pe(this).is(":disabled")&&sn.test(this.nodeName)&&!an.test(e)&&(this.checked||!Be.test(e))}).map(function(e,t){var n=pe(this).val();return null==n?null:pe.isArray(n)?pe.map(n,function(e){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),pe.ajaxSettings.xhr=void 0!==e.ActiveXObject?function(){return this.isLocal?ee():re.documentMode>8?Z():/^(get|post|head|put|delete|options)$/i.test(this.type)&&Z()||ee()}:Z;var un=0,ln={},cn=pe.ajaxSettings.xhr();e.attachEvent&&e.attachEvent("onunload",function(){for(var e in ln)ln[e](void 0,!0)}),fe.cors=!!cn&&"withCredentials"in cn,cn=fe.ajax=!!cn,cn&&pe.ajaxTransport(function(t){if(!t.crossDomain||fe.cors){var n;return{send:function(r,i){var o,a=t.xhr(),s=++un;if(a.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(o in t.xhrFields)a[o]=t.xhrFields[o];t.mimeType&&a.overrideMimeType&&a.overrideMimeType(t.mimeType),t.crossDomain||r["X-Requested-With"]||(r["X-Requested-With"]="XMLHttpRequest");for(o in r)void 0!==r[o]&&a.setRequestHeader(o,r[o]+"");a.send(t.hasContent&&t.data||null),n=function(e,r){var o,u,l;if(n&&(r||4===a.readyState))if(delete ln[s],n=void 0,a.onreadystatechange=pe.noop,r)4!==a.readyState&&a.abort();else{l={},o=a.status,"string"==typeof a.responseText&&(l.text=a.responseText);try{u=a.statusText}catch(c){u=""}o||!t.isLocal||t.crossDomain?1223===o&&(o=204):o=l.text?200:404}l&&i(o,u,l,a.getAllResponseHeaders())},t.async?4===a.readyState?e.setTimeout(n):a.onreadystatechange=ln[s]=n:n()},abort:function(){n&&n(void 0,!0)}}}}),pe.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(e){return pe.globalEval(e),e}}}),pe.ajaxPrefilter("script",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),pe.ajaxTransport("script",function(e){if(e.crossDomain){var t,n=re.head||pe("head")[0]||re.documentElement;return{send:function(r,i){t=re.createElement("script"),t.async=!0,e.scriptCharset&&(t.charset=e.scriptCharset),t.src=e.url,t.onload=t.onreadystatechange=function(e,n){(n||!t.readyState||/loaded|complete/.test(t.readyState))&&(t.onload=t.onreadystatechange=null,t.parentNode&&t.parentNode.removeChild(t),t=null,n||i(200,"success"))},n.insertBefore(t,n.firstChild)},abort:function(){t&&t.onload(void 0,!0)}}}});var fn=[],dn=/(=)\?(?=&|$)|\?\?/;pe.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=fn.pop()||pe.expando+"_"+Wt++;return this[e]=!0,e}}),pe.ajaxPrefilter("json jsonp",function(t,n,r){var i,o,a,s=t.jsonp!==!1&&(dn.test(t.url)?"url":"string"==typeof t.data&&0===(t.contentType||"").indexOf("application/x-www-form-urlencoded")&&dn.test(t.data)&&"data");if(s||"jsonp"===t.dataTypes[0])return i=t.jsonpCallback=pe.isFunction(t.jsonpCallback)?t.jsonpCallback():t.jsonpCallback,s?t[s]=t[s].replace(dn,"$1"+i):t.jsonp!==!1&&(t.url+=(It.test(t.url)?"&":"?")+t.jsonp+"="+i),t.converters["script json"]=function(){return a||pe.error(i+" was not called"),a[0]},t.dataTypes[0]="json",o=e[i],e[i]=function(){a=arguments},r.always(function(){void 0===o?pe(e).removeProp(i):e[i]=o,t[i]&&(t.jsonpCallback=n.jsonpCallback,fn.push(i)),a&&pe.isFunction(o)&&o(a[0]),a=o=void 0}),"script"}),pe.parseHTML=function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||re;var r=Te.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=y([e],t,i),i&&i.length&&pe(i).remove(),pe.merge([],r.childNodes))};var pn=pe.fn.load;return pe.fn.load=function(e,t,n){if("string"!=typeof e&&pn)return pn.apply(this,arguments);var r,i,o,a=this,s=e.indexOf(" ");return s>-1&&(r=pe.trim(e.slice(s,e.length)),e=e.slice(0,s)),pe.isFunction(t)?(n=t,t=void 0):t&&"object"==typeof t&&(i="POST"),a.length>0&&pe.ajax({url:e,type:i||"GET",dataType:"html",data:t}).done(function(e){o=arguments,a.html(r?pe("
    ").append(pe.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},pe.each(["ajaxStart","ajaxStop","ajaxComplete","ajaxError","ajaxSuccess","ajaxSend"],function(e,t){pe.fn[t]=function(e){return this.on(t,e)}}),pe.expr.filters.animated=function(e){return pe.grep(pe.timers,function(t){return e===t.elem}).length},pe.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l,c=pe.css(e,"position"),f=pe(e),d={};"static"===c&&(e.style.position="relative"),s=f.offset(),o=pe.css(e,"top"),u=pe.css(e,"left"),l=("absolute"===c||"fixed"===c)&&pe.inArray("auto",[o,u])>-1,l?(r=f.position(),a=r.top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),pe.isFunction(t)&&(t=t.call(e,n,pe.extend({},s))),null!=t.top&&(d.top=t.top-s.top+a),null!=t.left&&(d.left=t.left-s.left+i),"using"in t?t.using.call(e,d):f.css(d)}},pe.fn.extend({offset:function(e){if(arguments.length)return void 0===e?this:this.each(function(t){pe.offset.setOffset(this,e,t)});var t,n,r={top:0,left:0},i=this[0],o=i&&i.ownerDocument;if(o)return t=o.documentElement,pe.contains(t,i)?("undefined"!=typeof i.getBoundingClientRect&&(r=i.getBoundingClientRect()),n=te(o),{top:r.top+(n.pageYOffset||t.scrollTop)-(t.clientTop||0),left:r.left+(n.pageXOffset||t.scrollLeft)-(t.clientLeft||0)}):r},position:function(){if(this[0]){var e,t,n={top:0,left:0},r=this[0];return"fixed"===pe.css(r,"position")?t=r.getBoundingClientRect():(e=this.offsetParent(),t=this.offset(),pe.nodeName(e[0],"html")||(n=e.offset()),n.top+=pe.css(e[0],"borderTopWidth",!0),n.left+=pe.css(e[0],"borderLeftWidth",!0)),{top:t.top-n.top-pe.css(r,"marginTop",!0),left:t.left-n.left-pe.css(r,"marginLeft",!0)}}},offsetParent:function(){return this.map(function(){ diff --git a/static/admin/js/layui/lay/modules/laydate.js b/static/admin/js/layui/lay/modules/laydate.js index 9f6a1db..ae0a591 100755 --- a/static/admin/js/layui/lay/modules/laydate.js +++ b/static/admin/js/layui/lay/modules/laydate.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;!function(){"use strict";var e=window.layui&&layui.define,t={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,n=t.length-1,a=n;a>0;a--)if("interactive"===t[a].readyState){e=t[a].src;break}return e||t[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),getStyle:function(e,t){var n=e.currentStyle?e.currentStyle:window.getComputedStyle(e,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](t)},link:function(e,a,i){if(n.path){var r=document.getElementsByTagName("head")[0],o=document.createElement("link");"string"==typeof a&&(i=a);var s=(i||e).replace(/\.|\//g,""),l="layuicss-"+s,d=0;o.rel="stylesheet",o.href=n.path+e,o.id=l,document.getElementById(l)||r.appendChild(o),"function"==typeof a&&!function c(){return++d>80?window.console&&console.error("laydate.css: Invalid"):void(1989===parseInt(t.getStyle(document.getElementById(l),"width"))?a():setTimeout(c,100))}()}}},n={v:"5.0.9",config:{},index:window.laydate&&window.laydate.v?1e5:0,path:t.getPath,set:function(e){var t=this;return t.config=w.extend({},t.config,e),t},ready:function(a){var i="laydate",r="",o=(e?"modules/laydate/":"theme/")+"default/laydate.css?v="+n.v+r;return e?layui.addcss(o,a,i):t.link(o,a,i),this}},a=function(){var e=this;return{hint:function(t){e.hint.call(e,t)},config:e.config}},i="laydate",r=".layui-laydate",o="layui-this",s="laydate-disabled",l="开始日期超出了结束日期
    建议重新选择",d=[100,2e5],c="layui-laydate-static",m="layui-laydate-list",u="laydate-selected",h="layui-laydate-hint",y="laydate-day-prev",f="laydate-day-next",p="layui-laydate-footer",g=".laydate-btns-confirm",v="laydate-time-text",D=".laydate-btns-time",T=function(e){var t=this;t.index=++n.index,t.config=w.extend({},t.config,n.config,e),n.ready(function(){t.init()})},w=function(e){return new C(e)},C=function(e){for(var t=0,n="object"==typeof e?[e]:(this.selector=e,document.querySelectorAll(e||null));t0)return n[0].getAttribute(e)}():n.each(function(n,a){a.setAttribute(e,t)})},C.prototype.removeAttr=function(e){return this.each(function(t,n){n.removeAttribute(e)})},C.prototype.html=function(e){return this.each(function(t,n){n.innerHTML=e})},C.prototype.val=function(e){return this.each(function(t,n){n.value=e})},C.prototype.append=function(e){return this.each(function(t,n){"object"==typeof e?n.appendChild(e):n.innerHTML=n.innerHTML+e})},C.prototype.remove=function(e){return this.each(function(t,n){e?n.removeChild(e):n.parentNode.removeChild(n)})},C.prototype.on=function(e,t){return this.each(function(n,a){a.attachEvent?a.attachEvent("on"+e,function(e){e.target=e.srcElement,t.call(a,e)}):a.addEventListener(e,t,!1)})},C.prototype.off=function(e,t){return this.each(function(n,a){a.detachEvent?a.detachEvent("on"+e,t):a.removeEventListener(e,t,!1)})},T.isLeapYear=function(e){return e%4===0&&e%100!==0||e%400===0},T.prototype.config={type:"date",range:!1,format:"yyyy-MM-dd",value:null,isInitValue:!0,min:"1900-1-1",max:"2099-12-31",trigger:"focus",show:!1,showBottom:!0,btns:["clear","now","confirm"],lang:"cn",theme:"default",position:null,calendar:!1,mark:{},zIndex:null,done:null,change:null},T.prototype.lang=function(){var e=this,t=e.config,n={cn:{weeks:["日","一","二","三","四","五","六"],time:["时","分","秒"],timeTips:"选择时间",startTime:"开始时间",endTime:"结束时间",dateTips:"返回日期",month:["一","二","三","四","五","六","七","八","九","十","十一","十二"],tools:{confirm:"确定",clear:"清空",now:"现在"}},en:{weeks:["Su","Mo","Tu","We","Th","Fr","Sa"],time:["Hours","Minutes","Seconds"],timeTips:"Select Time",startTime:"Start Time",endTime:"End Time",dateTips:"Select Date",month:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],tools:{confirm:"Confirm",clear:"Clear",now:"Now"}}};return n[t.lang]||n.cn},T.prototype.init=function(){var e=this,t=e.config,n="yyyy|y|MM|M|dd|d|HH|H|mm|m|ss|s",a="static"===t.position,i={year:"yyyy",month:"yyyy-MM",date:"yyyy-MM-dd",time:"HH:mm:ss",datetime:"yyyy-MM-dd HH:mm:ss"};t.elem=w(t.elem),t.eventElem=w(t.eventElem),t.elem[0]&&(t.range===!0&&(t.range="-"),t.format===i.date&&(t.format=i[t.type]),e.format=t.format.match(new RegExp(n+"|.","g"))||[],e.EXP_IF="",e.EXP_SPLIT="",w.each(e.format,function(t,a){var i=new RegExp(n).test(a)?"\\d{"+function(){return new RegExp(n).test(e.format[0===t?t+1:t-1]||"")?/^yyyy|y$/.test(a)?4:a.length:/^yyyy$/.test(a)?"1,4":/^y$/.test(a)?"1,308":"1,2"}()+"}":"\\"+a;e.EXP_IF=e.EXP_IF+i,e.EXP_SPLIT=e.EXP_SPLIT+"("+i+")"}),e.EXP_IF=new RegExp("^"+(t.range?e.EXP_IF+"\\s\\"+t.range+"\\s"+e.EXP_IF:e.EXP_IF)+"$"),e.EXP_SPLIT=new RegExp("^"+e.EXP_SPLIT+"$",""),e.isInput(t.elem[0])||"focus"===t.trigger&&(t.trigger="click"),t.elem.attr("lay-key")||(t.elem.attr("lay-key",e.index),t.eventElem.attr("lay-key",e.index)),t.mark=w.extend({},t.calendar&&"cn"===t.lang?{"0-1-1":"元旦","0-2-14":"情人","0-3-8":"妇女","0-3-12":"植树","0-4-1":"愚人","0-5-1":"劳动","0-5-4":"青年","0-6-1":"儿童","0-9-10":"教师","0-9-18":"国耻","0-10-1":"国庆","0-12-25":"圣诞"}:{},t.mark),w.each(["min","max"],function(e,n){var a=[],i=[];if("number"==typeof t[n]){var r=t[n],o=(new Date).getTime(),s=864e5,l=new Date(r?r0)return!0;var a=w.elem("div",{"class":"layui-laydate-header"}),i=[function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-y"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-prev-m"});return e.innerHTML="",e}(),function(){var e=w.elem("div",{"class":"laydate-set-ym"}),t=w.elem("span"),n=w.elem("span");return e.appendChild(t),e.appendChild(n),e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-m"});return e.innerHTML="",e}(),function(){var e=w.elem("i",{"class":"layui-icon laydate-icon laydate-next-y"});return e.innerHTML="",e}()],d=w.elem("div",{"class":"layui-laydate-content"}),c=w.elem("table"),m=w.elem("thead"),u=w.elem("tr");w.each(i,function(e,t){a.appendChild(t)}),m.appendChild(u),w.each(new Array(6),function(e){var t=c.insertRow(0);w.each(new Array(7),function(a){if(0===e){var i=w.elem("th");i.innerHTML=n.weeks[a],u.appendChild(i)}t.insertCell(a)})}),c.insertBefore(m,c.children[0]),d.appendChild(c),r[e]=w.elem("div",{"class":"layui-laydate-main laydate-main-list-"+e}),r[e].appendChild(a),r[e].appendChild(d),o.push(i),s.push(d),l.push(c)}),w(d).html(function(){var e=[],i=[];return"datetime"===t.type&&e.push(''+n.timeTips+""),w.each(t.btns,function(e,r){var o=n.tools[r]||"btn";t.range&&"now"===r||(a&&"clear"===r&&(o="cn"===t.lang?"重置":"Reset"),i.push(''+o+""))}),e.push('"),e.join("")}()),w.each(r,function(e,t){i.appendChild(t)}),t.showBottom&&i.appendChild(d),/^#/.test(t.theme)){var m=w.elem("style"),u=["#{{id}} .layui-laydate-header{background-color:{{theme}};}","#{{id}} .layui-this{background-color:{{theme}} !important;}"].join("").replace(/{{id}}/g,e.elemID).replace(/{{theme}}/g,t.theme);"styleSheet"in m?(m.setAttribute("type","text/css"),m.styleSheet.cssText=u):m.innerHTML=u,w(i).addClass("laydate-theme-molv"),i.appendChild(m)}e.remove(T.thisElemDate),a?t.elem.append(i):(document.body.appendChild(i),e.position()),e.checkDate().calendar(),e.changeEvent(),T.thisElemDate=e.elemID,"function"==typeof t.ready&&t.ready(w.extend({},t.dateTime,{month:t.dateTime.month+1}))},T.prototype.remove=function(e){var t=this,n=(t.config,w("#"+(e||t.elemID)));return n.hasClass(c)||t.checkDate(function(){n.remove()}),t},T.prototype.position=function(){var e=this,t=e.config,n=e.bindElem||t.elem[0],a=n.getBoundingClientRect(),i=e.elem.offsetWidth,r=e.elem.offsetHeight,o=function(e){return e=e?"scrollLeft":"scrollTop",document.body[e]|document.documentElement[e]},s=function(e){return document.documentElement[e?"clientWidth":"clientHeight"]},l=5,d=a.left,c=a.bottom;d+i+l>s("width")&&(d=s("width")-i-l),c+r+l>s()&&(c=a.top>r?a.top-r:s()-r,c-=2*l),t.position&&(e.elem.style.position=t.position),e.elem.style.left=d+("fixed"===t.position?0:o(1))+"px",e.elem.style.top=c+("fixed"===t.position?0:o())+"px"},T.prototype.hint=function(e){var t=this,n=(t.config,w.elem("div",{"class":h}));t.elem&&(n.innerHTML=e||"",w(t.elem).find("."+h).remove(),t.elem.appendChild(n),clearTimeout(t.hinTimer),t.hinTimer=setTimeout(function(){w(t.elem).find("."+h).remove()},3e3))},T.prototype.getAsYM=function(e,t,n){return n?t--:t++,t<0&&(t=11,e--),t>11&&(t=0,e++),[e,t]},T.prototype.systemDate=function(e){var t=e||new Date;return{year:t.getFullYear(),month:t.getMonth(),date:t.getDate(),hours:e?e.getHours():0,minutes:e?e.getMinutes():0,seconds:e?e.getSeconds():0}},T.prototype.checkDate=function(e){var t,a,i=this,r=(new Date,i.config),o=r.dateTime=r.dateTime||i.systemDate(),s=i.bindElem||r.elem[0],l=(i.isInput(s)?"val":"html",i.isInput(s)?s.value:"static"===r.position?"":s.innerHTML),c=function(e){e.year>d[1]&&(e.year=d[1],a=!0),e.month>11&&(e.month=11,a=!0),e.hours>23&&(e.hours=0,a=!0),e.minutes>59&&(e.minutes=0,e.hours++,a=!0),e.seconds>59&&(e.seconds=0,e.minutes++,a=!0),t=n.getEndDate(e.month+1,e.year),e.date>t&&(e.date=t,a=!0)},m=function(e,t,n){var o=["startTime","endTime"];t=(t.match(i.EXP_SPLIT)||[]).slice(1),n=n||0,r.range&&(i[o[n]]=i[o[n]]||{}),w.each(i.format,function(s,l){var c=parseFloat(t[s]);t[s].length必须遵循下述格式:
    "+(r.range?r.format+" "+r.range+" "+r.format:r.format)+"
    已为你重置"),a=!0):l&&l.constructor===Date?r.dateTime=i.systemDate(l):(r.dateTime=i.systemDate(),delete i.startState,delete i.endState,delete i.startDate,delete i.endDate,delete i.startTime,delete i.endTime),c(o),a&&l&&i.setValue(r.range?i.endDate?i.parse():"":i.parse()),e&&e(),i)},T.prototype.mark=function(e,t){var n,a=this,i=a.config;return w.each(i.mark,function(e,a){var i=e.split("-");i[0]!=t[0]&&0!=i[0]||i[1]!=t[1]&&0!=i[1]||i[2]!=t[2]||(n=a||t[2])}),n&&e.html(''+n+""),a},T.prototype.limit=function(e,t,n,a){var i,r=this,o=r.config,l={},d=o[n>41?"endDate":"dateTime"],c=w.extend({},d,t||{});return w.each({now:c,min:o.min,max:o.max},function(e,t){l[e]=r.newDate(w.extend({year:t.year,month:t.month,date:t.date},function(){var e={};return w.each(a,function(n,a){e[a]=t[a]}),e}())).getTime()}),i=l.nowl.max,e&&e[i?"addClass":"removeClass"](s),i},T.prototype.calendar=function(e){var t,a,i,r=this,s=r.config,l=e||s.dateTime,c=new Date,m=r.lang(),u="date"!==s.type&&"datetime"!==s.type,h=e?1:0,y=w(r.table[h]).find("td"),f=w(r.elemHeader[h][2]).find("span");if(l.yeard[1]&&(l.year=d[1],r.hint("最高只能支持到公元"+d[1]+"年")),r.firstDate||(r.firstDate=w.extend({},l)),c.setFullYear(l.year,l.month,1),t=c.getDay(),a=n.getEndDate(l.month||12,l.year),i=n.getEndDate(l.month+1,l.year),w.each(y,function(e,n){var d=[l.year,l.month],c=0;n=w(n),n.removeAttr("class"),e=t&&e=n.firstDate.year&&(r.month=a.max.month,r.date=a.max.date),n.limit(w(i),r,t),M++}),w(u[f?0:1]).attr("lay-ym",M-8+"-"+T[1]).html(b+p+" - "+(M-1+p))}else if("month"===e)w.each(new Array(12),function(e){var i=w.elem("li",{"lay-ym":e}),s={year:T[0],month:e};e+1==T[1]&&w(i).addClass(o),i.innerHTML=r.month[e]+(f?"月":""),d.appendChild(i),T[0]=n.firstDate.year&&(s.date=a.max.date),n.limit(w(i),s,t)}),w(u[f?0:1]).attr("lay-ym",T[0]+"-"+T[1]).html(T[0]+p);else if("time"===e){var E=function(){w(d).find("ol").each(function(e,a){w(a).find("li").each(function(a,i){n.limit(w(i),[{hours:a},{hours:n[x].hours,minutes:a},{hours:n[x].hours,minutes:n[x].minutes,seconds:a}][e],t,[["hours"],["hours","minutes"],["hours","minutes","seconds"]][e])})}),a.range||n.limit(w(n.footer).find(g),n[x],0,["hours","minutes","seconds"])};a.range?n[x]||(n[x]={hours:0,minutes:0,seconds:0}):n[x]=i,w.each([24,60,60],function(e,t){var a=w.elem("li"),i=["

    "+r.time[e]+"

      "];w.each(new Array(t),function(t){i.push(""+w.digit(t,2)+"")}),a.innerHTML=i.join("")+"
    ",d.appendChild(a)}),E()}if(y&&h.removeChild(y),h.appendChild(d),"year"===e||"month"===e)w(n.elemMain[t]).addClass("laydate-ym-show"),w(d).find("li").on("click",function(){var r=0|w(this).attr("lay-ym");if(!w(this).hasClass(s)){if(0===t)i[e]=r,l&&(n.startDate[e]=r),n.limit(w(n.footer).find(g),null,0);else if(l)n.endDate[e]=r;else{var c="year"===e?n.getAsYM(r,T[1]-1,"sub"):n.getAsYM(T[0],r,"sub");w.extend(i,{year:c[0],month:c[1]})}"year"===a.type||"month"===a.type?(w(d).find("."+o).removeClass(o),w(this).addClass(o),"month"===a.type&&"year"===e&&(n.listYM[t][0]=r,l&&(n[["startDate","endDate"][t]].year=r),n.list("month",t))):(n.checkDate("limit").calendar(),n.closeList()),n.setBtnStatus(),a.range||n.done(null,"change"),w(n.footer).find(D).removeClass(s)}});else{var S=w.elem("span",{"class":v}),k=function(){w(d).find("ol").each(function(e){var t=this,a=w(t).find("li");t.scrollTop=30*(n[x][C[e]]-2),t.scrollTop<=0&&a.each(function(e,n){if(!w(this).hasClass(s))return t.scrollTop=30*(e-2),!0})})},H=w(c[2]).find("."+v);k(),S.innerHTML=a.range?[r.startTime,r.endTime][t]:r.timeTips,w(n.elemMain[t]).addClass("laydate-time-show"),H[0]&&H.remove(),c[2].appendChild(S),w(d).find("ol").each(function(e){var t=this;w(t).find("li").on("click",function(){var r=0|this.innerHTML;w(this).hasClass(s)||(a.range?n[x][C[e]]=r:i[C[e]]=r,w(t).find("."+o).removeClass(o),w(this).addClass(o),E(),k(),(n.endDate||"time"===a.type)&&n.done(null,"change"),n.setBtnStatus())})})}return n},T.prototype.listYM=[],T.prototype.closeList=function(){var e=this;e.config;w.each(e.elemCont,function(t,n){w(this).find("."+m).remove(),w(e.elemMain[t]).removeClass("laydate-ym-show laydate-time-show")}),w(e.elem).find("."+v).remove()},T.prototype.setBtnStatus=function(e,t,n){var a,i=this,r=i.config,o=w(i.footer).find(g),d=r.range&&"date"!==r.type&&"time"!==r.type;d&&(t=t||i.startDate,n=n||i.endDate,a=i.newDate(t).getTime()>i.newDate(n).getTime(),i.limit(null,t)||i.limit(null,n)?o.addClass(s):o[a?"addClass":"removeClass"](s),e&&a&&i.hint("string"==typeof e?l.replace(/日期/g,e):l))},T.prototype.parse=function(e,t){var n=this,a=n.config,i=t||(e?w.extend({},n.endDate,n.endTime):a.range?w.extend({},n.startDate,n.startTime):a.dateTime),r=n.format.concat();return w.each(r,function(e,t){/yyyy|y/.test(t)?r[e]=w.digit(i.year,t.length):/MM|M/.test(t)?r[e]=w.digit(i.month+1,t.length):/dd|d/.test(t)?r[e]=w.digit(i.date,t.length):/HH|H/.test(t)?r[e]=w.digit(i.hours,t.length):/mm|m/.test(t)?r[e]=w.digit(i.minutes,t.length):/ss|s/.test(t)&&(r[e]=w.digit(i.seconds,t.length))}),a.range&&!e?r.join("")+" "+a.range+" "+n.parse(1):r.join("")},T.prototype.newDate=function(e){return e=e||{},new Date(e.year||1,e.month||0,e.date||1,e.hours||0,e.minutes||0,e.seconds||0)},T.prototype.setValue=function(e){var t=this,n=t.config,a=t.bindElem||n.elem[0],i=t.isInput(a)?"val":"html";return"static"===n.position||w(a)[i](e||""),this},T.prototype.stampRange=function(){var e,t,n=this,a=n.config,i=w(n.elem).find("td");if(a.range&&!n.endDate&&w(n.footer).find(g).addClass(s),n.endDate)return e=n.newDate({year:n.startDate.year,month:n.startDate.month,date:n.startDate.date}).getTime(),t=n.newDate({year:n.endDate.year,month:n.endDate.month,date:n.endDate.date}).getTime(),e>t?n.hint(l):void w.each(i,function(a,i){var r=w(i).attr("lay-ymd").split("-"),s=n.newDate({year:r[0],month:r[1]-1,date:r[2]}).getTime();w(i).removeClass(u+" "+o),s!==e&&s!==t||w(i).addClass(w(i).hasClass(y)||w(i).hasClass(f)?u:o),s>e&&s','
    '+f+"
    ",'
    ','',"
    ","
    "].join(""));return l.ie&&l.ie<8?c.removeClass("layui-hide").addClass(o):(d[0]&&d.remove(),s.call(a,m,c[0],y),c.addClass("layui-hide").after(m),a.index)},c.prototype.getContent=function(t){var e=u(t);if(e[0])return d(e[0].document.body.innerHTML)},c.prototype.getText=function(t){var i=u(t);if(i[0])return e(i[0].document.body).text()},c.prototype.setContent=function(t,i,a){var l=u(t);l[0]&&(a?e(l[0].document.body).append(i):e(l[0].document.body).html(i),layedit.sync(t))},c.prototype.sync=function(t){var i=u(t);if(i[0]){var a=e("#"+i[1].attr("textarea"));a.val(d(i[0].document.body.innerHTML))}},c.prototype.getSelection=function(t){var e=u(t);if(e[0]){var i=m(e[0].document);return document.selection?i.text:i.toString()}};var s=function(t,i,a){var l=this,n=t.find("iframe");n.css({height:a.height}).on("load",function(){var o=n.contents(),r=n.prop("contentWindow"),c=o.find("head"),s=e([""].join("")),u=o.find("body");c.append(s),u.attr("contenteditable","true").css({"min-height":a.height}).html(i.value||""),y.apply(l,[r,n,i,a]),g.call(l,r,t,a)})},u=function(t){var i=e("#LAY_layedit_"+t),a=i.prop("contentWindow");return[a,i]},d=function(t){return 8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),t},y=function(t,a,n,o){var r=t.document,c=e(r.body);c.on("keydown",function(t){var e=t.keyCode;if(13===e){var a=m(r),l=p(a),n=l.parentNode;if("pre"===n.tagName.toLowerCase()){if(t.shiftKey)return;return i.msg("请暂时用shift+enter"),!1}r.execCommand("formatBlock",!1,"

    ")}}),e(n).parents("form").on("submit",function(){var t=c.html();8==l.ie&&(t=t.replace(/<.+>/g,function(t){return t.toLowerCase()})),n.value=t}),c.on("paste",function(e){r.execCommand("formatBlock",!1,"

    "),setTimeout(function(){f.call(t,c),n.value=c.html()},100)})},f=function(t){var i=this;i.document;t.find("*[style]").each(function(){var t=this.style.textAlign;this.removeAttribute("style"),e(this).css({"text-align":t||""})}),t.find("table").addClass("layui-table"),t.find("script,link").remove()},m=function(t){return t.selection?t.selection.createRange():t.getSelection().getRangeAt(0)},p=function(t){return t.endContainer||t.parentElement().childNodes[0]},v=function(t,i,a){var l=this.document,n=document.createElement(t);for(var o in i)n.setAttribute(o,i[o]);if(n.removeAttribute("text"),l.selection){var r=a.text||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.pasteHTML(e(n).prop("outerHTML")),a.select()}else{var r=a.toString()||i.text;if("a"===t&&!r)return;r&&(n.innerHTML=r),a.deleteContents(),a.insertNode(n)}},h=function(t,i){var a=this.document,l="layedit-tool-active",n=p(m(a)),o=function(e){return t.find(".layedit-tool-"+e)};i&&i[i.hasClass(l)?"removeClass":"addClass"](l),t.find(">i").removeClass(l),o("unlink").addClass(r),e(n).parents().each(function(){var t=this.tagName.toLowerCase(),e=this.style.textAlign;"b"!==t&&"strong"!==t||o("b").addClass(l),"i"!==t&&"em"!==t||o("i").addClass(l),"u"===t&&o("u").addClass(l),"strike"===t&&o("d").addClass(l),"p"===t&&("center"===e?o("center").addClass(l):"right"===e?o("right").addClass(l):o("left").addClass(l)),"a"===t&&(o("link").addClass(l),o("unlink").removeClass(r))})},g=function(t,a,l){var n=t.document,o=e(n.body),c={link:function(i){var a=p(i),l=e(a).parent();b.call(o,{href:l.attr("href"),target:l.attr("target")},function(e){var a=l[0];"A"===a.tagName?a.href=e.url:v.call(t,"a",{target:e.target,href:e.url,text:e.url},i)})},unlink:function(t){n.execCommand("unlink")},face:function(e){x.call(this,function(i){v.call(t,"img",{src:i.src,alt:i.alt},e)})},image:function(a){var n=this;layui.use("upload",function(o){var r=l.uploadImage||{};o.render({url:r.url,method:r.type,elem:e(n).find("input")[0],done:function(e){0==e.code?(e.data=e.data||{},v.call(t,"img",{src:e.data.src,alt:e.data.title},a)):i.msg(e.msg||"上传失败")}})})},code:function(e){k.call(o,function(i){v.call(t,"pre",{text:i.code,"lay-lang":i.lang},e)})},help:function(){i.open({type:2,title:"帮助",area:["600px","380px"],shadeClose:!0,shade:.1,skin:"layui-layer-msg",content:["http://www.layui.com/about/layedit/help.html","no"]})}},s=a.find(".layui-layedit-tool"),u=function(){var i=e(this),a=i.attr("layedit-event"),l=i.attr("lay-command");if(!i.hasClass(r)){o.focus();var u=m(n);u.commonAncestorContainer;l?(n.execCommand(l),/justifyLeft|justifyCenter|justifyRight/.test(l)&&n.execCommand("formatBlock",!1,"

    "),setTimeout(function(){o.focus()},10)):c[a]&&c[a].call(this,u),h.call(t,s,i)}},d=/image/;s.find(">i").on("mousedown",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)||u.call(this)}).on("click",function(){var t=e(this),i=t.attr("layedit-event");d.test(i)&&u.call(this)}),o.on("click",function(){h.call(t,s),i.close(x.index)})},b=function(t,e){var l=this,n=i.open({type:1,id:"LAY_layedit_link",area:"350px",shade:.05,shadeClose:!0,moveType:1,title:"超链接",skin:"layui-layer-msg",content:['

      ','
    • ','','
      ','',"
      ","
    • ",'
    • ','','
      ','",'","
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(t,n){var o="submit(layedit-link-yes)";a.render("radio"),t.find(".layui-btn-primary").on("click",function(){i.close(n),l.focus()}),a.on(o,function(t){i.close(b.index),e&&e(t.field)})}});b.index=n},x=function(t){var a=function(){var t=["[微笑]","[嘻嘻]","[哈哈]","[可爱]","[可怜]","[挖鼻]","[吃惊]","[害羞]","[挤眼]","[闭嘴]","[鄙视]","[爱你]","[泪]","[偷笑]","[亲亲]","[生病]","[太开心]","[白眼]","[右哼哼]","[左哼哼]","[嘘]","[衰]","[委屈]","[吐]","[哈欠]","[抱抱]","[怒]","[疑问]","[馋嘴]","[拜拜]","[思考]","[汗]","[困]","[睡]","[钱]","[失望]","[酷]","[色]","[哼]","[鼓掌]","[晕]","[悲伤]","[抓狂]","[黑线]","[阴险]","[怒骂]","[互粉]","[心]","[伤心]","[猪头]","[熊猫]","[兔子]","[ok]","[耶]","[good]","[NO]","[赞]","[来]","[弱]","[草泥马]","[神马]","[囧]","[浮云]","[给力]","[围观]","[威武]","[奥特曼]","[礼物]","[钟]","[话筒]","[蜡烛]","[蛋糕]"],e={};return layui.each(t,function(t,i){e[i]=layui.cache.dir+"images/face/"+t+".gif"}),e}();return x.hide=x.hide||function(t){"face"!==e(t.target).attr("layedit-event")&&i.close(x.index)},x.index=i.tips(function(){var t=[];return layui.each(a,function(e,i){t.push('
  • '+e+'
  • ')}),'
      '+t.join("")+"
    "}(),this,{tips:1,time:0,skin:"layui-box layui-util-face",maxWidth:500,success:function(l,n){l.css({marginTop:-4,marginLeft:-10}).find(".layui-clear>li").on("click",function(){t&&t({src:a[this.title],alt:this.title}),i.close(n)}),e(document).off("click",x.hide).on("click",x.hide)}})},k=function(t){var e=this,l=i.open({type:1,id:"LAY_layedit_code",area:"550px",shade:.05,shadeClose:!0,moveType:1,title:"插入代码",skin:"layui-layer-msg",content:['
      ','
    • ','','
      ','","
      ","
    • ",'
    • ','','
      ','',"
      ","
    • ",'
    • ','','',"
    • ","
    "].join(""),success:function(l,n){var o="submit(layedit-code-yes)";a.render("select"),l.find(".layui-btn-primary").on("click",function(){i.close(n),e.focus()}),a.on(o,function(e){i.close(k.index),t&&t(e.field)})}});k.index=l},C={html:'',strong:'',italic:'',underline:'',del:'',"|":'',left:'',center:'',right:'',link:'',unlink:'',face:'',image:'',code:'',help:''},w=new c;t(n,w)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/layer.js b/static/admin/js/layui/lay/modules/layer.js index 41b5510..c6d5378 100755 --- a/static/admin/js/layui/lay/modules/layer.js +++ b/static/admin/js/layui/lay/modules/layer.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;!function(e,t){"use strict";var i,n,a=e.layui&&layui.define,o={getPath:function(){var e=document.currentScript?document.currentScript.src:function(){for(var e,t=document.scripts,i=t.length-1,n=i;n>0;n--)if("interactive"===t[n].readyState){e=t[n].src;break}return e||t[i].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),config:{},end:{},minIndex:0,minLeft:[],btn:["确定","取消"],type:["dialog","page","iframe","loading","tips"],getStyle:function(t,i){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](i)},link:function(t,i,n){if(r.path){var a=document.getElementsByTagName("head")[0],s=document.createElement("link");"string"==typeof i&&(n=i);var l=(n||t).replace(/\.|\//g,""),f="layuicss-"+l,c=0;s.rel="stylesheet",s.href=r.path+t,s.id=f,document.getElementById(f)||a.appendChild(s),"function"==typeof i&&!function u(){return++c>80?e.console&&console.error("layer.css: Invalid"):void(1989===parseInt(o.getStyle(document.getElementById(f),"width"))?i():setTimeout(u,100))}()}}},r={v:"3.1.1",ie:function(){var t=navigator.userAgent.toLowerCase();return!!(e.ActiveXObject||"ActiveXObject"in e)&&((t.match(/msie\s(\d+)/)||[])[1]||"11")}(),index:e.layer&&e.layer.v?1e5:0,path:o.getPath,config:function(e,t){return e=e||{},r.cache=o.config=i.extend({},o.config,e),r.path=o.config.path||r.path,"string"==typeof e.extend&&(e.extend=[e.extend]),o.config.path&&r.ready(),e.extend?(a?layui.addcss("modules/layer/"+e.extend):o.link("theme/"+e.extend),this):this},ready:function(e){var t="layer",i="",n=(a?"modules/layer/":"theme/")+"default/layer.css?v="+r.v+i;return a?layui.addcss(n,e,t):o.link(n,e,t),this},alert:function(e,t,n){var a="function"==typeof t;return a&&(n=t),r.open(i.extend({content:e,yes:n},a?{}:t))},confirm:function(e,t,n,a){var s="function"==typeof t;return s&&(a=n,n=t),r.open(i.extend({content:e,btn:o.btn,yes:n,btn2:a},s?{}:t))},msg:function(e,n,a){var s="function"==typeof n,f=o.config.skin,c=(f?f+" "+f+"-msg":"")||"layui-layer-msg",u=l.anim.length-1;return s&&(a=n),r.open(i.extend({content:e,time:3e3,shade:!1,skin:c,title:!1,closeBtn:!1,btn:!1,resize:!1,end:a},s&&!o.config.skin?{skin:c+" layui-layer-hui",anim:u}:function(){return n=n||{},(n.icon===-1||n.icon===t&&!o.config.skin)&&(n.skin=c+" "+(n.skin||"layui-layer-hui")),n}()))},load:function(e,t){return r.open(i.extend({type:3,icon:e||0,resize:!1,shade:.01},t))},tips:function(e,t,n){return r.open(i.extend({type:4,content:[e,t],closeBtn:!1,time:3e3,shade:!1,resize:!1,fixed:!1,maxWidth:210},n))}},s=function(e){var t=this;t.index=++r.index,t.config=i.extend({},t.config,o.config,e),document.body?t.creat():setTimeout(function(){t.creat()},30)};s.pt=s.prototype;var l=["layui-layer",".layui-layer-title",".layui-layer-main",".layui-layer-dialog","layui-layer-iframe","layui-layer-content","layui-layer-btn","layui-layer-close"];l.anim=["layer-anim-00","layer-anim-01","layer-anim-02","layer-anim-03","layer-anim-04","layer-anim-05","layer-anim-06"],s.pt.config={type:0,shade:.3,fixed:!0,move:l[1],title:"信息",offset:"auto",area:"auto",closeBtn:1,time:0,zIndex:19891014,maxWidth:360,anim:0,isOutAnim:!0,icon:-1,moveType:1,resize:!0,scrollbar:!0,tips:2},s.pt.vessel=function(e,t){var n=this,a=n.index,r=n.config,s=r.zIndex+a,f="object"==typeof r.title,c=r.maxmin&&(1===r.type||2===r.type),u=r.title?'
    '+(f?r.title[0]:r.title)+"
    ":"";return r.zIndex=s,t([r.shade?'
    ':"",'
    '+(e&&2!=r.type?"":u)+'
    '+(0==r.type&&r.icon!==-1?'':"")+(1==r.type&&e?"":r.content||"")+'
    '+function(){var e=c?'':"";return r.closeBtn&&(e+=''),e}()+""+(r.btn?function(){var e="";"string"==typeof r.btn&&(r.btn=[r.btn]);for(var t=0,i=r.btn.length;t'+r.btn[t]+"";return'
    '+e+"
    "}():"")+(r.resize?'':"")+"
    "],u,i('
    ')),n},s.pt.creat=function(){var e=this,t=e.config,a=e.index,s=t.content,f="object"==typeof s,c=i("body");if(!t.id||!i("#"+t.id)[0]){switch("string"==typeof t.area&&(t.area="auto"===t.area?["",""]:[t.area,""]),t.shift&&(t.anim=t.shift),6==r.ie&&(t.fixed=!1),t.type){case 0:t.btn="btn"in t?t.btn:o.btn[0],r.closeAll("dialog");break;case 2:var s=t.content=f?t.content:[t.content||"","auto"];t.content='';break;case 3:delete t.title,delete t.closeBtn,t.icon===-1&&0===t.icon,r.closeAll("loading");break;case 4:f||(t.content=[t.content,"body"]),t.follow=t.content[1],t.content=t.content[0]+'',delete t.title,t.tips="object"==typeof t.tips?t.tips:[t.tips,!0],t.tipsMore||r.closeAll("tips")}if(e.vessel(f,function(n,r,u){c.append(n[0]),f?function(){2==t.type||4==t.type?function(){i("body").append(n[1])}():function(){s.parents("."+l[0])[0]||(s.data("display",s.css("display")).show().addClass("layui-layer-wrap").wrap(n[1]),i("#"+l[0]+a).find("."+l[5]).before(r))}()}():c.append(n[1]),i(".layui-layer-move")[0]||c.append(o.moveElem=u),e.layero=i("#"+l[0]+a),t.scrollbar||l.html.css("overflow","hidden").attr("layer-full",a)}).auto(a),i("#layui-layer-shade"+e.index).css({"background-color":t.shade[1]||"#000",opacity:t.shade[0]||t.shade}),2==t.type&&6==r.ie&&e.layero.find("iframe").attr("src",s[0]),4==t.type?e.tips():e.offset(),t.fixed&&n.on("resize",function(){e.offset(),(/^\d+%$/.test(t.area[0])||/^\d+%$/.test(t.area[1]))&&e.auto(a),4==t.type&&e.tips()}),t.time<=0||setTimeout(function(){r.close(e.index)},t.time),e.move().callback(),l.anim[t.anim]){var u="layer-anim "+l.anim[t.anim];e.layero.addClass(u).one("webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend",function(){i(this).removeClass(u)})}t.isOutAnim&&e.layero.data("isOutAnim",!0)}},s.pt.auto=function(e){var t=this,a=t.config,o=i("#"+l[0]+e);""===a.area[0]&&a.maxWidth>0&&(r.ie&&r.ie<8&&a.btn&&o.width(o.innerWidth()),o.outerWidth()>a.maxWidth&&o.width(a.maxWidth));var s=[o.innerWidth(),o.innerHeight()],f=o.find(l[1]).outerHeight()||0,c=o.find("."+l[6]).outerHeight()||0,u=function(e){e=o.find(e),e.height(s[1]-f-c-2*(0|parseFloat(e.css("padding-top"))))};switch(a.type){case 2:u("iframe");break;default:""===a.area[1]?a.maxHeight>0&&o.outerHeight()>a.maxHeight?(s[1]=a.maxHeight,u("."+l[5])):a.fixed&&s[1]>=n.height()&&(s[1]=n.height(),u("."+l[5])):u("."+l[5])}return t},s.pt.offset=function(){var e=this,t=e.config,i=e.layero,a=[i.outerWidth(),i.outerHeight()],o="object"==typeof t.offset;e.offsetTop=(n.height()-a[1])/2,e.offsetLeft=(n.width()-a[0])/2,o?(e.offsetTop=t.offset[0],e.offsetLeft=t.offset[1]||e.offsetLeft):"auto"!==t.offset&&("t"===t.offset?e.offsetTop=0:"r"===t.offset?e.offsetLeft=n.width()-a[0]:"b"===t.offset?e.offsetTop=n.height()-a[1]:"l"===t.offset?e.offsetLeft=0:"lt"===t.offset?(e.offsetTop=0,e.offsetLeft=0):"lb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=0):"rt"===t.offset?(e.offsetTop=0,e.offsetLeft=n.width()-a[0]):"rb"===t.offset?(e.offsetTop=n.height()-a[1],e.offsetLeft=n.width()-a[0]):e.offsetTop=t.offset),t.fixed||(e.offsetTop=/%$/.test(e.offsetTop)?n.height()*parseFloat(e.offsetTop)/100:parseFloat(e.offsetTop),e.offsetLeft=/%$/.test(e.offsetLeft)?n.width()*parseFloat(e.offsetLeft)/100:parseFloat(e.offsetLeft),e.offsetTop+=n.scrollTop(),e.offsetLeft+=n.scrollLeft()),i.attr("minLeft")&&(e.offsetTop=n.height()-(i.find(l[1]).outerHeight()||0),e.offsetLeft=i.css("left")),i.css({top:e.offsetTop,left:e.offsetLeft})},s.pt.tips=function(){var e=this,t=e.config,a=e.layero,o=[a.outerWidth(),a.outerHeight()],r=i(t.follow);r[0]||(r=i("body"));var s={width:r.outerWidth(),height:r.outerHeight(),top:r.offset().top,left:r.offset().left},f=a.find(".layui-layer-TipsG"),c=t.tips[0];t.tips[1]||f.remove(),s.autoLeft=function(){s.left+o[0]-n.width()>0?(s.tipLeft=s.left+s.width-o[0],f.css({right:12,left:"auto"})):s.tipLeft=s.left},s.where=[function(){s.autoLeft(),s.tipTop=s.top-o[1]-10,f.removeClass("layui-layer-TipsB").addClass("layui-layer-TipsT").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left+s.width+10,s.tipTop=s.top,f.removeClass("layui-layer-TipsL").addClass("layui-layer-TipsR").css("border-bottom-color",t.tips[1])},function(){s.autoLeft(),s.tipTop=s.top+s.height+10,f.removeClass("layui-layer-TipsT").addClass("layui-layer-TipsB").css("border-right-color",t.tips[1])},function(){s.tipLeft=s.left-o[0]-10,s.tipTop=s.top,f.removeClass("layui-layer-TipsR").addClass("layui-layer-TipsL").css("border-bottom-color",t.tips[1])}],s.where[c-1](),1===c?s.top-(n.scrollTop()+o[1]+16)<0&&s.where[2]():2===c?n.width()-(s.left+s.width+o[0]+16)>0||s.where[3]():3===c?s.top-n.scrollTop()+s.height+o[1]+16-n.height()>0&&s.where[0]():4===c&&o[0]+16-s.left>0&&s.where[1](),a.find("."+l[5]).css({"background-color":t.tips[1],"padding-right":t.closeBtn?"30px":""}),a.css({left:s.tipLeft-(t.fixed?n.scrollLeft():0),top:s.tipTop-(t.fixed?n.scrollTop():0)})},s.pt.move=function(){var e=this,t=e.config,a=i(document),s=e.layero,l=s.find(t.move),f=s.find(".layui-layer-resize"),c={};return t.move&&l.css("cursor","move"),l.on("mousedown",function(e){e.preventDefault(),t.move&&(c.moveStart=!0,c.offset=[e.clientX-parseFloat(s.css("left")),e.clientY-parseFloat(s.css("top"))],o.moveElem.css("cursor","move").show())}),f.on("mousedown",function(e){e.preventDefault(),c.resizeStart=!0,c.offset=[e.clientX,e.clientY],c.area=[s.outerWidth(),s.outerHeight()],o.moveElem.css("cursor","se-resize").show()}),a.on("mousemove",function(i){if(c.moveStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1],l="fixed"===s.css("position");if(i.preventDefault(),c.stX=l?0:n.scrollLeft(),c.stY=l?0:n.scrollTop(),!t.moveOut){var f=n.width()-s.outerWidth()+c.stX,u=n.height()-s.outerHeight()+c.stY;af&&(a=f),ou&&(o=u)}s.css({left:a,top:o})}if(t.resize&&c.resizeStart){var a=i.clientX-c.offset[0],o=i.clientY-c.offset[1];i.preventDefault(),r.style(e.index,{width:c.area[0]+a,height:c.area[1]+o}),c.isResize=!0,t.resizing&&t.resizing(s)}}).on("mouseup",function(e){c.moveStart&&(delete c.moveStart,o.moveElem.hide(),t.moveEnd&&t.moveEnd(s)),c.resizeStart&&(delete c.resizeStart,o.moveElem.hide())}),e},s.pt.callback=function(){function e(){var e=a.cancel&&a.cancel(t.index,n);e===!1||r.close(t.index)}var t=this,n=t.layero,a=t.config;t.openLayer(),a.success&&(2==a.type?n.find("iframe").on("load",function(){a.success(n,t.index)}):a.success(n,t.index)),6==r.ie&&t.IE6(n),n.find("."+l[6]).children("a").on("click",function(){var e=i(this).index();if(0===e)a.yes?a.yes(t.index,n):a.btn1?a.btn1(t.index,n):r.close(t.index);else{var o=a["btn"+(e+1)]&&a["btn"+(e+1)](t.index,n);o===!1||r.close(t.index)}}),n.find("."+l[7]).on("click",e),a.shadeClose&&i("#layui-layer-shade"+t.index).on("click",function(){r.close(t.index)}),n.find(".layui-layer-min").on("click",function(){var e=a.min&&a.min(n);e===!1||r.min(t.index,a)}),n.find(".layui-layer-max").on("click",function(){i(this).hasClass("layui-layer-maxmin")?(r.restore(t.index),a.restore&&a.restore(n)):(r.full(t.index,a),setTimeout(function(){a.full&&a.full(n)},100))}),a.end&&(o.end[t.index]=a.end)},o.reselect=function(){i.each(i("select"),function(e,t){var n=i(this);n.parents("."+l[0])[0]||1==n.attr("layer")&&i("."+l[0]).length<1&&n.removeAttr("layer").show(),n=null})},s.pt.IE6=function(e){i("select").each(function(e,t){var n=i(this);n.parents("."+l[0])[0]||"none"===n.css("display")||n.attr({layer:"1"}).hide(),n=null})},s.pt.openLayer=function(){var e=this;r.zIndex=e.config.zIndex,r.setTop=function(e){var t=function(){r.zIndex++,e.css("z-index",r.zIndex+1)};return r.zIndex=parseInt(e[0].style.zIndex),e.on("mousedown",t),r.zIndex}},o.record=function(e){var t=[e.width(),e.height(),e.position().top,e.position().left+parseFloat(e.css("margin-left"))];e.find(".layui-layer-max").addClass("layui-layer-maxmin"),e.attr({area:t})},o.rescollbar=function(e){l.html.attr("layer-full")==e&&(l.html[0].style.removeProperty?l.html[0].style.removeProperty("overflow"):l.html[0].style.removeAttribute("overflow"),l.html.removeAttr("layer-full"))},e.layer=r,r.getChildFrame=function(e,t){return t=t||i("."+l[4]).attr("times"),i("#"+l[0]+t).find("iframe").contents().find(e)},r.getFrameIndex=function(e){return i("#"+e).parents("."+l[4]).attr("times")},r.iframeAuto=function(e){if(e){var t=r.getChildFrame("html",e).outerHeight(),n=i("#"+l[0]+e),a=n.find(l[1]).outerHeight()||0,o=n.find("."+l[6]).outerHeight()||0;n.css({height:t+a+o}),n.find("iframe").css({height:t})}},r.iframeSrc=function(e,t){i("#"+l[0]+e).find("iframe").attr("src",t)},r.style=function(e,t,n){var a=i("#"+l[0]+e),r=a.find(".layui-layer-content"),s=a.attr("type"),f=a.find(l[1]).outerHeight()||0,c=a.find("."+l[6]).outerHeight()||0;a.attr("minLeft");s!==o.type[3]&&s!==o.type[4]&&(n||(parseFloat(t.width)<=260&&(t.width=260),parseFloat(t.height)-f-c<=64&&(t.height=64+f+c)),a.css(t),c=a.find("."+l[6]).outerHeight(),s===o.type[2]?a.find("iframe").css({height:parseFloat(t.height)-f-c}):r.css({height:parseFloat(t.height)-f-c-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom"))}))},r.min=function(e,t){var a=i("#"+l[0]+e),s=a.find(l[1]).outerHeight()||0,f=a.attr("minLeft")||181*o.minIndex+"px",c=a.css("position");o.record(a),o.minLeft[0]&&(f=o.minLeft[0],o.minLeft.shift()),a.attr("position",c),r.style(e,{width:180,height:s,left:f,top:n.height()-s,position:"fixed",overflow:"hidden"},!0),a.find(".layui-layer-min").hide(),"page"===a.attr("type")&&a.find(l[4]).hide(),o.rescollbar(e),a.attr("minLeft")||o.minIndex++,a.attr("minLeft",f)},r.restore=function(e){var t=i("#"+l[0]+e),n=t.attr("area").split(",");t.attr("type");r.style(e,{width:parseFloat(n[0]),height:parseFloat(n[1]),top:parseFloat(n[2]),left:parseFloat(n[3]),position:t.attr("position"),overflow:"visible"},!0),t.find(".layui-layer-max").removeClass("layui-layer-maxmin"),t.find(".layui-layer-min").show(),"page"===t.attr("type")&&t.find(l[4]).show(),o.rescollbar(e)},r.full=function(e){var t,a=i("#"+l[0]+e);o.record(a),l.html.attr("layer-full")||l.html.css("overflow","hidden").attr("layer-full",e),clearTimeout(t),t=setTimeout(function(){var t="fixed"===a.css("position");r.style(e,{top:t?0:n.scrollTop(),left:t?0:n.scrollLeft(),width:n.width(),height:n.height()},!0),a.find(".layui-layer-min").hide()},100)},r.title=function(e,t){var n=i("#"+l[0]+(t||r.index)).find(l[1]);n.html(e)},r.close=function(e){var t=i("#"+l[0]+e),n=t.attr("type"),a="layer-anim-close";if(t[0]){var s="layui-layer-wrap",f=function(){if(n===o.type[1]&&"object"===t.attr("conType")){t.children(":not(."+l[5]+")").remove();for(var a=t.find("."+s),r=0;r<2;r++)a.unwrap();a.css("display",a.data("display")).removeClass(s)}else{if(n===o.type[2])try{var f=i("#"+l[4]+e)[0];f.contentWindow.document.write(""),f.contentWindow.close(),t.find("."+l[5])[0].removeChild(f)}catch(c){}t[0].innerHTML="",t.remove()}"function"==typeof o.end[e]&&o.end[e](),delete o.end[e]};t.data("isOutAnim")&&t.addClass("layer-anim "+a),i("#layui-layer-moves, #layui-layer-shade"+e).remove(),6==r.ie&&o.reselect(),o.rescollbar(e),t.attr("minLeft")&&(o.minIndex--,o.minLeft.push(t.attr("minLeft"))),r.ie&&r.ie<10||!t.data("isOutAnim")?f():setTimeout(function(){f()},200)}},r.closeAll=function(e){i.each(i("."+l[0]),function(){var t=i(this),n=e?t.attr("type")===e:1;n&&r.close(t.attr("times")),n=null})};var f=r.cache||{},c=function(e){return f.skin?" "+f.skin+" "+f.skin+"-"+e:""};r.prompt=function(e,t){var a="";if(e=e||{},"function"==typeof e&&(t=e),e.area){var o=e.area;a='style="width: '+o[0]+"; height: "+o[1]+';"',delete e.area}var s,l=2==e.formType?'":function(){return''}(),f=e.success;return delete e.success,r.open(i.extend({type:1,btn:["确定","取消"],content:l,skin:"layui-layer-prompt"+c("prompt"),maxWidth:n.width(),success:function(t){s=t.find(".layui-layer-input"),s.val(e.value||"").focus(),"function"==typeof f&&f(t)},resize:!1,yes:function(i){var n=s.val();""===n?s.focus():n.length>(e.maxlength||500)?r.tips("最多输入"+(e.maxlength||500)+"个字数",s,{tips:1}):t&&t(n,i,s)}},e))},r.tab=function(e){e=e||{};var t=e.tab||{},n="layui-this",a=e.success;return delete e.success,r.open(i.extend({type:1,skin:"layui-layer-tab"+c("tab"),resize:!1,title:function(){var e=t.length,i=1,a="";if(e>0)for(a=''+t[0].title+"";i"+t[i].title+"";return a}(),content:'
      '+function(){var e=t.length,i=1,a="";if(e>0)for(a='
    • '+(t[0].content||"no content")+"
    • ";i'+(t[i].content||"no content")+"";return a}()+"
    ",success:function(t){var o=t.find(".layui-layer-title").children(),r=t.find(".layui-layer-tabmain").children();o.on("mousedown",function(t){t.stopPropagation?t.stopPropagation():t.cancelBubble=!0;var a=i(this),o=a.index();a.addClass(n).siblings().removeClass(n),r.eq(o).show().siblings().hide(),"function"==typeof e.change&&e.change(o)}),"function"==typeof a&&a(t)}},e))},r.photos=function(t,n,a){function o(e,t,i){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,t(n)},void(n.onerror=function(e){n.onerror=null,i(e)}))}var s={};if(t=t||{},t.photos){var l=t.photos.constructor===Object,f=l?t.photos:{},u=f.data||[],d=f.start||0;s.imgIndex=(0|d)+1,t.img=t.img||"img";var y=t.success;if(delete t.success,l){if(0===u.length)return r.msg("没有图片")}else{var p=i(t.photos),h=function(){u=[],p.find(t.img).each(function(e){var t=i(this);t.attr("layer-index",e),u.push({alt:t.attr("alt"),pid:t.attr("layer-pid"),src:t.attr("layer-src")||t.attr("src"),thumb:t.attr("src")})})};if(h(),0===u.length)return;if(n||p.on("click",t.img,function(){var e=i(this),n=e.attr("layer-index");r.photos(i.extend(t,{photos:{start:n,data:u,tab:t.tab},full:t.full}),!0),h()}),!n)return}s.imgprev=function(e){s.imgIndex--,s.imgIndex<1&&(s.imgIndex=u.length),s.tabimg(e)},s.imgnext=function(e,t){s.imgIndex++,s.imgIndex>u.length&&(s.imgIndex=1,t)||s.tabimg(e)},s.keyup=function(e){if(!s.end){var t=e.keyCode;e.preventDefault(),37===t?s.imgprev(!0):39===t?s.imgnext(!0):27===t&&r.close(s.index)}},s.tabimg=function(e){if(!(u.length<=1))return f.start=s.imgIndex-1,r.close(s.index),r.photos(t,!0,e)},s.event=function(){s.bigimg.hover(function(){s.imgsee.show()},function(){s.imgsee.hide()}),s.bigimg.find(".layui-layer-imgprev").on("click",function(e){e.preventDefault(),s.imgprev()}),s.bigimg.find(".layui-layer-imgnext").on("click",function(e){e.preventDefault(),s.imgnext()}),i(document).on("keyup",s.keyup)},s.loadi=r.load(1,{shade:!("shade"in t)&&.9,scrollbar:!1}),o(u[d].src,function(n){r.close(s.loadi),s.index=r.open(i.extend({type:1,id:"layui-layer-photos",area:function(){var a=[n.width,n.height],o=[i(e).width()-100,i(e).height()-100];if(!t.full&&(a[0]>o[0]||a[1]>o[1])){var r=[a[0]/o[0],a[1]/o[1]];r[0]>r[1]?(a[0]=a[0]/r[0],a[1]=a[1]/r[0]):r[0]'+(u[d].alt||
    '+(u.length>1?'':"")+'
    '+(u[d].alt||"")+""+s.imgIndex+"/"+u.length+"
    ",success:function(e,i){s.bigimg=e.find(".layui-layer-phimg"),s.imgsee=e.find(".layui-layer-imguide,.layui-layer-imgbar"),s.event(e),t.tab&&t.tab(u[d],e),"function"==typeof y&&y(e)},end:function(){s.end=!0,i(document).off("keyup",s.keyup)}},t))},function(){r.close(s.loadi),r.msg("当前图片地址异常
    是否继续查看下一张?",{time:3e4,btn:["下一张","不看了"],yes:function(){u.length>1&&s.imgnext(!0,!0)}})})}},o.run=function(t){i=t,n=i(e),l.html=i("html"),r.open=function(e){var t=new s(e);return t.index}},e.layui&&layui.define?(r.ready(),layui.define("jquery",function(t){r.path=layui.cache.dir,o.run(layui.$),e.layer=r,t("layer",r)})):"function"==typeof define&&define.amd?define(["jquery"],function(){return o.run(e.jQuery),r}):function(){o.run(e.jQuery),r.ready()}()}(window); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/laypage.js b/static/admin/js/layui/lay/modules/laypage.js index 98690ac..fd07fda 100755 --- a/static/admin/js/layui/lay/modules/laypage.js +++ b/static/admin/js/layui/lay/modules/laypage.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define(function(e){"use strict";var a=document,t="getElementById",n="getElementsByTagName",i="laypage",r="layui-disabled",u=function(e){var a=this;a.config=e||{},a.config.index=++s.index,a.render(!0)};u.prototype.type=function(){var e=this.config;if("object"==typeof e.elem)return void 0===e.elem.length?2:3},u.prototype.view=function(){var e=this,a=e.config,t=a.groups="groups"in a?0|a.groups:5;a.layout="object"==typeof a.layout?a.layout:["prev","page","next"],a.count=0|a.count,a.curr=0|a.curr||1,a.limits="object"==typeof a.limits?a.limits:[10,20,30,40,50],a.limit=0|a.limit||10,a.pages=Math.ceil(a.count/a.limit)||1,a.curr>a.pages&&(a.curr=a.pages),t<0?t=1:t>a.pages&&(t=a.pages),a.prev="prev"in a?a.prev:"上一页",a.next="next"in a?a.next:"下一页";var n=a.pages>t?Math.ceil((a.curr+(t>1?1:0))/(t>0?t:1)):1,i={prev:function(){return a.prev?''+a.prev+"":""}(),page:function(){var e=[];if(a.count<1)return"";n>1&&a.first!==!1&&0!==t&&e.push(''+(a.first||1)+"");var i=Math.floor((t-1)/2),r=n>1?a.curr-i:1,u=n>1?function(){var e=a.curr+(t-i-1);return e>a.pages?a.pages:e}():t;for(u-r2&&e.push('');r<=u;r++)r===a.curr?e.push('"+r+""):e.push(''+r+"");return a.pages>t&&a.pages>u&&a.last!==!1&&(u+1…'),0!==t&&e.push(''+(a.last||a.pages)+"")),e.join("")}(),next:function(){return a.next?''+a.next+"":""}(),count:'共 '+a.count+" 条",limit:function(){var e=['"}(),refresh:['','',""].join(""),skip:function(){return['到第','','页',""].join("")}()};return['
    ',function(){var e=[];return layui.each(a.layout,function(a,t){i[t]&&e.push(i[t])}),e.join("")}(),"
    "].join("")},u.prototype.jump=function(e,a){if(e){var t=this,i=t.config,r=e.children,u=e[n]("button")[0],l=e[n]("input")[0],p=e[n]("select")[0],c=function(){var e=0|l.value.replace(/\s|\D/g,"");e&&(i.curr=e,t.render())};if(a)return c();for(var o=0,y=r.length;oi.pages||(i.curr=e,t.render())});p&&s.on(p,"change",function(){var e=this.value;i.curr*e>i.count&&(i.curr=Math.ceil(i.count/e)),i.limit=e,t.render()}),u&&s.on(u,"click",function(){c()})}},u.prototype.skip=function(e){if(e){var a=this,t=e[n]("input")[0];t&&s.on(t,"keyup",function(t){var n=this.value,i=t.keyCode;/^(37|38|39|40)$/.test(i)||(/\D/.test(n)&&(this.value=n.replace(/\D/,"")),13===i&&a.jump(e,!0))})}},u.prototype.render=function(e){var n=this,i=n.config,r=n.type(),u=n.view();2===r?i.elem&&(i.elem.innerHTML=u):3===r?i.elem.html(u):a[t](i.elem)&&(a[t](i.elem).innerHTML=u),i.jump&&i.jump(i,e);var s=a[t]("layui-laypage-"+i.index);n.jump(s),i.hash&&!e&&(location.hash="!"+i.hash+"="+i.curr),n.skip(s)};var s={render:function(e){var a=new u(e);return a.index},index:layui.laypage?layui.laypage.index+1e4:0,on:function(e,a,t){return e.attachEvent?e.attachEvent("on"+a,function(a){a.target=a.srcElement,t.call(e,a)}):e.addEventListener(a,t,!1),this}};e(i,s)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/laytpl.js b/static/admin/js/layui/lay/modules/laytpl.js index 6d8a36a..d482d4e 100755 --- a/static/admin/js/layui/lay/modules/laytpl.js +++ b/static/admin/js/layui/lay/modules/laytpl.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/mobile.js b/static/admin/js/layui/lay/modules/mobile.js index 8756d08..35cfdf0 100755 --- a/static/admin/js/layui/lay/modules/mobile.js +++ b/static/admin/js/layui/lay/modules/mobile.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define(function(i){i("layui.mobile",layui.v)});layui.define(function(e){"use strict";var r={open:"{{",close:"}}"},c={exp:function(e){return new RegExp(e,"g")},query:function(e,c,t){var o=["#([\\s\\S])+?","([^{#}])*?"][e||0];return n((c||"")+r.open+o+r.close+(t||""))},escape:function(e){return String(e||"").replace(/&(?!#?[a-zA-Z0-9]+;)/g,"&").replace(//g,">").replace(/'/g,"'").replace(/"/g,""")},error:function(e,r){var c="Laytpl Error:";return"object"==typeof console&&console.error(c+e+"\n"+(r||"")),c+e}},n=c.exp,t=function(e){this.tpl=e};t.pt=t.prototype,window.errors=0,t.pt.parse=function(e,t){var o=this,p=e,a=n("^"+r.open+"#",""),l=n(r.close+"$","");e=e.replace(/\s+|\r|\t|\n/g," ").replace(n(r.open+"#"),r.open+"# ").replace(n(r.close+"}"),"} "+r.close).replace(/\\/g,"\\\\").replace(n(r.open+"!(.+?)!"+r.close),function(e){return e=e.replace(n("^"+r.open+"!"),"").replace(n("!"+r.close),"").replace(n(r.open+"|"+r.close),function(e){return e.replace(/(.)/g,"\\$1")})}).replace(/(?="|')/g,"\\").replace(c.query(),function(e){return e=e.replace(a,"").replace(l,""),'";'+e.replace(/\\/g,"")+';view+="'}).replace(c.query(1),function(e){var c='"+(';return e.replace(/\s/g,"")===r.open+r.close?"":(e=e.replace(n(r.open+"|"+r.close),""),/^=/.test(e)&&(e=e.replace(/^=/,""),c='"+_escape_('),c+e.replace(/\\/g,"")+')+"')}),e='"use strict";var view = "'+e+'";return view;';try{return o.cache=e=new Function("d, _escape_",e),e(t,c.escape)}catch(u){return delete o.cache,c.error(u,p)}},t.pt.render=function(e,r){var n,t=this;return e?(n=t.cache?t.cache(e,c.escape):t.parse(t.tpl,e),r?void r(n):n):c.error("no data")};var o=function(e){return"string"!=typeof e?c.error("Template not found"):new t(e)};o.config=function(e){e=e||{};for(var c in e)r[c]=e[c]},o.v="1.2.0",e("laytpl",o)});layui.define(function(e){"use strict";var t=(window,document),i="querySelectorAll",n="getElementsByClassName",a=function(e){return t[i](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var i in e)t[i]=e[i];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var o=0,r=["layui-m-layer"],d=function(e){var t=this;t.config=l.extend(e),t.view()};d.prototype.view=function(){var e=this,i=e.config,s=t.createElement("div");e.id=s.id=r[0]+o,s.setAttribute("class",r[0]+" "+r[0]+(i.type||0)),s.setAttribute("index",o);var l=function(){var e="object"==typeof i.title;return i.title?'

    '+(e?i.title[0]:i.title)+"

    ":""}(),d=function(){"string"==typeof i.btn&&(i.btn=[i.btn]);var e,t=(i.btn||[]).length;return 0!==t&&i.btn?(e=''+i.btn[0]+"",2===t&&(e=''+i.btn[1]+""+e),'
    '+e+"
    "):""}();if(i.fixed||(i.top=i.hasOwnProperty("top")?i.top:100,i.style=i.style||"",i.style+=" top:"+(t.body.scrollTop+i.top)+"px"),2===i.type&&(i.content='

    '+(i.content||"")+"

    "),i.skin&&(i.anim="up"),"msg"===i.skin&&(i.shade=!1),s.innerHTML=(i.shade?"
    ':"")+'
    "+l+'
    '+i.content+"
    "+d+"
    ",!i.type||2===i.type){var y=t[n](r[0]+i.type),u=y.length;u>=1&&c.close(y[0].getAttribute("index"))}document.body.appendChild(s);var m=e.elem=a("#"+e.id)[0];i.success&&i.success(m),e.index=o++,e.action(i,m)},d.prototype.action=function(e,t){var i=this;e.time&&(l.timer[i.index]=setTimeout(function(){c.close(i.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),c.close(i.index)):e.yes?e.yes(i.index):c.close(i.index)};if(e.btn)for(var s=t[n]("layui-m-layerbtn")[0].children,o=s.length,r=0;r0&&e-1 in t)}function s(t){return A.call(t,function(t){return null!=t})}function u(t){return t.length>0?T.fn.concat.apply([],t):t}function c(t){return t.replace(/::/g,"/").replace(/([A-Z]+)([A-Z][a-z])/g,"$1_$2").replace(/([a-z\d])([A-Z])/g,"$1_$2").replace(/_/g,"-").toLowerCase()}function l(t){return t in F?F[t]:F[t]=new RegExp("(^|\\s)"+t+"(\\s|$)")}function f(t,e){return"number"!=typeof e||k[c(t)]?e:e+"px"}function h(t){var e,n;return $[t]||(e=L.createElement(t),L.body.appendChild(e),n=getComputedStyle(e,"").getPropertyValue("display"),e.parentNode.removeChild(e),"none"==n&&(n="block"),$[t]=n),$[t]}function p(t){return"children"in t?D.call(t.children):T.map(t.childNodes,function(t){if(1==t.nodeType)return t})}function d(t,e){var n,r=t?t.length:0;for(n=0;n]*>/,R=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,z=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,Z=/^(?:body|html)$/i,q=/([A-Z])/g,H=["val","css","html","text","data","width","height","offset"],I=["after","prepend","before","append"],V=L.createElement("table"),_=L.createElement("tr"),B={tr:L.createElement("tbody"),tbody:V,thead:V,tfoot:V,td:_,th:_,"*":L.createElement("div")},U=/complete|loaded|interactive/,X=/^[\w-]*$/,J={},W=J.toString,Y={},G=L.createElement("div"),K={tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},Q=Array.isArray||function(t){return t instanceof Array};return Y.matches=function(t,e){if(!e||!t||1!==t.nodeType)return!1;var n=t.matches||t.webkitMatchesSelector||t.mozMatchesSelector||t.oMatchesSelector||t.matchesSelector;if(n)return n.call(t,e);var r,i=t.parentNode,o=!i;return o&&(i=G).appendChild(t),r=~Y.qsa(i,e).indexOf(t),o&&G.removeChild(t),r},C=function(t){return t.replace(/-+(.)?/g,function(t,e){return e?e.toUpperCase():""})},N=function(t){return A.call(t,function(e,n){return t.indexOf(e)==n})},Y.fragment=function(t,e,n){var r,i,a;return R.test(t)&&(r=T(L.createElement(RegExp.$1))),r||(t.replace&&(t=t.replace(z,"<$1>")),e===E&&(e=M.test(t)&&RegExp.$1),e in B||(e="*"),a=B[e],a.innerHTML=""+t,r=T.each(D.call(a.childNodes),function(){a.removeChild(this)})),o(n)&&(i=T(r),T.each(n,function(t,e){H.indexOf(t)>-1?i[t](e):i.attr(t,e)})),r},Y.Z=function(t,e){return new d(t,e)},Y.isZ=function(t){return t instanceof Y.Z},Y.init=function(t,n){var r;if(!t)return Y.Z();if("string"==typeof t)if(t=t.trim(),"<"==t[0]&&M.test(t))r=Y.fragment(t,RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}else{if(e(t))return T(L).ready(t);if(Y.isZ(t))return t;if(Q(t))r=s(t);else if(i(t))r=[t],t=null;else if(M.test(t))r=Y.fragment(t.trim(),RegExp.$1,n),t=null;else{if(n!==E)return T(n).find(t);r=Y.qsa(L,t)}}return Y.Z(r,t)},T=function(t,e){return Y.init(t,e)},T.extend=function(t){var e,n=D.call(arguments,1);return"boolean"==typeof t&&(e=t,t=n.shift()),n.forEach(function(n){m(t,n,e)}),t},Y.qsa=function(t,e){var n,r="#"==e[0],i=!r&&"."==e[0],o=r||i?e.slice(1):e,a=X.test(o);return t.getElementById&&a&&r?(n=t.getElementById(o))?[n]:[]:1!==t.nodeType&&9!==t.nodeType&&11!==t.nodeType?[]:D.call(a&&!r&&t.getElementsByClassName?i?t.getElementsByClassName(o):t.getElementsByTagName(e):t.querySelectorAll(e))},T.contains=L.documentElement.contains?function(t,e){return t!==e&&t.contains(e)}:function(t,e){for(;e&&(e=e.parentNode);)if(e===t)return!0;return!1},T.type=t,T.isFunction=e,T.isWindow=n,T.isArray=Q,T.isPlainObject=o,T.isEmptyObject=function(t){var e;for(e in t)return!1;return!0},T.isNumeric=function(t){var e=Number(t),n=typeof t;return null!=t&&"boolean"!=n&&("string"!=n||t.length)&&!isNaN(e)&&isFinite(e)||!1},T.inArray=function(t,e,n){return O.indexOf.call(e,t,n)},T.camelCase=C,T.trim=function(t){return null==t?"":String.prototype.trim.call(t)},T.uuid=0,T.support={},T.expr={},T.noop=function(){},T.map=function(t,e){var n,r,i,o=[];if(a(t))for(r=0;r=0?t:t+this.length]},toArray:function(){return this.get()},size:function(){return this.length},remove:function(){return this.each(function(){null!=this.parentNode&&this.parentNode.removeChild(this)})},each:function(t){return O.every.call(this,function(e,n){return t.call(e,n,e)!==!1}),this},filter:function(t){return e(t)?this.not(this.not(t)):T(A.call(this,function(e){return Y.matches(e,t)}))},add:function(t,e){return T(N(this.concat(T(t,e))))},is:function(t){return this.length>0&&Y.matches(this[0],t)},not:function(t){var n=[];if(e(t)&&t.call!==E)this.each(function(e){t.call(this,e)||n.push(this)});else{var r="string"==typeof t?this.filter(t):a(t)&&e(t.item)?D.call(t):T(t);this.forEach(function(t){r.indexOf(t)<0&&n.push(t)})}return T(n)},has:function(t){return this.filter(function(){return i(t)?T.contains(this,t):T(this).find(t).size()})},eq:function(t){return t===-1?this.slice(t):this.slice(t,+t+1)},first:function(){var t=this[0];return t&&!i(t)?t:T(t)},last:function(){var t=this[this.length-1];return t&&!i(t)?t:T(t)},find:function(t){var e,n=this;return e=t?"object"==typeof t?T(t).filter(function(){var t=this;return O.some.call(n,function(e){return T.contains(e,t)})}):1==this.length?T(Y.qsa(this[0],t)):this.map(function(){return Y.qsa(this,t)}):T()},closest:function(t,e){var n=[],i="object"==typeof t&&T(t);return this.each(function(o,a){for(;a&&!(i?i.indexOf(a)>=0:Y.matches(a,t));)a=a!==e&&!r(a)&&a.parentNode;a&&n.indexOf(a)<0&&n.push(a)}),T(n)},parents:function(t){for(var e=[],n=this;n.length>0;)n=T.map(n,function(t){if((t=t.parentNode)&&!r(t)&&e.indexOf(t)<0)return e.push(t),t});return v(e,t)},parent:function(t){return v(N(this.pluck("parentNode")),t)},children:function(t){return v(this.map(function(){return p(this)}),t)},contents:function(){return this.map(function(){return this.contentDocument||D.call(this.childNodes)})},siblings:function(t){return v(this.map(function(t,e){return A.call(p(e.parentNode),function(t){return t!==e})}),t)},empty:function(){return this.each(function(){this.innerHTML=""})},pluck:function(t){return T.map(this,function(e){return e[t]})},show:function(){return this.each(function(){"none"==this.style.display&&(this.style.display=""),"none"==getComputedStyle(this,"").getPropertyValue("display")&&(this.style.display=h(this.nodeName))})},replaceWith:function(t){return this.before(t).remove()},wrap:function(t){var n=e(t);if(this[0]&&!n)var r=T(t).get(0),i=r.parentNode||this.length>1;return this.each(function(e){T(this).wrapAll(n?t.call(this,e):i?r.cloneNode(!0):r)})},wrapAll:function(t){if(this[0]){T(this[0]).before(t=T(t));for(var e;(e=t.children()).length;)t=e.first();T(t).append(this)}return this},wrapInner:function(t){var n=e(t);return this.each(function(e){var r=T(this),i=r.contents(),o=n?t.call(this,e):t;i.length?i.wrapAll(o):r.append(o)})},unwrap:function(){return this.parent().each(function(){T(this).replaceWith(T(this).children())}),this},clone:function(){return this.map(function(){return this.cloneNode(!0)})},hide:function(){return this.css("display","none")},toggle:function(t){return this.each(function(){var e=T(this);(t===E?"none"==e.css("display"):t)?e.show():e.hide()})},prev:function(t){return T(this.pluck("previousElementSibling")).filter(t||"*")},next:function(t){return T(this.pluck("nextElementSibling")).filter(t||"*")},html:function(t){return 0 in arguments?this.each(function(e){var n=this.innerHTML;T(this).empty().append(g(this,t,e,n))}):0 in this?this[0].innerHTML:null},text:function(t){return 0 in arguments?this.each(function(e){var n=g(this,t,e,this.textContent);this.textContent=null==n?"":""+n}):0 in this?this.pluck("textContent").join(""):null},attr:function(t,e){var n;return"string"!=typeof t||1 in arguments?this.each(function(n){if(1===this.nodeType)if(i(t))for(j in t)y(this,j,t[j]);else y(this,t,g(this,e,n,this.getAttribute(t)))}):0 in this&&1==this[0].nodeType&&null!=(n=this[0].getAttribute(t))?n:E},removeAttr:function(t){return this.each(function(){1===this.nodeType&&t.split(" ").forEach(function(t){y(this,t)},this)})},prop:function(t,e){return t=K[t]||t,1 in arguments?this.each(function(n){this[t]=g(this,e,n,this[t])}):this[0]&&this[0][t]},removeProp:function(t){return t=K[t]||t,this.each(function(){delete this[t]})},data:function(t,e){var n="data-"+t.replace(q,"-$1").toLowerCase(),r=1 in arguments?this.attr(n,e):this.attr(n);return null!==r?b(r):E},val:function(t){return 0 in arguments?(null==t&&(t=""),this.each(function(e){this.value=g(this,t,e,this.value)})):this[0]&&(this[0].multiple?T(this[0]).find("option").filter(function(){return this.selected}).pluck("value"):this[0].value)},offset:function(t){if(t)return this.each(function(e){var n=T(this),r=g(this,t,e,n.offset()),i=n.offsetParent().offset(),o={top:r.top-i.top,left:r.left-i.left};"static"==n.css("position")&&(o.position="relative"),n.css(o)});if(!this.length)return null;if(L.documentElement!==this[0]&&!T.contains(L.documentElement,this[0]))return{top:0,left:0};var e=this[0].getBoundingClientRect();return{left:e.left+window.pageXOffset,top:e.top+window.pageYOffset,width:Math.round(e.width),height:Math.round(e.height)}},css:function(e,n){if(arguments.length<2){var r=this[0];if("string"==typeof e){if(!r)return;return r.style[C(e)]||getComputedStyle(r,"").getPropertyValue(e)}if(Q(e)){if(!r)return;var i={},o=getComputedStyle(r,"");return T.each(e,function(t,e){i[e]=r.style[C(e)]||o.getPropertyValue(e)}),i}}var a="";if("string"==t(e))n||0===n?a=c(e)+":"+f(e,n):this.each(function(){this.style.removeProperty(c(e))});else for(j in e)e[j]||0===e[j]?a+=c(j)+":"+f(j,e[j])+";":this.each(function(){this.style.removeProperty(c(j))});return this.each(function(){this.style.cssText+=";"+a})},index:function(t){return t?this.indexOf(T(t)[0]):this.parent().children().indexOf(this[0])},hasClass:function(t){return!!t&&O.some.call(this,function(t){return this.test(x(t))},l(t))},addClass:function(t){return t?this.each(function(e){if("className"in this){S=[];var n=x(this),r=g(this,t,e,n);r.split(/\s+/g).forEach(function(t){T(this).hasClass(t)||S.push(t)},this),S.length&&x(this,n+(n?" ":"")+S.join(" "))}}):this},removeClass:function(t){return this.each(function(e){if("className"in this){if(t===E)return x(this,"");S=x(this),g(this,t,e,S).split(/\s+/g).forEach(function(t){S=S.replace(l(t)," ")}),x(this,S.trim())}})},toggleClass:function(t,e){return t?this.each(function(n){var r=T(this),i=g(this,t,n,x(this));i.split(/\s+/g).forEach(function(t){(e===E?!r.hasClass(t):e)?r.addClass(t):r.removeClass(t)})}):this},scrollTop:function(t){if(this.length){var e="scrollTop"in this[0];return t===E?e?this[0].scrollTop:this[0].pageYOffset:this.each(e?function(){this.scrollTop=t}:function(){this.scrollTo(this.scrollX,t)})}},scrollLeft:function(t){if(this.length){var e="scrollLeft"in this[0];return t===E?e?this[0].scrollLeft:this[0].pageXOffset:this.each(e?function(){this.scrollLeft=t}:function(){this.scrollTo(t,this.scrollY)})}},position:function(){if(this.length){var t=this[0],e=this.offsetParent(),n=this.offset(),r=Z.test(e[0].nodeName)?{top:0,left:0}:e.offset();return n.top-=parseFloat(T(t).css("margin-top"))||0,n.left-=parseFloat(T(t).css("margin-left"))||0,r.top+=parseFloat(T(e[0]).css("border-top-width"))||0,r.left+=parseFloat(T(e[0]).css("border-left-width"))||0,{top:n.top-r.top,left:n.left-r.left}}},offsetParent:function(){return this.map(function(){for(var t=this.offsetParent||L.body;t&&!Z.test(t.nodeName)&&"static"==T(t).css("position");)t=t.offsetParent;return t})}},T.fn.detach=T.fn.remove,["width","height"].forEach(function(t){var e=t.replace(/./,function(t){return t[0].toUpperCase()});T.fn[t]=function(i){var o,a=this[0];return i===E?n(a)?a["inner"+e]:r(a)?a.documentElement["scroll"+e]:(o=this.offset())&&o[t]:this.each(function(e){a=T(this),a.css(t,g(this,i,e,a[t]()))})}}),I.forEach(function(e,n){var r=n%2;T.fn[e]=function(){var e,i,o=T.map(arguments,function(n){var r=[];return e=t(n),"array"==e?(n.forEach(function(t){return t.nodeType!==E?r.push(t):T.zepto.isZ(t)?r=r.concat(t.get()):void(r=r.concat(Y.fragment(t)))}),r):"object"==e||null==n?n:Y.fragment(n)}),a=this.length>1;return o.length<1?this:this.each(function(t,e){i=r?e:e.parentNode,e=0==n?e.nextSibling:1==n?e.firstChild:2==n?e:null;var s=T.contains(L.documentElement,i);o.forEach(function(t){if(a)t=t.cloneNode(!0);else if(!i)return T(t).remove();i.insertBefore(t,e),s&&w(t,function(t){if(!(null==t.nodeName||"SCRIPT"!==t.nodeName.toUpperCase()||t.type&&"text/javascript"!==t.type||t.src)){var e=t.ownerDocument?t.ownerDocument.defaultView:window;e.eval.call(e,t.innerHTML)}})})})},T.fn[r?e+"To":"insert"+(n?"Before":"After")]=function(t){return T(t)[e](this),this}}),Y.Z.prototype=d.prototype=T.fn,Y.uniq=N,Y.deserializeValue=b,T.zepto=Y,T}();!function(t){function e(t){return t._zid||(t._zid=h++)}function n(t,n,o,a){if(n=r(n),n.ns)var s=i(n.ns);return(v[e(t)]||[]).filter(function(t){return t&&(!n.e||t.e==n.e)&&(!n.ns||s.test(t.ns))&&(!o||e(t.fn)===e(o))&&(!a||t.sel==a)})}function r(t){var e=(""+t).split(".");return{e:e[0],ns:e.slice(1).sort().join(" ")}}function i(t){return new RegExp("(?:^| )"+t.replace(" "," .* ?")+"(?: |$)")}function o(t,e){return t.del&&!y&&t.e in x||!!e}function a(t){return b[t]||y&&x[t]||t}function s(n,i,s,u,l,h,p){var d=e(n),m=v[d]||(v[d]=[]);i.split(/\s/).forEach(function(e){if("ready"==e)return t(document).ready(s);var i=r(e);i.fn=s,i.sel=l,i.e in b&&(s=function(e){var n=e.relatedTarget;if(!n||n!==this&&!t.contains(this,n))return i.fn.apply(this,arguments)}),i.del=h;var d=h||s;i.proxy=function(t){if(t=c(t),!t.isImmediatePropagationStopped()){t.data=u;var e=d.apply(n,t._args==f?[t]:[t].concat(t._args));return e===!1&&(t.preventDefault(),t.stopPropagation()),e}},i.i=m.length,m.push(i),"addEventListener"in n&&n.addEventListener(a(i.e),i.proxy,o(i,p))})}function u(t,r,i,s,u){var c=e(t);(r||"").split(/\s/).forEach(function(e){n(t,e,i,s).forEach(function(e){delete v[c][e.i],"removeEventListener"in t&&t.removeEventListener(a(e.e),e.proxy,o(e,u))})})}function c(e,n){return!n&&e.isDefaultPrevented||(n||(n=e),t.each(T,function(t,r){var i=n[t];e[t]=function(){return this[r]=w,i&&i.apply(n,arguments)},e[r]=E}),e.timeStamp||(e.timeStamp=Date.now()),(n.defaultPrevented!==f?n.defaultPrevented:"returnValue"in n?n.returnValue===!1:n.getPreventDefault&&n.getPreventDefault())&&(e.isDefaultPrevented=w)),e}function l(t){var e,n={originalEvent:t};for(e in t)j.test(e)||t[e]===f||(n[e]=t[e]);return c(n,t)}var f,h=1,p=Array.prototype.slice,d=t.isFunction,m=function(t){return"string"==typeof t},v={},g={},y="onfocusin"in window,x={focus:"focusin",blur:"focusout"},b={mouseenter:"mouseover",mouseleave:"mouseout"};g.click=g.mousedown=g.mouseup=g.mousemove="MouseEvents",t.event={add:s,remove:u},t.proxy=function(n,r){var i=2 in arguments&&p.call(arguments,2);if(d(n)){var o=function(){return n.apply(r,i?i.concat(p.call(arguments)):arguments)};return o._zid=e(n),o}if(m(r))return i?(i.unshift(n[r],n),t.proxy.apply(null,i)):t.proxy(n[r],n);throw new TypeError("expected function")},t.fn.bind=function(t,e,n){return this.on(t,e,n)},t.fn.unbind=function(t,e){return this.off(t,e)},t.fn.one=function(t,e,n,r){return this.on(t,e,n,r,1)};var w=function(){return!0},E=function(){return!1},j=/^([A-Z]|returnValue$|layer[XY]$|webkitMovement[XY]$)/,T={preventDefault:"isDefaultPrevented",stopImmediatePropagation:"isImmediatePropagationStopped",stopPropagation:"isPropagationStopped"};t.fn.delegate=function(t,e,n){return this.on(e,t,n)},t.fn.undelegate=function(t,e,n){return this.off(e,t,n)},t.fn.live=function(e,n){return t(document.body).delegate(this.selector,e,n),this},t.fn.die=function(e,n){return t(document.body).undelegate(this.selector,e,n),this},t.fn.on=function(e,n,r,i,o){var a,c,h=this;return e&&!m(e)?(t.each(e,function(t,e){h.on(t,n,r,e,o)}),h):(m(n)||d(i)||i===!1||(i=r,r=n,n=f),i!==f&&r!==!1||(i=r,r=f),i===!1&&(i=E),h.each(function(f,h){o&&(a=function(t){return u(h,t.type,i),i.apply(this,arguments)}),n&&(c=function(e){var r,o=t(e.target).closest(n,h).get(0);if(o&&o!==h)return r=t.extend(l(e),{currentTarget:o,liveFired:h}),(a||i).apply(o,[r].concat(p.call(arguments,1)))}),s(h,e,i,r,n,c||a)}))},t.fn.off=function(e,n,r){var i=this;return e&&!m(e)?(t.each(e,function(t,e){i.off(t,n,e)}),i):(m(n)||d(r)||r===!1||(r=n,n=f),r===!1&&(r=E),i.each(function(){u(this,e,r,n)}))},t.fn.trigger=function(e,n){return e=m(e)||t.isPlainObject(e)?t.Event(e):c(e),e._args=n,this.each(function(){e.type in x&&"function"==typeof this[e.type]?this[e.type]():"dispatchEvent"in this?this.dispatchEvent(e):t(this).triggerHandler(e,n)})},t.fn.triggerHandler=function(e,r){var i,o;return this.each(function(a,s){i=l(m(e)?t.Event(e):e),i._args=r,i.target=s,t.each(n(s,e.type||e),function(t,e){if(o=e.proxy(i),i.isImmediatePropagationStopped())return!1})}),o},"focusin focusout focus blur load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select keydown keypress keyup error".split(" ").forEach(function(e){t.fn[e]=function(t){return 0 in arguments?this.bind(e,t):this.trigger(e)}}),t.Event=function(t,e){m(t)||(e=t,t=e.type);var n=document.createEvent(g[t]||"Events"),r=!0;if(e)for(var i in e)"bubbles"==i?r=!!e[i]:n[i]=e[i];return n.initEvent(t,r,!0),c(n)}}(e),function(t){function e(e,n,r){var i=t.Event(n);return t(e).trigger(i,r),!i.isDefaultPrevented()}function n(t,n,r,i){if(t.global)return e(n||x,r,i)}function r(e){e.global&&0===t.active++&&n(e,null,"ajaxStart")}function i(e){e.global&&!--t.active&&n(e,null,"ajaxStop")}function o(t,e){var r=e.context;return e.beforeSend.call(r,t,e)!==!1&&n(e,r,"ajaxBeforeSend",[t,e])!==!1&&void n(e,r,"ajaxSend",[t,e])}function a(t,e,r,i){var o=r.context,a="success";r.success.call(o,t,a,e),i&&i.resolveWith(o,[t,a,e]),n(r,o,"ajaxSuccess",[e,r,t]),u(a,e,r)}function s(t,e,r,i,o){var a=i.context;i.error.call(a,r,e,t),o&&o.rejectWith(a,[r,e,t]),n(i,a,"ajaxError",[r,i,t||e]),u(e,r,i)}function u(t,e,r){var o=r.context;r.complete.call(o,e,t),n(r,o,"ajaxComplete",[e,r]),i(r)}function c(t,e,n){if(n.dataFilter==l)return t;var r=n.context;return n.dataFilter.call(r,t,e)}function l(){}function f(t){return t&&(t=t.split(";",2)[0]),t&&(t==T?"html":t==j?"json":w.test(t)?"script":E.test(t)&&"xml")||"text"}function h(t,e){return""==e?t:(t+"&"+e).replace(/[&?]{1,2}/,"?")}function p(e){e.processData&&e.data&&"string"!=t.type(e.data)&&(e.data=t.param(e.data,e.traditional)),!e.data||e.type&&"GET"!=e.type.toUpperCase()&&"jsonp"!=e.dataType||(e.url=h(e.url,e.data),e.data=void 0)}function d(e,n,r,i){return t.isFunction(n)&&(i=r,r=n,n=void 0),t.isFunction(r)||(i=r,r=void 0),{url:e,data:n,success:r,dataType:i}}function m(e,n,r,i){var o,a=t.isArray(n),s=t.isPlainObject(n);t.each(n,function(n,u){o=t.type(u),i&&(n=r?i:i+"["+(s||"object"==o||"array"==o?n:"")+"]"),!i&&a?e.add(u.name,u.value):"array"==o||!r&&"object"==o?m(e,u,r,n):e.add(n,u)})}var v,g,y=+new Date,x=window.document,b=/)<[^<]*)*<\/script>/gi,w=/^(?:text|application)\/javascript/i,E=/^(?:text|application)\/xml/i,j="application/json",T="text/html",S=/^\s*$/,C=x.createElement("a");C.href=window.location.href,t.active=0,t.ajaxJSONP=function(e,n){if(!("type"in e))return t.ajax(e);var r,i,u=e.jsonpCallback,c=(t.isFunction(u)?u():u)||"Zepto"+y++,l=x.createElement("script"),f=window[c],h=function(e){t(l).triggerHandler("error",e||"abort")},p={abort:h};return n&&n.promise(p),t(l).on("load error",function(o,u){clearTimeout(i),t(l).off().remove(),"error"!=o.type&&r?a(r[0],p,e,n):s(null,u||"error",p,e,n),window[c]=f,r&&t.isFunction(f)&&f(r[0]),f=r=void 0}),o(p,e)===!1?(h("abort"),p):(window[c]=function(){r=arguments},l.src=e.url.replace(/\?(.+)=\?/,"?$1="+c),x.head.appendChild(l),e.timeout>0&&(i=setTimeout(function(){h("timeout")},e.timeout)),p)},t.ajaxSettings={type:"GET",beforeSend:l,success:l,error:l,complete:l,context:null,global:!0,xhr:function(){return new window.XMLHttpRequest},accepts:{script:"text/javascript, application/javascript, application/x-javascript",json:j,xml:"application/xml, text/xml",html:T,text:"text/plain"},crossDomain:!1,timeout:0,processData:!0,cache:!0,dataFilter:l},t.ajax=function(e){var n,i,u=t.extend({},e||{}),d=t.Deferred&&t.Deferred();for(v in t.ajaxSettings)void 0===u[v]&&(u[v]=t.ajaxSettings[v]);r(u),u.crossDomain||(n=x.createElement("a"),n.href=u.url,n.href=n.href,u.crossDomain=C.protocol+"//"+C.host!=n.protocol+"//"+n.host),u.url||(u.url=window.location.toString()),(i=u.url.indexOf("#"))>-1&&(u.url=u.url.slice(0,i)),p(u);var m=u.dataType,y=/\?.+=\?/.test(u.url);if(y&&(m="jsonp"),u.cache!==!1&&(e&&e.cache===!0||"script"!=m&&"jsonp"!=m)||(u.url=h(u.url,"_="+Date.now())),"jsonp"==m)return y||(u.url=h(u.url,u.jsonp?u.jsonp+"=?":u.jsonp===!1?"":"callback=?")),t.ajaxJSONP(u,d);var b,w=u.accepts[m],E={},j=function(t,e){E[t.toLowerCase()]=[t,e]},T=/^([\w-]+:)\/\//.test(u.url)?RegExp.$1:window.location.protocol,N=u.xhr(),O=N.setRequestHeader;if(d&&d.promise(N),u.crossDomain||j("X-Requested-With","XMLHttpRequest"),j("Accept",w||"*/*"),(w=u.mimeType||w)&&(w.indexOf(",")>-1&&(w=w.split(",",2)[0]),N.overrideMimeType&&N.overrideMimeType(w)),(u.contentType||u.contentType!==!1&&u.data&&"GET"!=u.type.toUpperCase())&&j("Content-Type",u.contentType||"application/x-www-form-urlencoded"),u.headers)for(g in u.headers)j(g,u.headers[g]);if(N.setRequestHeader=j,N.onreadystatechange=function(){if(4==N.readyState){N.onreadystatechange=l,clearTimeout(b);var e,n=!1;if(N.status>=200&&N.status<300||304==N.status||0==N.status&&"file:"==T){if(m=m||f(u.mimeType||N.getResponseHeader("content-type")),"arraybuffer"==N.responseType||"blob"==N.responseType)e=N.response;else{e=N.responseText;try{e=c(e,m,u),"script"==m?(0,eval)(e):"xml"==m?e=N.responseXML:"json"==m&&(e=S.test(e)?null:t.parseJSON(e))}catch(r){n=r}if(n)return s(n,"parsererror",N,u,d)}a(e,N,u,d)}else s(N.statusText||null,N.status?"error":"abort",N,u,d)}},o(N,u)===!1)return N.abort(),s(null,"abort",N,u,d),N;var P=!("async"in u)||u.async;if(N.open(u.type,u.url,P,u.username,u.password),u.xhrFields)for(g in u.xhrFields)N[g]=u.xhrFields[g];for(g in E)O.apply(N,E[g]);return u.timeout>0&&(b=setTimeout(function(){N.onreadystatechange=l,N.abort(),s(null,"timeout",N,u,d)},u.timeout)),N.send(u.data?u.data:null),N},t.get=function(){return t.ajax(d.apply(null,arguments))},t.post=function(){var e=d.apply(null,arguments);return e.type="POST",t.ajax(e)},t.getJSON=function(){var e=d.apply(null,arguments);return e.dataType="json",t.ajax(e)},t.fn.load=function(e,n,r){if(!this.length)return this;var i,o=this,a=e.split(/\s/),s=d(e,n,r),u=s.success;return a.length>1&&(s.url=a[0],i=a[1]),s.success=function(e){o.html(i?t("
    ").html(e.replace(b,"")).find(i):e),u&&u.apply(o,arguments)},t.ajax(s),this};var N=encodeURIComponent;t.param=function(e,n){var r=[];return r.add=function(e,n){t.isFunction(n)&&(n=n()),null==n&&(n=""),this.push(N(e)+"="+N(n))},m(r,e,n),r.join("&").replace(/%20/g,"+")}}(e),function(t){t.fn.serializeArray=function(){var e,n,r=[],i=function(t){return t.forEach?t.forEach(i):void r.push({name:e,value:t})};return this[0]&&t.each(this[0].elements,function(r,o){n=o.type,e=o.name,e&&"fieldset"!=o.nodeName.toLowerCase()&&!o.disabled&&"submit"!=n&&"reset"!=n&&"button"!=n&&"file"!=n&&("radio"!=n&&"checkbox"!=n||o.checked)&&i(t(o).val())}),r},t.fn.serialize=function(){var t=[];return this.serializeArray().forEach(function(e){t.push(encodeURIComponent(e.name)+"="+encodeURIComponent(e.value))}),t.join("&")},t.fn.submit=function(e){if(0 in arguments)this.bind("submit",e);else if(this.length){var n=t.Event("submit");this.eq(0).trigger(n),n.isDefaultPrevented()||this.get(0).submit()}return this}}(e),function(){try{getComputedStyle(void 0)}catch(t){var e=getComputedStyle;window.getComputedStyle=function(t,n){try{return e(t,n)}catch(r){return null}}}}(),t("zepto",e)});layui.define(["layer-mobile","zepto"],function(e){"use strict";var t=layui.zepto,a=layui["layer-mobile"],i=(layui.device(),"layui-upload-enter"),n="layui-upload-iframe",r={icon:2,shift:6},o={file:"文件",video:"视频",audio:"音频"};a.msg=function(e){return a.open({content:e||"",skin:"msg",time:2})};var s=function(e){this.options=e};s.prototype.init=function(){var e=this,a=e.options,r=t("body"),s=t(a.elem||".layui-upload-file"),u=t('');return t("#"+n)[0]||r.append(u),s.each(function(r,s){s=t(s);var u='
    ',l=s.attr("lay-type")||a.type;a.unwrap||(u='
    '+u+''+(s.attr("lay-title")||a.title||"上传"+(o[l]||"图片"))+"
    "),u=t(u),a.unwrap||u.on("dragover",function(e){e.preventDefault(),t(this).addClass(i)}).on("dragleave",function(){t(this).removeClass(i)}).on("drop",function(){t(this).removeClass(i)}),s.parent("form").attr("target")===n&&(a.unwrap?s.unwrap():(s.parent().next().remove(),s.unwrap().unwrap())),s.wrap(u),s.off("change").on("change",function(){e.action(this,l)})})},s.prototype.action=function(e,i){var o=this,s=o.options,u=e.value,l=t(e),p=l.attr("lay-ext")||s.ext||"";if(u){switch(i){case"file":if(p&&!RegExp("\\w\\.("+p+")$","i").test(escape(u)))return a.msg("不支持该文件格式",r),e.value="";break;case"video":if(!RegExp("\\w\\.("+(p||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(u)))return a.msg("不支持该视频格式",r),e.value="";break;case"audio":if(!RegExp("\\w\\.("+(p||"mp3|wav|mid")+")$","i").test(escape(u)))return a.msg("不支持该音频格式",r),e.value="";break;default:if(!RegExp("\\w\\.("+(p||"jpg|png|gif|bmp|jpeg")+")$","i").test(escape(u)))return a.msg("不支持该图片格式",r),e.value=""}s.before&&s.before(e),l.parent().submit();var c=t("#"+n),f=setInterval(function(){var t;try{t=c.contents().find("body").text()}catch(i){a.msg("上传接口存在跨域",r),clearInterval(f)}if(t){clearInterval(f),c.contents().find("body").html("");try{t=JSON.parse(t)}catch(i){return t={},a.msg("请对上传接口返回JSON字符",r)}"function"==typeof s.success&&s.success(t,e)}},30);e.value=""}},e("upload-mobile",function(e){var t=new s(e=e||{});t.init()})});layui.define(function(i){i("layim-mobile",layui.v)});layui["layui.mobile"]||layui.config({base:layui.cache.dir+"lay/modules/mobile/"}).extend({"layer-mobile":"layer-mobile",zepto:"zepto","upload-mobile":"upload-mobile","layim-mobile":"layim-mobile"}),layui.define(["layer-mobile","zepto","layim-mobile"],function(l){l("mobile",{layer:layui["layer-mobile"],layim:layui["layim-mobile"]})}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/rate.js b/static/admin/js/layui/lay/modules/rate.js index 290b009..762fb3a 100755 --- a/static/admin/js/layui/lay/modules/rate.js +++ b/static/admin/js/layui/lay/modules/rate.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(e){"use strict";var a=layui.jquery,i={config:{},index:layui.rate?layui.rate.index+1e4:0,set:function(e){var i=this;return i.config=a.extend({},i.config,e),i},on:function(e,a){return layui.onevent.call(this,n,e,a)}},l=function(){var e=this,a=e.config;return{setvalue:function(a){e.setvalue.call(e,a)},config:a}},n="rate",t="layui-rate",o="layui-icon-rate",s="layui-icon-rate-solid",u="layui-icon-rate-half",r="layui-icon-rate-solid layui-icon-rate-half",c="layui-icon-rate-solid layui-icon-rate",f="layui-icon-rate layui-icon-rate-half",v=function(e){var l=this;l.index=++i.index,l.config=a.extend({},l.config,i.config,e),l.render()};v.prototype.config={length:5,text:!1,readonly:!1,half:!1,value:0,theme:""},v.prototype.render=function(){var e=this,i=e.config,l=i.theme?'style="color: '+i.theme+';"':"";i.elem=a(i.elem),parseInt(i.value)!==i.value&&(i.half||(i.value=Math.ceil(i.value)-i.value<.5?Math.ceil(i.value):Math.floor(i.value)));for(var n='
      ",u=1;u<=i.length;u++){var r='
    • ";i.half&&parseInt(i.value)!==i.value&&u==Math.ceil(i.value)?n=n+'
    • ":n+=r}n+="
    "+(i.text?''+i.value+"星":"")+"";var c=i.elem,f=c.next("."+t);f[0]&&f.remove(),e.elemTemp=a(n),i.span=e.elemTemp.next("span"),i.setText&&i.setText(i.value),c.html(e.elemTemp),c.addClass("layui-inline"),i.readonly||e.action()},v.prototype.setvalue=function(e){var a=this,i=a.config;i.value=e,a.render()},v.prototype.action=function(){var e=this,i=e.config,l=e.elemTemp,n=l.find("i").width();l.children("li").each(function(e){var t=e+1,v=a(this);v.on("click",function(e){if(i.value=t,i.half){var o=e.pageX-a(this).offset().left;o<=n/2&&(i.value=i.value-.5)}i.text&&l.next("span").text(i.value+"星"),i.choose&&i.choose(i.value),i.setText&&i.setText(i.value)}),v.on("mousemove",function(e){if(l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+t+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half){var c=e.pageX-a(this).offset().left;c<=n/2&&v.children("i").addClass(u).removeClass(s)}}),v.on("mouseleave",function(){l.find("i").each(function(){a(this).addClass(o).removeClass(r)}),l.find("i:lt("+Math.floor(i.value)+")").each(function(){a(this).addClass(s).removeClass(f)}),i.half&&parseInt(i.value)!==i.value&&l.children("li:eq("+Math.floor(i.value)+")").children("i").addClass(u).removeClass(c)})})},v.prototype.events=function(){var e=this;e.config},i.render=function(e){var a=new v(e);return l.call(a)},e(n,i)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/slider.js b/static/admin/js/layui/lay/modules/slider.js index 7197405..446378d 100755 --- a/static/admin/js/layui/lay/modules/slider.js +++ b/static/admin/js/layui/lay/modules/slider.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ - ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",f="layui-slider-input-btn",m="layui-slider-hover",p=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};p.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},p.prototype.render=function(){var e=this,t=e.config;if(t.min=t.min<0?0:t.min,t.range){t.value="object"==typeof t.value?t.value:[t.min,t.value];var a=Math.min(t.value[0],t.value[1]),n=Math.max(t.value[0],t.value[1]);t.value[0]=a>t.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),f=v-r+"%";r+="%",v+="%"}else{t.value="object"==typeof t.value?Math.min(t.value[0],t.value[1]):t.value,t.value=t.value>t.min?t.value:t.min;var f=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var m=t.disabled?"#c2c2c2":t.theme,p='
    '+(t.tips?'
    ':"")+'
    '+(t.range?'
    ':"")+"
    ",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(p),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x
    ')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('
    ');h.css("position","relative"),h.append(w),h.find("."+c).children("input").val(t.value),"vertical"===t.type?w.css({left:0,top:-48}):e.elemTemp.css("margin-right",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find("."+u).addClass(l)):e.slide(),e.elemTemp.find("."+u).on("mouseover",function(){var a="vertical"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find("."+o),l="vertical"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data("value"),u=t.setTips?t.setTips(r):r;e.elemTemp.find("."+d).html(u),"vertical"===t.type?e.elemTemp.find("."+d).css({bottom:s+"%","margin-bottom":"20px",display:"inline-block"}):e.elemTemp.find("."+d).css({left:s+"%",display:"inline-block"})}).on("mouseout",function(){e.elemTemp.find("."+d).css("display","none")})},p.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,p=function(){return"vertical"===l.type?l.height:s[0].offsetWidth},h=s.find("."+o),y=s.next("."+v),g=y.children("."+c).children("input").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css("vertical"===l.type?"bottom":"left",e+"%");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;"vertical"===l.type?(s.find("."+d).css({bottom:e+"%","margin-bottom":"20px"}),t=T(p()-h[0].offsetTop-h.height()),a=l.range?T(p()-h[1].offsetTop-h.height()):0):s.find("."+d).css("left",e+"%"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);"vertical"===l.type?s.find("."+r).css({height:o+"%",bottom:n+"%"}):s.find("."+r).css({width:o+"%",left:n+"%"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children("."+c).children("input").val(g),h.eq(i).data("value",u),u=l.setTips?l.setTips(u):u,s.find("."+d).html(u),l.range){var v=[h.eq(0).data("value"),h.eq(1).data("value")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/p()*100/b,t=Math.round(i)*b;return e==p()&&(t=Math.ceil(i)*b),t},w=i(['
    p()&&(r=p());var o=r/p()*100/b;x(o,e),t.addClass(m),s.find("."+d).show(),i.preventDefault()},o=function(){t.removeClass(m),s.find("."+d).hide()};M(r,o)})}),s.on("click",function(e){var t=i("."+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n="vertical"===l.type?p()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>p()&&(n=p());var s=n/p()*100/b;a=l.range?"vertical"===l.type?Math.abs(n-parseInt(i(h[0]).css("bottom")))>Math.abs(n-parseInt(i(h[1]).css("bottom")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children("."+f).fadeIn("fast")},function(){var e=i(this);e.children("."+f).fadeOut("fast")}),y.children("."+f).children("i").each(function(e){i(this).on("click",function(){g=1==e?g-bl.max?l.max:Number(g)+b;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=el.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children("."+c).children("input").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on("change",q)},p.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new p(e);return a.call(i)},e(n,t)}); \ No newline at end of file +/** layui-v2.4.5 MIT License By https://www.layui.com */ + ;layui.define("jquery",function(e){"use strict";var i=layui.jquery,t={config:{},index:layui.slider?layui.slider.index+1e4:0,set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,n,e,i)}},a=function(){var e=this,i=e.config;return{setValue:function(i,t){return e.slide("set",i,t||0)},config:i}},n="slider",l="layui-disabled",s="layui-slider",r="layui-slider-bar",o="layui-slider-wrap",u="layui-slider-wrap-btn",d="layui-slider-tips",v="layui-slider-input",c="layui-slider-input-txt",m="layui-slider-input-btn",p="layui-slider-hover",f=function(e){var a=this;a.index=++t.index,a.config=i.extend({},a.config,t.config,e),a.render()};f.prototype.config={type:"default",min:0,max:100,value:0,step:1,showstep:!1,tips:!0,input:!1,range:!1,height:200,disabled:!1,theme:"#009688"},f.prototype.render=function(){var e=this,t=e.config;if(t.step<1&&(t.step=1),t.maxt.min?a:t.min,t.value[1]=n>t.min?n:t.min,t.value[0]=t.value[0]>t.max?t.max:t.value[0],t.value[1]=t.value[1]>t.max?t.max:t.value[1];var r=Math.floor((t.value[0]-t.min)/(t.max-t.min)*100),v=Math.floor((t.value[1]-t.min)/(t.max-t.min)*100),m=v-r+"%";r+="%",v+="%"}else{"object"==typeof t.value&&(t.value=Math.min.apply(null,t.value)),t.valuet.max&&(t.value=t.max);var m=Math.floor((t.value-t.min)/(t.max-t.min)*100)+"%"}var p=t.disabled?"#c2c2c2":t.theme,f='
    '+(t.tips?'
    ':"")+'
    '+(t.range?'
    ':"")+"
    ",h=i(t.elem),y=h.next("."+s);if(y[0]&&y.remove(),e.elemTemp=i(f),t.range?(e.elemTemp.find("."+o).eq(0).data("value",t.value[0]),e.elemTemp.find("."+o).eq(1).data("value",t.value[1])):e.elemTemp.find("."+o).data("value",t.value),h.html(e.elemTemp),"vertical"===t.type&&e.elemTemp.height(t.height+"px"),t.showstep){for(var g=(t.max-t.min)/t.step,b="",x=1;x
    ')}e.elemTemp.append(b)}if(t.input&&!t.range){var w=i('
    ');h.css("position","relative"),h.append(w),h.find("."+c).children("input").val(t.value),"vertical"===t.type?w.css({left:0,top:-48}):e.elemTemp.css("margin-right",w.outerWidth()+15)}t.disabled?(e.elemTemp.addClass(l),e.elemTemp.find("."+u).addClass(l)):e.slide(),e.elemTemp.find("."+u).on("mouseover",function(){var a="vertical"===t.type?t.height:e.elemTemp[0].offsetWidth,n=e.elemTemp.find("."+o),l="vertical"===t.type?a-i(this).parent()[0].offsetTop-n.height():i(this).parent()[0].offsetLeft,s=l/a*100,r=i(this).parent().data("value"),u=t.setTips?t.setTips(r):r;e.elemTemp.find("."+d).html(u),"vertical"===t.type?e.elemTemp.find("."+d).css({bottom:s+"%","margin-bottom":"20px",display:"inline-block"}):e.elemTemp.find("."+d).css({left:s+"%",display:"inline-block"})}).on("mouseout",function(){e.elemTemp.find("."+d).css("display","none")})},f.prototype.slide=function(e,t,a){var n=this,l=n.config,s=n.elemTemp,f=function(){return"vertical"===l.type?l.height:s[0].offsetWidth},h=s.find("."+o),y=s.next("."+v),g=y.children("."+c).children("input").val(),b=100/((l.max-l.min)/Math.ceil(l.step)),x=function(e,i){e=Math.ceil(e)*b>100?Math.ceil(e)*b:Math.round(e)*b,e=e>100?100:e,h.eq(i).css("vertical"===l.type?"bottom":"left",e+"%");var t=T(h[0].offsetLeft),a=l.range?T(h[1].offsetLeft):0;"vertical"===l.type?(s.find("."+d).css({bottom:e+"%","margin-bottom":"20px"}),t=T(f()-h[0].offsetTop-h.height()),a=l.range?T(f()-h[1].offsetTop-h.height()):0):s.find("."+d).css("left",e+"%"),t=t>100?100:t,a=a>100?100:a;var n=Math.min(t,a),o=Math.abs(t-a);"vertical"===l.type?s.find("."+r).css({height:o+"%",bottom:n+"%"}):s.find("."+r).css({width:o+"%",left:n+"%"});var u=l.min+Math.round((l.max-l.min)*e/100);if(g=u,y.children("."+c).children("input").val(g),h.eq(i).data("value",u),u=l.setTips?l.setTips(u):u,s.find("."+d).html(u),l.range){var v=[h.eq(0).data("value"),h.eq(1).data("value")];v[0]>v[1]&&v.reverse()}l.change&&l.change(l.range?v:u)},T=function(e){var i=e/f()*100/b,t=Math.round(i)*b;return e==f()&&(t=Math.ceil(i)*b),t},w=i(['
    f()&&(r=f());var o=r/f()*100/b;x(o,e),t.addClass(p),s.find("."+d).show(),i.preventDefault()},o=function(){t.removeClass(p),s.find("."+d).hide()};M(r,o)})}),s.on("click",function(e){var t=i("."+u);if(!t.is(event.target)&&0===t.has(event.target).length&&t.length){var a,n="vertical"===l.type?f()-e.clientY+i(this).offset().top:e.clientX-i(this).offset().left;n<0&&(n=0),n>f()&&(n=f());var s=n/f()*100/b;a=l.range?"vertical"===l.type?Math.abs(n-parseInt(i(h[0]).css("bottom")))>Math.abs(n-parseInt(i(h[1]).css("bottom")))?1:0:Math.abs(n-h[0].offsetLeft)>Math.abs(n-h[1].offsetLeft)?1:0:0,x(s,a),e.preventDefault()}}),y.hover(function(){var e=i(this);e.children("."+m).fadeIn("fast")},function(){var e=i(this);e.children("."+m).fadeOut("fast")}),y.children("."+m).children("i").each(function(e){i(this).on("click",function(){g=1==e?g-l.stepl.max?l.max:Number(g)+l.step;var i=(g-l.min)/(l.max-l.min)*100/b;x(i,0)})});var q=function(){var e=this.value;e=isNaN(e)?0:e,e=el.max?l.max:e,this.value=e;var i=(e-l.min)/(l.max-l.min)*100/b;x(i,0)};y.children("."+c).children("input").on("keydown",function(e){13===e.keyCode&&(e.preventDefault(),q.call(this))}).on("change",q)},f.prototype.events=function(){var e=this;e.config},t.render=function(e){var i=new f(e);return a.call(i)},e(n,t)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/table.js b/static/admin/js/layui/lay/modules/table.js index 34fc4cd..e9217db 100755 --- a/static/admin/js/layui/lay/modules/table.js +++ b/static/admin/js/layui/lay/modules/table.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ - ;layui.define(["laytpl","laypage","layer","form","util"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=(layui.util,layui.hint()),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,s,e,t)}},c=function(){var e=this,t=e.config,i=t.id||t.index;return i&&(c.config[i]=t),{reload:function(t){e.reload.call(e,t)},setColsWidth:function(){e.setColsWidth.call(e)},config:t}},s="table",u=".layui-table",y="layui-hide",h="layui-none",f="layui-table-view",p=".layui-table-tool",v=".layui-table-box",m=".layui-table-init",g=".layui-table-header",b=".layui-table-body",x=".layui-table-main",k=".layui-table-fixed",C=".layui-table-fixed-l",w=".layui-table-fixed-r",T=".layui-table-total",A=".layui-table-page",L=".layui-table-sort",S="layui-table-edit",N="layui-table-hover",W=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# var isSort = !(item2.colGroup) && item2.sort; }}",'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
    ','
    ','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(isSort){ }}",'',"{{# } }}","{{# } }}","
    ","
    "].join("")},_=['',"","
    "].join(""),E=['
    ',"{{# if(d.data.toolbar){ }}",'
    ','
    ','
    ',"
    ","{{# } }}",'
    ',"{{# if(d.loading){ }}",'
    ','',"
    ","{{# } }}","{{# var left, right; }}",'
    ',W(),"
    ",'
    ',_,"
    ","{{# if(left){ }}",'
    ','
    ',W({fixed:!0}),"
    ",'
    ',_,"
    ","
    ","{{# }; }}","{{# if(right){ }}",'
    ','
    ',W({fixed:"right"}),'
    ',"
    ",'
    ',_,"
    ","
    ","{{# }; }}","
    ","{{# if(d.data.totalRow){ }}",'
    ','','',"
    ","
    ","{{# } }}","{{# if(d.data.page){ }}",'
    ','
    ',"
    ","{{# } }}","","
    "].join(""),R=t(window),H=t(document),j=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};j.prototype.config={limit:10,loading:!0,cellMinWidth:60,defaultToolbar:["filter","exports","print"],text:{none:"无数据"}},j.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id")||a.index,a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;a.height&&/^full-\d+$/.test(a.height)&&(e.fullHeightGap=a.height.split("-")[1],a.height=R.height()-e.fullHeightGap),e.setInit();var l=a.elem,n=l.next("."+f),o=e.elem=t(i(E).render({VIEW_CLASS:f,data:a,index:e.index}));if(a.index=e.index,n[0]&&n.remove(),l.after(o),e.layTool=o.find(p),e.layBox=o.find(v),e.layHeader=o.find(g),e.layMain=o.find(x),e.layBody=o.find(b),e.layFixed=o.find(k),e.layFixLeft=o.find(C),e.layFixRight=o.find(w),e.layTotal=o.find(T),e.layPage=o.find(A),e.renderToolbar(),e.fullSize(),a.cols.length>1){var r=e.layFixed.find(g).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},j.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,radio:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},j.prototype.setInit=function(e){var t=this,i=t.config;return i.clientWidth=i.width||function(){var e=function(t){var a,l;t=t||i.elem.parent(),a=t.width();try{l="none"===t.css("display")}catch(n){}return!t[0]||a&&!l?a:e(t.parent())};return e()}(),"width"===e?i.clientWidth:void layui.each(i.cols,function(e,a){layui.each(a,function(l,n){if(!n)return void a.splice(l,1);if(n.key=e+"-"+l,n.hide=n.hide||!1,n.colGroup||n.colspan>1){var o=0;layui.each(i.cols[e+1],function(t,i){i.HAS_PARENT||o>1&&o==n.colspan||(i.HAS_PARENT=!0,i.parentKey=e+"-"+l,o+=parseInt(i.colspan>1?i.colspan:1))}),n.colGroup=!0}t.initOpts(n)})})},j.prototype.renderToolbar=function(){var e=this,a=e.config,l=['
    ','
    ','
    '].join(""),n=e.layTool.find(".layui-table-tool-temp");if("default"===a.toolbar)n.html(l);else if(a.toolbar){var o=t(a.toolbar).html()||"";o&&n.html(i(o).render(a))}var r={filter:{title:"筛选列",layEvent:"LAYTABLE_COLS",icon:"layui-icon-cols"},exports:{title:"导出",layEvent:"LAYTABLE_EXPORT",icon:"layui-icon-export"},print:{title:"打印",layEvent:"LAYTABLE_PRINT",icon:"layui-icon-print"}},d=[];"object"==typeof a.defaultToolbar&&layui.each(a.defaultToolbar,function(e,t){var i=r[t];i&&d.push('
    ')}),e.layTool.find(".layui-table-tool-self").html(d.join(""))},j.prototype.setParentCol=function(e,t){var i=this,a=i.config,l=i.layHeader.find('th[data-key="'+a.index+"-"+t+'"]'),n=parseInt(l.attr("colspan"))||0;if(l[0]){var o=t.split("-"),r=a.cols[o[0]][o[1]];e?n--:n++,l.attr("colspan",n),l[n<1?"addClass":"removeClass"](y),r.colspan=n,r.hide=n<1;var d=l.data("parentkey");d&&i.setParentCol(e,d)}},j.prototype.setColsPatch=function(){var e=this,t=e.config;layui.each(t.cols,function(t,i){layui.each(i,function(t,i){i.hide&&e.setParentCol(i.hide,i.parentKey)})})},j.prototype.setColsWidth=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=e.setInit("width");e.eachCols(function(e,t){t.hide||i++}),o=o-function(){return"line"===t.skin||"nob"===t.skin?2:i+1}()-e.getScrollWidth(e.layMain[0])-1;var r=function(e){layui.each(t.cols,function(i,r){layui.each(r,function(i,d){var c=0,s=d.minWidth||t.cellMinWidth;return d?void(d.colGroup||d.hide||(e?l&&ln&&a&&(l=(o-n)/a)};r(),r(!0),e.autoColNums=a,e.eachCols(function(i,a){var n=a.minWidth||t.cellMinWidth;a.colGroup||a.hide||(0===a.width?e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(l>=n?l:n)+"px"}):/\d+%$/.test(a.width)&&e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(parseFloat(a.width)/100*o)+"px"}))});var d=e.layMain.width()-e.getScrollWidth(e.layMain[0])-e.layMain.children("table").outerWidth();if(e.autoColNums&&d>=-i&&d<=i){var c=function(t){var i;return t=t||e.layHeader.eq(0).find("thead th:last-child"),i=t.data("field"),!i&&t.prev()[0]?c(t.prev()):t},s=c(),u=s.data("key");e.getCssRule(u,function(t){var i=t.style.width||s.outerWidth();t.style.width=parseFloat(i)+d+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px")})}e.loading(!0)},j.prototype.reload=function(e){var i=this;i.config.data&&i.config.data.constructor===Array&&delete i.config.data,i.config=t.extend({},i.config,e),i.render()},j.prototype.page=1,j.prototype.pullData=function(e){var i=this,a=i.config,l=a.request,n=a.response,o=function(){"object"==typeof a.initSort&&i.sort(a.initSort.field,a.initSort.type)};if(i.startTime=(new Date).getTime(),a.url){var r={};r[l.pageName]=e,r[l.limitName]=a.limit;var d=t.extend(r,a.where);a.contentType&&0==a.contentType.indexOf("application/json")&&(d=JSON.stringify(d)),t.ajax({type:a.method||"get",url:a.url,contentType:a.contentType,data:d,dataType:"json",headers:a.headers||{},success:function(t){"function"==typeof a.parseData&&(t=a.parseData(t)||t),t[n.statusName]!=n.statusCode?(i.renderForm(),i.layMain.html('
    '+(t[n.msgName]||"返回的数据不符合规范,正确的成功状态码 ("+n.statusName+") 应为:"+n.statusCode)+"
    ")):(i.renderData(t,e,t[n.countName]),o(),a.time=(new Date).getTime()-i.startTime+" ms"),i.setColsWidth(),"function"==typeof a.done&&a.done(t,e,t[n.countName])},error:function(e,t){i.layMain.html('
    数据接口请求异常:'+t+"
    "),i.renderForm(),i.setColsWidth()}})}else if(a.data&&a.data.constructor===Array){var c={},s=e*a.limit-a.limit;c[n.dataName]=a.data.concat().splice(s,a.limit),c[n.countName]=a.data.length,i.renderData(c,e,a.data.length),o(),i.setColsWidth(),"function"==typeof a.done&&a.done(c,e,c[n.countName])}},j.prototype.eachCols=function(e){var t=this;return d.eachCols(null,e,t.config.cols),t},j.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],f=[],p=[],v=[],m=function(){var e;return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(a,l){var o=[],u=[],h=[],m=a+s.limit*(n-1)+1;0!==l.length&&(r||(l[d.config.indexName]=a),c.eachCols(function(n,r){var c=r.field||n,f=s.index+"-"+r.key,p=l[c];if(void 0!==p&&null!==p||(p=""),!r.colGroup){var v=['','
    '+function(){var n=t.extend(!0,{LAY_INDEX:m},l),o=d.config.checkName;switch(r.type){case"checkbox":return'";case"radio":return n[o]&&(e=a),'';case"numbers":return m}return r.toolbar?i(t(r.toolbar).html()||"").render(n):r.templet?function(){return"function"==typeof r.templet?r.templet(n):i(t(r.templet).html()||String(p)).render(n)}():p}(),"
    "].join("");o.push(v),r.fixed&&"right"!==r.fixed&&u.push(v),"right"===r.fixed&&h.push(v)}}),f.push(''+o.join("")+""),p.push(''+u.join("")+""),v.push(''+h.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+h).remove(),c.layMain.find("tbody").html(f.join("")),c.layFixLeft.find("tbody").html(p.join("")),c.layFixRight.find("tbody").html(v.join("")),c.renderForm(),"number"==typeof e&&c.setThisRowChecked(e),c.syncCheckAll(),c.scrollPatch(),l.close(c.tipsIndex),s.HAS_SET_COLS_PATCH||c.setColsPatch(),void(s.HAS_SET_COLS_PATCH=!0))};return c.key=s.id||s.index,d.cache[c.key]=u,c.layPage[0==o||0===u.length&&1==n?"addClass":"removeClass"](y),r?m():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+h).remove(),c.layMain.append('
    '+s.text.none+"
    ")):(m(),c.renderTotal(u),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.loading(),c.pullData(e.curr))}},s.page),s.page.count=o,a.render(s.page))))},j.prototype.renderTotal=function(e){var t=this,i=t.config,a={};if(i.totalRow){layui.each(e,function(e,i){0!==i.length&&t.eachCols(function(e,t){var l=t.field||e,n=i[l];t.totalRow&&(a[l]=(a[l]||0)+(parseFloat(n)||0))})});var l=[];t.eachCols(function(e,t){var n=t.field||e;if(!t.hide){var o=['",'
    '+function(){var e=t.totalRowText||"";return t.totalRow?a[n]||e:e}(),"
    "].join("");l.push(o)}}),t.layTotal.find("tbody").html(""+l.join("")+"")}},j.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},j.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},j.prototype.setThisRowChecked=function(e){var t=this,i=(t.config,"layui-table-click"),a=t.layBody.find('tr[data-index="'+e+'"]');a.addClass(i).siblings("tr").removeClass(i)},j.prototype.sort=function(e,i,a,l){var n,r,c=this,u={},y=c.config,h=y.elem.attr("lay-filter"),f=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field"),p=e.data("key");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var v=c.layHeader.find("th .laytable-cell-"+p).find(L);c.layHeader.find("th").find(L).removeAttr("lay-sort"),v.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},"asc"===i?r=layui.sort(f,n):"desc"===i?r=layui.sort(f,n,!0):(r=layui.sort(f,d.config.indexName),delete c.sortKey),u[y.response.dataName]=r,c.renderData(u,c.page,c.count,!0),l&&layui.event.call(e,s,"sort("+h+")",{field:n,type:i})},j.prototype.loading=function(e){var i=this,a=i.config;a.loading&&(e?(i.layInit&&i.layInit.remove(),delete i.layInit,i.layBox.find(m).remove()):(i.layInit=t(['
    ','',"
    "].join("")),i.layBox.append(i.layInit)))},j.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},j.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},j.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(i,a){if(a.selectorText===".laytable-cell-"+e)return t(a),!0})},j.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=R.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),a&&(e=parseFloat(a)-(t.layHeader.outerHeight()||38),i.toolbar&&(e-=t.layTool.outerHeight()||50),i.totalRow&&(e-=t.layTotal.outerHeight()||40),i.page&&(e=e-(t.layPage.outerHeight()||41)-2),t.layMain.css("height",e))},j.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},j.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=(e.getScrollWidth(e.layMain[0]),i.outerWidth()-e.layMain.width()),o=function(e){if(a&&l){if(e=e.eq(0),!e.find(".layui-table-patch")[0]){var i=t('
    ');i.find("div").css({width:a}),e.find("tr").append(i)}}else e.find(".layui-table-patch").remove()};o(e.layHeader),o(e.layTotal);var r=e.layMain.height(),d=r-l;e.layFixed.find(b).css("height",i.height()>d?d:"auto"),e.layFixRight[n>0?"removeClass":"addClass"](y),e.layFixRight.css("right",a-1)},j.prototype.events=function(){var e,a=this,o=a.config,c=t("body"),u={},h=a.layHeader.find("th"),f=".layui-table-cell",p=o.elem.attr("lay-filter");a.layTool.on("click","*[lay-event]",function(e){var i=t(this),c=i.attr("lay-event"),u=function(e){var l=t(e.list),n=t('
      ');n.html(l),i.find(".layui-table-tool-panel")[0]||i.append(n),a.renderForm(),n.on("click",function(e){layui.stope(e)}),e.done&&e.done(n,l)};switch(layui.stope(e),H.trigger("table.tool.panel.remove"),l.close(a.tipsIndex),c){case"LAYTABLE_COLS":u({list:function(){var e=[];return a.eachCols(function(t,i){i.field&&"normal"==i.type&&e.push('
    • ')}),e.join("")}(),done:function(){n.on("checkbox(LAY_TABLE_TOOL_COLS)",function(e){var i=t(e.elem),l=this.checked,n=i.data("key"),r=i.data("parentkey");layui.each(o.cols,function(e,t){layui.each(t,function(t,i){if(e+"-"+t===n){var d=i.hide;i.hide=!l,a.elem.find('*[data-key="'+o.index+"-"+n+'"]')[l?"removeClass":"addClass"](y),d!=i.hide&&a.setParentCol(!l,r),a.fullSize(),a.scrollPatch(),a.setColsWidth()}})})})}});break;case"LAYTABLE_EXPORT":r.ie?l.tips("导出功能不支持 IE,请用 Chrome 等高级浏览器导出",this,{tips:3}):u({list:function(){return['
    • 导出到 Csv 文件
    • ','
    • 导出到 Excel 文件
    • '].join("")}(),done:function(e,i){i.on("click",function(){var e=t(this).data("type");d.exportFile(o.id,null,e)})}});break;case"LAYTABLE_PRINT":var h=window.open("打印窗口","_blank"),f=[""].join(""),v=t(a.layHeader.html());v.append(a.layMain.find("table").html()),v.find("th.layui-table-patch").remove(),v.find(".layui-table-col-special").remove(),h.document.write(f+v.prop("outerHTML")),h.document.close(),h.print(),h.close()}layui.event.call(this,s,"toolbar("+p+")",t.extend({event:c,config:o},{}))}),h.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.data("unresize")||u.resizeStart||(u.allowResize=i.width()-l<=10,c.css("cursor",u.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);u.resizeStart||c.css("cursor","")}).on("mousedown",function(e){var i=t(this);if(u.allowResize){var l=i.data("key");e.preventDefault(),u.resizeStart=!0,u.offset=[e.clientX,e.clientY],a.getCssRule(l,function(e){var t=e.style.width||i.outerWidth();u.rule=e,u.ruleWidth=parseFloat(t),u.minWidth=i.data("minwidth")||o.cellMinWidth})}}),H.on("mousemove",function(t){if(u.resizeStart){if(t.preventDefault(),u.rule){var i=u.ruleWidth+t.clientX-u.offset[0];i');return l[0].value=e.data("content")||a.text(),e.find("."+S)[0]||e.append(l),void l.focus()}}).on("mouseenter","td",function(){x.call(this)}).on("mouseleave","td",function(){x.call(this,"hide")});var g="layui-table-grid-down",x=function(e){var i=t(this),a=i.children(f);if(e)i.find(".layui-table-grid-down").remove();else if(a.prop("scrollWidth")>a.outerWidth()){if(a.find("."+g)[0])return;i.append('
      ')}};a.layBody.on("click","."+g,function(){var e=t(this),i=e.parent(),n=i.children(f);a.tipsIndex=l.tips(['
      ',n.html(),"
      ",''].join(""),n[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:a.elem.width()/2,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}})}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),i=e.parents("tr").eq(0).data("index");layui.event.call(this,s,"tool("+p+")",v.call(this,{event:e.attr("lay-event")})),a.setThisRowChecked(i)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layTotal.scrollLeft(i),a.layFixed.find(b).scrollTop(n),l.close(a.tipsIndex)}),H.on("click",function(){H.trigger("table.remove.tool.panel")}),H.on("table.remove.tool.panel",function(){t(".layui-table-tool-panel").remove()}),R.on("resize",function(){a.fullSize(),a.scrollPatch(),a.setColsWidth()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':u+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},c.config={},d.eachCols=function(e,i,a){var l=c.config[e]||{},n=[],o=0;a=t.extend(!0,[],a||l.cols),layui.each(a,function(e,t){layui.each(t,function(t,i){if(i.colGroup){var l=0;o++,i.CHILD_COLS=[],layui.each(a[e+1],function(e,t){t.PARENT_COL_INDEX||l>1&&l==i.colspan||(t.PARENT_COL_INDEX=o,i.CHILD_COLS.push(t),l+=parseInt(t.colspan>1?t.colspan:1))})}i.PARENT_COL_INDEX||n.push(i)})});var r=function(e){layui.each(e||n,function(e,t){return t.CHILD_COLS?r(t.CHILD_COLS):void("function"==typeof i&&i(e,t))})};r()},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},d.exportFile=function(e,t,i){t=t||d.clearCacheKey(d.cache[e]),i=i||"csv";var a=c.config[e]||{},l={csv:"text/csv",xls:"application/vnd.ms-excel"}[i],n=document.createElement("a");return r.ie?o.error("IE_NOT_SUPPORT_EXPORTS"):(n.href="data:"+l+";charset=utf-8,\ufeff"+encodeURIComponent(function(){var i=[],a=[];return layui.each(t,function(t,l){var n=[];"object"==typeof e?(layui.each(e,function(e,a){0==t&&i.push(a||"")}),layui.each(d.clearCacheKey(l),function(e,t){n.push(t)})):d.eachCols(e,function(e,a){a.field&&"normal"==a.type&&!a.hide&&(0==t&&i.push(a.title||""),n.push(l[a.field]))}),a.push(n.join(","))}),i.join(",")+"\r\n"+a.join("\r\n")}()),n.download=(a.title||"table_"+(a.index||""))+"."+i,document.body.appendChild(n),n.click(),void document.body.removeChild(n))},d.reload=function(e,i){var a=c.config[e];return i=i||{},a?(i.data&&i.data.constructor===Array&&delete a.data,d.render(t.extend(!0,{},a,i))):o.error("The ID option was not found in the table instance")},d.render=function(e){var t=new j(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(s,d)}); \ No newline at end of file +/** layui-v2.4.5 MIT License By https://www.layui.com */ + ;layui.define(["laytpl","laypage","layer","form","util"],function(e){"use strict";var t=layui.$,i=layui.laytpl,a=layui.laypage,l=layui.layer,n=layui.form,o=(layui.util,layui.hint()),r=layui.device(),d={config:{checkName:"LAY_CHECKED",indexName:"LAY_TABLE_INDEX"},cache:{},index:layui.table?layui.table.index+1e4:0,set:function(e){var i=this;return i.config=t.extend({},i.config,e),i},on:function(e,t){return layui.onevent.call(this,u,e,t)}},c=function(){var e=this,t=e.config,i=t.id||t.index;return i&&(c.that[i]=e,c.config[i]=t),{reload:function(t){e.reload.call(e,t)},setColsWidth:function(){e.setColsWidth.call(e)},resize:function(){e.resize.call(e)},config:t}},s=function(e){var t=c.config[e];return t||o.error("The ID option was not found in the table instance"),t||null},u="table",h=".layui-table",y="layui-hide",f="layui-none",p="layui-table-view",v=".layui-table-tool",m=".layui-table-box",g=".layui-table-init",b=".layui-table-header",x=".layui-table-body",k=".layui-table-main",C=".layui-table-fixed",w=".layui-table-fixed-l",T=".layui-table-fixed-r",A=".layui-table-total",L=".layui-table-page",S=".layui-table-sort",N="layui-table-edit",W="layui-table-hover",_=function(e){var t='{{#if(item2.colspan){}} colspan="{{item2.colspan}}"{{#} if(item2.rowspan){}} rowspan="{{item2.rowspan}}"{{#}}}';return e=e||{},['',"","{{# layui.each(d.data.cols, function(i1, item1){ }}","","{{# layui.each(item1, function(i2, item2){ }}",'{{# if(item2.fixed && item2.fixed !== "right"){ left = true; } }}','{{# if(item2.fixed === "right"){ right = true; } }}',function(){return e.fixed&&"right"!==e.fixed?'{{# if(item2.fixed && item2.fixed !== "right"){ }}':"right"===e.fixed?'{{# if(item2.fixed === "right"){ }}':""}(),"{{# var isSort = !(item2.colGroup) && item2.sort; }}",'",e.fixed?"{{# }; }}":"","{{# }); }}","","{{# }); }}","","
      ','
      ','{{# if(item2.type === "checkbox"){ }}','',"{{# } else { }}",'{{item2.title||""}}',"{{# if(isSort){ }}",'',"{{# } }}","{{# } }}","
      ","
      "].join("")},E=['',"","
      "].join(""),z=['
      ',"{{# if(d.data.toolbar){ }}",'
      ','
      ','
      ',"
      ","{{# } }}",'
      ',"{{# if(d.data.loading){ }}",'
      ','',"
      ","{{# } }}","{{# var left, right; }}",'
      ',_(),"
      ",'
      ',E,"
      ","{{# if(left){ }}",'
      ','
      ',_({fixed:!0}),"
      ",'
      ',E,"
      ","
      ","{{# }; }}","{{# if(right){ }}",'
      ','
      ',_({fixed:"right"}),'
      ',"
      ",'
      ',E,"
      ","
      ","{{# }; }}","
      ","{{# if(d.data.totalRow){ }}",'
      ','','',"
      ","
      ","{{# } }}","{{# if(d.data.page){ }}",'
      ','
      ',"
      ","{{# } }}","","
      "].join(""),H=t(window),R=t(document),F=function(e){var i=this;i.index=++d.index,i.config=t.extend({},i.config,d.config,e),i.render()};F.prototype.config={limit:10,loading:!0,cellMinWidth:60,defaultToolbar:["filter","exports","print"],autoSort:!0,text:{none:"无数据"}},F.prototype.render=function(){var e=this,a=e.config;if(a.elem=t(a.elem),a.where=a.where||{},a.id=a.id||a.elem.attr("id")||e.index,a.request=t.extend({pageName:"page",limitName:"limit"},a.request),a.response=t.extend({statusName:"code",statusCode:0,msgName:"msg",dataName:"data",countName:"count"},a.response),"object"==typeof a.page&&(a.limit=a.page.limit||a.limit,a.limits=a.page.limits||a.limits,e.page=a.page.curr=a.page.curr||1,delete a.page.elem,delete a.page.jump),!a.elem[0])return e;a.height&&/^full-\d+$/.test(a.height)&&(e.fullHeightGap=a.height.split("-")[1],a.height=H.height()-e.fullHeightGap),e.setInit();var l=a.elem,n=l.next("."+p),o=e.elem=t(i(z).render({VIEW_CLASS:p,data:a,index:e.index}));if(a.index=e.index,n[0]&&n.remove(),l.after(o),e.layTool=o.find(v),e.layBox=o.find(m),e.layHeader=o.find(b),e.layMain=o.find(k),e.layBody=o.find(x),e.layFixed=o.find(C),e.layFixLeft=o.find(w),e.layFixRight=o.find(T),e.layTotal=o.find(A),e.layPage=o.find(L),e.renderToolbar(),e.fullSize(),a.cols.length>1){var r=e.layFixed.find(b).find("th");r.height(e.layHeader.height()-1-parseFloat(r.css("padding-top"))-parseFloat(r.css("padding-bottom")))}e.pullData(e.page),e.events()},F.prototype.initOpts=function(e){var t=this,i=(t.config,{checkbox:48,radio:48,space:15,numbers:40});e.checkbox&&(e.type="checkbox"),e.space&&(e.type="space"),e.type||(e.type="normal"),"normal"!==e.type&&(e.unresize=!0,e.width=e.width||i[e.type])},F.prototype.setInit=function(e){var t=this,i=t.config;return i.clientWidth=i.width||function(){var e=function(t){var a,l;t=t||i.elem.parent(),a=t.width();try{l="none"===t.css("display")}catch(n){}return!t[0]||a&&!l?a:e(t.parent())};return e()}(),"width"===e?i.clientWidth:void layui.each(i.cols,function(e,a){layui.each(a,function(l,n){if(!n)return void a.splice(l,1);if(n.key=e+"-"+l,n.hide=n.hide||!1,n.colGroup||n.colspan>1){var o=0;layui.each(i.cols[e+1],function(t,i){i.HAS_PARENT||o>1&&o==n.colspan||(i.HAS_PARENT=!0,i.parentKey=e+"-"+l,o+=parseInt(i.colspan>1?i.colspan:1))}),n.colGroup=!0}t.initOpts(n)})})},F.prototype.renderToolbar=function(){var e=this,a=e.config,l=['
      ','
      ','
      '].join(""),n=e.layTool.find(".layui-table-tool-temp");if("default"===a.toolbar)n.html(l);else if("string"==typeof a.toolbar){var o=t(a.toolbar).html()||"";o&&n.html(i(o).render(a))}var r={filter:{title:"筛选列",layEvent:"LAYTABLE_COLS",icon:"layui-icon-cols"},exports:{title:"导出",layEvent:"LAYTABLE_EXPORT",icon:"layui-icon-export"},print:{title:"打印",layEvent:"LAYTABLE_PRINT",icon:"layui-icon-print"}},d=[];"object"==typeof a.defaultToolbar&&layui.each(a.defaultToolbar,function(e,t){var i=r[t];i&&d.push('
      ')}),e.layTool.find(".layui-table-tool-self").html(d.join(""))},F.prototype.setParentCol=function(e,t){var i=this,a=i.config,l=i.layHeader.find('th[data-key="'+a.index+"-"+t+'"]'),n=parseInt(l.attr("colspan"))||0;if(l[0]){var o=t.split("-"),r=a.cols[o[0]][o[1]];e?n--:n++,l.attr("colspan",n),l[n<1?"addClass":"removeClass"](y),r.colspan=n,r.hide=n<1;var d=l.data("parentkey");d&&i.setParentCol(e,d)}},F.prototype.setColsPatch=function(){var e=this,t=e.config;layui.each(t.cols,function(t,i){layui.each(i,function(t,i){i.hide&&e.setParentCol(i.hide,i.parentKey)})})},F.prototype.setColsWidth=function(){var e=this,t=e.config,i=0,a=0,l=0,n=0,o=e.setInit("width");e.eachCols(function(e,t){t.hide||i++}),o=o-function(){return"line"===t.skin||"nob"===t.skin?2:i+1}()-e.getScrollWidth(e.layMain[0])-1;var r=function(e){layui.each(t.cols,function(i,r){layui.each(r,function(i,d){var c=0,s=d.minWidth||t.cellMinWidth;return d?void(d.colGroup||d.hide||(e?l&&ln&&a&&(l=(o-n)/a)};r(),r(!0),e.autoColNums=a,e.eachCols(function(i,a){var n=a.minWidth||t.cellMinWidth;a.colGroup||a.hide||(0===a.width?e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(l>=n?l:n)+"px"}):/\d+%$/.test(a.width)&&e.getCssRule(t.index+"-"+a.key,function(e){e.style.width=Math.floor(parseFloat(a.width)/100*o)+"px"}))});var d=e.layMain.width()-e.getScrollWidth(e.layMain[0])-e.layMain.children("table").outerWidth();if(e.autoColNums&&d>=-i&&d<=i){var c=function(t){var i;return t=t||e.layHeader.eq(0).find("thead th:last-child"),i=t.data("field"),!i&&t.prev()[0]?c(t.prev()):t},s=c(),u=s.data("key");e.getCssRule(u,function(t){var i=t.style.width||s.outerWidth();t.style.width=parseFloat(i)+d+"px",e.layMain.height()-e.layMain.prop("clientHeight")>0&&(t.style.width=parseFloat(t.style.width)-1+"px")})}e.loading(!0)},F.prototype.resize=function(){var e=this;e.fullSize(),e.setColsWidth(),e.scrollPatch()},F.prototype.reload=function(e){var i=this;i.config.data&&i.config.data.constructor===Array&&delete i.config.data,i.config=t.extend({},i.config,e),i.render()},F.prototype.page=1,F.prototype.pullData=function(e){var i=this,a=i.config,l=a.request,n=a.response,o=function(){"object"==typeof a.initSort&&i.sort(a.initSort.field,a.initSort.type)};if(i.startTime=(new Date).getTime(),a.url){var r={};r[l.pageName]=e,r[l.limitName]=a.limit;var d=t.extend(r,a.where);a.contentType&&0==a.contentType.indexOf("application/json")&&(d=JSON.stringify(d)),t.ajax({type:a.method||"get",url:a.url,contentType:a.contentType,data:d,dataType:"json",headers:a.headers||{},success:function(t){"function"==typeof a.parseData&&(t=a.parseData(t)||t),t[n.statusName]!=n.statusCode?(i.renderForm(),i.layMain.html('
      '+(t[n.msgName]||"返回的数据不符合规范,正确的成功状态码 ("+n.statusName+") 应为:"+n.statusCode)+"
      ")):(i.renderData(t,e,t[n.countName]),o(),a.time=(new Date).getTime()-i.startTime+" ms"),i.setColsWidth(),"function"==typeof a.done&&a.done(t,e,t[n.countName])},error:function(e,t){i.layMain.html('
      数据接口请求异常:'+t+"
      "),i.renderForm(),i.setColsWidth()}})}else if(a.data&&a.data.constructor===Array){var c={},s=e*a.limit-a.limit;c[n.dataName]=a.data.concat().splice(s,a.limit),c[n.countName]=a.data.length,i.renderData(c,e,a.data.length),o(),i.setColsWidth(),"function"==typeof a.done&&a.done(c,e,c[n.countName])}},F.prototype.eachCols=function(e){var t=this;return d.eachCols(null,e,t.config.cols),t},F.prototype.renderData=function(e,n,o,r){var c=this,s=c.config,u=e[s.response.dataName]||[],h=[],p=[],v=[],m=function(){var e;return!r&&c.sortKey?c.sort(c.sortKey.field,c.sortKey.sort,!0):(layui.each(u,function(a,l){var o=[],u=[],f=[],m=a+s.limit*(n-1)+1;0!==l.length&&(r||(l[d.config.indexName]=a),c.eachCols(function(n,r){var c=r.field||n,h=s.index+"-"+r.key,p=l[c];if(void 0!==p&&null!==p||(p=""),!r.colGroup){var v=['','
      '+function(){var n=t.extend(!0,{LAY_INDEX:m},l),o=d.config.checkName;switch(r.type){case"checkbox":return'";case"radio":return n[o]&&(e=a),'';case"numbers":return m}return r.toolbar?i(t(r.toolbar).html()||"").render(n):r.templet?function(){return"function"==typeof r.templet?r.templet(n):i(t(r.templet).html()||String(p)).render(n)}():p}(),"
      "].join("");o.push(v),r.fixed&&"right"!==r.fixed&&u.push(v),"right"===r.fixed&&f.push(v)}}),h.push(''+o.join("")+""),p.push(''+u.join("")+""),v.push(''+f.join("")+""))}),c.layBody.scrollTop(0),c.layMain.find("."+f).remove(),c.layMain.find("tbody").html(h.join("")),c.layFixLeft.find("tbody").html(p.join("")),c.layFixRight.find("tbody").html(v.join("")),c.renderForm(),"number"==typeof e&&c.setThisRowChecked(e),c.syncCheckAll(),c.haveInit?c.scrollPatch():setTimeout(function(){c.scrollPatch()},50),c.haveInit=!0,l.close(c.tipsIndex),s.HAS_SET_COLS_PATCH||c.setColsPatch(),void(s.HAS_SET_COLS_PATCH=!0))};return c.key=s.id||s.index,d.cache[c.key]=u,c.layPage[0==o||0===u.length&&1==n?"addClass":"removeClass"](y),r?m():0===u.length?(c.renderForm(),c.layFixed.remove(),c.layMain.find("tbody").html(""),c.layMain.find("."+f).remove(),c.layMain.append('
      '+s.text.none+"
      ")):(m(),c.renderTotal(u),void(s.page&&(s.page=t.extend({elem:"layui-table-page"+s.index,count:o,limit:s.limit,limits:s.limits||[10,20,30,40,50,60,70,80,90],groups:3,layout:["prev","page","next","skip","count","limit"],prev:'',next:'',jump:function(e,t){t||(c.page=e.curr,s.limit=e.limit,c.loading(),c.pullData(e.curr))}},s.page),s.page.count=o,a.render(s.page))))},F.prototype.renderTotal=function(e){var t=this,i=t.config,a={};if(i.totalRow){layui.each(e,function(e,i){0!==i.length&&t.eachCols(function(e,t){var l=t.field||e,n=i[l];t.totalRow&&(a[l]=(a[l]||0)+(parseFloat(n)||0))})});var l=[];t.eachCols(function(e,t){var n=t.field||e,o=['','
      '+function(){var e=t.totalRowText||"";return t.totalRow?parseFloat(a[n]).toFixed(2)||e:e}(),"
      "].join("");l.push(o)}),t.layTotal.find("tbody").html(""+l.join("")+"")}},F.prototype.getColElem=function(e,t){var i=this,a=i.config;return e.eq(0).find(".laytable-cell-"+(a.index+"-"+t)+":eq(0)")},F.prototype.renderForm=function(e){n.render(e,"LAY-table-"+this.index)},F.prototype.setThisRowChecked=function(e){var t=this,i=(t.config,"layui-table-click"),a=t.layBody.find('tr[data-index="'+e+'"]');a.addClass(i).siblings("tr").removeClass(i)},F.prototype.sort=function(e,i,a,l){var n,r,c=this,s={},h=c.config,y=h.elem.attr("lay-filter"),f=d.cache[c.key];"string"==typeof e&&c.layHeader.find("th").each(function(i,a){var l=t(this),o=l.data("field");if(o===e)return e=l,n=o,!1});try{var n=n||e.data("field"),p=e.data("key");if(c.sortKey&&!a&&n===c.sortKey.field&&i===c.sortKey.sort)return;var v=c.layHeader.find("th .laytable-cell-"+p).find(S);c.layHeader.find("th").find(S).removeAttr("lay-sort"),v.attr("lay-sort",i||null),c.layFixed.find("th")}catch(m){return o.error("Table modules: Did not match to field")}c.sortKey={field:n,sort:i},h.autoSort&&("asc"===i?r=layui.sort(f,n):"desc"===i?r=layui.sort(f,n,!0):(r=layui.sort(f,d.config.indexName),delete c.sortKey)),s[h.response.dataName]=r||f,c.renderData(s,c.page,c.count,!0),l&&layui.event.call(e,u,"sort("+y+")",{field:n,type:i})},F.prototype.loading=function(e){var i=this,a=i.config;a.loading&&(e?(i.layInit&&i.layInit.remove(),delete i.layInit,i.layBox.find(g).remove()):(i.layInit=t(['
      ','',"
      "].join("")),i.layBox.append(i.layInit)))},F.prototype.setCheckData=function(e,t){var i=this,a=i.config,l=d.cache[i.key];l[e]&&l[e].constructor!==Array&&(l[e][a.checkName]=t)},F.prototype.syncCheckAll=function(){var e=this,t=e.config,i=e.layHeader.find('input[name="layTableCheckbox"]'),a=function(i){return e.eachCols(function(e,a){"checkbox"===a.type&&(a[t.checkName]=i)}),i};i[0]&&(d.checkStatus(e.key).isAll?(i[0].checked||(i.prop("checked",!0),e.renderForm("checkbox")),a(!0)):(i[0].checked&&(i.prop("checked",!1),e.renderForm("checkbox")),a(!1)))},F.prototype.getCssRule=function(e,t){var i=this,a=i.elem.find("style")[0],l=a.sheet||a.styleSheet||{},n=l.cssRules||l.rules;layui.each(n,function(i,a){if(a.selectorText===".laytable-cell-"+e)return t(a),!0})},F.prototype.fullSize=function(){var e,t=this,i=t.config,a=i.height;t.fullHeightGap&&(a=H.height()-t.fullHeightGap,a<135&&(a=135),t.elem.css("height",a)),a&&(e=parseFloat(a)-(t.layHeader.outerHeight()||38),i.toolbar&&(e-=t.layTool.outerHeight()||50),i.totalRow&&(e-=t.layTotal.outerHeight()||40),i.page&&(e=e-(t.layPage.outerHeight()||41)-2),t.layMain.css("height",e))},F.prototype.getScrollWidth=function(e){var t=0;return e?t=e.offsetWidth-e.clientWidth:(e=document.createElement("div"),e.style.width="100px",e.style.height="100px",e.style.overflowY="scroll",document.body.appendChild(e),t=e.offsetWidth-e.clientWidth,document.body.removeChild(e)),t},F.prototype.scrollPatch=function(){var e=this,i=e.layMain.children("table"),a=e.layMain.width()-e.layMain.prop("clientWidth"),l=e.layMain.height()-e.layMain.prop("clientHeight"),n=(e.getScrollWidth(e.layMain[0]),i.outerWidth()-e.layMain.width()),o=function(e){if(a&&l){if(e=e.eq(0),!e.find(".layui-table-patch")[0]){var i=t('
      ');i.find("div").css({width:a}),e.find("tr").append(i)}}else e.find(".layui-table-patch").remove()};o(e.layHeader),o(e.layTotal);var r=e.layMain.height(),d=r-l;e.layFixed.find(x).css("height",i.height()>=d?d:"auto"),e.layFixRight[n>0?"removeClass":"addClass"](y),e.layFixRight.css("right",a-1)},F.prototype.events=function(){var e,a=this,o=a.config,c=t("body"),s={},h=a.layHeader.find("th"),f=".layui-table-cell",p=o.elem.attr("lay-filter");a.layTool.on("click","*[lay-event]",function(e){var i=t(this),c=i.attr("lay-event"),s=function(e){var l=t(e.list),n=t('
        ');n.html(l),o.height&&n.css("max-height",o.height-(a.layTool.outerHeight()||50)),i.find(".layui-table-tool-panel")[0]||i.append(n),a.renderForm(),n.on("click",function(e){layui.stope(e)}),e.done&&e.done(n,l)};switch(layui.stope(e),R.trigger("table.tool.panel.remove"),l.close(a.tipsIndex),c){case"LAYTABLE_COLS":s({list:function(){var e=[];return a.eachCols(function(t,i){i.field&&"normal"==i.type&&e.push('
      • ')}),e.join("")}(),done:function(){n.on("checkbox(LAY_TABLE_TOOL_COLS)",function(e){var i=t(e.elem),l=this.checked,n=i.data("key"),r=i.data("parentkey");layui.each(o.cols,function(e,t){layui.each(t,function(t,i){if(e+"-"+t===n){var d=i.hide;i.hide=!l,a.elem.find('*[data-key="'+o.index+"-"+n+'"]')[l?"removeClass":"addClass"](y),d!=i.hide&&a.setParentCol(!l,r),a.resize()}})})})}});break;case"LAYTABLE_EXPORT":r.ie?l.tips("导出功能不支持 IE,请用 Chrome 等高级浏览器导出",this,{tips:3}):s({list:function(){return['
      • 导出到 Csv 文件
      • ','
      • 导出到 Excel 文件
      • '].join("")}(),done:function(e,i){i.on("click",function(){var e=t(this).data("type");d.exportFile(o.id,null,e)})}});break;case"LAYTABLE_PRINT":var h=window.open("打印窗口","_blank"),f=[""].join(""),v=t(a.layHeader.html());v.append(a.layMain.find("table").html()),v.find("th.layui-table-patch").remove(),v.find(".layui-table-col-special").remove(),h.document.write(f+v.prop("outerHTML")),h.document.close(),h.print(),h.close()}layui.event.call(this,u,"toolbar("+p+")",t.extend({event:c,config:o},{}))}),h.on("mousemove",function(e){var i=t(this),a=i.offset().left,l=e.clientX-a;i.data("unresize")||s.resizeStart||(s.allowResize=i.width()-l<=10,c.css("cursor",s.allowResize?"col-resize":""))}).on("mouseleave",function(){t(this);s.resizeStart||c.css("cursor","")}).on("mousedown",function(e){var i=t(this);if(s.allowResize){var l=i.data("key");e.preventDefault(),s.resizeStart=!0,s.offset=[e.clientX,e.clientY],a.getCssRule(l,function(e){var t=e.style.width||i.outerWidth();s.rule=e,s.ruleWidth=parseFloat(t),s.minWidth=i.data("minwidth")||o.cellMinWidth})}}),R.on("mousemove",function(t){if(s.resizeStart){if(t.preventDefault(),s.rule){var i=s.ruleWidth+t.clientX-s.offset[0];i');return n[0].value=i.data("content")||l.text(),i.find("."+N)[0]||i.append(n),n.focus(),void layui.stope(e)}}).on("mouseenter","td",function(){b.call(this)}).on("mouseleave","td",function(){b.call(this,"hide")});var g="layui-table-grid-down",b=function(e){var i=t(this),a=i.children(f);if(e)i.find(".layui-table-grid-down").remove();else if(a.prop("scrollWidth")>a.outerWidth()){if(a.find("."+g)[0])return;i.append('
        ')}};a.layBody.on("click","."+g,function(e){var i=t(this),n=i.parent(),d=n.children(f);a.tipsIndex=l.tips(['
        ',d.html(),"
        ",''].join(""),d[0],{tips:[3,""],time:-1,anim:-1,maxWidth:r.ios||r.android?300:a.elem.width()/2,isOutAnim:!1,skin:"layui-table-tips",success:function(e,t){e.find(".layui-table-tips-c").on("click",function(){l.close(t)})}}),layui.stope(e)}),a.layBody.on("click","*[lay-event]",function(){var e=t(this),i=e.parents("tr").eq(0).data("index");layui.event.call(this,u,"tool("+p+")",v.call(this,{event:e.attr("lay-event")})),a.setThisRowChecked(i)}),a.layMain.on("scroll",function(){var e=t(this),i=e.scrollLeft(),n=e.scrollTop();a.layHeader.scrollLeft(i),a.layTotal.scrollLeft(i),a.layFixed.find(x).scrollTop(n),l.close(a.tipsIndex)}),R.on("click",function(){R.trigger("table.remove.tool.panel")}),R.on("table.remove.tool.panel",function(){t(".layui-table-tool-panel").remove()}),H.on("resize",function(){a.resize()})},d.init=function(e,i){i=i||{};var a=this,l=t(e?'table[lay-filter="'+e+'"]':h+"[lay-data]"),n="Table element property lay-data configuration item has a syntax error: ";return l.each(function(){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){o.error(n+l)}var c=[],s=t.extend({elem:this,cols:[],data:[],skin:a.attr("lay-skin"),size:a.attr("lay-size"),even:"string"==typeof a.attr("lay-even")},d.config,i,l);e&&a.hide(),a.find("thead>tr").each(function(e){s.cols[e]=[],t(this).children().each(function(i){var a=t(this),l=a.attr("lay-data");try{l=new Function("return "+l)()}catch(r){return o.error(n+l)}var d=t.extend({title:a.text(),colspan:a.attr("colspan")||0,rowspan:a.attr("rowspan")||0},l);d.colspan<2&&c.push(d),s.cols[e].push(d)})}),a.find("tbody>tr").each(function(e){var i=t(this),a={};i.children("td").each(function(e,i){var l=t(this),n=l.data("field");if(n)return a[n]=l.html()}),layui.each(c,function(e,t){var l=i.children("td").eq(e);a[t.field]=l.html()}),s.data[e]=a}),d.render(s)}),a},c.that={},c.config={},d.eachCols=function(e,i,a){var l=c.config[e]||{},n=[],o=0;a=t.extend(!0,[],a||l.cols),layui.each(a,function(e,t){layui.each(t,function(t,i){if(i.colGroup){var l=0;o++,i.CHILD_COLS=[],layui.each(a[e+1],function(e,t){t.PARENT_COL_INDEX||l>1&&l==i.colspan||(t.PARENT_COL_INDEX=o,i.CHILD_COLS.push(t),l+=parseInt(t.colspan>1?t.colspan:1))})}i.PARENT_COL_INDEX||n.push(i)})});var r=function(e){layui.each(e||n,function(e,t){return t.CHILD_COLS?r(t.CHILD_COLS):void("function"==typeof i&&i(e,t))})};r()},d.checkStatus=function(e){var t=0,i=0,a=[],l=d.cache[e]||[];return layui.each(l,function(e,l){return l.constructor===Array?void i++:void(l[d.config.checkName]&&(t++,a.push(d.clearCacheKey(l))))}),{data:a,isAll:!!l.length&&t===l.length-i}},d.exportFile=function(e,t,i){t=t||d.clearCacheKey(d.cache[e]),i=i||"csv";var a=c.config[e]||{},l={csv:"text/csv",xls:"application/vnd.ms-excel"}[i],n=document.createElement("a");return r.ie?o.error("IE_NOT_SUPPORT_EXPORTS"):(n.href="data:"+l+";charset=utf-8,\ufeff"+encodeURIComponent(function(){var i=[],a=[];return layui.each(t,function(t,l){var n=[];"object"==typeof e?(layui.each(e,function(e,a){0==t&&i.push(a||"")}),layui.each(d.clearCacheKey(l),function(e,t){n.push(t)})):d.eachCols(e,function(e,a){a.field&&"normal"==a.type&&!a.hide&&(0==t&&i.push(a.title||""),n.push(l[a.field]))}),a.push(n.join(","))}),i.join(",")+"\r\n"+a.join("\r\n")}()),n.download=(a.title||"table_"+(a.index||""))+"."+i,document.body.appendChild(n),n.click(),void document.body.removeChild(n))},d.resize=function(e){if(e){var t=s(e);if(!t)return;c.that[e].resize()}else layui.each(c.that,function(){this.resize()})},d.reload=function(e,i){i=i||{};var a=s(e);if(a)return i.data&&i.data.constructor===Array&&delete a.data,d.render(t.extend(!0,{},a,i))},d.render=function(e){var t=new F(e);return c.call(t)},d.clearCacheKey=function(e){return e=t.extend({},e),delete e[d.config.checkName],delete e[d.config.indexName],e},d.init(),e(u,d)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/tree.js b/static/admin/js/layui/lay/modules/tree.js index af9ca31..e8c053d 100755 --- a/static/admin/js/layui/lay/modules/tree.js +++ b/static/admin/js/layui/lay/modules/tree.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(e){"use strict";var o=layui.$,a=layui.hint(),i="layui-tree-enter",r=function(e){this.options=e},t={arrow:["",""],checkbox:["",""],radio:["",""],branch:["",""],leaf:""};r.prototype.init=function(e){var o=this;e.addClass("layui-box layui-tree"),o.options.skin&&e.addClass("layui-tree-skin-"+o.options.skin),o.tree(e),o.on(e)},r.prototype.tree=function(e,a){var i=this,r=i.options,n=a||r.nodes;layui.each(n,function(a,n){var l=n.children&&n.children.length>0,c=o('
          '),s=o(["
        • ",function(){return l?''+(n.spread?t.arrow[1]:t.arrow[0])+"":""}(),function(){return r.check?''+("checkbox"===r.check?t.checkbox[0]:"radio"===r.check?t.radio[0]:"")+"":""}(),function(){return'"+(''+(l?n.spread?t.branch[1]:t.branch[0]:t.leaf)+"")+(""+(n.name||"未命名")+"")}(),"
        • "].join(""));l&&(s.append(c),i.tree(c,n.children)),e.append(s),"function"==typeof r.click&&i.click(s,n),i.spread(s,n),r.drag&&i.drag(s,n)})},r.prototype.click=function(e,o){var a=this,i=a.options;e.children("a").on("click",function(e){layui.stope(e),i.click(o)})},r.prototype.spread=function(e,o){var a=this,i=(a.options,e.children(".layui-tree-spread")),r=e.children("ul"),n=e.children("a"),l=function(){e.data("spread")?(e.data("spread",null),r.removeClass("layui-show"),i.html(t.arrow[0]),n.find(".layui-icon").html(t.branch[0])):(e.data("spread",!0),r.addClass("layui-show"),i.html(t.arrow[1]),n.find(".layui-icon").html(t.branch[1]))};r[0]&&(i.on("click",l),n.on("dblclick",l))},r.prototype.on=function(e){var a=this,r=a.options,t="layui-tree-drag";e.find("i").on("selectstart",function(e){return!1}),r.drag&&o(document).on("mousemove",function(e){var i=a.move;if(i.from){var r=(i.to,o('
          '));e.preventDefault(),o("."+t)[0]||o("body").append(r);var n=o("."+t)[0]?o("."+t):r;n.addClass("layui-show").html(i.from.elem.children("a").html()),n.css({left:e.pageX+10,top:e.pageY+10})}}).on("mouseup",function(){var e=a.move;e.from&&(e.from.elem.children("a").removeClass(i),e.to&&e.to.elem.children("a").removeClass(i),a.move={},o("."+t).remove())})},r.prototype.move={},r.prototype.drag=function(e,a){var r=this,t=(r.options,e.children("a")),n=function(){var t=o(this),n=r.move;n.from&&(n.to={item:a,elem:e},t.addClass(i))};t.on("mousedown",function(){var o=r.move;o.from={item:a,elem:e}}),t.on("mouseenter",n).on("mousemove",n).on("mouseleave",function(){var e=o(this),a=r.move;a.from&&(delete a.to,e.removeClass(i))})},e("tree",function(e){var i=new r(e=e||{}),t=o(e.elem);return t[0]?void i.init(t):a.error("layui.tree 没有找到"+e.elem+"元素")})}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/upload.js b/static/admin/js/layui/lay/modules/upload.js index 1ede8d6..404d534 100755 --- a/static/admin/js/layui/lay/modules/upload.js +++ b/static/admin/js/layui/lay/modules/upload.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("layer",function(e){"use strict";var i=layui.$,t=layui.layer,n=layui.hint(),a=layui.device(),o={config:{},set:function(e){var t=this;return t.config=i.extend({},t.config,e),t},on:function(e,i){return layui.onevent.call(this,r,e,i)}},l=function(){var e=this;return{upload:function(i){e.upload.call(e,i)},config:e.config}},r="upload",u="layui-upload-file",c="layui-upload-form",f="layui-upload-iframe",s="layui-upload-choose",p=function(e){var t=this;t.config=i.extend({},t.config,o.config,e),t.render()};p.prototype.config={accept:"images",exts:"",auto:!0,bindAction:"",url:"",field:"file",method:"post",data:{},drag:!0,size:0,number:0,multiple:!1},p.prototype.render=function(e){var t=this,e=t.config;e.elem=i(e.elem),e.bindAction=i(e.bindAction),t.file(),t.events()},p.prototype.file=function(){var e=this,t=e.config,n=e.elemFile=i(['"].join("")),o=t.elem.next();(o.hasClass(u)||o.hasClass(c))&&o.remove(),a.ie&&a.ie<10&&t.elem.wrap('
          '),e.isFile()?(e.elemFile=t.elem,t.field=t.elem[0].name):t.elem.after(n),a.ie&&a.ie<10&&e.initIE()},p.prototype.initIE=function(){var e=this,t=e.config,n=i(''),a=i(['
          ',"
          "].join(""));i("#"+f)[0]||i("body").append(n),t.elem.next().hasClass(c)||(e.elemFile.wrap(a),t.elem.next("."+c).append(function(){var e=[];return layui.each(t.data,function(i,t){t="function"==typeof t?t():t,e.push('')}),e.join("")}()))},p.prototype.msg=function(e){return t.msg(e,{icon:2,shift:6})},p.prototype.isFile=function(){var e=this.config.elem[0];if(e)return"input"===e.tagName.toLocaleLowerCase()&&"file"===e.type},p.prototype.preview=function(e){var i=this;window.FileReader&&layui.each(i.chooseFiles,function(i,t){var n=new FileReader;n.readAsDataURL(t),n.onload=function(){e&&e(i,t,this.result)}})},p.prototype.upload=function(e,t){var n,o=this,l=o.config,r=o.elemFile[0],u=function(){var t=0,n=0,a=e||o.files||o.chooseFiles||r.files,u=function(){l.multiple&&t+n===o.fileLength&&"function"==typeof l.allDone&&l.allDone({total:o.fileLength,successful:t,aborted:n})};layui.each(a,function(e,a){var r=new FormData;r.append(l.field,a),layui.each(l.data,function(e,i){i="function"==typeof i?i():i,r.append(e,i)}),i.ajax({url:l.url,type:"post",data:r,contentType:!1,processData:!1,dataType:"json",headers:l.headers||{},success:function(i){t++,d(e,i),u()},error:function(){n++,o.msg("请求上传接口出现异常"),m(e),u()}})})},c=function(){var e=i("#"+f);o.elemFile.parent().submit(),clearInterval(p.timer),p.timer=setInterval(function(){var i,t=e.contents().find("body");try{i=t.text()}catch(n){o.msg("获取上传后的响应信息出现异常"),clearInterval(p.timer),m()}i&&(clearInterval(p.timer),t.html(""),d(0,i))},30)},d=function(e,i){if(o.elemFile.next("."+s).remove(),r.value="","object"!=typeof i)try{i=JSON.parse(i)}catch(t){return i={},o.msg("请对上传接口返回有效JSON")}"function"==typeof l.done&&l.done(i,e||0,function(e){o.upload(e)})},m=function(e){l.auto&&(r.value=""),"function"==typeof l.error&&l.error(e||0,function(e){o.upload(e)})},h=l.exts,v=function(){var i=[];return layui.each(e||o.chooseFiles,function(e,t){i.push(t.name)}),i}(),g={preview:function(e){o.preview(e)},upload:function(e,i){var t={};t[e]=i,o.upload(t)},pushFile:function(){return o.files=o.files||{},layui.each(o.chooseFiles,function(e,i){o.files[e]=i}),o.files},resetFile:function(e,i,t){var n=new File([i],t);o.files=o.files||{},o.files[e]=n}},y=function(){if("choose"!==t&&!l.auto||(l.choose&&l.choose(g),"choose"!==t))return l.before&&l.before(g),a.ie?a.ie>9?u():c():void u()};if(v=0===v.length?r.value.match(/[^\/\\]+\..+/g)||[]||"":v,0!==v.length){switch(l.accept){case"file":if(h&&!RegExp("\\w\\.("+h+")$","i").test(escape(v)))return o.msg("选择的文件中包含不支持的格式"),r.value="";break;case"video":if(!RegExp("\\w\\.("+(h||"avi|mp4|wma|rmvb|rm|flash|3gp|flv")+")$","i").test(escape(v)))return o.msg("选择的视频中包含不支持的格式"),r.value="";break;case"audio":if(!RegExp("\\w\\.("+(h||"mp3|wav|mid")+")$","i").test(escape(v)))return o.msg("选择的音频中包含不支持的格式"),r.value="";break;default:if(layui.each(v,function(e,i){RegExp("\\w\\.("+(h||"jpg|png|gif|bmp|jpeg$")+")","i").test(escape(i))||(n=!0)}),n)return o.msg("选择的图片中包含不支持的格式"),r.value=""}if(o.fileLength=function(){var i=0,t=e||o.files||o.chooseFiles||r.files;return layui.each(t,function(){i++}),i}(),l.number&&o.fileLength>l.number)return o.msg("同时最多只能上传的数量为:"+l.number);if(l.size>0&&!(a.ie&&a.ie<10)){var F;if(layui.each(o.chooseFiles,function(e,i){if(i.size>1024*l.size){var t=l.size/1024;t=t>=1?t.toFixed(2)+"MB":l.size+"KB",r.value="",F=t}}),F)return o.msg("文件不能超过"+F)}y()}},p.prototype.events=function(){var e=this,t=e.config,o=function(i){e.chooseFiles={},layui.each(i,function(i,t){var n=(new Date).getTime();e.chooseFiles[n+"-"+i]=t})},l=function(i,n){var a=e.elemFile,o=i.length>1?i.length+"个文件":(i[0]||{}).name||a[0].value.match(/[^\/\\]+\..+/g)||[]||"";a.next().hasClass(s)&&a.next().remove(),e.upload(null,"choose"),e.isFile()||t.choose||a.after(''+o+"")};t.elem.off("upload.start").on("upload.start",function(){var a=i(this),o=a.attr("lay-data");if(o)try{o=new Function("return "+o)(),e.config=i.extend({},t,o)}catch(l){n.error("Upload element property lay-data configuration item has a syntax error: "+o)}e.config.item=a,e.elemFile[0].click()}),a.ie&&a.ie<10||t.elem.off("upload.over").on("upload.over",function(){var e=i(this);e.attr("lay-over","")}).off("upload.leave").on("upload.leave",function(){var e=i(this);e.removeAttr("lay-over")}).off("upload.drop").on("upload.drop",function(n,a){var r=i(this),u=a.originalEvent.dataTransfer.files||[];r.removeAttr("lay-over"),o(u),t.auto?e.upload(u):l(u)}),e.elemFile.off("upload.change").on("upload.change",function(){var i=this.files||[];o(i),t.auto?e.upload():l(i)}),t.bindAction.off("upload.action").on("upload.action",function(){e.upload()}),t.elem.data("haveEvents")||(e.elemFile.on("change",function(){i(this).trigger("upload.change")}),t.elem.on("click",function(){e.isFile()||i(this).trigger("upload.start")}),t.drag&&t.elem.on("dragover",function(e){e.preventDefault(),i(this).trigger("upload.over")}).on("dragleave",function(e){i(this).trigger("upload.leave")}).on("drop",function(e){e.preventDefault(),i(this).trigger("upload.drop",e)}),t.bindAction.on("click",function(){i(this).trigger("upload.action")}),t.elem.data("haveEvents",!0))},o.render=function(e){var i=new p(e);return l.call(i)},e(r,o)}); \ No newline at end of file diff --git a/static/admin/js/layui/lay/modules/util.js b/static/admin/js/layui/lay/modules/util.js index f0bf4d2..8d38508 100755 --- a/static/admin/js/layui/lay/modules/util.js +++ b/static/admin/js/layui/lay/modules/util.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ +/** layui-v2.4.5 MIT License By https://www.layui.com */ ;layui.define("jquery",function(t){"use strict";var e=layui.$,i={fixbar:function(t){var i,a,n="layui-fixbar",r="layui-fixbar-top",o=e(document),l=e("body");t=e.extend({showHeight:200},t),t.bar1=t.bar1===!0?"":t.bar1,t.bar2=t.bar2===!0?"":t.bar2,t.bgcolor=t.bgcolor?"background-color:"+t.bgcolor:"";var c=[t.bar1,t.bar2,""],g=e(['
            ',t.bar1?'
          • '+c[0]+"
          • ":"",t.bar2?'
          • '+c[1]+"
          • ":"",'
          • '+c[2]+"
          • ","
          "].join("")),s=g.find("."+r),u=function(){var e=o.scrollTop();e>=t.showHeight?i||(s.show(),i=1):i&&(s.hide(),i=0)};e("."+n)[0]||("object"==typeof t.css&&g.css(t.css),l.append(g),u(),g.find("li").on("click",function(){var i=e(this),a=i.attr("lay-type");"top"===a&&e("html,body").animate({scrollTop:0},200),t.click&&t.click.call(this,a)}),o.on("scroll",function(){clearTimeout(a),a=setTimeout(function(){u()},100)}))},countdown:function(t,e,i){var a=this,n="function"==typeof e,r=new Date(t).getTime(),o=new Date(!e||n?(new Date).getTime():e).getTime(),l=r-o,c=[Math.floor(l/864e5),Math.floor(l/36e5)%24,Math.floor(l/6e4)%60,Math.floor(l/1e3)%60];n&&(i=e);var g=setTimeout(function(){a.countdown(t,o+1e3,i)},1e3);return i&&i(l>0?c:[0,0,0,0],e,g),l<=0&&clearTimeout(g),g},timeAgo:function(t,e){var i=this,a=[[],[]],n=(new Date).getTime()-new Date(t).getTime();return n>6912e5?(n=new Date(t),a[0][0]=i.digit(n.getFullYear(),4),a[0][1]=i.digit(n.getMonth()+1),a[0][2]=i.digit(n.getDate()),e||(a[1][0]=i.digit(n.getHours()),a[1][1]=i.digit(n.getMinutes()),a[1][2]=i.digit(n.getSeconds())),a[0].join("-")+" "+a[1].join(":")):n>=864e5?(n/1e3/60/60/24|0)+"天前":n>=36e5?(n/1e3/60/60|0)+"小时前":n>=12e4?(n/1e3/60|0)+"分钟前":n<0?"未来":"刚刚"},digit:function(t,e){var i="";t=String(t),e=e||2;for(var a=t.length;a/g,">").replace(/'/g,"'").replace(/"/g,""")}};!function(t,e,i){"$:nomunge";function a(){n=e[l](function(){r.each(function(){var e=t(this),i=e.width(),a=e.height(),n=t.data(this,g);(i!==n.w||a!==n.h)&&e.trigger(c,[n.w=i,n.h=a])}),a()},o[s])}var n,r=t([]),o=t.resize=t.extend(t.resize,{}),l="setTimeout",c="resize",g=c+"-special-event",s="delay",u="throttleWindow";o[s]=250,o[u]=!0,t.event.special[c]={setup:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.add(e),t.data(this,g,{w:e.width(),h:e.height()}),1===r.length&&a()},teardown:function(){if(!o[u]&&this[l])return!1;var e=t(this);r=r.not(e),e.removeData(g),r.length||clearTimeout(n)},add:function(e){function a(e,a,r){var o=t(this),l=t.data(this,g)||{};l.w=a!==i?a:o.width(),l.h=r!==i?r:o.height(),n.apply(this,arguments)}if(!o[u]&&this[l])return!1;var n;return t.isFunction(e)?(n=e,a):(n=e.handler,void(e.handler=a))}}}(e,window),t("util",i)}); \ No newline at end of file diff --git a/static/admin/js/layui/layui.js b/static/admin/js/layui/layui.js index 3897c9d..3cd51c2 100755 --- a/static/admin/js/layui/layui.js +++ b/static/admin/js/layui/layui.js @@ -1,2 +1,2 @@ -/** layui-v2.4.3 MIT License By https://www.layui.com */ - ;!function(e){"use strict";var t=document,o={modules:{},status:{},timeout:10,event:{}},n=function(){this.v="2.4.3"},r=function(){var e=t.currentScript?t.currentScript.src:function(){for(var e,o=t.scripts,n=o.length-1,r=n;r>0;r--)if("interactive"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),i=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},a="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return"function"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":o.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||a?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+" timeout"):void(1989===parseInt(a.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return"function"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,"function"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,"function"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),o.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||"layui",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o="object"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return"value"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oi?1:r0;r--)if("interactive"===o[r].readyState){e=o[r].src;break}return e||o[n].src}();return e.substring(0,e.lastIndexOf("/")+1)}(),i=function(t){e.console&&console.error&&console.error("Layui hint: "+t)},a="undefined"!=typeof opera&&"[object Opera]"===opera.toString(),u={layer:"modules/layer",laydate:"modules/laydate",laypage:"modules/laypage",laytpl:"modules/laytpl",layim:"modules/layim",layedit:"modules/layedit",form:"modules/form",upload:"modules/upload",tree:"modules/tree",table:"modules/table",element:"modules/element",rate:"modules/rate",colorpicker:"modules/colorpicker",slider:"modules/slider",carousel:"modules/carousel",flow:"modules/flow",util:"modules/util",code:"modules/code",jquery:"modules/jquery",mobile:"modules/mobile","layui.all":"../layui.all"};n.prototype.cache=o,n.prototype.define=function(e,t){var n=this,r="function"==typeof e,i=function(){var e=function(e,t){layui[e]=t,o.status[e]=!0};return"function"==typeof t&&t(function(n,r){e(n,r),o.callback[n]=function(){t(e)}}),this};return r&&(t=e,e=[]),layui["layui.all"]||!layui["layui.all"]&&layui["layui.mobile"]?i.call(n):(n.use(e,i),n)},n.prototype.use=function(e,n,l){function s(e,t){var n="PLaySTATION 3"===navigator.platform?/^complete$/:/^(complete|loaded)$/;("load"===e.type||n.test((e.currentTarget||e.srcElement).readyState))&&(o.modules[f]=t,d.removeChild(v),function r(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void(o.status[f]?c():setTimeout(r,4))}())}function c(){l.push(layui[f]),e.length>1?y.use(e.slice(1),n,l):"function"==typeof n&&n.apply(layui,l)}var y=this,p=o.dir=o.dir?o.dir:r,d=t.getElementsByTagName("head")[0];e="string"==typeof e?[e]:e,window.jQuery&&jQuery.fn.on&&(y.each(e,function(t,o){"jquery"===o&&e.splice(t,1)}),layui.jquery=layui.$=jQuery);var f=e[0],m=0;if(l=l||[],o.host=o.host||(p.match(/\/\/([\s\S]+?)\//)||["//"+location.host+"/"])[0],0===e.length||layui["layui.all"]&&u[f]||!layui["layui.all"]&&layui["layui.mobile"]&&u[f])return c(),y;if(o.modules[f])!function g(){return++m>1e3*o.timeout/4?i(f+" is not a valid module"):void("string"==typeof o.modules[f]&&o.status[f]?c():setTimeout(g,4))}();else{var v=t.createElement("script"),h=(u[f]?p+"lay/":/^\{\/\}/.test(y.modules[f])?"":o.base||"")+(y.modules[f]||f)+".js";h=h.replace(/^\{\/\}/,""),v.async=!0,v.charset="utf-8",v.src=h+function(){var e=o.version===!0?o.v||(new Date).getTime():o.version||"";return e?"?v="+e:""}(),d.appendChild(v),!v.attachEvent||v.attachEvent.toString&&v.attachEvent.toString().indexOf("[native code")<0||a?v.addEventListener("load",function(e){s(e,h)},!1):v.attachEvent("onreadystatechange",function(e){s(e,h)}),o.modules[f]=h}return y},n.prototype.getStyle=function(t,o){var n=t.currentStyle?t.currentStyle:e.getComputedStyle(t,null);return n[n.getPropertyValue?"getPropertyValue":"getAttribute"](o)},n.prototype.link=function(e,n,r){var a=this,u=t.createElement("link"),l=t.getElementsByTagName("head")[0];"string"==typeof n&&(r=n);var s=(r||e).replace(/\.|\//g,""),c=u.id="layuicss-"+s,y=0;return u.rel="stylesheet",u.href=e+(o.debug?"?v="+(new Date).getTime():""),u.media="all",t.getElementById(c)||l.appendChild(u),"function"!=typeof n?a:(function p(){return++y>1e3*o.timeout/100?i(e+" timeout"):void(1989===parseInt(a.getStyle(t.getElementById(c),"width"))?function(){n()}():setTimeout(p,100))}(),a)},o.callback={},n.prototype.factory=function(e){if(layui[e])return"function"==typeof o.callback[e]?o.callback[e]:null},n.prototype.addcss=function(e,t,n){return layui.link(o.dir+"css/"+e,t,n)},n.prototype.img=function(e,t,o){var n=new Image;return n.src=e,n.complete?t(n):(n.onload=function(){n.onload=null,"function"==typeof t&&t(n)},void(n.onerror=function(e){n.onerror=null,"function"==typeof o&&o(e)}))},n.prototype.config=function(e){e=e||{};for(var t in e)o[t]=e[t];return this},n.prototype.modules=function(){var e={};for(var t in u)e[t]=u[t];return e}(),n.prototype.extend=function(e){var t=this;e=e||{};for(var o in e)t[o]||t.modules[o]?i("模块名 "+o+" 已被占用"):t.modules[o]=e[o];return t},n.prototype.router=function(e){var t=this,e=e||location.hash,o={path:[],search:{},hash:(e.match(/[^#](#.*$)/)||[])[1]||""};return/^#\//.test(e)?(e=e.replace(/^#\//,""),o.href="/"+e,e=e.replace(/([^#])(#.*$)/,"$1").split("/")||[],t.each(e,function(e,t){/^\w+=/.test(t)?function(){t=t.split("="),o.search[t[0]]=t[1]}():o.path.push(t)}),o):o},n.prototype.data=function(t,o,n){if(t=t||"layui",n=n||localStorage,e.JSON&&e.JSON.parse){if(null===o)return delete n[t];o="object"==typeof o?o:{key:o};try{var r=JSON.parse(n[t])}catch(i){var r={}}return"value"in o&&(r[o.key]=o.value),o.remove&&delete r[o.key],n[t]=JSON.stringify(r),o.key?r[o.key]:r}},n.prototype.sessionData=function(e,t){return this.data(e,t,sessionStorage)},n.prototype.device=function(t){var o=navigator.userAgent.toLowerCase(),n=function(e){var t=new RegExp(e+"/([^\\s\\_\\-]+)");return e=(o.match(t)||[])[1],e||!1},r={os:function(){return/windows/.test(o)?"windows":/linux/.test(o)?"linux":/iphone|ipod|ipad|ios/.test(o)?"ios":/mac/.test(o)?"mac":void 0}(),ie:function(){return!!(e.ActiveXObject||"ActiveXObject"in e)&&((o.match(/msie\s(\d+)/)||[])[1]||"11")}(),weixin:n("micromessenger")};return t&&!r[t]&&(r[t]=n(t)),r.android=/android/.test(o),r.ios="ios"===r.os,r},n.prototype.hint=function(){return{error:i}},n.prototype.each=function(e,t){var o,n=this;if("function"!=typeof t)return n;if(e=e||[],e.constructor===Object){for(o in e)if(t.call(e[o],o,e[o]))break}else for(o=0;oi?1:r Date: Sun, 4 Nov 2018 19:00:12 +0800 Subject: [PATCH 18/32] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E6=9C=AA=E5=AE=9A=E4=B9=89=E7=9A=84=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复变量未定义的错误 --- app/admin/controller/Module.php | 269 ++++++++++++++++++------------- app/admin/controller/Plugins.php | 4 +- app/admin/view/module/index.php | 10 +- app/admin/view/plugins/index.php | 11 +- 4 files changed, 167 insertions(+), 127 deletions(-) diff --git a/app/admin/controller/Module.php b/app/admin/controller/Module.php index a3aae98..1e2589c 100644 --- a/app/admin/controller/Module.php +++ b/app/admin/controller/Module.php @@ -45,10 +45,6 @@ class Module extends Admin 'title' => '未安装模块', 'url' => 'admin/module/index?status=0', ], - [ - 'title' => '应用市场', - 'url' => 'http://store.hisiphp.com/modules', - ], [ 'title' => '导入模块', 'url' => 'admin/module/import', @@ -72,7 +68,7 @@ class Module extends Admin $map = []; $map['status'] = $status; $map['system'] = 0; - $modules = ModuleModel::where($map)->order('sort,id')->column('id,title,author,intro,icon,default,system,app_id,identifier,config,name,version,status'); + $modules = ModuleModel::where($map)->order('sort,id')->column('id,title,author,intro,icon,default,system,app_keys,identifier,config,name,version,status'); if ($status == 0) { // 自动将本地未入库的模块导入数据库 $all_module = ModuleModel::order('sort,id')->column('id,name', 'name'); @@ -105,7 +101,7 @@ class Module extends Admin $sql['status'] = 0; $sql['default'] = 0; $sql['system'] = 0; - $sql['app_id'] = 0; + $sql['app_keys'] = ''; $db = ModuleModel::create($sql); $sql['id'] = $db->id; $modules = array_merge($modules, [$sql]); @@ -113,6 +109,7 @@ class Module extends Admin } } + $this->assign('emptyTips', '未发现相关模块,快去 应用市场 看看吧!'); $this->assign('data_list', array_values($modules)); $this->assign('tab_data', $tab_data); $this->assign('tab_type', 1); @@ -324,125 +321,32 @@ class Module extends Admin */ public function install($id = 0) { + if ($this->request->isPost()) { + $result = self::execInstall($id, 1); + if ($result !== true) { + return $this->error($result); + } + return $this->success('模块已安装成功', url('index?status=2')); + } + $mod = ModuleModel::where('id', $id)->find(); if (!$mod) { return $this->error('模块不存在'); } + if ($mod['status'] > 0) { return $this->error('请勿重复安装此模块'); } - $mod_path = APP_PATH.$mod['name'].DS; + + $modPath = APP_PATH.$mod['name'].DS; + // 模块自定义配置 - if (!file_exists($mod_path.'info.php')) { + if (!file_exists($modPath.'info.php')) { return $this->error('模块配置文件不存在[info.php]'); } - $info = include_once $mod_path.'info.php'; - if ($this->request->isPost()) { - // 过滤系统表 - foreach ($info['tables'] as $t) { - if (in_array($t, config('hs_system.tables'))) { - return $this->error('模块数据表与系统表重复['.$t.']'); - } - } - - $post = $this->request->post(); - // 导入SQL - $sql_file = realpath($mod_path.'sql'.DS.'install.sql'); - if (file_exists($sql_file)) { - $sql = file_get_contents($sql_file); - $sql_list = parse_sql($sql, 0, [$info['db_prefix'] => config('database.prefix')]); - if ($sql_list) { - if ($post['clear'] == 1) {// 清空所有数据 - foreach ($info['tables'] as $table) { - if (Db::query("SHOW TABLES LIKE '".config('database.prefix').$table."'")) { - Db::execute('DROP TABLE IF EXISTS `'.config('database.prefix').$table.'`;'); - } - } - } - $sql_list = array_filter($sql_list); - foreach ($sql_list as $v) { - // 过滤sql里面的系统表 - foreach (config('hs_system.tables') as $t) { - if (stripos($v, '`'.config('database.prefix').$t.'`') !== false) { - return $this->error('install.sql文件含有系统表['.$t.']'); - } - } - if (stripos($v, 'DROP TABLE') === false) { - try { - Db::execute($v); - } catch(\Exception $e) { - if ($post['clear'] == 1) { - return $this->error('导入SQL失败,请检查install.sql的语句是否正确或者表是否存在'); - } else { - return $this->error('导入SQL失败,请尝试选择清除旧数据'); - } - } - } - } - } - } - // 导入路由 - if ( file_exists($mod_path.'route.php') ) { - copy($mod_path.'route.php', APP_PATH.$mod['name'].'Route.php'); - } + $info = include_once $modPath.'info.php'; - // 导入菜单 - if ( file_exists($mod_path.'menu.php') ) { - $menus = include_once $mod_path.'menu.php'; - // 如果不是数组且不为空就当JSON数据转换 - if (!is_array($menus) && !empty($menus)) { - $menus = json_decode($menus, 1); - } - if (MenuModel::importMenu($menus, $mod['name']) == false) { - // 执行回滚 - MenuModel::where('module', $mod['name'])->delete(); - return $this->error('添加菜单失败,请重新安装'); - } - } - - // 导入模块钩子 - if (!empty($info['hooks'])) { - $hook_mod = model('AdminHook'); - foreach ($info['hooks'] as $k => $v) { - $map = []; - $map['name'] = $k; - $map['intro'] = $v; - $map['source'] = 'module.'.$mod['name']; - $hook_mod->storage($map); - } - } - cache('hook_plugins', null); - // 导入模块配置 - if (isset($info['config']) && !empty($info['config'])) { - $menu = []; - $menu['pid'] = 10; - $menu['module'] = $mod['name']; - $menu['title'] = $mod['title'].'配置'; - $menu['url'] = 'admin/system/index'; - $menu['param'] = 'group='.$mod['name']; - $menu['system'] = 0; - $menu['debug'] = 0; - $menu['sort'] = 100; - $menu['status'] = 1; - $menu_mod = new MenuModel; - $menu_mod->storage($menu); - ModuleModel::where('id', $id)->setField('config', json_encode($info['config'], 1)); - } - // 更新模块基础信息 - $sqlmap = []; - $sqlmap['title'] = $info['title']; - $sqlmap['identifier'] = $info['identifier']; - $sqlmap['intro'] = $info['intro']; - $sqlmap['author'] = $info['author']; - $sqlmap['url'] = $info['author_url']; - $sqlmap['version'] = $info['version']; - $sqlmap['status'] = 2; - ModuleModel::where('id', $id)->update($sqlmap); - ModuleModel::moduleRoute(true); - ConfigModel::getConfig('', true); - return $this->success('模块已安装成功', url('index?status=2')); - } // 模块依赖检查 foreach ($info['module_depend'] as $k => $v) { if (!isset($v[3])) { @@ -477,13 +381,152 @@ class Module extends Admin } $info['module_depend'][$k] = $v; } + // 插件依赖检查 TODO + $info['id'] = $mod['id']; + $this->assign('tables', $this->checkTable($info['tables'])); $this->assign('data_info', $info); return $this->fetch(); } + /** + * 执行模块安装 + * @date 2018-11-01 + * @access public + * @author 橘子俊 364666827@qq.com + * @param int $id 模块ID + * @param integer $clear 清空旧数据 + * @return bool|string + */ + public function execInstall($id, $clear = 1) + { + $mod = ModuleModel::where('id', $id)->find(); + if (!$mod) { + return '模块不存在'; + } + if ($mod['status'] > 0) { + return '请勿重复安装此模块'; + } + + $modPath = APP_PATH.$mod['name'].DS; + // 模块自定义配置 + if (!file_exists($modPath.'info.php')) { + return '模块配置文件不存在[info.php]'; + } + + $info = include_once $modPath.'info.php'; + + // 过滤系统表 + foreach ($info['tables'] as $t) { + if (in_array($t, config('hs_system.tables'))) { + return '模块数据表与系统表重复['.$t.']'; + } + } + + // 导入SQL + $sqlFile = realpath($modPath.'sql'.DS.'install.sql'); + if (file_exists($sqlFile)) { + $sql = file_get_contents($sqlFile); + $sqlList = parse_sql($sql, 0, [$info['db_prefix'] => config('database.prefix')]); + if ($sqlList) { + if ($clear == 1) {// 清空所有数据 + foreach ($info['tables'] as $table) { + if (Db::query("SHOW TABLES LIKE '".config('database.prefix').$table."'")) { + Db::execute('DROP TABLE IF EXISTS `'.config('database.prefix').$table.'`;'); + } + } + } + $sqlList = array_filter($sqlList); + foreach ($sqlList as $v) { + // 过滤sql里面的系统表 + foreach (config('hs_system.tables') as $t) { + if (stripos($v, '`'.config('database.prefix').$t.'`') !== false) { + return 'install.sql文件含有系统表['.$t.']'; + } + } + if (stripos($v, 'DROP TABLE') === false) { + try { + Db::execute($v); + } catch(\Exception $e) { + if ($clear == 1) { + return '导入SQL失败,请检查install.sql的语句是否正确或者表是否存在'; + } else { + return '导入SQL失败,请尝试选择清除旧数据'; + } + } + } + } + } + } + + // 导入路由 + if ( file_exists($modPath.'route.php') ) { + copy($modPath.'route.php', APP_PATH.$mod['name'].'Route.php'); + } + + // 导入菜单 + if ( file_exists($modPath.'menu.php') ) { + $menus = include_once $modPath.'menu.php'; + // 如果不是数组且不为空就当JSON数据转换 + if (!is_array($menus) && !empty($menus)) { + $menus = json_decode($menus, 1); + } + if (MenuModel::importMenu($menus, $mod['name']) == false) { + // 执行回滚 + MenuModel::where('module', $mod['name'])->delete(); + return '添加菜单失败,请重新安装'; + } + } + + // 导入模块钩子 + if (!empty($info['hooks'])) { + $hook_mod = model('AdminHook'); + foreach ($info['hooks'] as $k => $v) { + $map = []; + $map['name'] = $k; + $map['intro'] = $v; + $map['source'] = 'module.'.$mod['name']; + $hook_mod->storage($map); + } + } + + // 清空缓存钩子插件 + cache('hook_plugins', null); + + // 导入模块配置 + if (isset($info['config']) && !empty($info['config'])) { + $menu = []; + $menu['pid'] = 10; + $menu['module'] = $mod['name']; + $menu['title'] = $mod['title'].'配置'; + $menu['url'] = 'admin/system/index'; + $menu['param'] = 'group='.$mod['name']; + $menu['system'] = 0; + $menu['debug'] = 0; + $menu['sort'] = 100; + $menu['status'] = 1; + $menuModel = new MenuModel; + $menuModel->storage($menu); + ModuleModel::where('id', $id)->setField('config', json_encode($info['config'], 1)); + } + + // 更新模块基础信息 + $sqlmap = []; + $sqlmap['title'] = $info['title']; + $sqlmap['identifier'] = $info['identifier']; + $sqlmap['intro'] = $info['intro']; + $sqlmap['author'] = $info['author']; + $sqlmap['url'] = $info['author_url']; + $sqlmap['version'] = $info['version']; + $sqlmap['status'] = 2; + ModuleModel::where('id', $id)->update($sqlmap); + ModuleModel::moduleRoute(true); + ConfigModel::getConfig('', true); + return true; + } + /** * 模块图标上传 * @author 橘子俊 <364666827@qq.com> diff --git a/app/admin/controller/Plugins.php b/app/admin/controller/Plugins.php index f0a6182..4a4822a 100644 --- a/app/admin/controller/Plugins.php +++ b/app/admin/controller/Plugins.php @@ -96,6 +96,8 @@ class Plugins extends Admin } } } + + $this->assign('emptyTips', '未发现相关模块,快去 应用市场 看看吧!'); $this->assign('data_list', array_values($plugins)); $this->assign('tab_data', $tab_data); $this->assign('tab_type', 1); @@ -519,4 +521,4 @@ class Plugins extends Admin } return plugins_run($plugin.'/'.$controller.'/'.$action, $params); } -} \ No newline at end of file +} diff --git a/app/admin/view/module/index.php b/app/admin/view/module/index.php index 5833ea0..52de7a3 100644 --- a/app/admin/view/module/index.php +++ b/app/admin/view/module/index.php @@ -17,7 +17,7 @@ - {volist name="data_list" id="vo" empty="未发现相关模块,快去 应用市场 看看吧!"} + {volist name="data_list" id="vo" empty="$emptyTips"}
          @@ -48,27 +48,27 @@ 删除 {/case} {case value="1"} - {if condition="config('develop.app_debug') && $vo['app_id'] eq 0"} + {if condition="config('develop.app_debug') && empty($vo['app_keys'])"} 编辑 {/if} {if condition="!empty($vo['config'])"} 配置 {/if} - {if condition="$vo['app_id'] gt 0"} + {if condition="$vo['app_keys']"} 更新 {else /} {/if} 卸载 {/case} {case value="2"} - {if condition="config('develop.app_debug') && $vo['app_id'] eq 0"} + {if condition="config('develop.app_debug') && empty($vo['app_keys'])"} 编辑 {/if} {if condition="!empty($vo['config'])"} 配置 {/if} 主题 - {if condition="$vo['app_id'] gt 0"} + {if condition="$vo['app_keys']"} 更新 {else /} {/if} diff --git a/app/admin/view/plugins/index.php b/app/admin/view/plugins/index.php index a11ca28..029d61f 100644 --- a/app/admin/view/plugins/index.php +++ b/app/admin/view/plugins/index.php @@ -15,7 +15,7 @@ - {volist name="data_list" id="vo" empty="未发现相关插件,快去 应用市场 看看吧!"} + {volist name="data_list" id="vo" empty="$emptyTips"}
          @@ -41,26 +41,21 @@ {switch name="vo['status']"} {case value="0"} 安装 - {if condition="$vo['app_id'] eq 0"} - - {/if} 删除 {/case} {case value="1"} 配置 - {if condition="$vo['app_id'] gt 0"} + {if condition="$vo['app_keys']"} 更新 {else /} - {/if} 卸载 {/case} {case value="2"} 配置 - {if condition="$vo['app_id'] gt 0"} + {if condition="$vo['app_keys']"} 更新 {else /} - {/if} 卸载 {/case} -- Gitee From 5ce6fca969354b40189ef0192ae8f98ef719869e Mon Sep 17 00:00:00 2001 From: hisiphp Date: Thu, 8 Nov 2018 20:03:31 +0800 Subject: [PATCH 19/32] =?UTF-8?q?=E5=8D=87=E7=BA=A7=E5=88=B01.0.10?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [新增] 应用市场后台一键安装 [优化] 后台开发模式和页面Trace配置 [优化] 应用市场一键推送安装 [更新] layui升级到2.4.5 [修复] 添加管理员出现csrf攻击 [修复] 其他已知BUG --- app/admin/controller/Menu.php | 45 ++-- app/admin/controller/Store.php | 302 ++++++++++++++++++++++++++ app/admin/controller/System.php | 42 +++- app/admin/controller/Upgrade.php | 2 +- app/admin/controller/User.php | 6 +- app/admin/model/AdminMenu.php | 3 +- app/admin/validate/AdminMenu.php | 9 +- app/admin/validate/AdminUser.php | 6 +- app/admin/validate/Plugins.php | 2 +- app/admin/view/block/bind_cloud.php | 22 ++ app/admin/view/menu/form.php | 1 + app/admin/view/module/theme.php | 2 +- app/admin/view/store/index.php | 318 ++++++++++++++++++++++++++++ app/admin/view/system/index.php | 1 + app/admin/view/upgrade/index.php | 53 +---- app/admin/view/user/info.php | 1 + app/admin/view/user/userform.php | 1 + app/common/behavior/Base.php | 39 ++-- app/common/util/Cloud.php | 13 +- app/config.php | 4 +- app/extra/hs_system.php | 1 - app/index/home/Error.php | 2 +- app/install/sql/install.sql | 40 ++-- static/admin/css/style.css | 17 +- static/admin/js/md5.js | 227 ++++++++++++++++++++ version.php | 2 +- 26 files changed, 1009 insertions(+), 152 deletions(-) create mode 100644 app/admin/controller/Store.php create mode 100644 app/admin/view/block/bind_cloud.php create mode 100644 app/admin/view/store/index.php create mode 100644 static/admin/js/md5.js diff --git a/app/admin/controller/Menu.php b/app/admin/controller/Menu.php index c10a55f..eec2322 100644 --- a/app/admin/controller/Menu.php +++ b/app/admin/controller/Menu.php @@ -24,27 +24,6 @@ class Menu extends Admin * @author 橘子俊 <364666827@qq.com> * @return mixed */ - // public function index($pid = 0) - // { - // $modules = MenuModel::where('pid', 0)->field('id,title')->order('sort asc')->select(); - // $tab_data = []; - // foreach ($modules as $key => $value) { - // $tab_data['menu'][$key]['title'] = $value['title']; - // $tab_data['menu'][$key]['url'] = '?pid='.$value['id']; - // } - // if ($pid == 0) { - // $pid = $modules[0]['id']; - // } - // $menu_list = MenuModel::getAllChild($pid, 0); - // // print_r($menu_list); - // $tab_data['current'] = url('?pid='.$pid); - - // $this->assign('menu_list', $menu_list); - // $this->assign('pid', $pid); - // $this->assign('tab_data', $tab_data); - // $this->assign('tab_type', 1); - // return $this->fetch(); - // } public function index() { $menu_list = MenuModel::getAllChild(0, 0); @@ -72,7 +51,7 @@ class Menu extends Admin if (!$model->storage()) { return $this->error($model->getError()); } - return $this->success('保存成功。', url('index')); + return $this->success('保存成功', url('index')); } $this->assign('module_option', model('AdminModule')->getOption($mod)); $this->assign('menu_option', self::menuOption($pid)); @@ -91,13 +70,13 @@ class Menu extends Admin if (!$model->storage()) { return $this->error($model->getError()); } - return $this->success('保存成功。', url('index')); + return $this->success('保存成功', url('index')); } $row = MenuModel::where('id', $id)->find(); // admin模块 只允许超级管理员在开发模式下修改 if ($row['module'] == 'admin' && (ADMIN_ID != 1 || config('develop.app_debug') == 0)) { - return $this->error('禁止修改系统模块!'); + return $this->error('禁止修改系统模块'); } // 多语言 if (config('sys.multi_language') == 1) { @@ -156,7 +135,7 @@ class Menu extends Admin $id = input('param.ids/a'); $model = new MenuModel(); if ($model->del($id)) { - return $this->success('删除成功。'); + return $this->success('删除成功'); } return $this->error($model->getError()); } @@ -173,13 +152,13 @@ class Menu extends Admin $map['id'] = $id; $menu = MenuModel::where($map)->field('pid,title,icon,module,url,param,target,debug,system,nav,sort')->find()->toArray(); if (!$menu) { - return $this->error('模块不存在!'); + return $this->error('模块不存在'); } if ($menu['pid'] > 0 && $menu['url'] != 'admin/plugins/run') { - return $this->error('只能通过顶级菜单导出!'); + return $this->error('只能通过顶级菜单导出'); } if ($menu['url'] == 'admin/plugins/run' && MenuModel::where('id', $menu['pid'])->value('url') == 'admin/plugins/run') { - return $this->error('只能通过顶级菜单导出!'); + return $this->error('只能通过顶级菜单导出'); } unset($menu['pid'], $menu['id']); $menus = []; @@ -211,14 +190,14 @@ class Menu extends Admin { $id = input('param.id/d'); if (!$id) { - return $this->error('参数传递错误!'); + return $this->error('参数传递错误'); } $map = []; $map['id'] = $id; $row = MenuModel::where($map)->find()->toArray(); if (!$row) { - return $this->error('您添加的菜单不存在!'); + return $this->error('您添加的菜单不存在'); } unset($row['id'], $map['id']); @@ -227,7 +206,7 @@ class Menu extends Admin $map['uid'] = ADMIN_ID; $row['pid'] = $map['pid'] = 4; if (MenuModel::where($map)->find()) { - return $this->error('您已添加过此快捷菜单!'); + return $this->error('您已添加过此快捷菜单'); } $row['uid'] = ADMIN_ID; $row['debug'] = 0; @@ -236,9 +215,9 @@ class Menu extends Admin $model = new MenuModel(); $res = $model->storage($row); if ($res === false) { - return $this->error('快捷菜单添加失败!'); + return $this->error('快捷菜单添加失败'); } - return $this->success('快捷菜单添加成功。'); + return $this->success('快捷菜单添加成功'); } /** diff --git a/app/admin/controller/Store.php b/app/admin/controller/Store.php new file mode 100644 index 0000000..0d1615c --- /dev/null +++ b/app/admin/controller/Store.php @@ -0,0 +1,302 @@ +,开发者QQ群:50304283 +// +---------------------------------------------------------------------- +namespace app\admin\controller; +use app\admin\model\AdminLog as LogModel; +use app\admin\model\AdminModule as ModuleModel; +use app\admin\model\AdminPlugins as PluginsModel; +use app\common\util\Cloud; +use app\common\util\Dir; +use app\common\util\PclZip; +use think\Loader; +/** + * 应用市场控制器 + * @package app\admin\controller + */ +class Store extends Admin +{ + /** + * 初始化方法 + */ + protected function _initialize() + { + parent::_initialize(); + $this->tempPath = RUNTIME_PATH.'app/'; + $this->cloud = new Cloud(config('hs_cloud.identifier'), $this->tempPath); + } + + /** + * 应用列表 + * @author 橘子俊 <364666827@qq.com> + * @return mixed + */ + public function index() + { + if ($this->request->isAjax()) { + $data = $param = []; + $param['page'] = $this->request->param('page/d', 1); + $param['cat_id'] = $this->request->param('cat_id/d', 0); + $param['type'] = $this->request->param('type/d', 1); + $param['limit'] = $this->request->param('limit/d', 10); + + $data['code'] = 0; + $cloudData = $this->cloud->data($param)->api('apps'); + if ($cloudData['code'] == 1) { + if ($param['type'] == 1) { + $locApp = ModuleModel::where('system', 0)->column('identifier,version'); + } else { + $locApp = PluginsModel::where('system', 0)->column('identifier,version'); + } + + $apps = []; + foreach ($cloudData['data']['apps'] as $k => $v) { + $v['install'] = 0; + $v['upgrade'] = 0; + // 检查是否已有安装某个分支 + foreach ($v['branchs'] as $kk => $vv) { + if (array_key_exists($kk, $locApp)) { + $v['install'] = $kk; + if (version_compare($vv['version'], $locApp[$kk], '>')) { + $v['upgrade'] = 1; + } + continue; + } + } + $apps[] = $v; + } + $data['data'] = $apps; + $data['count'] = $cloudData['data']['count']; + } else { + $data['code'] = 1; + } + + return json($data); + } + + $data['cats'] = []; + // 因应用太少,暂时隐藏分类筛选 + // $data['cats'] = cache('cloud_app_cats'); + // if (!$data['cats']) { + // $cats = $this->cloud->api('cats'); + // $data['cats'] = $cats['data']; + // cache('cloud_app_cats', $data['cats']); + // } + + $this->assign('api_url', $this->cloud->apiUrl()); + $this->assign('data', $data); + return $this->fetch(); + } + + /** + * 安装应用 + * @date 2018-10-31 + * @access public + * @author 橘子俊 364666827@qq.com + * @return json + */ + public function install() + { + $appType = $this->request->get('app_type'); + $appName = $this->request->get('app_name'); + $appKeys = $this->request->get('app_keys'); + $branchId = $this->request->get('branch_id'); + $orderId = $this->request->get('order_id'); + + $data = []; + $data['branch_id'] = $branchId; + $data['order_id'] = $orderId; + $data['app_keys'] = $appKeys; + + // 下载应用安装包 + $file = $this->cloud->data($data)->down('downAppInstall'); + if (!file_exists($file)) { + return $this->error('安装文件获取失败,请稍后在试!'); + } + + // 解压包路径 + $unzipPath = $this->tempPath.basename($file,".zip"); + if (!is_dir($unzipPath)) { + Dir::create($unzipPath, 0777, true); + } + + // 解压升级包 + $archive = new PclZip(); + $archive->PclZip($file); + if(!$archive->extract(PCLZIP_OPT_PATH, $unzipPath, PCLZIP_OPT_REPLACE_NEWER)) { + return $this->error('安装失败,请检查[/runtime/app/]文件夹权限'); + } + + if ($appType == 1) {// 模块安装 + $res = self::_moduleInstall($appName, $appKeys, $file, $unzipPath); + } else if ($appType == 2) {// 插件安装 + $res = self::_pluginsInstall($appName, $appKeys, $file, $unzipPath); + } else {// 主题安装 + $res = self::_themeInstall($appName, $appKeys, $file, $unzipPath); + } + + if ($res === true) { + return $this->success('安装成功'); + } + + return $this->error($res); + } + + /** + * 安装模块 + * @date 2018-10-31 + * @access private + * @author 橘子俊 364666827@qq.com + * @param string $appName 应用名称 + * @param string $appKeys 应用私钥 + * @param string $file 安装包路径 + * @param string $unzipPath 解压路径 + * @return bool|string + */ + private function _moduleInstall($appName, $appKeys, $file, $unzipPath) + { + // 防止重复安装 + if (ModuleModel::where('name', $appName)->find()) { + return true; + } + + // 应用目录 + $appPath = $unzipPath.'/upload/app/'.$appName.'/'; + + // 导入模块路由 + if (is_file($appPath.'route.php')) { + Dir::copyDir($appPath.'route.php', APP_PATH); + } + + // 获取模块信息 + $info = include_once $appPath.'info.php'; + + if (!is_dir(APP_PATH.$appName)) { + Dir::create(APP_PATH.$appName, 0777, true); + } + Dir::copyDir($appPath, APP_PATH.$appName); + + // 复制static目录 + if (is_dir($unzipPath.'/upload/static')) { + Dir::copyDir($unzipPath.'/upload/static', ROOT_PATH.'static'); + } + + // 复制theme目录 + if (is_dir($unzipPath.'/upload/theme')) { + Dir::copyDir($unzipPath.'/upload/theme', ROOT_PATH.'theme'); + } + + // 复制应用图标 + $icon = ROOT_DIR.'static/admin/image/app.png'; + $soureIcon = $unzipPath.'/upload/app/'.$appName.DS.$appName.'.png'; + if (is_file($soureIcon)) { + copy($soureIcon, ROOT_PATH.'static/app_icon/'.$appName.'.png'); + $icon = ROOT_DIR.'static/app_icon/'.$appName.'.png'; + } + + // 删除临时目录和安装包 + Dir::delDir($unzipPath); + @unlink($file); + + // 注册模块 + $sqlmap = []; + $sqlmap['name'] = $appName; + $sqlmap['identifier'] = $info['identifier']; + $sqlmap['title'] = $info['title']; + $sqlmap['intro'] = $info['intro']; + $sqlmap['icon'] = $icon; + $sqlmap['version'] = $info['version']; + $sqlmap['author'] = $info['author']; + $sqlmap['url'] = $info['author_url']; + $sqlmap['app_id'] = 0;// 无效 + $sqlmap['app_keys'] = $appKeys; + $sqlmap['config'] = ''; + $result = ModuleModel::create($sqlmap); + if (!$result) { + return '异常错误,请点此进入模块管理页面手动安装!'; + } + + clearstatcache(); + $result = Loader::controller('admin/module', 'controller')->execInstall($result->id, 1); + if ($result !== true) { + return $result.'

          点此进入模块管理页面手动安装!'; + } + return true; + } + + /** + * 安装插件 + * @date 2018-10-31 + * @access private + * @author 橘子俊 364666827@qq.com + * @param string $appName 应用名称 + * @param string $appKeys 应用私钥 + * @param string $file 安装包路径 + * @param string $unzipPath 解压路径 + * @return bool|string + */ + private function _pluginsInstall($appName, $appKeys, $file, $unzipPath) + { + // 防止重复安装 + if (PluginsModel::where('name', $appName)->find()) { + return true; + } + + $appPath = $unzipPath.'/upload/plugins/'.$appName.'/'; + $info = include_once $appPath.'info.php'; + + // 复制到插件目录 + Dir::copyDir($appPath, ROOT_PATH.'plugins/'.$appName); + // 删除临时目录和安装包 + Dir::delDir($unzipPath); + @unlink($file); + + // 注册插件 + $sqlmap = []; + $sqlmap['name'] = $appName; + $sqlmap['identifier'] = $info['identifier']; + $sqlmap['title'] = $info['title']; + $sqlmap['intro'] = $info['intro']; + $sqlmap['icon'] = ROOT_DIR.'plugins/'.$appName.'/'.$appName.'.png'; + $sqlmap['version'] = $info['version']; + $sqlmap['author'] = $info['author']; + $sqlmap['url'] = $info['author_url']; + $sqlmap['app_id'] = 0; + $sqlmap['app_keys'] = $appKeys; + $sqlmap['config'] = ''; + $result = PluginsModel::create($sqlmap); + if (!$result) { + return '异常错误,请点此进入插件管理页面手动安装!'; + } + + $result = Loader::controller('admin/plugins', 'controller')->execInstall($result->id, 1); + if ($result !== true) { + return $result.'

          点此进入插件管理页面手动安装!'; + } + + clearstatcache(); + return true; + } + + /** + * 安装主题 + * @date 2018-10-31 + * @access private + * @author 橘子俊 364666827@qq.com + * @param string $appName 应用名称 + * @param string $appKeys 应用私钥 + * @param string $file 安装包路径 + * @param string $unzipPath 解压路径 + * @return bool|string + */ + private function _themeInstall($appName, $appKeys, $file, $unzipPath) + { + return true; + } +} diff --git a/app/admin/controller/System.php b/app/admin/controller/System.php index 1389b73..78c33c2 100644 --- a/app/admin/controller/System.php +++ b/app/admin/controller/System.php @@ -12,6 +12,7 @@ namespace app\admin\controller; use app\admin\model\AdminConfig as ConfigModel; use app\admin\model\AdminModule as ModuleModel; use app\admin\model\AdminPlugins as PluginsModel; +use think\Validate; /** * 系统设置控制器 * @package app\admin\controller @@ -35,7 +36,18 @@ class System extends Admin } else { $ids = $data['id'] = ''; } - unset($data['upload']);// 清除上传字段 + + // 清除上传字段 + unset($data['upload']); + + // token 验证 + $validate = new Validate([ + '__token__' => 'token', + ]); + if (!$validate->check($data)) { + return $this->error($validate->getError()); + } + // 非系统模块配置保存 if (isset($data['module'])) { $row = ModuleModel::where('name', $data['module'])->field('id,config')->find()->toArray(); @@ -54,11 +66,12 @@ class System extends Admin } if (ModuleModel::where('id', $row['id'])->setField('config', json_encode($row['config'], 1)) === false) { - return $this->error('保存失败!'); + return $this->error('保存失败'); } ModuleModel::getConfig('', true); - return $this->success('保存成功。'); + return $this->success('保存成功'); } + // 系统模块配置保存 if (!$types) return false; $admin_path = config('sys.admin_path'); @@ -67,9 +80,11 @@ class System extends Admin ConfigModel::where('name', $k)->update(['value' => 0]); continue; } + if ($v == 'checkbox' && isset($ids[$k])) { $ids[$k] = json_encode($ids[$k], 1); } + // 修改后台管理目录 if ($k == 'admin_path' && $ids[$k] != config('sys.admin_path')) { if (is_file(ROOT_PATH.config('sys.admin_path')) && is_writable(ROOT_PATH.config('sys.admin_path'))) { @@ -82,11 +97,22 @@ class System extends Admin } ConfigModel::where('name', $k)->update(['value' => $ids[$k]]); } + // 更新配置缓存 - ConfigModel::getConfig('', true); + $config = ConfigModel::getConfig('', true); + + if ($group == 'develop') { + if (file_exists(ROOT_PATH.'.env')) { + unlink(ROOT_PATH.'.env'); + } + $env = "//设置开启调试模式\napp_debug = " . ($config['develop']['app_debug'] ? 'true' : 'false'); + $env .= "\n//应用Trace\napp_trace = " . ($config['develop']['app_trace'] ? 'true' : 'false'); + file_put_contents(ROOT_PATH.'.env', $env); + } - return $this->success('保存成功。', ROOT_DIR.$admin_path.'/admin/system/index/group/'.$group.'.html'); + return $this->success('保存成功', ROOT_DIR.$admin_path.'/admin/system/index/group/'.$group.'.html'); } + $tab_data = []; foreach (config('sys.config_group') as $key => $value) { $arr = []; @@ -94,6 +120,7 @@ class System extends Admin $arr['url'] = '?group='.$key; $tab_data['menu'][] = $arr; } + $map = []; $map['group'] = $group; $map['status'] = 1; @@ -104,6 +131,7 @@ class System extends Admin $v['options'] = parse_attr($v['options']); } } + // 模块配置 $module = ModuleModel::where('status', 2)->column('name,title,config', 'name'); foreach ($module as $mod) { @@ -123,8 +151,10 @@ class System extends Admin } } } + $tab_data['current'] = url('?group='.$group); $_GET['group'] = $group; + $this->assign('data_list', $data_list); $this->assign('tab_data', $tab_data); $this->assign('tab_type', 1); @@ -146,7 +176,7 @@ define('APP_PATH', __DIR__ . '/app/'); define('ENTRANCE', 'admin'); // 检查是否安装 -if(!is_file(APP_PATH.'install/install.lock')) { +if(!is_file(APP_PATH.'install/install.lock') && !is_file(APP_PATH.'install.lock')) { header('Location: /'); exit; } diff --git a/app/admin/controller/Upgrade.php b/app/admin/controller/Upgrade.php index 8c40c36..9bafbcc 100644 --- a/app/admin/controller/Upgrade.php +++ b/app/admin/controller/Upgrade.php @@ -87,7 +87,7 @@ class Upgrade extends Admin if (isset($res['code']) && $res['code'] == 1) { // 缓存站点标识 $file = APP_PATH.'extra'.DS.'hs_cloud.php'; - $str = " '".$res['data']."'];\n"; + $str = " '".$res['data']."'];\n"; if (file_exists($file)) { unlink($file); } diff --git a/app/admin/controller/User.php b/app/admin/controller/User.php index 8d1bb0b..8d622e6 100644 --- a/app/admin/controller/User.php +++ b/app/admin/controller/User.php @@ -122,7 +122,7 @@ class User extends Admin if($result !== true) { return $this->error($result); } - unset($data['id'], $data['password_confirm']); + unset($data['id'], $data['password_confirm'], $data['__token__']); $data['last_login_ip'] = ''; $data['auth'] = ''; if (!UserModel::create($data)) { @@ -182,7 +182,7 @@ class User extends Admin if ($data['password'] == '') { unset($data['password']); } - unset($data['password_confirm']); + unset($data['password_confirm'], $data['__token__']); if (!UserModel::update($data)) { return $this->error('修改失败'); @@ -234,7 +234,7 @@ class User extends Admin if ($data['password'] == '') { unset($data['password']); } - unset($data['password_confirm']); + unset($data['password_confirm'], $data['__token__']); if (!UserModel::update($data)) { return $this->error('修改失败'); diff --git a/app/admin/model/AdminMenu.php b/app/admin/model/AdminMenu.php index 45bf625..5f3ce4a 100644 --- a/app/admin/model/AdminMenu.php +++ b/app/admin/model/AdminMenu.php @@ -65,6 +65,7 @@ class AdminMenu extends Model $this->error = $valid->getError(); return false; } + unset($data['__token__']); $title = $data['title']; if (isset($data['id']) && !empty($data['id'])) { if (config('sys.multi_language') == 1) { @@ -90,7 +91,7 @@ class AdminMenu extends Model } } if (!$res) { - $this->error = '保存失败!'; + $this->error = '保存失败'; return false; } self::getMainMenu(true); diff --git a/app/admin/validate/AdminMenu.php b/app/admin/validate/AdminMenu.php index db0ab7d..f586870 100644 --- a/app/admin/validate/AdminMenu.php +++ b/app/admin/validate/AdminMenu.php @@ -21,10 +21,13 @@ class AdminMenu extends Validate { //定义验证规则 protected $rule = [ + 'title|菜单名称' => 'require|chsDash', 'url|菜单链接' => 'require|checkUrl:thinkphp', - 'module|所属模块' => 'require', - 'pid|所属菜单' => 'require', - 'title|菜单名称' => 'require', + 'module|所属模块' => 'require|alphaDash', + 'pid|所属菜单' => 'require|number', + 'status|状态' => 'require|in:0,1', + 'system|系统菜单' => 'require|in:0,1', + 'nav|后台导航' => 'require|in:0,1|token', ]; //定义验证提示 diff --git a/app/admin/validate/AdminUser.php b/app/admin/validate/AdminUser.php index 0bf4d5c..4363c7d 100644 --- a/app/admin/validate/AdminUser.php +++ b/app/admin/validate/AdminUser.php @@ -20,12 +20,12 @@ class AdminUser extends Validate { //定义验证规则 protected $rule = [ - 'username|用户名' => 'require|alphaNum|unique:admin_user', 'nick|昵称' => 'require|unique:admin_user', 'role_id|角色' => 'requireWith:role_id|notIn:0,1', 'email|邮箱' => 'requireWith:email|email|unique:admin_user', 'password|密码' => 'require|length:6,20|confirm', 'mobile|手机号' => 'requireWith:mobile|regex:^1\d{10}', + 'username|用户名' => 'require|alphaNum|unique:admin_user|token', ]; //定义验证提示 @@ -44,10 +44,10 @@ class AdminUser extends Validate //定义验证场景 protected $scene = [ //更新 - 'update' => ['username', 'email', 'password' => 'length:6,20|confirm', 'mobile', 'role_id'], + 'update' => ['username', 'email', 'password' => 'length:6,20|confirm', 'role_id', 'mobile'], //更新个人信息 'info' => ['username', 'email', 'password' => 'length:6,20|confirm', 'mobile'], //登录 - 'login' => ['username' => 'require|token', 'password' => 'length:6,20'], + 'login' => ['username' => 'require', 'password' => 'length:6,20|token'], ]; } diff --git a/app/admin/validate/Plugins.php b/app/admin/validate/Plugins.php index e39895e..c50583d 100644 --- a/app/admin/validate/Plugins.php +++ b/app/admin/validate/Plugins.php @@ -20,7 +20,7 @@ class Plugins extends Validate { //定义验证规则 protected $rule = [ - 'name|插件名' => 'require|unique:admin_plugins', + 'name|插件名' => 'require|alphaDash|unique:admin_plugins', 'title|插件标题' => 'require', 'identifier|插件标识' => 'require|unique:admin_plugins', ]; diff --git a/app/admin/view/block/bind_cloud.php b/app/admin/view/block/bind_cloud.php new file mode 100644 index 0000000..9d8f5de --- /dev/null +++ b/app/admin/view/block/bind_cloud.php @@ -0,0 +1,22 @@ + \ No newline at end of file diff --git a/app/admin/view/menu/form.php b/app/admin/view/menu/form.php index b24cdef..1142dfe 100644 --- a/app/admin/view/menu/form.php +++ b/app/admin/view/menu/form.php @@ -84,6 +84,7 @@
          + {:token()} diff --git a/app/admin/view/module/theme.php b/app/admin/view/module/theme.php index fc66809..0fb1d17 100644 --- a/app/admin/view/module/theme.php +++ b/app/admin/view/module/theme.php @@ -1,4 +1,4 @@ -
            +
              {volist name="data_list" id="vo"}
            • diff --git a/app/admin/view/store/index.php b/app/admin/view/store/index.php new file mode 100644 index 0000000..34b395a --- /dev/null +++ b/app/admin/view/store/index.php @@ -0,0 +1,318 @@ + +
              +{include file="block/layui" /} + + + + + +{include file="block/bind_cloud" /} \ No newline at end of file diff --git a/app/admin/view/system/index.php b/app/admin/view/system/index.php index 0edd9bb..c71c940 100644 --- a/app/admin/view/system/index.php +++ b/app/admin/view/system/index.php @@ -140,6 +140,7 @@ {/volist}
              + {:token()}
              diff --git a/app/admin/view/upgrade/index.php b/app/admin/view/upgrade/index.php index e9fb34e..2e7b47b 100644 --- a/app/admin/view/upgrade/index.php +++ b/app/admin/view/upgrade/index.php @@ -28,32 +28,11 @@ - {include file="block/layui" /} - {include file="block/bind_cloud" /} \ No newline at end of file -- Gitee From d4422ddf631e9ecd4da33b9b97284b3b1cc66202 Mon Sep 17 00:00:00 2001 From: hisiphp Date: Sun, 11 Nov 2018 20:52:19 +0800 Subject: [PATCH 25/32] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3940206..654422b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,7 @@ www WEB部署目录(或者子目录) [短信服务(阿里云通信、腾讯云短信、聚合数据短信API)](http://store.hisiphp.com/detail/1000008.html?from=oschina) +[通用RESTful API(接口文档自动生成,输入参数自动检查,集成Oauth授权登录、在线测试工具等)](https://store.hisiphp.com/detail/1000014.html?from=oschina) ## QQ交流群 [群①:50304283(2000人)](http://shang.qq.com/wpa/qunwpa?idkey=f70e4d4e0ad2ed6ad67a8b467475e695b286d536c7ff850db945542188871fc6)、[群②:640279557](http://shang.qq.com/wpa/qunwpa?idkey=7f77ff420f91ae529eef4045557d25553f3362f4c076d575a09974396597c88c)、[群③:679881764](http://shang.qq.com/wpa/qunwpa?idkey=a242a5d4d68dea7f073176be3fcc6ebd68e03bb6ed238827cbd2f00baae3f21f)、[群④:375815448](http://shang.qq.com/wpa/qunwpa?idkey=409636b5d168ddb78d13d9785a59a5c7ab6f5e0e65f3ee4059e36cd83ebacacd) -- Gitee From 4d3c30222539e513d52bde4b081e6627de96309e Mon Sep 17 00:00:00 2001 From: hisiphp Date: Mon, 12 Nov 2018 14:53:44 +0800 Subject: [PATCH 26/32] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 654422b..a33913e 100644 --- a/README.md +++ b/README.md @@ -54,14 +54,14 @@ www WEB部署目录(或者子目录) [响应式轻博客](http://store.hisiphp.com/detail/1000009.html?from=oschina) +[通用RESTful API(接口文档自动生成,输入参数自动检查,集成Oauth授权登录、在线测试工具等)](https://store.hisiphp.com/detail/1000014.html?from=oschina) + ## 插件推荐 [第三方授权登录(QQ登录、微信登录、新浪微博登录)](http://store.hisiphp.com/detail/1000002.html?from=oschina) [短信服务(阿里云通信、腾讯云短信、聚合数据短信API)](http://store.hisiphp.com/detail/1000008.html?from=oschina) -[通用RESTful API(接口文档自动生成,输入参数自动检查,集成Oauth授权登录、在线测试工具等)](https://store.hisiphp.com/detail/1000014.html?from=oschina) - ## QQ交流群 [群①:50304283(2000人)](http://shang.qq.com/wpa/qunwpa?idkey=f70e4d4e0ad2ed6ad67a8b467475e695b286d536c7ff850db945542188871fc6)、[群②:640279557](http://shang.qq.com/wpa/qunwpa?idkey=7f77ff420f91ae529eef4045557d25553f3362f4c076d575a09974396597c88c)、[群③:679881764](http://shang.qq.com/wpa/qunwpa?idkey=a242a5d4d68dea7f073176be3fcc6ebd68e03bb6ed238827cbd2f00baae3f21f)、[群④:375815448](http://shang.qq.com/wpa/qunwpa?idkey=409636b5d168ddb78d13d9785a59a5c7ab6f5e0e65f3ee4059e36cd83ebacacd) -- Gitee From b982e3acbfc8bb1fa9d217321d35f811e4b36a35 Mon Sep 17 00:00:00 2001 From: hisiphp Date: Wed, 14 Nov 2018 09:55:27 +0800 Subject: [PATCH 27/32] Update app/admin/view/store/index.php --- app/admin/view/store/index.php | 95 +++++++++++++++++++--------------- 1 file changed, 53 insertions(+), 42 deletions(-) diff --git a/app/admin/view/store/index.php b/app/admin/view/store/index.php index dcbff84..a0f03b7 100644 --- a/app/admin/view/store/index.php +++ b/app/admin/view/store/index.php @@ -25,7 +25,7 @@ return value == "" && (value = null), value; }; - var install = function install(param) { + var install = function (param) { $('.app-info').html('
              正在安装应用中,请勿刷新或关闭此页面
              '); $.get('{:url('install')}', param, function(res) { if (res.code == 1) { @@ -39,6 +39,42 @@ }, 'json'); }; + var popBindCloud = function () + { + layer.open({ + title:'登录云平台 / 注册云平台', + id:'popLoginBox', + area:'380px', + content:$('#popCloudBind').html(), + btn:['确认绑定', '取消'], + btnAlign:'c', + move:false, + yes:function(index) { + var tips = $('#resultTips'), + account = $('#cloudAccount').val(), + password = $('#cloudPassword').val(); + tips.html('请稍后,云平台通信中...'); + $.post('{:url("upgrade/index")}', {account: account, password: password}, function(res) { + if (res.code == 1) { + layer.msg(res.msg); + setTimeout(function() { + location.reload(); + }, 3000); + } else { + tips.addClass('red').html(res.msg); + setTimeout(function() { + tips.removeClass('red').html(''); + }, 3000); + } + }); + return false; + }, + success: function() { + $('#cloudForm .layui-word-aux').html('温馨提示:您需要登录云平台后才能安装此应用'); + } + }); + } + var specSelect = function() { var payment = $('.payment.layui-btn-normal').attr('data-value'), branchId = $('.branch_id.layui-btn-normal').attr('data-value'), @@ -63,7 +99,7 @@ $('.app-order-id').html('点此安装'); } else { $('.app-price').html('¥'+price); - $('.app-order-id').html('支付时请备注订单号:'+res.order_id+'
              点此查看常见问题'); + $('.app-order-id').html('支付时请备注订单号:'+res.order_id+'
              点此查看常见问题'); $('#qrcode').attr('src', res.qrcode).show(); // 定时刷新支付状态 var checkOrder = function() { @@ -110,7 +146,7 @@ return '¥'+d.price; }} ,{field:'sales', width:70, title: '下载'} - ,{width:160, title: '操作', templet: '#buttonTpl'} + ,{width:160, title: '操作     绑定云平台', templet: '#buttonTpl'} ]] ,done:function(res, curr, count) { var type = getParam('type'), catId = getParam('cat_id'); @@ -145,38 +181,7 @@ $(document).on('click', '.pop-install', function() { var that = $(this), data = new Function('return '+ that.attr('data-data'))(); if (identifier == null || identifier == '') { - layer.open({ - title:'登录云平台 / 注册云平台', - id:'popLoginBox', - area:'380px', - content:$('#popCloudBind').html(), - btn:['确认绑定', '取消'], - btnAlign:'c', - move:false, - yes:function(index) { - var tips = $('#resultTips'), - account = $('#cloudAccount').val(), - password = $('#cloudPassword').val(); - tips.html('请稍后,云平台通信中...'); - $.post('{:url("upgrade/index")}', {account: account, password: password}, function(res) { - if (res.code == 1) { - layer.msg(res.msg); - setTimeout(function() { - location.reload(); - }, 3000); - } else { - tips.addClass('red').html(res.msg); - setTimeout(function() { - tips.removeClass('red').html(''); - }, 3000); - } - }); - return false; - }, - success: function() { - $('#cloudForm .layui-word-aux').html('温馨提示:您需要登录云平台后才能安装此应用'); - } - }); + popBindCloud(); } else { laytpl($('#installTpl').html()).render(data, function(html) { layer.open({ @@ -199,6 +204,11 @@ return false; }); + // 弹出绑定云平台界面 + $(document).on('click', '.j-bind-cloud', function() { + popBindCloud(); + }); + // 应用分支和支付方式切换 $(document).on('click', '.app-spec-a', function() { var that = $(this); @@ -216,12 +226,12 @@ install(param); }); - $(document).on('click', '#showQuestion', function() { + $(document).on('click', '.j-show-question', function() { layer.open({ type: 1, title: '购买常见问题', shadeClose: true, - area:['500px', '300px'], + area:['580px', '300px'], content: $('#questionTpl').html() }); }); @@ -232,18 +242,19 @@ json.push({alt: that.attr('data-title'), pid: '123', src: 'https://store.hisiphp.com'+data[i], 'thumb': 'https://store.hisiphp.com'+data[i]}); } layer.photos({photos: {status: 1, start: 0, title: that.attr('data-title'), id: 1, data: json}}); - }) + }); });