1334 lines
54 KiB
PHP
1334 lines
54 KiB
PHP
<?php
|
||
/**
|
||
* Restored captcha adapter monolith.
|
||
*/
|
||
|
||
defined('ABSPATH') || exit;
|
||
|
||
|
||
if (!function_exists('aksrt_get_captcha_config')):
|
||
/**
|
||
* 获取人机验证完整配置
|
||
*/
|
||
function aksrt_get_captcha_config() {
|
||
$default = array(
|
||
'enabled' => false,
|
||
'active_provider' => '',
|
||
'mode' => 'always',
|
||
'disabled_scenarios'=> array(),
|
||
);
|
||
return get_option('aksrt_captcha_config', $default);
|
||
}
|
||
endif;
|
||
|
||
if (!function_exists('aksrt_captcha_provider_is_configured')):
|
||
/**
|
||
* 当前服务商必填凭据是否齐全(避免空 captcha_id 打到极验 403)。
|
||
*
|
||
* @param string $provider
|
||
* @param array $pcfg
|
||
* @return bool
|
||
*/
|
||
function aksrt_captcha_provider_is_configured($provider, $pcfg = array()) {
|
||
$provider = sanitize_key((string) $provider);
|
||
if ($provider === '') {
|
||
return false;
|
||
}
|
||
if (!is_array($pcfg)) {
|
||
$pcfg = array();
|
||
}
|
||
$required = aksrt_captcha_required_fields($provider);
|
||
if (empty($required)) {
|
||
return false;
|
||
}
|
||
foreach ($required as $field) {
|
||
if (!isset($pcfg[$field]) || trim((string) $pcfg[$field]) === '') {
|
||
return false;
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
endif;
|
||
|
||
if (!function_exists('aksrt_is_captcha_enabled_for')):
|
||
/**
|
||
* 检查指定场景是否启用人机验证
|
||
*
|
||
* @param string $scene comment / login / register / lostpassword
|
||
* @return bool
|
||
*/
|
||
function aksrt_is_captcha_enabled_for($scene) {
|
||
$cfg = aksrt_get_captcha_config();
|
||
if (empty($cfg['enabled'])) return false;
|
||
if (empty($cfg['active_provider'])) return false;
|
||
$provider = (string) $cfg['active_provider'];
|
||
$pcfg = isset($cfg[$provider]) && is_array($cfg[$provider]) ? $cfg[$provider] : array();
|
||
// 启用但凭据未配齐时,前台不渲染/不拦截,避免空 captcha_id 请求第三方 403
|
||
if (!aksrt_captcha_provider_is_configured($provider, $pcfg)) {
|
||
return false;
|
||
}
|
||
$disabled = isset($cfg['disabled_scenarios']) ? $cfg['disabled_scenarios'] : array();
|
||
return !in_array($scene, $disabled, true);
|
||
}
|
||
endif;
|
||
|
||
if (!function_exists('aksrt_is_native_auth_admin_whitelist')):
|
||
/**
|
||
* 判断当前请求是否属于原生认证后台白名单。
|
||
*
|
||
* 前台登录/注册/找回密码已经由主题认证路由接管,
|
||
* 这里仅保留 wp-login.php 场景作为后台认证白名单,
|
||
* 避免影响 WordPress 后台登录、重新认证等原生流程。
|
||
*
|
||
* @return bool
|
||
*/
|
||
function aksrt_is_native_auth_admin_whitelist() {
|
||
return isset($GLOBALS['pagenow']) && $GLOBALS['pagenow'] === 'wp-login.php';
|
||
}
|
||
endif;
|
||
|
||
// ============================================================
|
||
// PHP 7.x 兼容辅助函数(替代 PHP 8.0+ 的 match 表达式)
|
||
// ============================================================
|
||
|
||
if (!function_exists('aksrt_captcha_field_names')):
|
||
/**
|
||
* 获取指定验证服务商的字段名列表(替代 match)
|
||
*
|
||
* @param string $provider 服务商标识
|
||
* @return array 字段名数组
|
||
*/
|
||
function aksrt_captcha_field_names($provider) {
|
||
$map = array(
|
||
'tencent' => array('secret_id', 'secret_key', 'app_id', 'app_secret_key'),
|
||
'aliyun' => array('region', 'prefix', 'scene_id', 'access_key_id', 'access_key_secret'),
|
||
'cloudflare' => array('site_key', 'secret_key', 'domain', 'theme', 'size'),
|
||
'geetest' => array('captcha_id', 'captcha_key', 'product'),
|
||
);
|
||
return isset($map[$provider]) ? $map[$provider] : array();
|
||
}
|
||
endif;
|
||
|
||
if (!function_exists('aksrt_captcha_required_fields')):
|
||
/**
|
||
* 获取指定验证服务商的必填字段列表(替代 match)
|
||
*
|
||
* @param string $provider 服务商标识
|
||
* @return array 必填字段名数组
|
||
*/
|
||
function aksrt_captcha_required_fields($provider) {
|
||
$map = array(
|
||
'tencent' => array('secret_id', 'secret_key', 'app_id', 'app_secret_key'),
|
||
'aliyun' => array('region', 'prefix', 'scene_id', 'access_key_id', 'access_key_secret'),
|
||
'cloudflare' => array('site_key', 'secret_key'),
|
||
'geetest' => array('captcha_id', 'captcha_key'),
|
||
);
|
||
return isset($map[$provider]) ? $map[$provider] : array();
|
||
}
|
||
endif;
|
||
|
||
// ============================================================
|
||
// 人机验证:获取客户端真实IP(穿透CDN/代理)
|
||
// ============================================================
|
||
|
||
if (!function_exists('aksrt_get_client_ip')) :
|
||
function aksrt_get_client_ip() {
|
||
// 按优先级检查代理头
|
||
$keys = array('HTTP_X_FORWARDED_FOR', 'HTTP_X_REAL_IP', 'HTTP_CLIENT_IP');
|
||
foreach ($keys as $key) {
|
||
if (!empty($_SERVER[$key])) {
|
||
$parts = explode(',', $_SERVER[$key]);
|
||
$ip = trim($parts[0]);
|
||
if (filter_var($ip, FILTER_VALIDATE_IP)) return $ip;
|
||
}
|
||
}
|
||
return isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : '0.0.0.0';
|
||
}
|
||
endif;
|
||
|
||
// ============================================================
|
||
// 腾讯云 API 3.0 TC3-HMAC-SHA256 签名函数
|
||
// ============================================================
|
||
|
||
if (!function_exists('aksrt_tencent_tc3_sign')) :
|
||
function aksrt_tencent_tc3_sign($secret_id, $secret_key, $service, $timestamp, $date, $action, $version, $payload) {
|
||
// 1. 拼接签名原文
|
||
$http_request_method = 'POST';
|
||
$canonical_uri = '/';
|
||
$canonical_querystring = '';
|
||
$content_type = 'application/json; charset=utf-8';
|
||
$host = 'captcha.tencentcloudapi.com';
|
||
|
||
$payload_hash = hash('sha256', $payload);
|
||
|
||
$canonical_headers = "content-type:{$content_type}\nhost:{$host}\n";
|
||
$signed_headers = 'content-type;host';
|
||
|
||
$canonical_request = "{$http_request_method}\n{$canonical_uri}\n{$canonical_querystring}\n{$canonical_headers}\n{$signed_headers}\n{$payload_hash}";
|
||
|
||
$algorithm = 'TC3-HMAC-SHA256';
|
||
$credential_scope = "{$date}/{$service}/tc3_request";
|
||
$string_to_sign = "{$algorithm}\n{$timestamp}\n{$credential_scope}\n" . hash('sha256', $canonical_request);
|
||
|
||
// 2. 计算签名密钥链
|
||
$secret_date = hash_hmac('sha256', $date, 'TC3' . $secret_key, true);
|
||
$secret_service = hash_hmac('sha256', $service, $secret_date, true);
|
||
$secret_signing = hash_hmac('sha256', 'tc3_request', $secret_service, true);
|
||
|
||
// 3. 计算签名
|
||
$signature = hash_hmac('sha256', $string_to_sign, $secret_signing);
|
||
|
||
// 4. 组装 Authorization Header(包含 SecretId)
|
||
$authorization = "{$algorithm} "
|
||
. "Credential={$secret_id}/{$credential_scope}, "
|
||
. "SignedHeaders={$signed_headers}, "
|
||
. "Signature={$signature}";
|
||
|
||
return array(
|
||
'Authorization' => $authorization,
|
||
'Content-Type' => $content_type,
|
||
'Host' => $host,
|
||
'X-TC-Action' => $action,
|
||
'X-TC-Timestamp'=> (string) $timestamp,
|
||
'X-TC-Version' => $version,
|
||
'X-TC-Region' => '',
|
||
);
|
||
}
|
||
|
||
|
||
|
||
function aksrt_verify_captcha($post_data) {
|
||
$cfg = aksrt_get_captcha_config();
|
||
if (empty($cfg['enabled']) || empty($cfg['active_provider'])) {
|
||
return true;
|
||
}
|
||
|
||
$provider = $cfg['active_provider'];
|
||
$pcfg = isset($cfg[$provider]) && is_array($cfg[$provider]) ? $cfg[$provider] : array();
|
||
$client_ip = aksrt_get_client_ip();
|
||
|
||
// 快速路径:客户端验证字段检查
|
||
$captcha_verified = isset($post_data['captcha_verified']) ? $post_data['captcha_verified'] : '';
|
||
if ($captcha_verified !== '1') {
|
||
return __('人机验证失败', 'aksrt');
|
||
}
|
||
|
||
$result = false;
|
||
$error_msg = '';
|
||
|
||
switch ($provider) {
|
||
|
||
// --- 腾讯云验证码 2.0 (API 3.0 DescribeCaptchaResult + TC3-HMAC-SHA256 签名) ---
|
||
case 'tencent':
|
||
$ticket = isset($post_data['tencent_ticket']) ? $post_data['tencent_ticket'] : '';
|
||
$randstr = isset($post_data['tencent_randstr']) ? $post_data['tencent_randstr'] : '';
|
||
if (!$ticket || !$randstr) {
|
||
$error_msg = '缺少验证参数';
|
||
break;
|
||
}
|
||
|
||
// 检测容灾票据 (trerror_ 前缀表示 SDK 加载失败或异常)
|
||
if (strpos($ticket, 'trerror_') === 0) {
|
||
$error_msg = '验证码SDK异常,请刷新页面重试 (容灾票据)';
|
||
break;
|
||
}
|
||
|
||
// 检查是否配置了云 API 密钥(TC3 签名必须)
|
||
$cloud_secret_id = isset($pcfg['secret_id']) ? $pcfg['secret_id'] : '';
|
||
$cloud_secret_key = isset($pcfg['secret_key']) ? $pcfg['secret_key'] : '';
|
||
if (!$cloud_secret_id || !$cloud_secret_key) {
|
||
$error_msg = '验证码配置不完整:缺少云 API 密钥 (SecretId/SecretKey)';
|
||
break;
|
||
}
|
||
|
||
// API 3.0 DescribeCaptchaResult 请求参数
|
||
$tc_action = 'DescribeCaptchaResult';
|
||
$tc_version = '2019-07-22';
|
||
$tc_service = 'captcha';
|
||
$tc_host = 'captcha.tencentcloudapi.com';
|
||
$tc_timestamp = time();
|
||
$tc_date = gmdate('Y-m-d', $tc_timestamp);
|
||
|
||
$tc_payload = wp_json_encode(array(
|
||
'CaptchaType' => 9,
|
||
'CaptchaAppId' => (int) (isset($pcfg['app_id']) ? $pcfg['app_id'] : 0),
|
||
'AppSecretKey' => isset($pcfg['app_secret_key']) ? $pcfg['app_secret_key'] : '',
|
||
'Ticket' => $ticket,
|
||
'Randstr' => $randstr,
|
||
'UserIp' => $client_ip,
|
||
));
|
||
|
||
// 生成 TC3-HMAC-SHA256 签名(使用云 API 密钥)
|
||
$headers = aksrt_tencent_tc3_sign(
|
||
$cloud_secret_id,
|
||
$cloud_secret_key,
|
||
$tc_service,
|
||
$tc_timestamp,
|
||
$tc_date,
|
||
$tc_action,
|
||
$tc_version,
|
||
$tc_payload
|
||
);
|
||
|
||
// 使用 cURL 发起签名请求(避免 WordPress HTTP API 干扰 headers)
|
||
$ch = curl_init("https://{$tc_host}/");
|
||
$curl_headers = array();
|
||
foreach ($headers as $k => $v) {
|
||
$curl_headers[] = "{$k}: {$v}";
|
||
}
|
||
curl_setopt_array($ch, array(
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_TIMEOUT => 10,
|
||
CURLOPT_POSTFIELDS => $tc_payload,
|
||
CURLOPT_HTTPHEADER => $curl_headers,
|
||
CURLOPT_SSL_VERIFYPEER => false,
|
||
CURLOPT_FOLLOWLOCATION => false,
|
||
));
|
||
$response_body = curl_exec($ch);
|
||
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||
$curl_error = curl_error($ch);
|
||
curl_close($ch);
|
||
|
||
if ($curl_error) {
|
||
$error_msg = '验证码服务请求失败: ' . $curl_error;
|
||
break;
|
||
}
|
||
|
||
$body = json_decode(!empty($response_body) ? $response_body : '{}', true);
|
||
|
||
// 检查 API 错误
|
||
if (isset($body['Response']['Error'])) {
|
||
$api_err = $body['Response']['Error'];
|
||
$err_code = isset($api_err['Code']) ? $api_err['Code'] : '';
|
||
$err_msg = isset($api_err['Message']) ? $api_err['Message'] : '';
|
||
$error_msg = '腾讯 API 调用失败: ' . (!empty($err_msg) ? $err_msg : (!empty($err_code) ? $err_code : '未知错误'));
|
||
break;
|
||
}
|
||
|
||
$resp = isset($body['Response']) && is_array($body['Response']) ? $body['Response'] : array();
|
||
$captcha_code = (int) (isset($resp['CaptchaCode']) ? $resp['CaptchaCode'] : 0);
|
||
|
||
// CaptchaCode: 1=验证通过, 其他=失败
|
||
if ($captcha_code === 1) {
|
||
$result = true;
|
||
} else {
|
||
$last_code = $captcha_code;
|
||
$last_msg = isset($resp['CaptchaMsg']) ? $resp['CaptchaMsg'] : '';
|
||
|
||
$messages = array(
|
||
0 => '验证未通过',
|
||
7 => 'Randstr不合法',
|
||
8 => 'Ticket已过期(有效期5分钟)',
|
||
9 => 'Ticket被重复使用',
|
||
15 => 'Ticket不合法(解密失败)',
|
||
16 => 'CaptchaAppId与Ticket不匹配',
|
||
21 => '票据校验异常(风控拦截)',
|
||
100 => '参数校验失败(AppId/SecretKey/Ticket不匹配)',
|
||
);
|
||
$msg_text = !empty($last_msg) ? $last_msg : (isset($messages[$last_code]) ? $messages[$last_code] : '未知错误');
|
||
$error_msg = '腾讯验证失败: ' . $msg_text . " (CaptchaCode: {$last_code})";
|
||
}
|
||
break;
|
||
|
||
// --- Cloudflare Turnstile ---
|
||
case 'cloudflare':
|
||
$token = isset($post_data['cf_turnstile_response']) ? $post_data['cf_turnstile_response'] : '';
|
||
if (!$token) {
|
||
$error_msg = '缺少验证参数';
|
||
break;
|
||
}
|
||
|
||
// Token 格式检查 (Turnstile token 通常是长字符串)
|
||
if (strlen($token) < 10) {
|
||
$error_msg = '验证码 token 格式异常';
|
||
break;
|
||
}
|
||
|
||
// 尝试方式1: 不带 remoteip(Cloudflare 自动使用服务器 IP)
|
||
// 如果失败再尝试方式2: 带 remoteip
|
||
$cf_attempts = array(
|
||
'without-remoteip' => array(
|
||
'body' => array(
|
||
'secret' => isset($pcfg['secret_key']) ? $pcfg['secret_key'] : '',
|
||
'response' => $token,
|
||
)
|
||
),
|
||
'with-remoteip' => array(
|
||
'body' => array(
|
||
'secret' => isset($pcfg['secret_key']) ? $pcfg['secret_key'] : '',
|
||
'response' => $token,
|
||
'remoteip' => $client_ip,
|
||
)
|
||
),
|
||
);
|
||
|
||
foreach ($cf_attempts as $mode => $attempt) {
|
||
$resp = wp_remote_post('https://challenges.cloudflare.com/turnstile/v0/siteverify', array(
|
||
'timeout' => 10,
|
||
'body' => $attempt['body'],
|
||
));
|
||
|
||
if (is_wp_error($resp)) {
|
||
continue;
|
||
}
|
||
|
||
$raw_body = wp_remote_retrieve_body($resp);
|
||
$body = json_decode($raw_body, true);
|
||
|
||
if (!empty($body['success'])) {
|
||
$result = true;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!$result) {
|
||
$error_codes = isset($body['error-codes']) && is_array($body['error-codes']) ? $body['error-codes'] : array();
|
||
$error_codes_str = implode(', ', $error_codes);
|
||
$error_msg = 'Cloudflare验证失败: ' . (!empty($error_codes_str) ? $error_codes_str : '未知错误');
|
||
}
|
||
break;
|
||
|
||
// --- 极验 GeeTest v4 ---
|
||
case 'geetest':
|
||
$lot_number = isset($post_data['geetest_lot_number']) ? $post_data['geetest_lot_number'] : '';
|
||
$captcha_output = isset($post_data['geetest_captcha_output']) ? $post_data['geetest_captcha_output'] : '';
|
||
$pass_token = isset($post_data['geetest_pass_token']) ? $post_data['geetest_pass_token'] : '';
|
||
$gen_time = isset($post_data['geetest_gen_time']) ? $post_data['geetest_gen_time'] : '';
|
||
if (!$lot_number || !$captcha_output || !$pass_token) {
|
||
$error_msg = '缺少验证参数';
|
||
break;
|
||
}
|
||
|
||
// 极验 v4 要求使用 HMAC-SHA256 签名(非普通 SHA256)
|
||
$captcha_key_val = isset($pcfg['captcha_key']) ? $pcfg['captcha_key'] : '';
|
||
$sign_token = hash_hmac('sha256', $lot_number, $captcha_key_val);
|
||
|
||
$resp = wp_remote_post('https://gcaptcha4.geetest.com/validate', array(
|
||
'timeout' => 10,
|
||
'body' => array(
|
||
'captcha_id' => isset($pcfg['captcha_id']) ? $pcfg['captcha_id'] : '',
|
||
'lot_number' => $lot_number,
|
||
'captcha_output' => $captcha_output,
|
||
'pass_token' => $pass_token,
|
||
'gen_time' => $gen_time,
|
||
'sign_token' => $sign_token,
|
||
),
|
||
));
|
||
|
||
if (is_wp_error($resp)) {
|
||
$error_msg = '验证请求失败: ' . $resp->get_error_message();
|
||
break;
|
||
}
|
||
|
||
$raw_body = wp_remote_retrieve_body($resp);
|
||
$body = json_decode($raw_body, true);
|
||
|
||
if (!empty($body['result']) && $body['result'] === 'success') {
|
||
$result = true;
|
||
} else {
|
||
$reason = isset($body['reason']) ? $body['reason'] : '';
|
||
$bmsg = isset($body['msg']) ? $body['msg'] : '';
|
||
$error_msg = '极验验证失败: ' . (!empty($reason) ? $reason : (!empty($bmsg) ? $bmsg : '未知错误'));
|
||
}
|
||
break;
|
||
|
||
// --- 阿里云 CAPTCHA 2.0 V3 (VerifyIntelligentCaptcha) ---
|
||
case 'aliyun':
|
||
$captcha_verify_param = isset($post_data['aliyun_captcha_verify_param']) ? $post_data['aliyun_captcha_verify_param'] : '';
|
||
if (!$captcha_verify_param) {
|
||
$error_msg = '缺少验证参数 (captchaVerifyParam)';
|
||
break;
|
||
}
|
||
|
||
// 确定地域对应的 API 端点
|
||
$region = isset($pcfg['region']) ? $pcfg['region'] : 'cn';
|
||
$endpoint = $region === 'sgp'
|
||
? 'captcha.ap-southeast-1.aliyuncs.com'
|
||
: 'captcha.cn-shanghai.aliyuncs.com';
|
||
|
||
$params = array(
|
||
'AccessKeyId' => isset($pcfg['access_key_id']) ? $pcfg['access_key_id'] : '',
|
||
'Action' => 'VerifyIntelligentCaptcha',
|
||
'Format' => 'JSON',
|
||
'SignatureMethod' => 'HMAC-SHA1',
|
||
'SignatureVersion' => '1.0',
|
||
'SignatureNonce' => uniqid('', true),
|
||
'Timestamp' => gmdate('Y-m-d\TH:i:s\Z'),
|
||
'Version' => '2023-03-05',
|
||
'CaptchaVerifyParam' => $captcha_verify_param,
|
||
);
|
||
|
||
// 可选: 传入 SceneId 校验一致性(防止前端篡改)
|
||
if (!empty($pcfg['scene_id'])) {
|
||
$params['SceneId'] = $pcfg['scene_id'];
|
||
}
|
||
|
||
$ak_secret = isset($pcfg['access_key_secret']) ? $pcfg['access_key_secret'] : '';
|
||
$params['Signature'] = aksrt_aliyun_afs_sign($params, $ak_secret);
|
||
|
||
$resp = wp_remote_post('https://' . $endpoint . '/', array(
|
||
'timeout' => 10,
|
||
'body' => $params,
|
||
));
|
||
|
||
if (is_wp_error($resp)) {
|
||
$error_msg = '验证请求失败: ' . $resp->get_error_message();
|
||
break;
|
||
}
|
||
|
||
$body = json_decode(wp_remote_retrieve_body($resp), true);
|
||
|
||
if (!empty($body['Success'])) {
|
||
$verify_result = isset($body['Result']['VerifyResult']) ? $body['Result']['VerifyResult'] : false;
|
||
$verify_code = isset($body['Result']['VerifyCode']) ? $body['Result']['VerifyCode'] : '';
|
||
|
||
if ($verify_result === true) {
|
||
$result = true;
|
||
} else {
|
||
// 根据 VerifyCode 映射错误信息
|
||
$code_messages = array(
|
||
'F001' => '疑似攻击请求,风险策略不通过',
|
||
'F002' => '验证参数为空',
|
||
'F003' => '验证参数格式不合法',
|
||
'F004' => '测试模式配置为不通过',
|
||
'F005' => '场景ID不合法',
|
||
'F006' => '场景ID与配置不匹配',
|
||
'F008' => '验证数据重复提交',
|
||
'F009' => '检测到虚拟设备环境',
|
||
'F010' => '同IP访问频率超限',
|
||
'F011' => '同设备访问频率超限',
|
||
'F012' => 'SceneID与前端配置不一致',
|
||
'F014' => '无初始化记录或已过期(20分钟)',
|
||
'F015' => '验证交互不通过',
|
||
'F018' => 'CaptchaVerifyParam重复使用',
|
||
'F019' => '验证请求超时(90秒)',
|
||
'F020' => 'CaptchaVerifyParam与场景ID不匹配',
|
||
);
|
||
$vc_display = !empty($verify_code) ? $verify_code : 'N/A';
|
||
$code_msg = isset($code_messages[$verify_code]) ? $code_messages[$verify_code] : ('未知错误 (' . $vc_display . ')');
|
||
$error_msg = '阿里云V3验证失败: ' . $code_msg;
|
||
}
|
||
} else {
|
||
$amsg = isset($body['Message']) ? $body['Message'] : '';
|
||
$error_msg = '阿里云V3验证失败: ' . (!empty($amsg) ? $amsg : '未知错误');
|
||
}
|
||
break;
|
||
|
||
default:
|
||
return true;
|
||
}
|
||
|
||
if ($result) {
|
||
return true;
|
||
}
|
||
|
||
return __('人机验证失败', 'aksrt');
|
||
}
|
||
endif;
|
||
|
||
/**
|
||
* 阿里云 OpenAPI 签名函数 (POP Signature v1.0 / HMAC-SHA1)
|
||
* 同时适用于 CAPTCHA 2.0 V3 (VerifyIntelligentCaptcha) 和旧版 AFS
|
||
*/
|
||
if (!function_exists('aksrt_aliyun_afs_sign')) :
|
||
function aksrt_aliyun_afs_sign($params, $secret) {
|
||
ksort($params);
|
||
$canonicalized = '';
|
||
foreach ($params as $k => $v) {
|
||
$canonicalized .= '&' . rawurlencode($k) . '=' . rawurlencode($v);
|
||
}
|
||
$string_to_sign = 'POST&%2F&' . rawurlencode(substr($canonicalized, 1));
|
||
$signature = base64_encode(hash_hmac('sha1', $string_to_sign, $secret . '&', true));
|
||
return $signature;
|
||
}
|
||
|
||
|
||
|
||
function aksrt_render_captcha($scene = 'comment') {
|
||
$cfg = aksrt_get_captcha_config();
|
||
if (empty($cfg['enabled']) || empty($cfg['active_provider'])) return;
|
||
if (!aksrt_is_captcha_enabled_for($scene)) return;
|
||
$provider = $cfg['active_provider'];
|
||
$pcfg = isset($cfg[$provider]) && is_array($cfg[$provider]) ? $cfg[$provider] : array();
|
||
if (!aksrt_captcha_provider_is_configured($provider, $pcfg)) {
|
||
if (current_user_can('manage_options')) {
|
||
echo '<div class="aksrt-captcha-misconfig" style="font-size:12px;color:#d03050;margin:8px 0;">';
|
||
echo esc_html(sprintf(
|
||
/* translators: %s: captcha provider key */
|
||
__('人机验证服务商「%s」已启用但凭据未配齐,前台已跳过渲染。请到「功能&权限 → 登录权限」补全配置。', 'aksrt'),
|
||
$provider
|
||
));
|
||
echo '</div>';
|
||
}
|
||
return;
|
||
}
|
||
|
||
$uid = 'aksrt-captcha-' . $scene . '-' . wp_unique_id();
|
||
$vid = 'captcha_verified_' . $scene . '_' . wp_unique_id();
|
||
|
||
// 构建 JS 配置
|
||
$js_config = array(
|
||
'provider' => $provider,
|
||
'scene' => $scene,
|
||
'uid' => $uid,
|
||
'vid' => $vid,
|
||
'tc' => array('app_id' => isset($pcfg['app_id']) ? (string) $pcfg['app_id'] : ''),
|
||
'cf' => array(
|
||
'site_key' => isset($pcfg['site_key']) ? (string) $pcfg['site_key'] : '',
|
||
'theme' => isset($pcfg['theme']) ? (string) $pcfg['theme'] : 'auto',
|
||
'size' => isset($pcfg['size']) ? (string) $pcfg['size'] : 'normal'
|
||
),
|
||
'gt' => array(
|
||
'captcha_id' => isset($pcfg['captcha_id']) ? trim((string) $pcfg['captcha_id']) : '',
|
||
'product' => isset($pcfg['product']) && $pcfg['product'] !== '' ? (string) $pcfg['product'] : 'bind',
|
||
),
|
||
'al' => array(
|
||
'region' => isset($pcfg['region']) ? (string) $pcfg['region'] : 'cn',
|
||
'prefix' => isset($pcfg['prefix']) ? (string) $pcfg['prefix'] : '',
|
||
'scene_id' => isset($pcfg['scene_id']) ? (string) $pcfg['scene_id'] : ''
|
||
),
|
||
);
|
||
?>
|
||
<div class="aksrt-captcha-wrap" data-provider="<?php echo esc_attr($provider); ?>" data-scene="<?php echo esc_attr($scene); ?>">
|
||
<div id="<?php echo esc_attr($uid); ?>" class="aksrt-captcha-box" style="display:none"></div>
|
||
<input type="hidden" name="captcha_verified" id="<?php echo esc_attr($vid); ?>" value="0">
|
||
</div>
|
||
<script>(function(){
|
||
var C=<?php echo wp_json_encode($js_config, JSON_UNESCAPED_UNICODE); ?>;
|
||
var root=window.AKSRTCaptchaState=window.AKSRTCaptchaState||{loaders:{}, scenes:{}};
|
||
|
||
function loadScriptOnce(key, src, readyCheck){
|
||
if(typeof readyCheck==='function' && readyCheck()){
|
||
return Promise.resolve();
|
||
}
|
||
if(root.loaders[key]){
|
||
return root.loaders[key];
|
||
}
|
||
root.loaders[key]=new Promise(function(resolve, reject){
|
||
var existing=document.querySelector('script[data-aksrt-sdk="'+key+'"]');
|
||
if(existing){
|
||
if(typeof readyCheck==='function' && readyCheck()){
|
||
resolve();
|
||
return;
|
||
}
|
||
existing.addEventListener('load', function(){ resolve(); }, {once:true});
|
||
existing.addEventListener('error', function(){ reject(new Error(key+' load failed')); }, {once:true});
|
||
return;
|
||
}
|
||
var script=document.createElement('script');
|
||
script.src=src;
|
||
script.async=true;
|
||
script.setAttribute('data-aksrt-sdk', key);
|
||
script.onload=function(){ resolve(); };
|
||
script.onerror=function(){ reject(new Error(key+' load failed')); };
|
||
document.head.appendChild(script);
|
||
});
|
||
return root.loaders[key];
|
||
}
|
||
|
||
function init(){
|
||
var box=document.getElementById(C.uid);
|
||
var h =document.getElementById(C.vid);
|
||
if(!box||!h) return;
|
||
|
||
// ---- 查找表单(含重试) ----
|
||
var form=null, tries=0, maxTries=10;
|
||
function findForm(){
|
||
form=box.closest('form');
|
||
if(!form&&C.scene==='comment') form=document.getElementById('commentform');
|
||
if(!form&&C.scene==='login') form=document.getElementById('aksrt-login-form')||document.querySelector('.auth-form');
|
||
return form;
|
||
}
|
||
(function retryFind(){
|
||
findForm();
|
||
if(!form && ++tries < maxTries){ setTimeout(retryFind, 150); }
|
||
else {
|
||
if(form){ bindSubmit(); }
|
||
setTimeout(function(){ loadProvider(); }, 0);
|
||
}
|
||
})();
|
||
|
||
// ---- 提交拦截(capture 阶段确保最先触发) ----
|
||
var verified=false;
|
||
var pendingAction=null;
|
||
function bindSubmit(){
|
||
form.addEventListener('submit', function(e){
|
||
if(verified || h.value==='1') return;
|
||
e.preventDefault();
|
||
e.stopImmediatePropagation();
|
||
pendingAction=null;
|
||
triggerCaptcha();
|
||
}, true);
|
||
}
|
||
|
||
// ---- 隐藏域写入 ----
|
||
function setField(name, value){
|
||
var container = form || box;
|
||
var el=container.querySelector('input[name="'+name+'"]');
|
||
if(!el){ el=document.createElement('input'); el.type='hidden'; el.name=name; container.appendChild(el); }
|
||
el.value=value;
|
||
}
|
||
|
||
// ---- 提交表单 ----
|
||
function submitForm(){
|
||
verified=true; h.value='1';
|
||
// 重置:如果页面未跳转(如评论被拒/审核),下次提交需重新验证
|
||
setTimeout(function(){
|
||
verified=false;
|
||
h.value='0';
|
||
// 清理已消耗的token字段,避免复用
|
||
['cf_turnstile_response','tencent_ticket','tencent_randstr','tencent_sid',
|
||
'geetest_lot_number','geetest_captcha_output','geetest_pass_token','geetest_gen_time',
|
||
'aliyun_afs_token','aliyun_session_id'].forEach(function(n){
|
||
var f=form.querySelector('input[name="'+n+'"]');
|
||
if(f) f.remove();
|
||
});
|
||
}, 3000);
|
||
if(form.requestSubmit){ form.requestSubmit(); }
|
||
else{ HTMLFormElement.prototype.submit.call(form); }
|
||
}
|
||
|
||
// ---- 提示信息 ----
|
||
function resetVerificationState(){
|
||
verified=false;
|
||
h.value='0';
|
||
var container = form || box;
|
||
['cf_turnstile_response','tencent_ticket','tencent_randstr','tencent_sid',
|
||
'geetest_lot_number','geetest_captcha_output','geetest_pass_token','geetest_gen_time',
|
||
'aliyun_afs_token','aliyun_session_id','aliyun_captcha_verify_param'].forEach(function(n){
|
||
var f=container.querySelector('input[name="'+n+'"]');
|
||
if(f) f.remove();
|
||
});
|
||
if(C.scene==='download'){
|
||
var myContainer=box.closest('[data-aksrt-download-captcha]');
|
||
if(myContainer){
|
||
document.querySelectorAll('[data-aksrt-download-captcha]').forEach(function(target){
|
||
if(target===myContainer) return;
|
||
var tgtH=target.querySelector('input[name="captcha_verified"]');
|
||
if(tgtH) tgtH.value='0';
|
||
['cf_turnstile_response','tencent_ticket','tencent_randstr','tencent_sid',
|
||
'geetest_lot_number','geetest_captcha_output','geetest_pass_token','geetest_gen_time',
|
||
'aliyun_captcha_verify_param'].forEach(function(n){
|
||
var f=target.querySelector('input[name="'+n+'"]');
|
||
if(f) f.remove();
|
||
});
|
||
});
|
||
}
|
||
}
|
||
}
|
||
function submitNativeForm(){
|
||
if(!form) return;
|
||
if(form.requestSubmit){ form.requestSubmit(); }
|
||
else{ HTMLFormElement.prototype.submit.call(form); }
|
||
}
|
||
submitForm=function(){
|
||
verified=true;
|
||
h.value='1';
|
||
if(C.scene==='download'){
|
||
var myContainer=box.closest('[data-aksrt-download-captcha]');
|
||
if(myContainer){
|
||
var tokenFields=['cf_turnstile_response','tencent_ticket','tencent_randstr','tencent_sid',
|
||
'geetest_lot_number','geetest_captcha_output','geetest_pass_token','geetest_gen_time',
|
||
'aliyun_captcha_verify_param'];
|
||
document.querySelectorAll('[data-aksrt-download-captcha]').forEach(function(target){
|
||
if(target===myContainer) return;
|
||
var tgtH=target.querySelector('input[name="captcha_verified"]');
|
||
if(tgtH) tgtH.value='1';
|
||
tokenFields.forEach(function(name){
|
||
var srcEl=myContainer.querySelector('input[name="'+name+'"]');
|
||
if(srcEl&&srcEl.value!==''){
|
||
var tgtEl=target.querySelector('input[name="'+name+'"]');
|
||
if(!tgtEl){tgtEl=document.createElement('input');tgtEl.type='hidden';tgtEl.name=name;target.appendChild(tgtEl);}
|
||
tgtEl.value=srcEl.value;
|
||
}
|
||
});
|
||
});
|
||
}
|
||
}
|
||
if(typeof pendingAction==='function'){
|
||
var action=pendingAction;
|
||
pendingAction=null;
|
||
action();
|
||
return;
|
||
}
|
||
setTimeout(function(){
|
||
resetVerificationState();
|
||
}, 3000);
|
||
submitNativeForm();
|
||
};
|
||
root.scenes[C.scene]={
|
||
verify:function(callback){
|
||
var retry=0;
|
||
function run(){
|
||
if(verified || h.value==='1'){
|
||
if(typeof callback==='function') callback();
|
||
return;
|
||
}
|
||
if(typeof triggerCaptcha==='function'){
|
||
pendingAction=(typeof callback==='function')?callback:null;
|
||
triggerCaptcha();
|
||
return;
|
||
}
|
||
if(retry++ < 20){
|
||
setTimeout(run, 150);
|
||
return;
|
||
}
|
||
showTip('验证码组件加载中,请稍候再试', '#e67e22');
|
||
}
|
||
run();
|
||
},
|
||
reset:resetVerificationState
|
||
};
|
||
|
||
function showTip(msg, color){
|
||
var t=box.querySelector('.captcha-tip');
|
||
if(!t){ t=document.createElement('div'); t.className='captcha-tip'; t.style.cssText='font-size:12px;margin-top:4px;text-align:left;'; box.appendChild(t); }
|
||
t.style.color=color||'#e74c3c'; t.textContent=msg;
|
||
setTimeout(function(){ if(t.parentNode) t.remove(); }, 4000);
|
||
}
|
||
|
||
// ---- 加载中状态 ----
|
||
function showLoading(){ box.innerHTML='<div style="text-align:center;padding:20px;color:#9ca3af;">验证码加载中...</div>'; }
|
||
function clearLoading(){ box.innerHTML=''; }
|
||
|
||
// ---- 各供应商回调入口 ----
|
||
var triggerCaptcha; // 由 loadProvider 赋值
|
||
|
||
function loadProvider(){
|
||
switch(C.provider){
|
||
case 'tencent': loadTencent(); break;
|
||
case 'cloudflare': loadCloudflare(); break;
|
||
case 'geetest': loadGeetest(); break;
|
||
case 'aliyun': loadAliyun(); break;
|
||
}
|
||
}
|
||
|
||
// ============ 腾讯云 Captcha 2.0 ============
|
||
function loadTencent(){
|
||
showLoading();
|
||
var script=document.createElement('script');
|
||
// 腾讯云验证码 2.0 SDK 地址
|
||
script.src='https://turing.captcha.qcloud.com/TJCaptcha.js';
|
||
script.onload=function(){
|
||
clearLoading();
|
||
var capIns=null;
|
||
triggerCaptcha=function(){
|
||
if(!capIns){
|
||
capIns=new TencentCaptcha(C.tc.app_id, function(res){
|
||
// res.ret: 0=验证成功, 2=用户主动关闭
|
||
// res.errorCode: 错误码(存在时为容灾场景)
|
||
// res.ticket: 票据, res.randstr: 随机串, res.sid: 链路ID
|
||
|
||
if(res.ret===2){
|
||
// 用户主动关闭,不处理
|
||
return;
|
||
}
|
||
|
||
// 容灾票据检测 (trerror_ 前缀 = SDK异常)
|
||
if(res.ret===0 && res.ticket){
|
||
if(res.ticket.indexOf('trerror_')===0){
|
||
showTip('验证码服务异常,请刷新页面重试','#e67e22');
|
||
return;
|
||
}
|
||
setField('tencent_ticket', res.ticket);
|
||
setField('tencent_randstr', res.randstr);
|
||
setField('tencent_sid', res.sid||'');
|
||
submitForm();
|
||
}
|
||
|
||
// errorCode 细分处理
|
||
if(res.errorCode){
|
||
var errorMap={
|
||
1001:'验证码组件加载失败',
|
||
1002:'验证超时',
|
||
1006:'验证码配置拉取失败',
|
||
1008:'验证码页面加载异常',
|
||
1012:'验证刷新次数过多',
|
||
1013:'验证网络异常'
|
||
};
|
||
showTip(errorMap[res.errorCode]||'验证码异常(errorCode:'+res.errorCode+')','#e74c3c');
|
||
}
|
||
}, {userLanguage:'zh-cn'});
|
||
}
|
||
capIns.show();
|
||
};
|
||
};
|
||
script.onerror=function(){
|
||
clearLoading();
|
||
showTip('验证码组件加载失败,请刷新重试','#e74c3c');
|
||
};
|
||
document.head.appendChild(script);
|
||
}
|
||
|
||
// ============ Cloudflare Turnstile (Modal-based) ============
|
||
function loadCloudflare(){
|
||
var cfWidgetId=null;
|
||
var cfVerifying=false;
|
||
var cfModal=null;
|
||
|
||
// Pre-load Turnstile API (defer rendering to when user clicks submit)
|
||
if(typeof turnstile==='undefined'){
|
||
window['onTurnstileLoad_'+C.uid]=function(){ /* api ready, widget rendered on demand */ };
|
||
var s=document.createElement('script');
|
||
s.src='https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad_'+C.uid+'&render=explicit';
|
||
document.head.appendChild(s);
|
||
}
|
||
|
||
// Render Turnstile widget inside the modal
|
||
function renderWidget(){
|
||
// Clean up any previous widget before rendering new one
|
||
if(cfWidgetId){
|
||
try { turnstile.remove(cfWidgetId); } catch(e) {}
|
||
cfWidgetId=null;
|
||
}
|
||
var widgetId='modalWidget_'+C.uid;
|
||
var widgetDiv=document.getElementById(widgetId);
|
||
if(!widgetDiv) return;
|
||
if(typeof turnstile==='undefined'){ setTimeout(renderWidget, 200); return; }
|
||
|
||
cfWidgetId=turnstile.render('#'+widgetId, {
|
||
sitekey: C.cf.site_key,
|
||
theme: C.cf.theme,
|
||
size: C.cf.size,
|
||
action: 'comment-verify',
|
||
callback: function(token){
|
||
if(!cfVerifying) return;
|
||
cfVerifying=false;
|
||
setField('cf_turnstile_response', token);
|
||
closeModal();
|
||
submitForm();
|
||
},
|
||
'error-callback': function(){
|
||
showTip('验证失败,请重试','#e74c3c');
|
||
cfWidgetId=null;
|
||
setTimeout(function(){ renderWidget(); }, 500);
|
||
},
|
||
'expired-callback': function(){
|
||
showTip('验证已过期,请重新验证','#e67e22');
|
||
cfWidgetId=null;
|
||
setTimeout(function(){ renderWidget(); }, 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
// Create modal overlay
|
||
function createModal(){
|
||
var widgetId='modalWidget_'+C.uid;
|
||
cfModal=document.createElement('div');
|
||
cfModal.className='aksrt-captcha-modal-overlay';
|
||
cfModal.innerHTML=
|
||
'<div class="aksrt-captcha-modal-backdrop"></div>'+
|
||
'<div class="aksrt-captcha-modal-box">'+
|
||
'<div class="aksrt-captcha-modal-header">'+
|
||
'<span class="aksrt-captcha-modal-title">人机验证</span>'+
|
||
'<button type="button" class="aksrt-captcha-modal-close">×</button>'+
|
||
'</div>'+
|
||
'<div class="aksrt-captcha-modal-body">'+
|
||
'<p class="aksrt-captcha-modal-hint">请完成验证后继续发表评论</p>'+
|
||
'<div id="'+widgetId+'" class="aksrt-captcha-modal-widget"></div>'+
|
||
'</div>'+
|
||
'</div>';
|
||
document.body.appendChild(cfModal);
|
||
|
||
var backdrop=cfModal.querySelector('.aksrt-captcha-modal-backdrop');
|
||
var closeBtn=cfModal.querySelector('.aksrt-captcha-modal-close');
|
||
var cancelFn=function(){
|
||
cfVerifying=false;
|
||
closeModal();
|
||
showTip('验证已取消','#e67e22');
|
||
};
|
||
backdrop.addEventListener('click', cancelFn);
|
||
closeBtn.addEventListener('click', cancelFn);
|
||
|
||
renderWidget();
|
||
}
|
||
|
||
function closeModal(){
|
||
if(cfWidgetId){
|
||
try { turnstile.remove(cfWidgetId); } catch(e) {}
|
||
cfWidgetId=null;
|
||
}
|
||
if(cfModal){
|
||
if(cfModal.parentNode) cfModal.parentNode.removeChild(cfModal);
|
||
cfModal=null;
|
||
}
|
||
}
|
||
|
||
triggerCaptcha=function(){
|
||
if(cfVerifying){
|
||
showTip('验证进行中,请稍候...','#e67e22');
|
||
return;
|
||
}
|
||
cfVerifying=true;
|
||
createModal();
|
||
};
|
||
}
|
||
|
||
// ============ 极验 GeeTest v4 ============
|
||
function loadGeetest(){
|
||
showLoading();
|
||
var captchaId=(C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : '';
|
||
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
|
||
if(!captchaId){
|
||
clearLoading();
|
||
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 登录权限」填写并保存','#e74c3c');
|
||
triggerCaptcha=function(){ showTip('极验 Captcha ID 未配置','#e74c3c'); };
|
||
return;
|
||
}
|
||
function render(){
|
||
clearLoading();
|
||
initGeetest4({captchaId:captchaId, product:product, hideSuccess:true}, function(capIns){
|
||
window['__gt_'+C.uid]=capIns;
|
||
function onSuccess(){
|
||
var r=capIns.getValidate();
|
||
if(r){
|
||
setField('geetest_lot_number', r.lot_number);
|
||
setField('geetest_captcha_output', r.captcha_output);
|
||
setField('geetest_pass_token', r.pass_token);
|
||
setField('geetest_gen_time', r.gen_time);
|
||
submitForm();
|
||
}
|
||
}
|
||
if(product==='bind' || product==='popup'){
|
||
capIns.onReady(function(){}).onSuccess(onSuccess).onClose(function(){});
|
||
}else{
|
||
capIns.appendTo('#'+C.uid).onSuccess(onSuccess).onClose(function(){});
|
||
}
|
||
});
|
||
triggerCaptcha=function(){
|
||
var ci=window['__gt_'+C.uid];
|
||
if(ci){
|
||
if(product==='bind' || product==='popup') ci.showCaptcha();
|
||
else showTip('请先完成人机验证','#e67e22');
|
||
}else{ showTip('验证码加载中,请稍候...','#e67e22'); }
|
||
};
|
||
}
|
||
if(typeof initGeetest4==='undefined'){
|
||
var s=document.createElement('script');
|
||
s.src='https://static.geetest.com/v4/gt4.js';
|
||
s.onload=render;
|
||
s.onerror=function(){ clearLoading(); showTip('极验组件加载失败','#e74c3c'); };
|
||
document.head.appendChild(s);
|
||
}else{ render(); }
|
||
}
|
||
|
||
// ============ 阿里云 CAPTCHA 2.0 V3 (AliyunCaptcha) ============
|
||
function loadAliyun(){
|
||
showLoading();
|
||
|
||
// 设置全局配置(必须在引入 JS 之前)
|
||
window.AliyunCaptchaConfig = {
|
||
region: C.al.region || 'cn',
|
||
prefix: C.al.prefix,
|
||
};
|
||
|
||
function render(){
|
||
clearLoading();
|
||
var captchaIns = null;
|
||
|
||
// 查找触发按钮
|
||
var btn = box.closest('form') ? box.closest('form').querySelector('button[type="submit"], input[type="submit"]') : null;
|
||
|
||
window.initAliyunCaptcha({
|
||
SceneId: C.al.scene_id,
|
||
mode: 'popup',
|
||
element: '#' + C.uid,
|
||
button: btn ? '#' + (btn.id || (function(){ btn.id = 'aksrt-captcha-btn-' + C.scene; return btn.id; })()) : null,
|
||
success: function(captchaVerifyParam){
|
||
setField('aliyun_captcha_verify_param', captchaVerifyParam);
|
||
submitForm();
|
||
},
|
||
fail: function(result){
|
||
showTip('验证失败: ' + (result || '请重试'), '#e74c3c');
|
||
captchaIns = null;
|
||
},
|
||
getInstance: function(instance){
|
||
captchaIns = instance;
|
||
},
|
||
slideStyle: {
|
||
width: 360,
|
||
height: 40,
|
||
},
|
||
language: 'cn',
|
||
});
|
||
|
||
triggerCaptcha = function(){
|
||
showTip('请先完成人机验证', '#e67e22');
|
||
if (captchaIns && captchaIns.show) {
|
||
captchaIns.show();
|
||
}
|
||
};
|
||
}
|
||
|
||
if (typeof window.initAliyunCaptcha === 'undefined') {
|
||
var s = document.createElement('script');
|
||
s.src = 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js';
|
||
s.onload = render;
|
||
s.onerror = function(){
|
||
clearLoading();
|
||
showTip('验证码组件加载失败,请刷新重试', '#e74c3c');
|
||
};
|
||
document.head.appendChild(s);
|
||
} else {
|
||
render();
|
||
}
|
||
}
|
||
loadTencent=function(){
|
||
showLoading();
|
||
loadScriptOnce('tencent', 'https://turing.captcha.qcloud.com/TJCaptcha.js', function(){
|
||
return typeof TencentCaptcha!=='undefined';
|
||
}).then(function(){
|
||
clearLoading();
|
||
var capIns=null;
|
||
triggerCaptcha=function(){
|
||
if(!capIns){
|
||
capIns=new TencentCaptcha(C.tc.app_id, function(res){
|
||
if(res.ret===2){
|
||
return;
|
||
}
|
||
if(res.ret===0 && res.ticket){
|
||
if(res.ticket.indexOf('trerror_')===0){
|
||
showTip('验证码服务异常,请刷新页面重试', '#e67e22');
|
||
return;
|
||
}
|
||
setField('tencent_ticket', res.ticket);
|
||
setField('tencent_randstr', res.randstr);
|
||
setField('tencent_sid', res.sid||'');
|
||
submitForm();
|
||
return;
|
||
}
|
||
if(res.errorCode){
|
||
var errorMap={
|
||
1001:'验证码组件加载失败',
|
||
1002:'验证码超时',
|
||
1006:'验证码配置拉取失败',
|
||
1008:'验证码页面加载异常',
|
||
1012:'验证码刷新次数过多',
|
||
1013:'验证码网络异常'
|
||
};
|
||
showTip(errorMap[res.errorCode]||('验证码异常(errorCode:'+res.errorCode+')'), '#e74c3c');
|
||
}
|
||
}, {userLanguage:'zh-cn'});
|
||
}
|
||
capIns.show();
|
||
};
|
||
}).catch(function(){
|
||
clearLoading();
|
||
showTip('验证码组件加载失败,请刷新重试', '#e74c3c');
|
||
});
|
||
};
|
||
|
||
loadCloudflare=function(){
|
||
var cfWidgetId=null;
|
||
var cfVerifying=false;
|
||
var cfModal=null;
|
||
|
||
if(typeof turnstile==='undefined'){
|
||
window['onTurnstileLoad_'+C.uid]=function(){};
|
||
loadScriptOnce(
|
||
'cloudflare',
|
||
'https://challenges.cloudflare.com/turnstile/v0/api.js?onload=onTurnstileLoad_'+C.uid+'&render=explicit',
|
||
function(){ return typeof turnstile!=='undefined'; }
|
||
);
|
||
}
|
||
|
||
function renderWidget(){
|
||
if(cfWidgetId){
|
||
try { turnstile.remove(cfWidgetId); } catch(e) {}
|
||
cfWidgetId=null;
|
||
}
|
||
if(typeof turnstile==='undefined'){
|
||
setTimeout(renderWidget, 200);
|
||
return;
|
||
}
|
||
cfWidgetId=turnstile.render('#modalWidget_'+C.uid, {
|
||
sitekey:C.cf.site_key,
|
||
theme:C.cf.theme,
|
||
size:C.cf.size,
|
||
action:'comment-verify',
|
||
callback:function(token){
|
||
if(!cfVerifying) return;
|
||
cfVerifying=false;
|
||
setField('cf_turnstile_response', token);
|
||
closeModal();
|
||
submitForm();
|
||
},
|
||
'error-callback':function(){
|
||
showTip('验证码失败,请重试', '#e74c3c');
|
||
cfWidgetId=null;
|
||
setTimeout(renderWidget, 500);
|
||
},
|
||
'expired-callback':function(){
|
||
showTip('验证码已过期,请重新验证', '#e67e22');
|
||
cfWidgetId=null;
|
||
setTimeout(renderWidget, 500);
|
||
}
|
||
});
|
||
}
|
||
|
||
function createModal(){
|
||
cfModal=document.createElement('div');
|
||
cfModal.className='aksrt-captcha-modal-overlay';
|
||
cfModal.innerHTML=
|
||
'<div class="aksrt-captcha-modal-backdrop"></div>'+
|
||
'<div class="aksrt-captcha-modal-box">'+
|
||
'<div class="aksrt-captcha-modal-header">'+
|
||
'<span class="aksrt-captcha-modal-title">人机验证</span>'+
|
||
'<button type="button" class="aksrt-captcha-modal-close">×</button>'+
|
||
'</div>'+
|
||
'<div class="aksrt-captcha-modal-body">'+
|
||
'<p class="aksrt-captcha-modal-hint">请完成验证后继续操作</p>'+
|
||
'<div id="modalWidget_'+C.uid+'" class="aksrt-captcha-modal-widget"></div>'+
|
||
'</div>'+
|
||
'</div>';
|
||
document.body.appendChild(cfModal);
|
||
var cancelFn=function(){
|
||
cfVerifying=false;
|
||
closeModal();
|
||
showTip('验证码已取消', '#e67e22');
|
||
};
|
||
cfModal.querySelector('.aksrt-captcha-modal-backdrop').addEventListener('click', cancelFn);
|
||
cfModal.querySelector('.aksrt-captcha-modal-close').addEventListener('click', cancelFn);
|
||
renderWidget();
|
||
}
|
||
|
||
function closeModal(){
|
||
if(cfWidgetId){
|
||
try { turnstile.remove(cfWidgetId); } catch(e) {}
|
||
cfWidgetId=null;
|
||
}
|
||
if(cfModal && cfModal.parentNode){
|
||
cfModal.parentNode.removeChild(cfModal);
|
||
}
|
||
cfModal=null;
|
||
}
|
||
|
||
triggerCaptcha=function(){
|
||
if(cfVerifying){
|
||
showTip('验证码进行中,请稍候', '#e67e22');
|
||
return;
|
||
}
|
||
cfVerifying=true;
|
||
createModal();
|
||
};
|
||
};
|
||
|
||
loadGeetest=function(){
|
||
showLoading();
|
||
var captchaId=(C.gt && C.gt.captcha_id) ? String(C.gt.captcha_id).trim() : '';
|
||
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
|
||
if(!captchaId){
|
||
clearLoading();
|
||
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 登录权限」填写并保存', '#e74c3c');
|
||
triggerCaptcha=function(){
|
||
showTip('极验 Captcha ID 未配置', '#e74c3c');
|
||
};
|
||
return;
|
||
}
|
||
function render(){
|
||
clearLoading();
|
||
initGeetest4({captchaId:captchaId, product:product, hideSuccess:true}, function(capIns){
|
||
window['__gt_'+C.uid]=capIns;
|
||
function onSuccess(){
|
||
var result=capIns.getValidate();
|
||
if(!result) return;
|
||
setField('geetest_lot_number', result.lot_number);
|
||
setField('geetest_captcha_output', result.captcha_output);
|
||
setField('geetest_pass_token', result.pass_token);
|
||
setField('geetest_gen_time', result.gen_time);
|
||
submitForm();
|
||
}
|
||
if(product==='bind' || product==='popup'){
|
||
capIns.onReady(function(){}).onSuccess(onSuccess).onClose(function(){});
|
||
}else{
|
||
capIns.appendTo('#'+C.uid).onSuccess(onSuccess).onClose(function(){});
|
||
}
|
||
});
|
||
triggerCaptcha=function(){
|
||
var instance=window['__gt_'+C.uid];
|
||
if(!instance){
|
||
showTip('验证码加载中,请稍候再试', '#e67e22');
|
||
return;
|
||
}
|
||
if(product==='bind' || product==='popup'){
|
||
instance.showCaptcha();
|
||
return;
|
||
}
|
||
showTip('请先完成人机验证', '#e67e22');
|
||
};
|
||
}
|
||
if(typeof initGeetest4==='undefined'){
|
||
loadScriptOnce('geetest', 'https://static.geetest.com/v4/gt4.js', function(){
|
||
return typeof initGeetest4!=='undefined';
|
||
}).then(render).catch(function(){
|
||
clearLoading();
|
||
showTip('极验组件加载失败', '#e74c3c');
|
||
});
|
||
return;
|
||
}
|
||
render();
|
||
};
|
||
|
||
loadAliyun=function(){
|
||
showLoading();
|
||
window.AliyunCaptchaConfig={
|
||
region:C.al.region||'cn',
|
||
prefix:C.al.prefix
|
||
};
|
||
|
||
function render(){
|
||
clearLoading();
|
||
var captchaIns=null;
|
||
var btn=box.closest('form') ? box.closest('form').querySelector('button[type="submit"], input[type="submit"]') : null;
|
||
window.initAliyunCaptcha({
|
||
SceneId:C.al.scene_id,
|
||
mode:'popup',
|
||
element:'#'+C.uid,
|
||
button:btn ? '#' + (btn.id || (function(){ btn.id='aksrt-captcha-btn-'+C.scene; return btn.id; })()) : null,
|
||
success:function(captchaVerifyParam){
|
||
setField('aliyun_captcha_verify_param', captchaVerifyParam);
|
||
submitForm();
|
||
},
|
||
fail:function(result){
|
||
showTip('验证码失败: ' + (result || '请重试'), '#e74c3c');
|
||
captchaIns=null;
|
||
},
|
||
getInstance:function(instance){
|
||
captchaIns=instance;
|
||
},
|
||
slideStyle:{width:360,height:40},
|
||
language:'cn'
|
||
});
|
||
|
||
triggerCaptcha=function(){
|
||
if(captchaIns && captchaIns.show){
|
||
captchaIns.show();
|
||
return;
|
||
}
|
||
showTip('验证码组件加载中,请稍候再试', '#e67e22');
|
||
};
|
||
}
|
||
|
||
if(typeof window.initAliyunCaptcha==='undefined'){
|
||
loadScriptOnce('aliyun', 'https://o.alicdn.com/captcha-frontend/aliyunCaptcha/AliyunCaptcha.js', function(){
|
||
return typeof window.initAliyunCaptcha!=='undefined';
|
||
}).then(render).catch(function(){
|
||
clearLoading();
|
||
showTip('验证码组件加载失败,请刷新重试', '#e74c3c');
|
||
});
|
||
return;
|
||
}
|
||
render();
|
||
};
|
||
|
||
}
|
||
|
||
if(document.readyState==='loading'){ document.addEventListener('DOMContentLoaded', init); }
|
||
else{ setTimeout(init, 50); }
|
||
})();</script>
|
||
<?php echo '<link rel="stylesheet" href="' . esc_url(AKSRT_URI . '/assets/css/shared/captcha.css') . '">'; ?>
|
||
<?php
|
||
}
|
||
endif;
|