fix(captcha): wire scene switches and rename menu to 人机验证
- Admin: 登录权限 → 人机验证; 停用场景 → 启用场景 (opt-in) - Normalize enabled_scenarios with legacy disabled_scenarios BC - Sign-in: never soft-bypass captcha; cancel unlocks button - Captcha JS: cancel path finishes pending callback as false
This commit is contained in:
1 parent
808d46664f
commit
bc96edf384
10 files changed
+143
-54
No files matched your search
@@ -42,10 +42,10 @@ export const permissionsSection = {
|
|||||||
schemaPage({
|
schemaPage({
|
||||||
key: 'permissions-login',
|
key: 'permissions-login',
|
||||||
name: 'PermissionsLogin',
|
name: 'PermissionsLogin',
|
||||||
label: '登录权限',
|
label: '人机验证',
|
||||||
route: '/permissions/login',
|
route: '/permissions/login',
|
||||||
title: '登录权限',
|
title: '人机验证',
|
||||||
desc: '登录验证、人机验证、注册限制配置。',
|
desc: '云验证码服务商、场景开关与密钥配置。',
|
||||||
canonicalDomain: 'permissions',
|
canonicalDomain: 'permissions',
|
||||||
section: 'login',
|
section: 'login',
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -115,12 +115,16 @@ async function load() {
|
|||||||
const comment = commentData?.data ?? commentData ?? {}
|
const comment = commentData?.data ?? commentData ?? {}
|
||||||
const captcha = captchaRes?.data ?? captchaRes ?? {}
|
const captcha = captchaRes?.data ?? captchaRes ?? {}
|
||||||
const moderation = moderationRes?.data ?? moderationRes ?? {}
|
const moderation = moderationRes?.data ?? moderationRes ?? {}
|
||||||
|
const enabledScenarios = Array.isArray(captcha.enabled_scenarios) ? captcha.enabled_scenarios : null
|
||||||
const disabledScenarios = Array.isArray(captcha.disabled_scenarios) ? captcha.disabled_scenarios : []
|
const disabledScenarios = Array.isArray(captcha.disabled_scenarios) ? captcha.disabled_scenarios : []
|
||||||
|
const commentSceneOn = enabledScenarios
|
||||||
|
? enabledScenarios.includes('comment')
|
||||||
|
: !disabledScenarios.includes('comment')
|
||||||
|
|
||||||
form.comment_require_login = !!comment.comment_require_login
|
form.comment_require_login = !!comment.comment_require_login
|
||||||
form.comment_allow_reply = comment.comment_allow_reply !== false
|
form.comment_allow_reply = comment.comment_allow_reply !== false
|
||||||
|
|
||||||
status.commentCaptchaEnabled = !!captcha.enabled && !!captcha.active_provider && !disabledScenarios.includes('comment')
|
status.commentCaptchaEnabled = !!captcha.enabled && !!captcha.active_provider && commentSceneOn
|
||||||
status.moderationEnabled = !!moderation.enabled
|
status.moderationEnabled = !!moderation.enabled
|
||||||
status.rateLimitEnabled = !!moderation.rate_limit_enabled
|
status.rateLimitEnabled = !!moderation.rate_limit_enabled
|
||||||
status.keywordRuleCount = splitKeywords(moderation.blocked_keywords).length
|
status.keywordRuleCount = splitKeywords(moderation.blocked_keywords).length
|
||||||
|
|||||||
@@ -54,7 +54,7 @@
|
|||||||
<n-form-item label="下载启用验证码">
|
<n-form-item label="下载启用验证码">
|
||||||
<n-switch v-model:value="form.download_captcha" />
|
<n-switch v-model:value="form.download_captcha" />
|
||||||
<template #feedback>
|
<template #feedback>
|
||||||
<span class="aksrt-hint-text">具体验证码服务商与场景白名单仍在“登录权限”页维护。</span>
|
<span class="aksrt-hint-text">具体验证码服务商与场景白名单仍在「人机验证」页维护。</span>
|
||||||
</template>
|
</template>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-form>
|
</n-form>
|
||||||
|
|||||||
@@ -227,9 +227,16 @@ async function load() {
|
|||||||
const captchaData = extractData(captchaRes)
|
const captchaData = extractData(captchaRes)
|
||||||
const dailySignin = creditData.daily_signin || {}
|
const dailySignin = creditData.daily_signin || {}
|
||||||
const inviteReward = creditData.invite_reward || {}
|
const inviteReward = creditData.invite_reward || {}
|
||||||
|
const enabledScenarios = Array.isArray(captchaData.enabled_scenarios)
|
||||||
|
? captchaData.enabled_scenarios
|
||||||
|
: null
|
||||||
const disabledScenarios = Array.isArray(captchaData.disabled_scenarios)
|
const disabledScenarios = Array.isArray(captchaData.disabled_scenarios)
|
||||||
? captchaData.disabled_scenarios
|
? captchaData.disabled_scenarios
|
||||||
: []
|
: []
|
||||||
|
const sceneOn = (key) => {
|
||||||
|
if (enabledScenarios) return enabledScenarios.includes(key)
|
||||||
|
return !disabledScenarios.includes(key)
|
||||||
|
}
|
||||||
|
|
||||||
creditSnapshot.value = creditData
|
creditSnapshot.value = creditData
|
||||||
authSnapshot.value = authData
|
authSnapshot.value = authData
|
||||||
@@ -249,8 +256,8 @@ async function load() {
|
|||||||
form.invite_register_field = authData.invite_register_field || 'optional'
|
form.invite_register_field = authData.invite_register_field || 'optional'
|
||||||
form.invite_reject_invalid = authData.invite_reject_invalid !== false
|
form.invite_reject_invalid = authData.invite_reject_invalid !== false
|
||||||
|
|
||||||
captchaSummary.register = !!captchaData.enabled && !!captchaData.active_provider && !disabledScenarios.includes('register')
|
captchaSummary.register = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('register')
|
||||||
captchaSummary.signin = !!captchaData.enabled && !!captchaData.active_provider && !disabledScenarios.includes('signin')
|
captchaSummary.signin = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('signin')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
message.error(error.message || '加载签到奖励配置失败')
|
message.error(error.message || '加载签到奖励配置失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ export const permissionsSchemas = {
|
|||||||
*/
|
*/
|
||||||
login: {
|
login: {
|
||||||
key: 'permissions.login',
|
key: 'permissions.login',
|
||||||
title: '登录权限',
|
title: '人机验证',
|
||||||
icon: 'fas fa-shield-alt',
|
icon: 'fas fa-shield-alt',
|
||||||
description: '管理登录、注册、评论等场景的人机验证与访问限制。',
|
description: '配置云验证码服务商,并选择需要强制人机验证的前端场景。',
|
||||||
defaults: {
|
defaults: {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
active_provider: '',
|
active_provider: '',
|
||||||
mode: 'always',
|
mode: 'always',
|
||||||
|
enabled_scenarios: [
|
||||||
|
'comment', 'login', 'register', 'bind', 'bind_phone',
|
||||||
|
'lostpassword', 'change_password', 'change_email', 'verify_email', 'download', 'signin',
|
||||||
|
],
|
||||||
disabled_scenarios: [],
|
disabled_scenarios: [],
|
||||||
tencent: {},
|
tencent: {},
|
||||||
aliyun: {},
|
aliyun: {},
|
||||||
@@ -28,7 +32,7 @@ export const permissionsSchemas = {
|
|||||||
title: '基础设置',
|
title: '基础设置',
|
||||||
icon: 'fas fa-cog',
|
icon: 'fas fa-cog',
|
||||||
fields: [
|
fields: [
|
||||||
{ key: 'enabled', label: '启用人机验证', type: 'switch', help: '开启后将在指定场景要求用户完成人机验证' },
|
{ key: 'enabled', label: '启用人机验证', type: 'switch', help: '总开关:关闭后所有场景均不验证' },
|
||||||
{
|
{
|
||||||
key: 'active_provider',
|
key: 'active_provider',
|
||||||
label: '当前服务商',
|
label: '当前服务商',
|
||||||
@@ -54,10 +58,10 @@ export const permissionsSchemas = {
|
|||||||
dependsOn: { key: 'enabled', value: true },
|
dependsOn: { key: 'enabled', value: true },
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'disabled_scenarios',
|
key: 'enabled_scenarios',
|
||||||
label: '停用场景',
|
label: '启用场景',
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
help: '勾选后表示该场景不启用人机验证;未勾选的场景会启用。',
|
help: '勾选 = 该场景需要人机验证。取消勾选则该场景放行。',
|
||||||
dependsOn: { key: 'enabled', value: true },
|
dependsOn: { key: 'enabled', value: true },
|
||||||
options: [
|
options: [
|
||||||
{ label: '发表评论', value: 'comment' },
|
{ label: '发表评论', value: 'comment' },
|
||||||
@@ -297,7 +301,7 @@ export const permissionsSchemas = {
|
|||||||
key: 'permissions.download',
|
key: 'permissions.download',
|
||||||
title: '下载权限',
|
title: '下载权限',
|
||||||
icon: 'fas fa-download',
|
icon: 'fas fa-download',
|
||||||
description: '下载门槛由 DownloadPermissions 页维护(cms_features);人机验证服务商在登录权限页。',
|
description: '下载门槛由 DownloadPermissions 页维护(cms_features);人机验证服务商在「人机验证」页。',
|
||||||
defaults: {},
|
defaults: {},
|
||||||
groups: [],
|
groups: [],
|
||||||
},
|
},
|
||||||
|
|||||||
+22
-14
@@ -114,14 +114,28 @@
|
|||||||
} catch (_) {}
|
} catch (_) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerCaptcha(callback) {
|
function triggerCaptcha(onSuccess, onFail) {
|
||||||
|
var mount = document.querySelector('[data-signin-captcha]');
|
||||||
var state = window.AKSRTCaptchaState;
|
var state = window.AKSRTCaptchaState;
|
||||||
var scene = state && state.scenes && state.scenes.signin;
|
var scene = state && state.scenes && state.scenes.signin;
|
||||||
|
|
||||||
if (scene && typeof scene.verify === 'function') {
|
if (scene && typeof scene.verify === 'function') {
|
||||||
scene.verify(callback);
|
scene.verify(function (ok) {
|
||||||
|
if (ok === false) {
|
||||||
|
if (typeof onFail === 'function') onFail('已取消验证');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onSuccess();
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback();
|
|
||||||
|
if (mount) {
|
||||||
|
if (typeof onFail === 'function') onFail('人机验证加载中,请稍候再试');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess();
|
||||||
}
|
}
|
||||||
|
|
||||||
btn.addEventListener('click', function () {
|
btn.addEventListener('click', function () {
|
||||||
@@ -129,19 +143,13 @@
|
|||||||
btn.innerHTML = '…';
|
btn.innerHTML = '…';
|
||||||
setTip('');
|
setTip('');
|
||||||
|
|
||||||
var captchaDone = false;
|
|
||||||
var timeoutId = setTimeout(function () {
|
|
||||||
if (!captchaDone) {
|
|
||||||
captchaDone = true;
|
|
||||||
doSignin();
|
|
||||||
}
|
|
||||||
}, 30000);
|
|
||||||
|
|
||||||
triggerCaptcha(function () {
|
triggerCaptcha(function () {
|
||||||
clearTimeout(timeoutId);
|
|
||||||
if (captchaDone) return;
|
|
||||||
captchaDone = true;
|
|
||||||
doSignin();
|
doSignin();
|
||||||
|
}, function (msg) {
|
||||||
|
resetCaptcha();
|
||||||
|
if (msg) setTip(msg);
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '签到';
|
||||||
});
|
});
|
||||||
|
|
||||||
function doSignin() {
|
function doSignin() {
|
||||||
|
|||||||
@@ -282,6 +282,16 @@ class AKSRT_Admin_Schema_Provider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Captcha: always surface normalized enabled_scenarios (legacy disabled list inverted).
|
||||||
|
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_get_captcha_config')) {
|
||||||
|
$normalized = aksrt_get_captcha_config();
|
||||||
|
foreach (['enabled', 'active_provider', 'enabled_scenarios', 'disabled_scenarios', 'tencent', 'aliyun', 'cloudflare', 'geetest', 'mode'] as $key) {
|
||||||
|
if (array_key_exists($key, $normalized)) {
|
||||||
|
$data[$key] = $normalized[$key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -302,6 +312,24 @@ class AKSRT_Admin_Schema_Provider {
|
|||||||
$stored[$key] = $value;
|
$stored[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Captcha: keep enabled_scenarios / disabled_scenarios mirrors in sync.
|
||||||
|
if ($option_key === 'aksrt_captcha_config' && function_exists('aksrt_captcha_scenes')) {
|
||||||
|
$all = array_keys(aksrt_captcha_scenes());
|
||||||
|
if (array_key_exists('enabled_scenarios', $stored)) {
|
||||||
|
$enabled = function_exists('aksrt_captcha_sanitize_scene_list')
|
||||||
|
? aksrt_captcha_sanitize_scene_list($stored['enabled_scenarios'])
|
||||||
|
: (array) $stored['enabled_scenarios'];
|
||||||
|
$stored['enabled_scenarios'] = $enabled;
|
||||||
|
$stored['disabled_scenarios'] = array_values(array_diff($all, $enabled));
|
||||||
|
} elseif (array_key_exists('disabled_scenarios', $stored)) {
|
||||||
|
$disabled = function_exists('aksrt_captcha_sanitize_scene_list')
|
||||||
|
? aksrt_captcha_sanitize_scene_list($stored['disabled_scenarios'])
|
||||||
|
: (array) $stored['disabled_scenarios'];
|
||||||
|
$stored['disabled_scenarios'] = $disabled;
|
||||||
|
$stored['enabled_scenarios'] = array_values(array_diff($all, $disabled));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$updated = update_option($option_key, $stored);
|
$updated = update_option($option_key, $stored);
|
||||||
AKSRT_Settings::flush_settings_cache();
|
AKSRT_Settings::flush_settings_cache();
|
||||||
|
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class AKSRT_Admin_Schema_Extensions_Security {
|
|||||||
['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
|
['key' => 'remove_more_wp_head', 'label' => '移除WP head多余代码', 'type' => 'switch', 'default' => true],
|
||||||
]],
|
]],
|
||||||
['key' => 'captcha-note', 'title' => '人机验证', 'fields' => [
|
['key' => 'captcha-note', 'title' => '人机验证', 'fields' => [
|
||||||
['key' => 'captcha_redirect_hint', 'label' => '配置入口', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '人机验证(腾讯云 / 阿里云 / Cloudflare / 极验)请到「功能&权限 → 登录权限」统一配置;本页不再维护验证码字段。'],
|
['key' => 'captcha_redirect_hint', 'label' => '配置入口', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '人机验证(腾讯云 / 阿里云 / Cloudflare / 极验)请到「功能&权限 → 人机验证」统一配置;本页不再维护验证码字段。'],
|
||||||
]],
|
]],
|
||||||
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
['key' => 'rate-limit', 'title' => '频率限制', 'fields' => [
|
||||||
['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false],
|
['key' => 'brush_limit_post_enabled', 'label' => '文章发布限制', 'type' => 'switch', 'default' => false],
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ class AKSRT_Admin_Schema_Permissions {
|
|||||||
'sections' => [
|
'sections' => [
|
||||||
'login' => [
|
'login' => [
|
||||||
'key' => 'login',
|
'key' => 'login',
|
||||||
'title' => '登录权限',
|
'title' => '人机验证',
|
||||||
'version' => '1.0.0',
|
'version' => '1.1.0',
|
||||||
'option_key' => 'aksrt_captcha_config',
|
'option_key' => 'aksrt_captcha_config',
|
||||||
'groups' => [
|
'groups' => [
|
||||||
['key' => 'basic', 'title' => '基础开关', 'fields' => [
|
['key' => 'basic', 'title' => '基础开关', 'fields' => [
|
||||||
@@ -32,7 +32,7 @@ class AKSRT_Admin_Schema_Permissions {
|
|||||||
['key' => 'mode', 'label' => '触发模式', 'type' => 'radio', 'default' => 'always', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
['key' => 'mode', 'label' => '触发模式', 'type' => 'radio', 'default' => 'always', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
||||||
['label' => '始终显示', 'value' => 'always'],
|
['label' => '始终显示', 'value' => 'always'],
|
||||||
]],
|
]],
|
||||||
['key' => 'disabled_scenarios', 'label' => '停用场景', 'type' => 'checkbox', 'default' => [], 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
['key' => 'enabled_scenarios', 'label' => '启用场景', 'type' => 'checkbox', 'default' => ['comment', 'login', 'register', 'bind', 'bind_phone', 'lostpassword', 'change_password', 'change_email', 'verify_email', 'download', 'signin'], 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
||||||
['label' => '发表评论', 'value' => 'comment'],
|
['label' => '发表评论', 'value' => 'comment'],
|
||||||
['label' => '用户登录', 'value' => 'login'],
|
['label' => '用户登录', 'value' => 'login'],
|
||||||
['label' => '用户注册', 'value' => 'register'],
|
['label' => '用户注册', 'value' => 'register'],
|
||||||
@@ -138,7 +138,7 @@ class AKSRT_Admin_Schema_Permissions {
|
|||||||
['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'],
|
['key' => 'download_role_size', 'label' => '角色文件大小限制', 'type' => 'textarea', 'default' => '', 'help' => '格式: role|max_size_mb,每行一条'],
|
||||||
]],
|
]],
|
||||||
['key' => 'captcha', 'title' => '安全设置', 'fields' => [
|
['key' => 'captcha', 'title' => '安全设置', 'fields' => [
|
||||||
['key' => 'download_captcha_hint', 'label' => '下载验证码', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '下载验证码门槛在「功能&权限 → 下载权限」页(cms_features.download_captcha)维护;服务商与场景在「登录权限」。'],
|
['key' => 'download_captcha_hint', 'label' => '下载验证码', 'type' => 'notice', 'alertType' => 'info', 'alertContent' => '下载验证码门槛在「功能&权限 → 下载权限」页(cms_features.download_captcha)维护;服务商与场景在「人机验证」。'],
|
||||||
['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'],
|
['key' => 'download_ip_limit', 'label' => 'IP下载限制', 'type' => 'number', 'default' => 0, 'min' => 0, 'help' => '同一IP每小时最大下载次数,0为不限制'],
|
||||||
]],
|
]],
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -46,9 +46,13 @@ function aksrt_captcha_scenes(): array {
|
|||||||
* 默认配置结构。
|
* 默认配置结构。
|
||||||
*/
|
*/
|
||||||
function aksrt_captcha_default_config(): array {
|
function aksrt_captcha_default_config(): array {
|
||||||
|
$all_scenes = array_keys(aksrt_captcha_scenes());
|
||||||
return [
|
return [
|
||||||
'enabled' => false,
|
'enabled' => false,
|
||||||
'active_provider' => '',
|
'active_provider' => '',
|
||||||
|
// Positive list: checked scenes require captcha. Default all scenes.
|
||||||
|
'enabled_scenarios' => $all_scenes,
|
||||||
|
// Legacy mirror kept for BC consumers / admin summaries.
|
||||||
'disabled_scenarios' => [],
|
'disabled_scenarios' => [],
|
||||||
'tencent' => [],
|
'tencent' => [],
|
||||||
'aliyun' => [],
|
'aliyun' => [],
|
||||||
@@ -57,6 +61,24 @@ function aksrt_captcha_default_config(): array {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sanitize a list of captcha scene keys against the whitelist.
|
||||||
|
*
|
||||||
|
* @param mixed $raw
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
|
function aksrt_captcha_sanitize_scene_list($raw): array {
|
||||||
|
$list = is_array($raw) ? $raw : [];
|
||||||
|
$clean = [];
|
||||||
|
foreach ($list as $scene) {
|
||||||
|
$scene = sanitize_key((string) $scene);
|
||||||
|
if ($scene !== '' && array_key_exists($scene, aksrt_captcha_scenes()) && !in_array($scene, $clean, true)) {
|
||||||
|
$clean[] = $scene;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return array_values($clean);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取人机验证完整配置(规范化,不合并历史脏键)。
|
* 获取人机验证完整配置(规范化,不合并历史脏键)。
|
||||||
*
|
*
|
||||||
@@ -68,20 +90,26 @@ function aksrt_get_captcha_config(): array {
|
|||||||
$cfg = [];
|
$cfg = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$default = aksrt_captcha_default_config();
|
$all_scenes = array_keys(aksrt_captcha_scenes());
|
||||||
$provider = isset($cfg['active_provider']) ? sanitize_key((string) $cfg['active_provider']) : '';
|
$provider = isset($cfg['active_provider']) ? sanitize_key((string) $cfg['active_provider']) : '';
|
||||||
if (!in_array($provider, aksrt_captcha_providers(), true)) {
|
if (!in_array($provider, aksrt_captcha_providers(), true)) {
|
||||||
$provider = '';
|
$provider = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
$disabled = isset($cfg['disabled_scenarios']) ? (array) $cfg['disabled_scenarios'] : [];
|
if (array_key_exists('enabled_scenarios', $cfg)) {
|
||||||
$disabled = array_values(array_filter(array_map('sanitize_key', $disabled), function ($s) {
|
// Explicit positive list (may be empty = no scenes).
|
||||||
return array_key_exists($s, aksrt_captcha_scenes());
|
$enabled = aksrt_captcha_sanitize_scene_list($cfg['enabled_scenarios']);
|
||||||
}));
|
} else {
|
||||||
|
// Legacy: only disabled list stored → all minus disabled.
|
||||||
|
$disabled_legacy = aksrt_captcha_sanitize_scene_list($cfg['disabled_scenarios'] ?? []);
|
||||||
|
$enabled = array_values(array_diff($all_scenes, $disabled_legacy));
|
||||||
|
}
|
||||||
|
$disabled = array_values(array_diff($all_scenes, $enabled));
|
||||||
|
|
||||||
$result = [
|
$result = [
|
||||||
'enabled' => !empty($cfg['enabled']),
|
'enabled' => !empty($cfg['enabled']),
|
||||||
'active_provider' => $provider,
|
'active_provider' => $provider,
|
||||||
|
'enabled_scenarios' => $enabled,
|
||||||
'disabled_scenarios' => $disabled,
|
'disabled_scenarios' => $disabled,
|
||||||
];
|
];
|
||||||
foreach (aksrt_captcha_providers() as $p) {
|
foreach (aksrt_captcha_providers() as $p) {
|
||||||
@@ -146,7 +174,11 @@ function aksrt_is_captcha_enabled_for($scene): bool {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !in_array($scene, $cfg['disabled_scenarios'], true);
|
$enabled = isset($cfg['enabled_scenarios']) && is_array($cfg['enabled_scenarios'])
|
||||||
|
? $cfg['enabled_scenarios']
|
||||||
|
: array_values(array_diff(array_keys(aksrt_captcha_scenes()), (array) ($cfg['disabled_scenarios'] ?? [])));
|
||||||
|
|
||||||
|
return in_array($scene, $enabled, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -546,7 +578,7 @@ function aksrt_render_captcha($scene = 'comment') {
|
|||||||
echo '<div class="aksrt-captcha-misconfig" style="font-size:12px;color:#d03050;margin:8px 0;">';
|
echo '<div class="aksrt-captcha-misconfig" style="font-size:12px;color:#d03050;margin:8px 0;">';
|
||||||
echo esc_html(sprintf(
|
echo esc_html(sprintf(
|
||||||
/* translators: %s: captcha provider key */
|
/* translators: %s: captcha provider key */
|
||||||
__('人机验证服务商「%s」已启用但凭据未配齐,前台已跳过渲染。请到「功能&权限 → 登录权限」补全配置。', 'aksrt'),
|
__('人机验证服务商「%s」已启用但凭据未配齐,前台已跳过渲染。请到「功能&权限 → 人机验证」补全配置。', 'aksrt'),
|
||||||
$provider
|
$provider
|
||||||
));
|
));
|
||||||
echo '</div>';
|
echo '</div>';
|
||||||
@@ -701,7 +733,7 @@ function submitForm(){
|
|||||||
verified=true; hField.value='1';
|
verified=true; hField.value='1';
|
||||||
if(C.scene==='download') syncDownloadSiblings(true);
|
if(C.scene==='download') syncDownloadSiblings(true);
|
||||||
if(typeof pendingAction==='function'){
|
if(typeof pendingAction==='function'){
|
||||||
var action=pendingAction; pendingAction=null; action(); return;
|
finishPending(true); return;
|
||||||
}
|
}
|
||||||
setTimeout(resetVerificationState, 3000);
|
setTimeout(resetVerificationState, 3000);
|
||||||
submitNativeForm();
|
submitNativeForm();
|
||||||
@@ -717,18 +749,24 @@ function showTip(msg, color){
|
|||||||
function showLoading(){ box.innerHTML='<div style="text-align:center;padding:20px;color:#9ca3af;">验证码加载中...</div>'; }
|
function showLoading(){ box.innerHTML='<div style="text-align:center;padding:20px;color:#9ca3af;">验证码加载中...</div>'; }
|
||||||
function clearLoading(){ box.innerHTML=''; }
|
function clearLoading(){ box.innerHTML=''; }
|
||||||
|
|
||||||
root.scenes[C.scene]={
|
function finishPending(ok){
|
||||||
|
var action=pendingAction; pendingAction=null;
|
||||||
|
if(typeof action==='function'){ action(!!ok); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelPending(msg){
|
||||||
|
if(msg) showTip(msg, '#e67e22');
|
||||||
|
finishPending(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
root.scenes[C.scene]={
|
||||||
verify:function(callback){
|
verify:function(callback){
|
||||||
var retry=0;
|
if(verified || hField.value==='1'){
|
||||||
function run(){
|
if(typeof callback==='function') callback(true);
|
||||||
if(verified || hField.value==='1'){
|
return;
|
||||||
if(typeof callback==='function') callback();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
pendingAction=(typeof callback==='function')?callback:null;
|
|
||||||
triggerCaptcha();
|
|
||||||
}
|
}
|
||||||
run();
|
pendingAction=(typeof callback==='function')?function(ok){ callback(ok!==false); }:null;
|
||||||
|
triggerCaptcha();
|
||||||
},
|
},
|
||||||
reset:resetVerificationState
|
reset:resetVerificationState
|
||||||
};
|
};
|
||||||
@@ -744,7 +782,7 @@ function loadTencent(){
|
|||||||
triggerCaptcha=function(){
|
triggerCaptcha=function(){
|
||||||
if(!capIns){
|
if(!capIns){
|
||||||
capIns=new TencentCaptcha(C.tc.app_id, function(res){
|
capIns=new TencentCaptcha(C.tc.app_id, function(res){
|
||||||
if(res.ret===2) return;
|
if(res.ret===2){ cancelPending('已取消验证'); return; }
|
||||||
if(res.ret===0 && res.ticket){
|
if(res.ret===0 && res.ticket){
|
||||||
if(res.ticket.indexOf('trerror_')===0){
|
if(res.ticket.indexOf('trerror_')===0){
|
||||||
showTip('验证码服务异常,请刷新页面重试', '#e67e22'); return;
|
showTip('验证码服务异常,请刷新页面重试', '#e67e22'); return;
|
||||||
@@ -803,7 +841,7 @@ function loadCloudflare(){
|
|||||||
'<div class="aksrt-captcha-modal-body"><p class="aksrt-captcha-modal-hint">请完成验证后继续操作</p>'+
|
'<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>';
|
'<div id="modalWidget_'+C.uid+'" class="aksrt-captcha-modal-widget"></div></div></div>';
|
||||||
document.body.appendChild(cfModal);
|
document.body.appendChild(cfModal);
|
||||||
var cancelFn=function(){ cfVerifying=false; closeModal(); showTip('验证码已取消', '#e67e22'); };
|
var cancelFn=function(){ cfVerifying=false; closeModal(); cancelPending('已取消验证'); };
|
||||||
cfModal.querySelector('.aksrt-captcha-modal-backdrop').addEventListener('click', cancelFn);
|
cfModal.querySelector('.aksrt-captcha-modal-backdrop').addEventListener('click', cancelFn);
|
||||||
cfModal.querySelector('.aksrt-captcha-modal-close').addEventListener('click', cancelFn);
|
cfModal.querySelector('.aksrt-captcha-modal-close').addEventListener('click', cancelFn);
|
||||||
renderWidget();
|
renderWidget();
|
||||||
@@ -828,7 +866,7 @@ function loadGeetest(){
|
|||||||
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
|
var product=(C.gt && C.gt.product) ? String(C.gt.product) : 'bind';
|
||||||
if(!captchaId){
|
if(!captchaId){
|
||||||
clearLoading();
|
clearLoading();
|
||||||
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 登录权限」填写并保存', '#e74c3c');
|
showTip('极验 Captcha ID 未配置,请到后台「功能&权限 → 人机验证」填写并保存', '#e74c3c');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
function render(){
|
function render(){
|
||||||
|
|||||||
Reference in new issue
Block a user