├── Action.php ├── LICENSE ├── Plugin.php ├── README.md ├── images ├── comment_page.png ├── login_page.jpg └── setting_page.jpg ├── lib └── class.geetestlib.php └── static ├── gt.js └── gt.min.js /Action.php: -------------------------------------------------------------------------------- 1 | on($this->request->is('do=ajaxResponseCaptchaData'))->ajaxResponseCaptchaData(); 15 | } 16 | 17 | public function ajaxResponseCaptchaData() 18 | { 19 | if (!$this->request->isAjax()) { 20 | $this->response->redirect('/'); 21 | } 22 | 23 | Typecho_Plugin::factory('Geetest')->responseCaptchaData(); 24 | } 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 雪山凌狐 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Plugin.php: -------------------------------------------------------------------------------- 1 | end = array(__CLASS__, 'renderCaptcha'); 33 | 34 | // 注册用户登录成功钩子 35 | Typecho_Plugin::factory('Widget_User')->loginSucceed = array(__CLASS__, 'verifyCaptcha'); 36 | 37 | // 评论钩子 38 | Typecho_Plugin::factory('Widget_Feedback')->comment = array(__CLASS__, 'commentCaptchaVerify'); 39 | Typecho_Plugin::factory('Widget_Feedback')->trackback = array(__CLASS__, 'commentCaptchaVerify'); 40 | Typecho_Plugin::factory('Widget_XmlRpc')->pingback = array(__CLASS__, 'commentCaptchaVerify'); 41 | 42 | // 暴露插件函数(用于在自定义表单中渲染极验验证,以及在自定义逻辑中调用极验验证) 43 | Typecho_Plugin::factory('Geetest')->renderCaptcha = array(__CLASS__, 'renderCaptcha'); 44 | Typecho_Plugin::factory('Geetest')->verifyCaptcha = array(__CLASS__, 'verifyCaptcha'); 45 | Typecho_Plugin::factory('Geetest')->responseCaptchaData = array(__CLASS__, 'responseCaptchaData'); 46 | } 47 | 48 | /** 49 | * 禁用插件方法,如果禁用失败,直接抛出异常 50 | * 51 | * @static 52 | * @access public 53 | * @return void 54 | * @throws Typecho_Plugin_Exception 55 | */ 56 | public static function deactivate() 57 | { 58 | Helper::removeAction('geetest'); 59 | } 60 | 61 | /** 62 | * 个人用户的配置面板 63 | * 64 | * @access public 65 | * @param Typecho_Widget_Helper_Form $form 66 | * @return void 67 | */ 68 | public static function personalConfig(Typecho_Widget_Helper_Form $form) 69 | { 70 | } 71 | 72 | /** 73 | * 获取插件配置面板 74 | * 75 | * @access public 76 | * @param Typecho_Widget_Helper_Form $form 配置面板 77 | * @return void 78 | */ 79 | public static function config(Typecho_Widget_Helper_Form $form) 80 | { 81 | 82 | $isOpenGeetestPage = new Typecho_Widget_Helper_Form_Element_Checkbox('isOpenGeetestPage', [ 83 | "typechoLogin" => _t('登录界面'), 84 | "typechoComment" => _t('评论页面') 85 | ], array(), _t('开启极验验证码的页面,勾选则开启'), _t('开启评论验证码后需在主题的评论的模板 comments.php 中添加如下字段:')); 86 | 87 | $captchaId = new Typecho_Widget_Helper_Form_Element_Text('captchaId', null, '', _t('公钥(ID):')); 88 | $privateKey = new Typecho_Widget_Helper_Form_Element_Text('privateKey', null, '', _t('私钥(KEY):')); 89 | 90 | $dismode = new Typecho_Widget_Helper_Form_Element_Select('dismod', array( 91 | 'float' => '浮动式(float)', 92 | 'embed' => '嵌入式(embed)', 93 | 'popup' => '弹出框(popup)' 94 | ), 'float', _t('展现形式:')); 95 | 96 | $cdnUrl = new Typecho_Widget_Helper_Form_Element_Text('cdnUrl', null, '', _t('引入JS的CDN加速地址:'), _t('注意使用 https 协议
留空默认引入本地/static/gt.js文件,不知道的可留空')); 97 | 98 | $debugMode = new Typecho_Widget_Helper_Form_Element_Select('debugMode', array( 99 | '0' => '关闭', 100 | '1' => '开启' 101 | ), '0', _t('调试模式:'), _t('开启时,不会禁用提交按钮,用于测试插件是否生效。')); 102 | 103 | $form->addInput($isOpenGeetestPage); 104 | $form->addInput($captchaId); 105 | $form->addInput($privateKey); 106 | $form->addInput($dismode); 107 | $form->addInput($cdnUrl); 108 | $form->addInput($debugMode); 109 | } 110 | 111 | /** 112 | * 响应验证码数据 113 | */ 114 | public static function responseCaptchaData() 115 | { 116 | @session_start(); 117 | 118 | $pluginOptions = Helper::options()->plugin('Geetest'); 119 | $geetestSdk = new GeetestLib($pluginOptions->captchaId, $pluginOptions->privateKey); 120 | 121 | $widgetRequest = Typecho_Widget::widget('Widget_Options')->request; 122 | $agent = $widgetRequest->getAgent(); 123 | 124 | $data = array( 125 | 'user_id' => rand(1000, 9999), 126 | 'client_type' => self::isMobile($agent) ? 'h5' : 'web', 127 | 'ip_address' => $widgetRequest->getIp() 128 | ); 129 | 130 | $_SESSION['gt_server_ok'] = $geetestSdk->pre_process($data, 1); 131 | $_SESSION['gt_user_id'] = $data['user_id']; 132 | 133 | echo $geetestSdk->get_response_str(); 134 | } 135 | 136 | /** 137 | * 渲染后台登陆 验证码 138 | */ 139 | public static function renderCaptcha() 140 | { 141 | // 判断是否登录页面 142 | $widgetOptions = Typecho_Widget::widget('Widget_Options'); 143 | $widgetRequest = $widgetOptions->request; 144 | $currentRequestUrl = $widgetRequest->getRequestUrl(); 145 | if (!stripos($currentRequestUrl, 'login.php')) { 146 | return; 147 | } 148 | // 取出插件的配置 149 | $pluginOptions = Helper::options()->plugin('Geetest'); 150 | $isOpenGeetestPage = $pluginOptions->isOpenGeetestPage; 151 | // 判断是否开启登陆页的验证码 152 | if (!in_array("typechoLogin", $isOpenGeetestPage)) { 153 | return; 154 | } 155 | $cdnUrl = ($pluginOptions->cdnUrl ? $pluginOptions->cdnUrl : Helper::options()->pluginUrl . '/Geetest/static/gt.min.js'); 156 | $debugMode = (bool)($pluginOptions->debugMode); 157 | 158 | $disableButtonJs = ''; 159 | $disableSubmitJs = ''; 160 | if (!$debugMode) { 161 | $disableButtonJs = 'jqFormSubmit.attr({disabled:true}).addClass("gt-btn-disabled");'; 162 | $disableSubmitJs = << 176 | #gt-captcha { line-height: 44px; } 177 | #gt-captcha .waiting { background-color: #e8e8e8; color: #4d4d4d; } 178 | .gt-btn-disabled { background-color: #a3b7c1!important; color: #fff!important; cursor: no-drop!important; } 179 | 180 | 181 | 182 | 231 | EOF; 232 | } 233 | 234 | /** 235 | * 渲染评论验证码 236 | * @throws Typecho_Plugin_Exception 237 | */ 238 | public static function commentCaptchaRender() { 239 | //判断插件是否激活 240 | $options = Typecho_Widget::widget('Widget_Options'); 241 | if (!isset($options->plugins['activated']['Geetest'])) { 242 | echo '
极验评论验证码插件未激活
'; 243 | return; 244 | } 245 | 246 | // 取出插件的配置 247 | $pluginOptions = Helper::options()->plugin('Geetest'); 248 | $isOpenGeetestPage = $pluginOptions->isOpenGeetestPage; 249 | //判断是否开启评论页的验证码 250 | if (!in_array("typechoComment", $isOpenGeetestPage)) { 251 | return; 252 | } 253 | $cdnUrl = ($pluginOptions->cdnUrl ? $pluginOptions->cdnUrl : Helper::options()->pluginUrl . '/Geetest/static/gt.min.js'); 254 | $debugMode = (bool)($pluginOptions->debugMode); 255 | 256 | $disableButtonJs = ''; 257 | $disableSubmitJs = ''; 258 | if (!$debugMode) { 259 | $disableButtonJs = '$("#sub_btn").attr({disabled:true}).addClass("gt-btn-disabled");'; 260 | $disableSubmitJs = << 274 | #gt-captcha { line-height: 44px; } 275 | .gt-btn-disabled { background-color: #a3b7c1!important; color: #fff!important; cursor: no-drop!important; } 276 | 277 | 278 | 279 | 324 | EOF; 325 | } 326 | 327 | /** 328 | * 评论验证码 校验 329 | * @access public 330 | * @param array $comment 评论内容 331 | */ 332 | public static function commentCaptchaVerify($comment) 333 | { 334 | // 取出插件的配置 335 | $pluginOptions = Helper::options()->plugin('Geetest'); 336 | $isOpenGeetestPage = $pluginOptions->isOpenGeetestPage; 337 | //判断是否开启评论页的验证码 338 | if (in_array("typechoComment", $isOpenGeetestPage)) { 339 | if (!self::_verifyCaptcha()) { 340 | echo ""; 341 | exit(); 342 | } 343 | } 344 | return $comment; 345 | 346 | } 347 | 348 | /** 349 | * 后台登陆验证码 校验 350 | */ 351 | public static function verifyCaptcha() 352 | { 353 | //取出插件的配置 354 | $pluginOptions = Helper::options()->plugin('Geetest'); 355 | $isOpenGeetestPage = $pluginOptions->isOpenGeetestPage; 356 | //判断是否开启评论页的验证码 357 | if (in_array("typechoLogin", $isOpenGeetestPage)) { 358 | if (!self::_verifyCaptcha()) { 359 | Typecho_Widget::widget('Widget_Notice')->set(_t('验证码错误'), 'error'); 360 | Typecho_Widget::widget('Widget_User')->logout(); 361 | Typecho_Widget::widget('Widget_Options')->response->goBack(); 362 | } 363 | } 364 | } 365 | 366 | /** 367 | * 校验验证码 方法 368 | * 369 | * @return int 370 | */ 371 | private static function _verifyCaptcha() 372 | { 373 | // 如果插件渲染失败,则默认验证不通过 374 | if (!isset($_POST['geetest_challenge']) || !isset($_POST['geetest_validate']) || !isset($_POST['geetest_seccode'])) { 375 | return 0; 376 | } 377 | 378 | @session_start(); 379 | 380 | $pluginOptions = Helper::options()->plugin('Geetest'); 381 | $geetestSdk = new GeetestLib($pluginOptions->captchaId, $pluginOptions->privateKey); 382 | 383 | if (!empty($_SESSION['gt_server_ok'])) { 384 | 385 | $widgetRequest = Typecho_Widget::widget('Widget_Options')->request; 386 | $agent = $widgetRequest->getAgent(); 387 | $clientType = self::isMobile($agent) ? 'h5' : 'web'; 388 | $ipAddress = $widgetRequest->getIp(); 389 | 390 | $data = array( 391 | 'user_id' => $_SESSION['gt_user_id'], 392 | 'client_type' => $clientType, 393 | 'ip_address' => $ipAddress 394 | ); 395 | 396 | return $geetestSdk->success_validate($_POST['geetest_challenge'], $_POST['geetest_validate'], $_POST['geetest_seccode'], $data); 397 | } 398 | 399 | return $geetestSdk->fail_validate($_POST['geetest_challenge'], $_POST['geetest_validate'], $_POST['geetest_seccode']); 400 | } 401 | 402 | /** 403 | * isMobile 404 | * 405 | * @static 406 | * @access public 407 | * @return boolean 408 | */ 409 | public static function isMobile($userAgent) 410 | { 411 | return preg_match('/android.+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i', $userAgent) || preg_match('/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i', substr($userAgent, 0, 4)); 412 | } 413 | 414 | } 415 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Geetest for Typecho 2 | 3 | 极验验证插件,用于用户登录、用户评论时使用极验提供的滑动验证码,适配了Material主题 4 | 5 | ### 更新说明 6 | 保留原插件的登陆验证功能,新增评论验证功能 7 | 8 | ### 使用方法 9 | 10 | #### 1 下载激活插件 11 | 下载插件后,解压,将文件夹名称改为 Geetest,上传到 /usr/plugins 目录下,在插件面板启用插件并配置即可使用; 12 | 13 | 或者直接在 Typecho 的插件目录下执行如下命令: 14 | ``` 15 | cd typechoPath/usr/plugins 16 | git clone https://github.com/noisky/typecho-plugin-geetest.git Geetest 17 | ``` 18 | 19 | #### 2 配置插件 20 | 极验验证码的 ID 和 KEY 需要到极验官网 `https://www.geetest.com/` 获取; 21 | 22 | 注册、创建应用的时候,基础版是免费的; 23 | 24 | 如需开启评论验证码,则需要在你的主题评论模板 `comment.php` 中的任意一行添加如下代码: 25 | ``` 26 |
27 | 28 | ``` 29 | 该功能需要JQuery插件支持,如果主题已经集成则不用引入该插件。 30 | 31 | **插件设置** 32 | 33 | ![插件配置范例图](https://cdn.jsdelivr.net/gh/noisky/typecho-plugin-geetest@master/images/setting_page.jpg) 34 | 35 | **后台登录验证码** 36 | 37 | ![后台登录验证码范例](https://cdn.jsdelivr.net/gh/noisky/typecho-plugin-geetest@master/images/login_page.jpg) 38 | 39 | **评论验证码** 40 | 41 | ![评论验证码范例](https://cdn.jsdelivr.net/gh/noisky/typecho-plugin-geetest@master/images/comment_page.png) 42 | 43 | ### Thanks 44 | 45 | @zhb127 46 | 47 | @xueshanlinghu 48 | -------------------------------------------------------------------------------- /images/comment_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noisky/typecho-plugin-geetest/3cd9d5e855504abd319d5fd110791c8b60d6f0a6/images/comment_page.png -------------------------------------------------------------------------------- /images/login_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noisky/typecho-plugin-geetest/3cd9d5e855504abd319d5fd110791c8b60d6f0a6/images/login_page.jpg -------------------------------------------------------------------------------- /images/setting_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/noisky/typecho-plugin-geetest/3cd9d5e855504abd319d5fd110791c8b60d6f0a6/images/setting_page.jpg -------------------------------------------------------------------------------- /lib/class.geetestlib.php: -------------------------------------------------------------------------------- 1 | captcha_id = $captcha_id; 18 | $this->private_key = $private_key; 19 | } 20 | 21 | /** 22 | * 判断极验服务器是否down机 23 | * 24 | * @param array $data 25 | * @return int 26 | */ 27 | public function pre_process($param, $new_captcha=1) { 28 | $data = array('gt'=>$this->captcha_id, 29 | 'new_captcha'=>$new_captcha 30 | ); 31 | $data = array_merge($data,$param); 32 | $query = http_build_query($data); 33 | $url = "http://api.geetest.com/register.php?" . $query; 34 | $challenge = $this->send_request($url); 35 | if (strlen($challenge) != 32) { 36 | $this->failback_process(); 37 | return 0; 38 | } 39 | $this->success_process($challenge); 40 | return 1; 41 | } 42 | 43 | /** 44 | * @param $challenge 45 | */ 46 | private function success_process($challenge) { 47 | $challenge = md5($challenge . $this->private_key); 48 | $result = array( 49 | 'success' => 1, 50 | 'gt' => $this->captcha_id, 51 | 'challenge' => $challenge, 52 | 'new_captcha'=>1 53 | ); 54 | $this->response = $result; 55 | } 56 | 57 | /** 58 | * 59 | */ 60 | private function failback_process() { 61 | $rnd1 = md5(rand(0, 100)); 62 | $rnd2 = md5(rand(0, 100)); 63 | $challenge = $rnd1 . substr($rnd2, 0, 2); 64 | $result = array( 65 | 'success' => 0, 66 | 'gt' => $this->captcha_id, 67 | 'challenge' => $challenge, 68 | 'new_captcha'=>1 69 | ); 70 | $this->response = $result; 71 | } 72 | 73 | /** 74 | * @return mixed 75 | */ 76 | public function get_response_str() { 77 | return json_encode($this->response); 78 | } 79 | 80 | /** 81 | * 返回数组方便扩展 82 | * 83 | * @return mixed 84 | */ 85 | public function get_response() { 86 | return $this->response; 87 | } 88 | 89 | /** 90 | * 正常模式获取验证结果 91 | * 92 | * @param string $challenge 93 | * @param string $validate 94 | * @param string $seccode 95 | * @param array $param 96 | * @return int 97 | */ 98 | public function success_validate($challenge, $validate, $seccode,$param, $json_format=1) { 99 | if (!$this->check_validate($challenge, $validate)) { 100 | return 0; 101 | } 102 | $query = array( 103 | "seccode" => $seccode, 104 | "timestamp"=>time(), 105 | "challenge"=>$challenge, 106 | "captchaid"=>$this->captcha_id, 107 | "json_format"=>$json_format, 108 | "sdk" => self::GT_SDK_VERSION 109 | ); 110 | $query = array_merge($query,$param); 111 | $url = "http://api.geetest.com/validate.php"; 112 | $codevalidate = $this->post_request($url, $query); 113 | $obj = json_decode($codevalidate,true); 114 | if ($obj === false){ 115 | return 0; 116 | } 117 | if ($obj['seccode'] == md5($seccode)) { 118 | return 1; 119 | } else { 120 | return 0; 121 | } 122 | } 123 | 124 | /** 125 | * 宕机模式获取验证结果 126 | * 127 | * @param $challenge 128 | * @param $validate 129 | * @param $seccode 130 | * @return int 131 | */ 132 | public function fail_validate($challenge, $validate, $seccode) { 133 | if(md5($challenge) == $validate){ 134 | return 1; 135 | }else{ 136 | return 0; 137 | } 138 | } 139 | 140 | /** 141 | * @param $challenge 142 | * @param $validate 143 | * @return bool 144 | */ 145 | private function check_validate($challenge, $validate) { 146 | if (strlen($validate) != 32) { 147 | return false; 148 | } 149 | if (md5($this->private_key . 'geetest' . $challenge) != $validate) { 150 | return false; 151 | } 152 | 153 | return true; 154 | } 155 | 156 | /** 157 | * GET 请求 158 | * 159 | * @param $url 160 | * @return mixed|string 161 | */ 162 | private function send_request($url) { 163 | 164 | if (function_exists('curl_exec')) { 165 | $ch = curl_init(); 166 | curl_setopt($ch, CURLOPT_URL, $url); 167 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); 168 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); 169 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 170 | $curl_errno = curl_errno($ch); 171 | $data = curl_exec($ch); 172 | curl_close($ch); 173 | if ($curl_errno >0) { 174 | return 0; 175 | }else{ 176 | return $data; 177 | } 178 | } else { 179 | $opts = array( 180 | 'http' => array( 181 | 'method' => "GET", 182 | 'timeout' => self::$connectTimeout + self::$socketTimeout, 183 | ) 184 | ); 185 | $context = stream_context_create($opts); 186 | $data = @file_get_contents($url, false, $context); 187 | if($data){ 188 | return $data; 189 | }else{ 190 | return 0; 191 | } 192 | } 193 | } 194 | 195 | /** 196 | * 197 | * @param $url 198 | * @param array $postdata 199 | * @return mixed|string 200 | */ 201 | private function post_request($url, $postdata = '') { 202 | if (!$postdata) { 203 | return false; 204 | } 205 | 206 | $data = http_build_query($postdata); 207 | if (function_exists('curl_exec')) { 208 | $ch = curl_init(); 209 | curl_setopt($ch, CURLOPT_URL, $url); 210 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 211 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, self::$connectTimeout); 212 | curl_setopt($ch, CURLOPT_TIMEOUT, self::$socketTimeout); 213 | 214 | //不可能执行到的代码 215 | if (!$postdata) { 216 | curl_setopt($ch, CURLOPT_USERAGENT, $_SERVER['HTTP_USER_AGENT']); 217 | } else { 218 | curl_setopt($ch, CURLOPT_POST, 1); 219 | curl_setopt($ch, CURLOPT_POSTFIELDS, $data); 220 | } 221 | $data = curl_exec($ch); 222 | 223 | if (curl_errno($ch)) { 224 | $err = sprintf("curl[%s] error[%s]", $url, curl_errno($ch) . ':' . curl_error($ch)); 225 | $this->triggerError($err); 226 | } 227 | 228 | curl_close($ch); 229 | } else { 230 | if ($postdata) { 231 | $opts = array( 232 | 'http' => array( 233 | 'method' => 'POST', 234 | 'header' => "Content-type: application/x-www-form-urlencoded\r\n" . "Content-Length: " . strlen($data) . "\r\n", 235 | 'content' => $data, 236 | 'timeout' => self::$connectTimeout + self::$socketTimeout 237 | ) 238 | ); 239 | $context = stream_context_create($opts); 240 | $data = file_get_contents($url, false, $context); 241 | } 242 | } 243 | 244 | return $data; 245 | } 246 | 247 | 248 | 249 | /** 250 | * @param $err 251 | */ 252 | private function triggerError($err) { 253 | trigger_error($err); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /static/gt.js: -------------------------------------------------------------------------------- 1 | /* initGeetest 1.0.0 2 | * 用于加载id对应的验证码库,并支持宕机模式 3 | * 暴露 initGeetest 进行验证码的初始化 4 | * 一般不需要用户进行修改 5 | */ 6 | (function (global, factory) { 7 | "use strict"; 8 | if (typeof module === "object" && typeof module.exports === "object") { 9 | // CommonJS 10 | module.exports = global.document ? 11 | factory(global, true) : 12 | function (w) { 13 | if (!w.document) { 14 | throw new Error("Geetest requires a window with a document"); 15 | } 16 | return factory(w); 17 | }; 18 | } else { 19 | factory(global); 20 | } 21 | })(typeof window !== "undefined" ? window : this, function (window, noGlobal) { 22 | "use strict"; 23 | if (typeof window === 'undefined') { 24 | throw new Error('Geetest requires browser environment'); 25 | } 26 | var document = window.document; 27 | var Math = window.Math; 28 | var head = document.getElementsByTagName("head")[0]; 29 | 30 | function _Object(obj) { 31 | this._obj = obj; 32 | } 33 | 34 | _Object.prototype = { 35 | _each: function (process) { 36 | var _obj = this._obj; 37 | for (var k in _obj) { 38 | if (_obj.hasOwnProperty(k)) { 39 | process(k, _obj[k]); 40 | } 41 | } 42 | return this; 43 | } 44 | }; 45 | function Config(config) { 46 | var self = this; 47 | new _Object(config)._each(function (key, value) { 48 | self[key] = value; 49 | }); 50 | } 51 | 52 | Config.prototype = { 53 | api_server: 'api.geetest.com', 54 | protocol: 'http://', 55 | type_path: '/gettype.php', 56 | fallback_config: { 57 | slide: { 58 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 59 | type: 'slide', 60 | slide: '/static/js/geetest.0.0.0.js' 61 | }, 62 | fullpage: { 63 | static_servers: ["static.geetest.com", "dn-staticdown.qbox.me"], 64 | type: 'fullpage', 65 | fullpage: '/static/js/fullpage.0.0.0.js' 66 | } 67 | }, 68 | _get_fallback_config: function () { 69 | var self = this; 70 | if (isString(self.type)) { 71 | return self.fallback_config[self.type]; 72 | } else if (self.new_captcha) { 73 | return self.fallback_config.fullpage; 74 | } else { 75 | return self.fallback_config.slide; 76 | } 77 | }, 78 | _extend: function (obj) { 79 | var self = this; 80 | new _Object(obj)._each(function (key, value) { 81 | self[key] = value; 82 | }) 83 | } 84 | }; 85 | var isNumber = function (value) { 86 | return (typeof value === 'number'); 87 | }; 88 | var isString = function (value) { 89 | return (typeof value === 'string'); 90 | }; 91 | var isBoolean = function (value) { 92 | return (typeof value === 'boolean'); 93 | }; 94 | var isObject = function (value) { 95 | return (typeof value === 'object' && value !== null); 96 | }; 97 | var isFunction = function (value) { 98 | return (typeof value === 'function'); 99 | }; 100 | var callbacks = {}; 101 | var status = {}; 102 | var random = function () { 103 | return parseInt(Math.random() * 10000) + (new Date()).valueOf(); 104 | }; 105 | var loadScript = function (url, cb) { 106 | var script = document.createElement("script"); 107 | script.charset = "UTF-8"; 108 | script.async = true; 109 | script.onerror = function () { 110 | cb(true); 111 | }; 112 | var loaded = false; 113 | script.onload = script.onreadystatechange = function () { 114 | if (!loaded && 115 | (!script.readyState || 116 | "loaded" === script.readyState || 117 | "complete" === script.readyState)) { 118 | 119 | loaded = true; 120 | setTimeout(function () { 121 | cb(false); 122 | }, 0); 123 | } 124 | }; 125 | script.src = url; 126 | head.appendChild(script); 127 | }; 128 | var normalizeDomain = function (domain) { 129 | return domain.replace(/^https?:\/\/|\/$/g, ''); 130 | }; 131 | var normalizePath = function (path) { 132 | path = path.replace(/\/+/g, '/'); 133 | if (path.indexOf('/') !== 0) { 134 | path = '/' + path; 135 | } 136 | return path; 137 | }; 138 | var normalizeQuery = function (query) { 139 | if (!query) { 140 | return ''; 141 | } 142 | var q = '?'; 143 | new _Object(query)._each(function (key, value) { 144 | if (isString(value) || isNumber(value) || isBoolean(value)) { 145 | q = q + encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 146 | } 147 | }); 148 | if (q === '?') { 149 | q = ''; 150 | } 151 | return q.replace(/&$/, ''); 152 | }; 153 | var makeURL = function (protocol, domain, path, query) { 154 | domain = normalizeDomain(domain); 155 | 156 | var url = normalizePath(path) + normalizeQuery(query); 157 | if (domain) { 158 | url = protocol + domain + url; 159 | } 160 | 161 | return url; 162 | }; 163 | var load = function (protocol, domains, path, query, cb) { 164 | var tryRequest = function (at) { 165 | 166 | var url = makeURL(protocol, domains[at], path, query); 167 | loadScript(url, function (err) { 168 | if (err) { 169 | if (at >= domains.length - 1) { 170 | cb(true); 171 | } else { 172 | tryRequest(at + 1); 173 | } 174 | } else { 175 | cb(false); 176 | } 177 | }); 178 | }; 179 | tryRequest(0); 180 | }; 181 | var jsonp = function (domains, path, config, callback) { 182 | if (isObject(config.getLib)) { 183 | config._extend(config.getLib); 184 | callback(config); 185 | return; 186 | } 187 | if (config.offline) { 188 | callback(config._get_fallback_config()); 189 | return; 190 | } 191 | var cb = "geetest_" + random(); 192 | window[cb] = function (data) { 193 | if (data.status === 'success') { 194 | callback(data.data); 195 | } else if (!data.status) { 196 | callback(data); 197 | } else { 198 | callback(config._get_fallback_config()); 199 | } 200 | window[cb] = undefined; 201 | try { 202 | delete window[cb]; 203 | } catch (e) { 204 | } 205 | }; 206 | load(config.protocol, domains, path, { 207 | gt: config.gt, 208 | callback: cb 209 | }, function (err) { 210 | if (err) { 211 | callback(config._get_fallback_config()); 212 | } 213 | }); 214 | }; 215 | var throwError = function (errorType, config) { 216 | var errors = { 217 | networkError: '网络错误' 218 | }; 219 | if (typeof config.onError === 'function') { 220 | config.onError(errors[errorType]); 221 | } else { 222 | throw new Error(errors[errorType]); 223 | } 224 | }; 225 | var detect = function () { 226 | return !!window.Geetest; 227 | }; 228 | if (detect()) { 229 | status.slide = "loaded"; 230 | } 231 | var initGeetest = function (userConfig, callback) { 232 | var config = new Config(userConfig); 233 | if (userConfig.https) { 234 | config.protocol = 'https://'; 235 | } else if (!userConfig.protocol) { 236 | config.protocol = window.location.protocol + '//'; 237 | } 238 | jsonp([config.api_server || config.apiserver], config.type_path, config, function (newConfig) { 239 | var type = newConfig.type; 240 | var init = function () { 241 | config._extend(newConfig); 242 | callback(new window.Geetest(config)); 243 | }; 244 | callbacks[type] = callbacks[type] || []; 245 | var s = status[type] || 'init'; 246 | if (s === 'init') { 247 | status[type] = 'loading'; 248 | callbacks[type].push(init); 249 | load(config.protocol, newConfig.static_servers || newConfig.domains, newConfig[type] || newConfig.path, null, function (err) { 250 | if (err) { 251 | status[type] = 'fail'; 252 | throwError('networkError', config); 253 | } else { 254 | status[type] = 'loaded'; 255 | var cbs = callbacks[type]; 256 | for (var i = 0, len = cbs.length; i < len; i = i + 1) { 257 | var cb = cbs[i]; 258 | if (isFunction(cb)) { 259 | cb(); 260 | } 261 | } 262 | callbacks[type] = []; 263 | } 264 | }); 265 | } else if (s === "loaded") { 266 | init(); 267 | } else if (s === "fail") { 268 | throwError('networkError', config); 269 | } else if (s === "loading") { 270 | callbacks[type].push(init); 271 | } 272 | }); 273 | }; 274 | window.initGeetest = initGeetest; 275 | return initGeetest; 276 | }); 277 | 278 | -------------------------------------------------------------------------------- /static/gt.min.js: -------------------------------------------------------------------------------- 1 | (function(b,a){if(typeof module==="object"&&typeof module.exports==="object"){module.exports=b.document?a(b,true):function(c){if(!c.document){throw new Error("Geetest requires a window with a document")}return a(c)}}else{a(b)}})(typeof window!=="undefined"?window:this,function(g,t){if(typeof g==="undefined"){throw new Error("Geetest requires browser environment")}var o=g.document;var i=g.Math;var d=o.getElementsByTagName("head")[0];function e(z){this._obj=z}e.prototype={_each:function(A){var B=this._obj;for(var z in B){if(B.hasOwnProperty(z)){A(z,B[z])}}return this}};function h(A){var z=this;new e(A)._each(function(B,C){z[B]=C})}h.prototype={api_server:"api.geetest.com",protocol:"http://",type_path:"/gettype.php",fallback_config:{slide:{static_servers:["static.geetest.com","dn-staticdown.qbox.me"],type:"slide",slide:"/static/js/geetest.0.0.0.js"},fullpage:{static_servers:["static.geetest.com","dn-staticdown.qbox.me"],type:"fullpage",fullpage:"/static/js/fullpage.0.0.0.js"}},_get_fallback_config:function(){var z=this;if(m(z.type)){return z.fallback_config[z.type]}else{if(z.new_captcha){return z.fallback_config.fullpage}else{return z.fallback_config.slide}}},_extend:function(A){var z=this;new e(A)._each(function(B,C){z[B]=C})}};var n=function(z){return(typeof z==="number")};var m=function(z){return(typeof z==="string")};var j=function(z){return(typeof z==="boolean")};var k=function(z){return(typeof z==="object"&&z!==null)};var c=function(z){return(typeof z==="function")};var x={};var q={};var a=function(){return parseInt(i.random()*10000)+(new Date()).valueOf()};var l=function(C,z){var A=o.createElement("script");A.charset="UTF-8";A.async=true;A.onerror=function(){z(true)};var B=false;A.onload=A.onreadystatechange=function(){if(!B&&(!A.readyState||"loaded"===A.readyState||"complete"===A.readyState)){B=true;setTimeout(function(){z(false)},0)}};A.src=C;d.appendChild(A)};var r=function(z){return z.replace(/^https?:\/\/|\/$/g,"")};var w=function(z){z=z.replace(/\/+/g,"/");if(z.indexOf("/")!==0){z="/"+z}return z};var v=function(A){if(!A){return""}var z="?";new e(A)._each(function(B,C){if(m(C)||n(C)||j(C)){z=z+encodeURIComponent(B)+"="+encodeURIComponent(C)+"&"}});if(z==="?"){z=""}return z.replace(/&$/,"")};var p=function(D,B,C,A){B=r(B);var z=w(C)+v(A);if(B){z=D+B+z}return z};var f=function(E,A,D,C,z){var B=function(F){var G=p(E,A[F],D,C);l(G,function(H){if(H){if(F>=A.length-1){z(true)}else{B(F+1)}}else{z(false)}})};B(0)};var y=function(A,C,B,D){if(k(B.getLib)){B._extend(B.getLib);D(B);return}if(B.offline){D(B._get_fallback_config());return}var z="geetest_"+a();g[z]=function(E){if(E.status==="success"){D(E.data)}else{if(!E.status){D(E)}else{D(B._get_fallback_config())}}g[z]=undefined;try{delete g[z]}catch(F){}};f(B.protocol,A,C,{gt:B.gt,callback:z},function(E){if(E){D(B._get_fallback_config())}})};var s=function(A,z){var B={networkError:"网络错误"};if(typeof z.onError==="function"){z.onError(B[A])}else{throw new Error(B[A])}};var u=function(){return !!g.Geetest};if(u()){q.slide="loaded"}var b=function(A,B){var z=new h(A);if(A.https){z.protocol="https://"}else{if(!A.protocol){z.protocol=g.location.protocol+"//"}}y([z.api_server||z.apiserver],z.type_path,z,function(C){var E=C.type;var F=function(){z._extend(C);B(new g.Geetest(z))};x[E]=x[E]||[];var D=q[E]||"init";if(D==="init"){q[E]="loading";x[E].push(F);f(z.protocol,C.static_servers||C.domains,C[E]||C.path,null,function(K){if(K){q[E]="fail";s("networkError",z)}else{q[E]="loaded";var I=x[E];for(var J=0,H=I.length;J