diff --git a/assets/admin/src/pages/users/UsersSignin.vue b/assets/admin/src/pages/users/UsersSignin.vue index 4f8c0b9c..35599db1 100644 --- a/assets/admin/src/pages/users/UsersSignin.vue +++ b/assets/admin/src/pages/users/UsersSignin.vue @@ -249,8 +249,8 @@ async function load() { form.invite_register_field = authData.invite_register_field || 'optional' form.invite_reject_invalid = authData.invite_reject_invalid !== false - captchaSummary.register = !!captchaData.enabled && !disabledScenarios.includes('register') - captchaSummary.signin = !!captchaData.enabled && !disabledScenarios.includes('signin') + captchaSummary.register = !!captchaData.enabled && !!captchaData.active_provider && !disabledScenarios.includes('register') + captchaSummary.signin = !!captchaData.enabled && !!captchaData.active_provider && !disabledScenarios.includes('signin') } catch (error) { message.error(error.message || '加载签到奖励配置失败') } finally { diff --git a/assets/js/sign-register.js b/assets/js/sign-register.js index 14e9323c..b82cde6f 100644 --- a/assets/js/sign-register.js +++ b/assets/js/sign-register.js @@ -205,35 +205,53 @@ submitBtn.textContent = '注册中...' } - const captchaData = collectCaptchaFields(form) - - fetch(REST_BASE + '/auth/phone-register', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ phone, code, password, invite_code: inviteCode, ...captchaData }), - }) - .then(r => r.json().then(data => ({ ok: r.ok, data }))) - .then(({ ok, data }) => { - if (ok && data.success) { - showToast(data.message || '注册成功', 'success') - setTimeout(() => { - window.location.href = data.data?.redirect || '/' - }, 500) - } else { - showToast(data.message || '注册失败', 'error') + const captchaState = window.AKSRTCaptchaState + const doRegister = () => { + const captchaData = collectCaptchaFields(form) + fetch(REST_BASE + '/auth/phone-register', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ phone, code, password, invite_code: inviteCode, ...captchaData }), + }) + .then(r => r.json().then(data => ({ ok: r.ok, data }))) + .then(({ ok, data }) => { + if (ok && data.success) { + showToast(data.message || '注册成功', 'success') + setTimeout(() => { + window.location.href = data.data?.redirect || '/' + }, 500) + } else { + showToast(data.message || '注册失败', 'error') + try { + if (captchaState && captchaState.scenes && captchaState.scenes.register) { + captchaState.scenes.register.reset() + } + } catch (e) { /* ignore */ } + if (submitBtn) { + submitBtn.disabled = false + submitBtn.textContent = '注册' + } + } + }) + .catch(() => { + showToast('网络错误', 'error') + try { + if (captchaState && captchaState.scenes && captchaState.scenes.register) { + captchaState.scenes.register.reset() + } + } catch (e) { /* ignore */ } if (submitBtn) { submitBtn.disabled = false submitBtn.textContent = '注册' } - } - }) - .catch(() => { - showToast('网络错误', 'error') - if (submitBtn) { - submitBtn.disabled = false - submitBtn.textContent = '注册' - } - }) + }) + } + + if (captchaState && captchaState.scenes && captchaState.scenes.register) { + captchaState.scenes.register.verify(doRegister) + } else { + doRegister() + } } function handlePhoneLogin(form) { diff --git a/inc/classes/class-aksrt-moderation.php b/inc/classes/class-aksrt-moderation.php index e1c4d880..4a274a0e 100644 --- a/inc/classes/class-aksrt-moderation.php +++ b/inc/classes/class-aksrt-moderation.php @@ -81,6 +81,24 @@ class AKSRT_Moderation { * Main moderation entry. */ public function process(array $commentdata): array { + + // 人机验证:覆盖 REST 与原生 wp-comments-post 两条提交合同(与审核开关无关) + if (function_exists('aksrt_require_captcha')) { + $payload = is_array($_POST ?? null) ? wp_unslash($_POST) : []; + if (isset($payload['captcha']) && is_array($payload['captcha'])) { + $payload = array_merge($payload, $payload['captcha']); + } + $captcha_result = aksrt_require_captcha('comment', $payload); + if ($captcha_result !== true) { + $msg = is_string($captcha_result) ? $captcha_result : __('人机验证失败', 'aksrt'); + wp_die( + esc_html($msg), + esc_html__('评论提交失败', 'aksrt'), + ['response' => 400, 'back_link' => true] + ); + } + } + $config = $this->get_config(); if (empty($config['enabled'])) { diff --git a/inc/core-hooks/part-3.php b/inc/core-hooks/part-3.php index a1ddf8b4..a8105eb9 100644 --- a/inc/core-hooks/part-3.php +++ b/inc/core-hooks/part-3.php @@ -92,12 +92,9 @@ add_action('pre_get_posts', function ($query) { // ============================================================ // 评论提交:单一合同 // ------------------------------------------------------------ -// 评论提交统一走 WordPress 原生 wp-comments-post.php,由前端 -// main.js initCommentSystem() 以 AJAX 拦截:它负责人机验证、频率 -// 限制、审核管线,并在成功后通过 reloadCommentList() 重新拉取, -// 列表仍由 aksrt_comment_callback 这一规范渲染器输出。 -// 不再保留并行的 aksrt_submit_comment 动作(避免绕过验证码的第二套提交合同)。 -// 详见 comment-contract.md。 +// 主路径:前端 comments.js → REST /comment/submit(aksrt_require_captcha)。 +// 兜底 / 原生 wp-comments-post.php 经 preprocess_comment 做人机验证,避免绕过。 +// 列表仍由 aksrt_comment_callback 统一渲染。 // ============================================================ // ============================================================