登录注册体系的源码级硬切换
This commit is contained in:
@@ -22,6 +22,7 @@
|
||||
!/tests/widget-system/icon-card-contract.test.cjs
|
||||
!/tests/widget-system/page-display-save-harness.php
|
||||
!/tests/widget-system/admin-ui-contract.test.cjs
|
||||
!/tests/widget-system/auth-source-contract.test.cjs
|
||||
!/tests/visual/docker-compose.zibll.yml
|
||||
!/tests/visual/zibll-Caddyfile
|
||||
!/tests/visual/README.md
|
||||
|
||||
@@ -14,7 +14,7 @@ AKSRT 是一款现代化的 WordPress 主题,采用**原生 WordPress 主题
|
||||
- **自定义管理面板 (SPA)** — Vue3 + Vite + Naive UI 的 12 个一级业务域后台
|
||||
- **统一设置契约** — Schema 驱动表单、域控制器、导入导出、按域重置、迁移状态与健康检查
|
||||
- **支付/下载主线** — 下载/付费内容中心、充值/VIP/分成/返佣/网关结构、10 分钟下载 token
|
||||
- **用户与增长** — OAuth 登录、邮箱验证门禁、签到奖励、邀请奖励、等级/徽章/认证/举报结构
|
||||
- **用户与增长** — 统一登录注册、OAuth 登录、签到奖励、邀请奖励、等级/徽章/认证/举报结构
|
||||
- **内容与交互** — 首页模块化、归档/搜索/404、单篇资源页、评论审核与通知中心
|
||||
- **运维工具** — 配置导出导入、dry-run 校验、邮件回滚、数据库与环境诊断
|
||||
- **自定义页面模板**:About / Friends / Login / Profile / Register
|
||||
@@ -60,9 +60,8 @@ AKSRT-theme/
|
||||
│ ├── pages/ # 自定义页面模板
|
||||
│ │ ├── page-about.php # 关于页面
|
||||
│ │ ├── page-friends.php # 友链墙
|
||||
│ │ ├── page-login.php # 登录页面
|
||||
│ │ ├── page-sign.php # 唯一登录注册页面
|
||||
│ │ ├── page-profile.php # 用户中心
|
||||
│ │ └── page-register.php # 注册页面
|
||||
│ │
|
||||
│ └── parts/ # 可复用模板片段
|
||||
│ ├── content-post-card.php # 文章卡片组件
|
||||
|
||||
@@ -3,7 +3,6 @@ export const toolsApiMethods = {
|
||||
* 扩展&增强 / 备份&导入 (Extensions & Tools)
|
||||
* ================================================================ */
|
||||
|
||||
getHealthEmailVerification() { return this.request('/health/email-verification') },
|
||||
getUpdateInfo() { return this.request('/tools/update-info') },
|
||||
detectUpdate() { return this.request('/tools/update-check', { method: 'POST', body: JSON.stringify({}) }) },
|
||||
dismissUpdate(version) { return this.request('/tools/update-dismiss', { method: 'POST', body: JSON.stringify({ version }) }) },
|
||||
|
||||
@@ -41,7 +41,7 @@ export const toolsSection = {
|
||||
label: '健康检查',
|
||||
route: '/tools/health',
|
||||
title: '健康检查',
|
||||
desc: '查看主题环境、数据表、邮箱验证迁移和系统诊断状态。',
|
||||
desc: '查看主题环境、数据表和系统诊断状态。',
|
||||
component: 'ToolsHealth',
|
||||
}),
|
||||
customPage({
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="健康检查"
|
||||
desc="集中查看运行环境、数据库表、邮箱验证迁移和系统诊断状态。"
|
||||
desc="集中查看运行环境、数据库表和系统诊断状态。"
|
||||
>
|
||||
<template #actions>
|
||||
<n-space>
|
||||
@@ -13,23 +13,6 @@
|
||||
</PageHeader>
|
||||
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>邮箱验证状态</h3>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-tag round :type="emailHealth.feature_enabled ? 'success' : 'default'">
|
||||
功能开关:{{ emailHealth.feature_enabled ? '已启用' : '未启用' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="emailHealth.rest_blocking_active ? 'warning' : 'default'">
|
||||
REST 阻断:{{ emailHealth.rest_blocking_active ? '已开启' : '仅记录/关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round type="info">
|
||||
待验证用户:{{ emailHealth.pending_users }}
|
||||
</n-tag>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
<div class="aksrt-health-grid">
|
||||
<n-card v-for="check in checkItems" :key="check.key" class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
@@ -55,11 +38,6 @@ const message = useMessage()
|
||||
|
||||
const loading = ref(true)
|
||||
const healthData = ref({})
|
||||
const emailHealth = ref({
|
||||
feature_enabled: false,
|
||||
pending_users: 0,
|
||||
rest_blocking_active: false,
|
||||
})
|
||||
|
||||
const checkItems = computed(() => {
|
||||
return Object.entries(healthData.value || {}).map(([key, value]) => ({
|
||||
@@ -86,12 +64,8 @@ function statusToTag(status) {
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [healthRes, emailRes] = await Promise.all([
|
||||
api.getHealthCheck(),
|
||||
api.getHealthEmailVerification(),
|
||||
])
|
||||
const healthRes = await api.getHealthCheck()
|
||||
healthData.value = extractData(healthRes).checks || {}
|
||||
emailHealth.value = extractData(emailRes)
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载健康检查失败')
|
||||
} finally {
|
||||
|
||||
@@ -23,9 +23,6 @@
|
||||
<n-tag round :type="form.referral_reward_enabled ? 'success' : 'default'">
|
||||
推广奖励:{{ form.referral_reward_enabled ? '已启用' : '已关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="form.referral_registration_enabled ? 'info' : 'default'">
|
||||
推广码注册:{{ form.referral_registration_enabled ? '开启' : '关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="captchaSummary.register ? 'info' : 'default'">
|
||||
注册验证码:{{ captchaSummary.register ? '启用' : '未启用' }}
|
||||
</n-tag>
|
||||
@@ -82,43 +79,6 @@
|
||||
<n-switch v-model:value="form.referral_reward_enabled" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="Ref 码长度">
|
||||
<n-input-number v-model:value="form.referral_code_length" :min="4" :max="12" style="width: 180px;" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">只影响新生成或管理员重新生成的推广码,已有 Ref 不会失效。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="首次触达窗口(天)">
|
||||
<n-input-number v-model:value="form.referral_window_days" :min="1" :max="365" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="开放推广码注册">
|
||||
<n-switch v-model:value="form.referral_registration_enabled" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">此开关只控制注册页推广码字段;现金返佣和积分奖励仍可独立使用同一 Ref 关系。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
|
||||
<template v-if="form.referral_registration_enabled">
|
||||
<n-form-item label="注册页推广码字段">
|
||||
<n-radio-group v-model:value="form.referral_register_field">
|
||||
<n-space size="large">
|
||||
<n-radio value="hidden">隐藏</n-radio>
|
||||
<n-radio value="optional">选填</n-radio>
|
||||
<n-radio value="required">必填</n-radio>
|
||||
</n-space>
|
||||
</n-radio-group>
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="无效推广码处理" v-if="form.referral_register_field === 'required'">
|
||||
<n-switch v-model:value="form.referral_reject_invalid" />
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">开启后,必填模式下无效推广码会直接拒绝注册。首次触达 Ref 始终优先于手填码。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</template>
|
||||
|
||||
<template v-if="form.referral_reward_enabled">
|
||||
<n-form-item label="推广人奖励">
|
||||
<n-input-number v-model:value="form.promoter_amount" :min="0" :max="100000" style="width: 180px;" />
|
||||
@@ -160,7 +120,6 @@ const message = useMessage()
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const creditSnapshot = ref({})
|
||||
const authSnapshot = ref({})
|
||||
|
||||
const captchaSummary = reactive({
|
||||
register: false,
|
||||
@@ -179,11 +138,6 @@ const form = reactive({
|
||||
level_points: 0,
|
||||
require_verify: true,
|
||||
ip_daily_cap: 3,
|
||||
referral_registration_enabled: true,
|
||||
referral_code_length: 12,
|
||||
referral_window_days: 30,
|
||||
referral_register_field: 'optional',
|
||||
referral_reject_invalid: true,
|
||||
})
|
||||
|
||||
const currencyLabel = computed(() => form.currency_name || '积分')
|
||||
@@ -229,14 +183,12 @@ function parseStreakText(value) {
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [creditRes, authRes, captchaRes] = await Promise.all([
|
||||
const [creditRes, captchaRes] = await Promise.all([
|
||||
api.getCreditSettings(),
|
||||
api.getAuthSettings(),
|
||||
api.getPermissionsLoginSettings(),
|
||||
])
|
||||
|
||||
const creditData = extractData(creditRes)
|
||||
const authData = extractData(authRes)
|
||||
const captchaData = extractData(captchaRes)
|
||||
const dailySignin = creditData.daily_signin || {}
|
||||
const referralReward = creditData.referral_reward || {}
|
||||
@@ -246,7 +198,6 @@ async function load() {
|
||||
const sceneOn = (key) => enabledScenarios.includes(key)
|
||||
|
||||
creditSnapshot.value = creditData
|
||||
authSnapshot.value = authData
|
||||
|
||||
form.credits_enabled = creditData.enabled !== false
|
||||
form.currency_name = creditData.currency_name || ''
|
||||
@@ -259,11 +210,6 @@ async function load() {
|
||||
form.level_points = Number(referralReward.level_points || 0)
|
||||
form.require_verify = referralReward.require_verify !== false
|
||||
form.ip_daily_cap = Number(referralReward.ip_daily_cap || 0)
|
||||
form.referral_registration_enabled = authData.referral_registration_enabled !== false
|
||||
form.referral_code_length = Number(authData.referral_code_length || 12)
|
||||
form.referral_window_days = Number(authData.referral_window_days || 30)
|
||||
form.referral_register_field = authData.referral_register_field || 'optional'
|
||||
form.referral_reject_invalid = authData.referral_reject_invalid !== false
|
||||
|
||||
captchaSummary.register = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('register')
|
||||
captchaSummary.signin = !!captchaData.enabled && !!captchaData.active_provider && sceneOn('signin')
|
||||
@@ -296,22 +242,9 @@ async function handleSave() {
|
||||
},
|
||||
}
|
||||
|
||||
const nextAuth = {
|
||||
...authSnapshot.value,
|
||||
referral_registration_enabled: form.referral_registration_enabled,
|
||||
referral_code_length: Number(form.referral_code_length || 12),
|
||||
referral_window_days: Number(form.referral_window_days || 30),
|
||||
referral_register_field: form.referral_register_field,
|
||||
referral_reject_invalid: form.referral_reject_invalid,
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
api.saveCreditSettings(nextCredit),
|
||||
api.saveAuthSettings(nextAuth),
|
||||
])
|
||||
await api.saveCreditSettings(nextCredit)
|
||||
|
||||
creditSnapshot.value = nextCredit
|
||||
authSnapshot.value = nextAuth
|
||||
message.success('签到奖励配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
|
||||
@@ -18,7 +18,7 @@ export const permissionsSchemas = {
|
||||
mode: 'always',
|
||||
enabled_scenarios: [
|
||||
'comment', 'login', 'register', 'bind', 'bind_phone',
|
||||
'lostpassword', 'change_password', 'change_email', 'verify_email', 'download', 'signin',
|
||||
'lostpassword', 'change_password', 'change_email', 'download', 'signin',
|
||||
],
|
||||
tencent: {},
|
||||
aliyun: {},
|
||||
@@ -71,7 +71,6 @@ export const permissionsSchemas = {
|
||||
{ label: '找回密码', value: 'lostpassword' },
|
||||
{ label: '修改密码', value: 'change_password' },
|
||||
{ label: '更改邮箱', value: 'change_email' },
|
||||
{ label: '邮箱验证', value: 'verify_email' },
|
||||
{ label: '资源下载', value: 'download' },
|
||||
{ label: '每日签到', value: 'signin' },
|
||||
{ label: '前台投稿', value: 'front_submit' },
|
||||
|
||||
@@ -1,107 +1,118 @@
|
||||
const verificationOptions = [
|
||||
{ label: '邮箱验证', value: 'email' },
|
||||
{ label: '手机验证', value: 'phone' },
|
||||
{ label: '邮箱或手机验证', value: 'email_phone' },
|
||||
];
|
||||
|
||||
export const authSchema = {
|
||||
key: 'users.auth',
|
||||
title: '注册登录',
|
||||
icon: 'fa fas fa-sign-in-alt',
|
||||
description: '注册方式、登录限制、密码策略配置。',
|
||||
description: '登录注册入口、验证规则、协议与认证界面配置。',
|
||||
defaults: {
|
||||
close_sign: false,
|
||||
user_close_signup: false,
|
||||
close_signup: false,
|
||||
close_signin: false,
|
||||
user_agreement_s: true,
|
||||
user_agreement_page: '',
|
||||
user_privacy_s: true,
|
||||
user_privacy_page: '',
|
||||
user_agreement_check: true,
|
||||
no_repetition_name: false,
|
||||
user_nickname_out: '赌博,博彩,代发,代理,刷单,六合彩,admin,test,管理员,客服',
|
||||
user_email_limit_type: 'black',
|
||||
user_email_out: '@tempmail.com\n@throwaway.email\n@guerrillamail.com',
|
||||
user_close_login: false,
|
||||
user_sign_type: 'page',
|
||||
user_sign_type: 'modal',
|
||||
sign_modal_auto_s: false,
|
||||
sign_modal_auto_waiting_time: 5,
|
||||
sign_modal_auto_expires: 1,
|
||||
replace_wp_login: true,
|
||||
admin_sign_key: '',
|
||||
invit_code_s: 'close',
|
||||
invit_code_open_desc: '如您受到邀请,可填写推广码。部分推广码会有额外积分赠送',
|
||||
invit_code_must_desc: '<div>本站需使用推广码注册</div><div>获取推广码请与客服联系</div>',
|
||||
user_signin_phone_s: false,
|
||||
user_signin_nopas_s: false,
|
||||
user_signin_nopas_type: 'email',
|
||||
user_signin_nopas_active: 'nopas_first',
|
||||
user_signup_captch: true,
|
||||
user_signin_nopas_active: 'nopas',
|
||||
user_signup_captch: false,
|
||||
captch_type: 'email',
|
||||
user_signup_no_repas: true,
|
||||
user_repas_captch_type: 'email',
|
||||
user_agreement_s: false,
|
||||
user_agreement_page: '',
|
||||
user_privacy_s: false,
|
||||
user_privacy_page: '',
|
||||
user_agreement_check: false,
|
||||
no_repetition_name: true,
|
||||
user_nickname_out: '赌博,博彩,彩票,性爱,色情,做爱,爱爱,淫秽,傻b,妈的,妈b,admin,test',
|
||||
user_email_limit_type: 'not',
|
||||
user_email_out: '',
|
||||
bind_phone: false,
|
||||
bind_reminder: 'close',
|
||||
bind_reminder_text: '请绑定手机号以正常使用',
|
||||
bind_reminder_text: '为了您的账户安全,请务必完成账户绑定',
|
||||
bind_reminder_expires: 24,
|
||||
mandatory_bind: 'close',
|
||||
mandatory_bind_text: '为了您的账户安全,请先完成账户绑定',
|
||||
email_set_captch: true,
|
||||
referral_registration_enabled: true,
|
||||
referral_register_field: 'optional',
|
||||
referral_reject_invalid: true,
|
||||
referral_code_length: 12,
|
||||
referral_window_days: 30,
|
||||
referral_open_desc: '推广码为选填,没有推广码也可以注册',
|
||||
referral_required_desc: '请输入推广码',
|
||||
sign_page_background: [],
|
||||
sign_theme_mode: 'default',
|
||||
sign_theme_mode: 'no',
|
||||
sign_show_header: true,
|
||||
sign_card_position: 'center',
|
||||
sign_show_logo: true,
|
||||
sign_card_position: 'right',
|
||||
sign_show_logo: false,
|
||||
sign_page_footer: '',
|
||||
sign_modal_background: [],
|
||||
sign_modal_show_logo: true,
|
||||
sign_modal_show_logo: false,
|
||||
user_logo: [],
|
||||
user_logo_dark: [],
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
key: 'register',
|
||||
title: '注册设置',
|
||||
icon: 'fa fas fa-user-plus',
|
||||
fields: [
|
||||
{ key: 'close_sign', label: '一键禁用登录注册', type: 'switch', default: false, help: '开启后前台禁用全部注册/登录入口(含社交登录),不影响后台登录' },
|
||||
{ key: 'user_close_signup', label: '关闭注册', type: 'switch', default: false },
|
||||
{ key: 'close_signin', label: '禁用账密登录', type: 'switch', default: false, help: '仅允许社交账号登录' },
|
||||
{ key: 'user_agreement_s', label: '用户协议', type: 'switch', default: true },
|
||||
{ key: 'user_agreement_page', label: '协议页面', type: 'input', default: '' },
|
||||
{ key: 'user_privacy_s', label: '隐私政策', type: 'switch', default: true },
|
||||
{ key: 'user_privacy_page', label: '隐私页面', type: 'input', default: '' },
|
||||
{ key: 'user_agreement_check', label: '强制勾选协议', type: 'switch', default: true },
|
||||
{ key: 'no_repetition_name', label: '禁止重复昵称', type: 'switch', default: false },
|
||||
{ key: 'user_nickname_out', label: '昵称黑名单关键词', type: 'textarea', default: '赌博,博彩,代发,代理,刷单,六合彩,admin,test,管理员,客服', help: '一行一个关键词,注册昵称包含关键词将被拒绝' },
|
||||
{ key: 'user_email_limit_type', label: '邮箱限制类型', type: 'radio', default: 'black', options: [{ label: '黑名单(拒绝以下后缀)', value: 'black' }, { label: '白名单(仅允许以下后缀)', value: 'white' }] },
|
||||
{ key: 'user_email_out', label: '邮箱限制后缀', type: 'textarea', default: '@tempmail.com\n@throwaway.email\n@guerrillamail.com', help: '一行一个邮箱后缀,如 @tempmail.com' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'login',
|
||||
title: '登录设置',
|
||||
key: 'entry',
|
||||
title: '登录注册',
|
||||
icon: 'fa fas fa-sign-in-alt',
|
||||
fields: [
|
||||
{ key: 'user_close_login', label: '关闭登录', type: 'switch', default: false },
|
||||
{ key: 'user_sign_type', label: '登录注册样式', type: 'radio', default: 'page', options: [{ label: '弹窗', value: 'modal' }, { label: '跳转页面', value: 'page' }] },
|
||||
{ key: 'sign_modal_auto_s', label: '自动弹出登录窗', type: 'switch', default: false, help: '仅在 user_sign_type=modal 时生效' },
|
||||
{ key: 'sign_modal_auto_waiting_time', label: '弹窗等待秒数', type: 'number', default: 5, min: 0, max: 60, dependsOn: { key: 'sign_modal_auto_s', value: true } },
|
||||
{ key: 'sign_modal_auto_expires', label: '弹窗周期(小时)', type: 'number', default: 1, min: 0, max: 720, dependsOn: { key: 'sign_modal_auto_s', value: true } },
|
||||
{ key: 'replace_wp_login', label: '替换WP登录页', type: 'switch', default: true },
|
||||
{ key: 'admin_sign_key', label: '应急登录 Key', type: 'input', default: '', help: '留空为禁用。管理员可通过 /wp-login.php?aksrt_bypass=<key> 绕过前台替换' },
|
||||
{ key: 'user_signin_phone_s', label: '手机号登录', type: 'switch', default: false, help: '允许用手机号作为用户名登录' },
|
||||
{ key: 'user_signin_nopas_s', label: '免密登录', type: 'switch', default: false },
|
||||
{ key: 'user_signin_nopas_type', label: '免密登录方式', type: 'radio', default: 'email', options: [{ label: '邮箱验证码', value: 'email' }, { label: '手机验证码', value: 'phone' }, { label: '邮箱或手机', value: 'email_phone' }], dependsOn: { key: 'user_signin_nopas_s', value: true } },
|
||||
{ key: 'user_signin_nopas_active', label: '免密显示模式', type: 'radio', default: 'nopas_first', options: [{ label: '优先免密', value: 'nopas_first' }, { label: '优先密码', value: 'password_first' }, { label: '仅免密', value: 'nopas_only' }], dependsOn: { key: 'user_signin_nopas_s', value: true } },
|
||||
{ key: 'close_sign', label: '禁用登录注册', type: 'switch', default: false, help: '关闭前台全部认证入口,不影响管理员后台应急登录' },
|
||||
{ key: 'close_signup', label: '禁用新用户注册', type: 'switch', default: false, dependsOn: { key: 'close_sign', value: false } },
|
||||
{ key: 'close_signin', label: '禁用账号登录/注册', type: 'switch', default: false, dependsOn: { key: 'close_sign', value: false }, help: '关闭账号密码和验证码登录注册,仅保留已启用的 OAuth 登录' },
|
||||
{ key: 'user_sign_type', label: '默认登录注册样式', type: 'radio', default: 'modal', options: [{ label: '弹窗显示', value: 'modal' }, { label: '独立页面', value: 'page' }] },
|
||||
{ key: 'sign_modal_auto_s', label: '自动弹出登录窗口', type: 'switch', default: false, dependsOn: { key: 'user_sign_type', value: 'modal' } },
|
||||
{ key: 'sign_modal_auto_waiting_time', label: '等待时间(秒)', type: 'number', default: 5, min: 1, max: 100, dependsOn: { key: 'sign_modal_auto_s', value: true } },
|
||||
{ key: 'sign_modal_auto_expires', label: '自动弹窗周期(小时)', type: 'number', default: 1, min: 0, max: 2000, dependsOn: { key: 'sign_modal_auto_s', value: true } },
|
||||
{ key: 'replace_wp_login', label: '代替 WP 自带登录页面', type: 'switch', default: true },
|
||||
{ key: 'admin_sign_key', label: '应急登录 Key', type: 'input', default: '', dependsOn: { key: 'replace_wp_login', value: true }, help: '通过 /wp-login.php?aksrt_bypass=<key> 使用 WP 登录页' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'signup_captcha',
|
||||
title: '注册验证',
|
||||
icon: 'fa fas fa-user',
|
||||
key: 'invitation',
|
||||
title: '推广码注册',
|
||||
icon: 'fa fas fa-ticket-alt',
|
||||
fields: [
|
||||
{ key: 'user_signup_captch', label: '注册需验证', type: 'switch', default: true, help: '注册时需完成邮箱或手机验证' },
|
||||
{ key: 'captch_type', label: '验证方式', type: 'radio', default: 'email', options: [{ label: '邮箱', value: 'email' }, { label: '手机', value: 'phone' }, { label: '邮箱或手机', value: 'email_phone' }], dependsOn: { key: 'user_signup_captch', value: true } },
|
||||
{ key: 'user_signup_no_repas', label: '无需重复密码', type: 'switch', default: true, help: '注册时不要求二次确认密码' },
|
||||
{ key: 'user_repas_captch_type', label: '找回密码验证方式', type: 'radio', default: 'email', options: [{ label: '邮箱', value: 'email' }, { label: '手机', value: 'phone' }, { label: '邮箱或手机', value: 'email_phone' }] },
|
||||
{ key: 'invit_code_s', label: '推广码注册', type: 'radio', default: 'close', options: [{ label: '关闭', value: 'close' }, { label: '选填推广码', value: 'open' }, { label: '必须填写推广码', value: 'must' }] },
|
||||
{ key: 'invit_code_open_desc', label: '选填提示文案', type: 'input', default: '如您受到邀请,可填写推广码。部分推广码会有额外积分赠送', dependsOn: { key: 'invit_code_s', value: 'open' } },
|
||||
{ key: 'invit_code_must_desc', label: '必填提示文案', type: 'textarea', default: '<div>本站需使用推广码注册</div><div>获取推广码请与客服联系</div>', dependsOn: { key: 'invit_code_s', value: 'must' }, help: '支持安全 HTML' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'verification',
|
||||
title: '登录与验证',
|
||||
icon: 'fa fas fa-shield-alt',
|
||||
fields: [
|
||||
{ key: 'user_signin_phone_s', label: '手机号登录', type: 'switch', default: false },
|
||||
{ key: 'user_signin_nopas_s', label: '免密登录', type: 'switch', default: false },
|
||||
{ key: 'user_signin_nopas_type', label: '免密登录方式', type: 'radio', default: 'email', options: verificationOptions, dependsOn: { key: 'user_signin_nopas_s', value: true } },
|
||||
{ key: 'user_signin_nopas_active', label: '登录方式显示模式', type: 'radio', default: 'nopas', options: [{ label: '优先免密登录', value: 'nopas' }, { label: '优先账号密码', value: 'pas' }, { label: '仅显示免密登录', value: 'only_nopas' }], dependsOn: { key: 'user_signin_nopas_s', value: true } },
|
||||
{ key: 'user_signup_captch', label: '注册需验证', type: 'switch', default: false },
|
||||
{ key: 'captch_type', label: '注册验证方式', type: 'radio', default: 'email', options: verificationOptions, dependsOn: { key: 'user_signup_captch', value: true } },
|
||||
{ key: 'user_signup_no_repas', label: '注册无需重复密码', type: 'switch', default: true, dependsOn: { key: 'user_signup_captch', value: true } },
|
||||
{ key: 'user_repas_captch_type', label: '找回密码验证方式', type: 'radio', default: 'email', options: verificationOptions },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'agreements',
|
||||
title: '协议与账号限制',
|
||||
icon: 'fa fas fa-file-contract',
|
||||
fields: [
|
||||
{ key: 'user_agreement_s', label: '显示用户协议', type: 'switch', default: false },
|
||||
{ key: 'user_agreement_page', label: '用户协议页面', type: 'input', default: '', dependsOn: { key: 'user_agreement_s', value: true } },
|
||||
{ key: 'user_privacy_s', label: '显示隐私协议', type: 'switch', default: false },
|
||||
{ key: 'user_privacy_page', label: '隐私协议页面', type: 'input', default: '', dependsOn: { key: 'user_privacy_s', value: true } },
|
||||
{ key: 'user_agreement_check', label: '协议必须手动勾选', type: 'switch', default: false },
|
||||
{ key: 'no_repetition_name', label: '禁止重复昵称', type: 'switch', default: true },
|
||||
{ key: 'user_nickname_out', label: '禁止的昵称关键词', type: 'textarea', default: '赌博,博彩,彩票,性爱,色情,做爱,爱爱,淫秽,傻b,妈的,妈b,admin,test' },
|
||||
{ key: 'user_email_limit_type', label: '邮箱限制方式', type: 'radio', default: 'not', options: [{ label: '黑名单', value: 'not' }, { label: '白名单', value: 'allow' }] },
|
||||
{ key: 'user_email_out', label: '邮箱限制关键词', type: 'textarea', default: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -109,39 +120,13 @@ export const authSchema = {
|
||||
title: '绑定设置',
|
||||
icon: 'fa fas fa-link',
|
||||
fields: [
|
||||
{ key: 'bind_phone', label: '绑定手机', type: 'switch', default: false },
|
||||
{ key: 'bind_reminder', label: '绑定提醒', type: 'radio', default: 'close', options: [{ label: '提醒绑定邮箱', value: 'email' }, { label: '提醒绑定手机', value: 'phone' }, { label: '提醒绑定邮箱和手机', value: 'email_phone' }, { label: '关闭', value: 'close' }] },
|
||||
{ key: 'bind_reminder_text', label: '提醒文案', type: 'input', default: '请绑定手机号以正常使用' },
|
||||
{ key: 'bind_reminder_expires', label: '提醒周期(小时)', type: 'number', default: 24, min: 1, max: 720, help: '用户关闭提醒后多少小时内不再提醒' },
|
||||
{ key: 'mandatory_bind', label: '强制绑定', type: 'radio', default: 'close', options: [{ label: '强制绑定邮箱', value: 'email' }, { label: '强制绑定手机', value: 'phone' }, { label: '强制绑定邮箱和手机', value: 'email_phone' }, { label: '关闭', value: 'close' }] },
|
||||
{ key: 'email_set_captch', label: '绑定邮箱需验证', type: 'switch', default: true, help: '绑定邮箱时需发送验证码确认' },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'referral_inline',
|
||||
title: '推广码注册',
|
||||
icon: 'fa fas fa-ticket-alt',
|
||||
fields: [
|
||||
{ key: 'referral_registration_enabled', label: '开放推广码注册', type: 'switch', default: true },
|
||||
{ key: 'referral_register_field', label: '注册页推广码字段', type: 'select', default: 'optional', options: [{ label: '隐藏', value: 'hidden' }, { label: '选填', value: 'optional' }, { label: '必填', value: 'required' }], dependsOn: { key: 'referral_registration_enabled', value: true } },
|
||||
{ key: 'referral_reject_invalid', label: '拒绝无效推广码', type: 'switch', default: true, dependsOn: { key: 'referral_registration_enabled', value: true } },
|
||||
{ key: 'referral_code_length', label: 'Ref 码长度', type: 'number', default: 12, min: 4, max: 12 },
|
||||
{ key: 'referral_window_days', label: '首次触达窗口(天)', type: 'number', default: 30, min: 1, max: 365 },
|
||||
{ key: 'referral_open_desc', label: '选填提示', type: 'input', default: '推广码为选填,没有推广码也可以注册', dependsOn: { key: 'referral_registration_enabled', value: true } },
|
||||
{ key: 'referral_required_desc', label: '必填提示', type: 'input', default: '请输入推广码', dependsOn: { key: 'referral_registration_enabled', value: true } },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sign_page_ui',
|
||||
title: '页面 UI 配置',
|
||||
icon: 'fa fas fa-paint-brush',
|
||||
fields: [
|
||||
{ key: 'sign_page_background', label: '页面背景图', type: 'gallery', default: [], help: '登录注册页背景图,多图随机' },
|
||||
{ key: 'sign_theme_mode', label: '主题模式', type: 'radio', default: 'default', options: [{ label: '默认', value: 'default' }, { label: '亮色', value: 'light' }, { label: '暗色', value: 'dark' }] },
|
||||
{ key: 'sign_show_header', label: '显示顶部导航', type: 'switch', default: true },
|
||||
{ key: 'sign_card_position', label: '卡片位置', type: 'radio', default: 'center', options: [{ label: '靠左', value: 'left' }, { label: '居中', value: 'center' }, { label: '靠右', value: 'right' }] },
|
||||
{ key: 'sign_show_logo', label: '显示 LOGO', type: 'switch', default: true },
|
||||
{ key: 'sign_page_footer', label: '页脚 HTML', type: 'textarea', default: '', help: '登录注册页自定义页脚内容,支持 HTML' },
|
||||
{ key: 'bind_phone', label: '手机绑定', type: 'switch', default: false },
|
||||
{ key: 'bind_reminder', label: '绑定提醒', type: 'radio', default: 'close', options: [{ label: '提醒绑定邮箱', value: 'email' }, { label: '提醒绑定手机', value: 'phone' }, { label: '提醒邮箱和手机', value: 'email_phone' }, { label: '关闭', value: 'close' }] },
|
||||
{ key: 'bind_reminder_text', label: '绑定提醒文案', type: 'textarea', default: '为了您的账户安全,请务必完成账户绑定' },
|
||||
{ key: 'bind_reminder_expires', label: '提醒周期(小时)', type: 'number', default: 24, min: 0, max: 2000 },
|
||||
{ key: 'mandatory_bind', label: '强制绑定', type: 'radio', default: 'close', options: [{ label: '必须绑定邮箱', value: 'email' }, { label: '必须绑定手机', value: 'phone' }, { label: '必须绑定邮箱和手机', value: 'email_phone' }, { label: '关闭', value: 'close' }] },
|
||||
{ key: 'mandatory_bind_text', label: '强制绑定文案', type: 'textarea', default: '为了您的账户安全,请先完成账户绑定' },
|
||||
{ key: 'email_set_captch', label: '绑定邮箱需验证', type: 'switch', default: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -149,8 +134,21 @@ export const authSchema = {
|
||||
title: '弹窗 UI 配置',
|
||||
icon: 'fa fas fa-window-restore',
|
||||
fields: [
|
||||
{ key: 'sign_modal_background', label: '弹窗背景图', type: 'gallery', default: [], help: '弹窗左侧背景图,多图随机' },
|
||||
{ key: 'sign_modal_show_logo', label: '弹窗显示 LOGO', type: 'switch', default: true },
|
||||
{ key: 'sign_modal_background', label: '左侧图片', type: 'gallery', default: [], help: '多图时随机显示;移动端隐藏' },
|
||||
{ key: 'sign_modal_show_logo', label: '显示 LOGO', type: 'switch', default: false },
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'sign_page_ui',
|
||||
title: '页面 UI 配置',
|
||||
icon: 'fa fas fa-paint-brush',
|
||||
fields: [
|
||||
{ key: 'sign_page_background', label: '页面背景图', type: 'gallery', default: [] },
|
||||
{ key: 'sign_theme_mode', label: '自定义主题', type: 'radio', default: 'no', options: [{ label: '保持默认', value: 'no' }, { label: '日间亮色', value: 'white-theme' }, { label: '夜间深色', value: 'dark-theme' }] },
|
||||
{ key: 'sign_show_header', label: '显示顶部导航', type: 'switch', default: true },
|
||||
{ key: 'sign_card_position', label: '卡片位置', type: 'radio', default: 'right', options: [{ label: '靠左', value: 'left' }, { label: '居中', value: 'center' }, { label: '靠右', value: 'right' }] },
|
||||
{ key: 'sign_show_logo', label: '显示卡片 LOGO', type: 'switch', default: false },
|
||||
{ key: 'sign_page_footer', label: '页脚内容', type: 'textarea', default: '', help: '支持安全 HTML' },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -158,9 +156,9 @@ export const authSchema = {
|
||||
title: '登录框 LOGO',
|
||||
icon: 'fa fas fa-image',
|
||||
fields: [
|
||||
{ key: 'user_logo', label: '日间 LOGO', type: 'gallery', default: [], help: '登录框专用日间 LOGO,留空使用站点全局 LOGO' },
|
||||
{ key: 'user_logo_dark', label: '夜间 LOGO', type: 'gallery', default: [], help: '登录框专用夜间 LOGO,留空使用站点全局夜间 LOGO' },
|
||||
{ key: 'user_logo', label: '日间 LOGO', type: 'gallery', default: [] },
|
||||
{ key: 'user_logo_dark', label: '夜间 LOGO', type: 'gallery', default: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
@import url("enhancements/core-ui.css");
|
||||
@import url("enhancements/components.css");
|
||||
@import url("enhancements/modal-motion.css");
|
||||
|
||||
@@ -5,27 +5,6 @@
|
||||
* 所有选择器使用 aksrt- 前缀避免冲突
|
||||
*/
|
||||
|
||||
.aksrt-modal-auth__error.is-success {
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
border-color: rgba(16, 185, 129, 0.22);
|
||||
color: #047857;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__checkbox {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__hint {
|
||||
margin: -4px 0 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted-color, #777);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
@keyframes aksrt-spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
@import url("modal-motion/base.css");
|
||||
@import url("modal-motion/tabs.css");
|
||||
@import url("modal-motion/panels.css");
|
||||
@import url("modal-motion/form.css");
|
||||
@import url("modal-motion/divider.css");
|
||||
@import url("modal-motion/oauth.css");
|
||||
@import url("modal-motion/footer.css");
|
||||
@import url("modal-motion/dark-mode.css");
|
||||
@import url("modal-motion/responsive.css");
|
||||
@import url("modal-motion/dark-mode-2.css");
|
||||
@@ -1,210 +0,0 @@
|
||||
/* ============================================================
|
||||
* 21. 弹窗登录/注册 — 1:1 legacy modal
|
||||
* ============================================================ */
|
||||
.aksrt-modal-auth {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 99999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: opacity 0.3s, visibility 0.3s;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth.is-open {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__overlay {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
backdrop-filter: none;
|
||||
-webkit-backdrop-filter: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__card {
|
||||
position: relative;
|
||||
background: var(--main-bg-color, var(--surface-1, #fff));
|
||||
border-radius: var(--main-radius, 8px);
|
||||
border: none;
|
||||
padding: 0;
|
||||
max-width: 400px;
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 20px);
|
||||
overflow: hidden;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08));
|
||||
z-index: 1;
|
||||
transform: scale(0.9);
|
||||
transition: transform 0.5s cubic-bezier(0.32, 0.85, 0.45, 1.18), width 0.3s;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth.is-open .aksrt-modal-auth__card {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__header {
|
||||
position: relative;
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
padding: 28px 24px 16px;
|
||||
min-height: 120px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
overflow: hidden;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-active, #c9206d) 100%);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__header::before,
|
||||
.aksrt-modal-auth__header::after {
|
||||
content: ' ';
|
||||
position: absolute;
|
||||
border-radius: 100%;
|
||||
background: rgba(255, 255, 255, 0.12);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__header::before {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
right: -60px;
|
||||
top: -80px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__header::after {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
left: -40px;
|
||||
bottom: -70px;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__close {
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
min-width: 28px;
|
||||
min-height: 28px;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
background: transparent;
|
||||
color: #fff;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: opacity 0.2s;
|
||||
font-size: 18px;
|
||||
z-index: 9;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__close:hover {
|
||||
opacity: 1;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__logo {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
border-radius: 50%;
|
||||
background: rgba(255, 255, 255, 0.18);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin: 0 auto 10px;
|
||||
border: 2px solid rgba(255, 255, 255, 0.35);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__title {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: normal;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__tabs,
|
||||
.aksrt-modal-auth__panel,
|
||||
.aksrt-modal-auth__body {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__panel {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth.bottom {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth.bottom .aksrt-modal-auth__card {
|
||||
max-width: calc(100% - 20px);
|
||||
margin-bottom: calc(10px + env(safe-area-inset-bottom, 0px));
|
||||
transform: translateY(110%);
|
||||
transition: transform 0.4s;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
box-shadow: 0 -10px 50px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth.bottom.is-open .aksrt-modal-auth__card {
|
||||
transform: translateY(0);
|
||||
transition: transform 0.6s cubic-bezier(0.28, 0.9, 0.34, 0.99);
|
||||
}
|
||||
|
||||
.modal-content,
|
||||
.aksrt-modal__content {
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--main-bg-color, var(--surface-1));
|
||||
border: none;
|
||||
box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08));
|
||||
}
|
||||
|
||||
.modal-colorful-header {
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
height: 120px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--this-color, #fff);
|
||||
background: var(--this-bg, var(--focus-color, var(--color-primary)));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.modal-colorful-header + * {
|
||||
margin-top: 120px;
|
||||
}
|
||||
|
||||
.modal-colorful-header > .close {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 10px;
|
||||
color: var(--this-color, #fff);
|
||||
opacity: 0.6;
|
||||
font-size: 18px;
|
||||
z-index: 9;
|
||||
}
|
||||
@@ -1,449 +0,0 @@
|
||||
|
||||
/* ============================================================
|
||||
* 响应式
|
||||
* ============================================================ */
|
||||
@media (max-width: 640px) {
|
||||
.aksrt-lightbox__nav {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.single-cover-slider__img {
|
||||
height: 240px;
|
||||
}
|
||||
|
||||
.single-cover-slider__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.reward-modal__card {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.reward-modal__qr {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
|
||||
.aksrt-cover-cards {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.aksrt-cover-card {
|
||||
min-height: 140px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 13. 无障碍:prefers-reduced-motion
|
||||
* 用户系统设置"减少动画"时,禁用所有非必要动效
|
||||
* ============================================================ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
animation-duration: 0.01ms !important;
|
||||
animation-iteration-count: 1 !important;
|
||||
transition-duration: 0.01ms !important;
|
||||
scroll-behavior: auto !important;
|
||||
}
|
||||
|
||||
.aksrt-skeleton {
|
||||
animation: none !important;
|
||||
}
|
||||
|
||||
[data-aksrt-animate] {
|
||||
opacity: 1 !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* legacy lazySizes state and animation contract. */
|
||||
.lazyloaded {
|
||||
transition: 0.3s;
|
||||
--lazy-animation: lazy_blur;
|
||||
}
|
||||
|
||||
.lazyloaded.img-icon {
|
||||
--lazy-animation: lazy_fade;
|
||||
}
|
||||
|
||||
img.lazyloaded:not(.lazyloadafter) {
|
||||
-webkit-animation: var(--lazy-animation) 0.6s;
|
||||
animation: var(--lazy-animation) 0.6s;
|
||||
}
|
||||
|
||||
@keyframes lazy_blur {
|
||||
0% { filter: blur(5px); }
|
||||
100% { filter: blur(0); }
|
||||
}
|
||||
|
||||
@keyframes lazy_fade {
|
||||
0% { opacity: 0; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes lazy_scale {
|
||||
0% { transform: scale(1.1); }
|
||||
80% { transform: unset; }
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 15. 错误反馈 shake 动画
|
||||
* 表单验证失败时的水平抖动效果
|
||||
* ============================================================ */
|
||||
@keyframes aksrtShake {
|
||||
0%, 100% { transform: translateX(0); }
|
||||
10%, 30%, 50%, 70%, 90% { transform: translateX(-4px); }
|
||||
20%, 40%, 60%, 80% { transform: translateX(4px); }
|
||||
}
|
||||
|
||||
.aksrt-shake {
|
||||
animation: aksrtShake 0.4s ease-in-out;
|
||||
}
|
||||
|
||||
/* 应用于 Naive UI 表单验证 */
|
||||
.n-form-item--error .n-input,
|
||||
.n-form-item--error .n-select,
|
||||
.n-form-item--error .n-input-number {
|
||||
animation: aksrtShake 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 16. FPS 加速模式:低端设备禁用 backdrop-filter
|
||||
* 模仿 legacy 的 body.fps-accelerat 模式
|
||||
* ============================================================ */
|
||||
body.fps-accelerat .site-nav,
|
||||
body.fps-accelerat .footer-tabbar,
|
||||
body.fps-accelerat .float-right .float-btn,
|
||||
body.fps-accelerat .aksrt-blur-bg {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
/* 模态框内禁用 backdrop-filter 避免渲染冲突 */
|
||||
body.modal-open .site-nav,
|
||||
body.dialog-open .site-nav {
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 17. .blur-bg 通用工具类
|
||||
* 模仿 legacy 的 .blur-bg,可在任意元素上应用毛玻璃效果
|
||||
* ============================================================ */
|
||||
.aksrt-blur-bg {
|
||||
background: var(--blur-bg);
|
||||
backdrop-filter: saturate(5) blur(20px);
|
||||
-webkit-backdrop-filter: saturate(5) blur(20px);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 18. 滚动触发动画延迟支持
|
||||
* data-aksrt-delay 属性控制入场延迟
|
||||
* ============================================================ */
|
||||
[data-aksrt-animate][data-aksrt-delay="1"] { transition-delay: 0.1s; }
|
||||
[data-aksrt-animate][data-aksrt-delay="2"] { transition-delay: 0.2s; }
|
||||
[data-aksrt-animate][data-aksrt-delay="3"] { transition-delay: 0.3s; }
|
||||
[data-aksrt-animate][data-aksrt-delay="4"] { transition-delay: 0.4s; }
|
||||
[data-aksrt-animate][data-aksrt-delay="5"] { transition-delay: 0.5s; }
|
||||
|
||||
/* ============================================================
|
||||
* 19. 全站动效节奏统一
|
||||
* 模仿 legacy 的统一 hover / slide / blur / drawer 语言
|
||||
* ============================================================ */
|
||||
|
||||
/* --- 全局过渡基线 --- */
|
||||
a, button, input, select, textarea {
|
||||
transition: border-color var(--dur-fast) var(--ease),
|
||||
box-shadow var(--dur-fast) var(--ease),
|
||||
background-color var(--dur-fast) var(--ease),
|
||||
color var(--dur-fast) var(--ease);
|
||||
}
|
||||
|
||||
/* --- 卡片 hover 统一 --- */
|
||||
.post-card, .widget, .aksrt-theme-box, .theme-box,
|
||||
.comment-body, .friend-card, .special-card,
|
||||
.resource-download-box {
|
||||
transition: box-shadow var(--dur) var(--ease),
|
||||
border-color var(--dur) var(--ease),
|
||||
transform var(--dur) var(--ease);
|
||||
}
|
||||
|
||||
.post-card:hover, .widget:hover, .friend-card:hover, .special-card:hover {
|
||||
box-shadow: none;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* --- 按钮 hover 统一 --- */
|
||||
.btn-primary, .btn-outline, .btn-lg, .btn-sm,
|
||||
.n-button, .aksrt-button {
|
||||
transition: all var(--dur-fast) var(--ease);
|
||||
}
|
||||
|
||||
.btn-primary:hover, .n-button--primary-type:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.btn-primary:active, .n-button--primary-type:active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* --- 输入框 focus 统一 --- */
|
||||
input:focus, textarea:focus, select:focus,
|
||||
.n-input--focus, .n-select--focus {
|
||||
box-shadow: var(--focus-ring);
|
||||
transition: box-shadow var(--dur-fast) var(--ease);
|
||||
}
|
||||
|
||||
/* --- 抽屉/模态框动效统一 --- */
|
||||
.n-drawer, .n-modal, .n-dialog {
|
||||
transition: all var(--dur) var(--ease-out);
|
||||
}
|
||||
|
||||
.n-drawer-mask, .n-modal-mask, .n-dialog-mask {
|
||||
transition: opacity var(--dur) var(--ease);
|
||||
}
|
||||
|
||||
/* --- Tab 切换动效 --- */
|
||||
.n-tabs-tab, .nav-tab, .aksrt-tab {
|
||||
transition: color var(--dur-fast) var(--ease),
|
||||
border-color var(--dur-fast) var(--ease),
|
||||
background-color var(--dur-fast) var(--ease);
|
||||
}
|
||||
|
||||
/* --- 下拉菜单动效 --- */
|
||||
.n-dropdown-menu, .aksrt-dropdown {
|
||||
transition: opacity var(--dur-fast) var(--ease),
|
||||
transform var(--dur-fast) var(--ease-out);
|
||||
transform-origin: top;
|
||||
}
|
||||
|
||||
/* --- 骨架屏动效 --- */
|
||||
.aksrt-skeleton {
|
||||
animation: aksrtSkeletonShimmer 1.5s infinite linear;
|
||||
background: linear-gradient(90deg, var(--skeleton-base) 25%, var(--skeleton-shine) 50%, var(--skeleton-base) 75%);
|
||||
background-size: 200% 100%;
|
||||
}
|
||||
|
||||
/* --- 页面切换动效 --- */
|
||||
.fade-slide-enter-active,
|
||||
.fade-slide-leave-active {
|
||||
transition: opacity var(--dur) var(--ease),
|
||||
transform var(--dur) var(--ease);
|
||||
}
|
||||
|
||||
.fade-slide-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateY(8px);
|
||||
}
|
||||
|
||||
.fade-slide-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
|
||||
/* --- 导航栏滚动动效 --- */
|
||||
.site-nav {
|
||||
transition: transform var(--dur) var(--ease),
|
||||
background-color var(--dur) var(--ease),
|
||||
box-shadow var(--dur) var(--ease);
|
||||
}
|
||||
|
||||
.site-nav.aksrt-nav--hidden {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
|
||||
/* --- 浮层弹出动效 --- */
|
||||
@keyframes aksrtPopIn {
|
||||
0% { opacity: 0; transform: scale(0.95) translateY(-4px); }
|
||||
100% { opacity: 1; transform: scale(1) translateY(0); }
|
||||
}
|
||||
|
||||
@keyframes aksrtPopOut {
|
||||
0% { opacity: 1; transform: scale(1) translateY(0); }
|
||||
100% { opacity: 0; transform: scale(0.95) translateY(-4px); }
|
||||
}
|
||||
|
||||
.aksrt-popover, .aksrt-tooltip, .aksrt-dropdown-panel {
|
||||
animation: aksrtPopIn var(--dur-fast) var(--ease-out) forwards;
|
||||
}
|
||||
|
||||
/* --- 滚动进度条动效 --- */
|
||||
.reading-progress-bar {
|
||||
transition: width 0.1s linear;
|
||||
}
|
||||
|
||||
/* --- 移动端抽屉动效 --- */
|
||||
@media (max-width: 767px) {
|
||||
.n-drawer {
|
||||
transition: transform var(--dur) var(--ease-out);
|
||||
}
|
||||
|
||||
.site-nav .n-drawer-body {
|
||||
transition: transform var(--dur) var(--ease-out);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- 暗色模式切换动效 --- */
|
||||
html.dark {
|
||||
transition: background-color 0.3s var(--ease),
|
||||
color 0.3s var(--ease);
|
||||
}
|
||||
|
||||
html.dark * {
|
||||
transition: background-color 0.3s var(--ease),
|
||||
border-color 0.3s var(--ease),
|
||||
color 0.3s var(--ease),
|
||||
box-shadow 0.3s var(--ease);
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 18. 合作伙伴无缝滚动 (marquee)
|
||||
* ============================================================ */
|
||||
.aksrt-marquee {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-marquee__inner {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
white-space: nowrap;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.aksrt-marquee__inner > * {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.aksrt-marquee__inner {
|
||||
animation: none !important;
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 19. 视频剧集切换按钮
|
||||
* ============================================================ */
|
||||
.switch-video,
|
||||
.switch-iframe {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 6px 14px;
|
||||
border: 1px solid var(--color-line, rgba(50, 50, 50, 0.1));
|
||||
border-radius: var(--radius, 8px);
|
||||
background: var(--surface-1, #fff);
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.switch-video:hover,
|
||||
.switch-iframe:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.switch-video.is-active,
|
||||
.switch-iframe.is-active {
|
||||
background: var(--color-primary);
|
||||
border-color: var(--color-primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 20. 一言轮播组件
|
||||
* ============================================================ */
|
||||
.aksrt-yiyan {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__content {
|
||||
min-height: 60px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__icon {
|
||||
font-size: 20px;
|
||||
color: var(--color-primary);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__text {
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
color: var(--color-ink-2, #4e5358);
|
||||
margin: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__from {
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-4, #999);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--color-line, rgba(50, 50, 50, 0.1));
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-yiyan__dot.is-active {
|
||||
background: var(--color-primary);
|
||||
width: 16px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* ============================================================
|
||||
* 底部弹出 Sheet — 对齐 legacy .modal.bottom
|
||||
* ============================================================ */
|
||||
.aksrt-modal--bottom .aksrt-modal-auth__card,
|
||||
.modal.bottom .modal-dialog {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
border-radius: var(--main-radius, 8px) var(--main-radius, 8px) 0 0;
|
||||
transform: translateY(110%);
|
||||
transition: transform 0.4s;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.aksrt-modal--bottom.is-open .aksrt-modal-auth__card,
|
||||
.modal.bottom.in .modal-dialog {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.aksrt-modal--bottom .aksrt-modal-auth__card,
|
||||
.modal.bottom .modal-dialog .modal-content {
|
||||
box-shadow: 0 -10px 50px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
/* Dark mode */
|
||||
.theme-dark .aksrt-modal-auth__card,
|
||||
html.dark .aksrt-modal-auth__card {
|
||||
background: #1e1e2e;
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.theme-dark .aksrt-modal-auth__field input,
|
||||
html.dark .aksrt-modal-auth__field input {
|
||||
background: #2a2a3a;
|
||||
border-color: rgba(255, 255, 255, 0.15);
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.theme-dark .aksrt-modal-auth__close,
|
||||
html.dark .aksrt-modal-auth__close {
|
||||
background: #2a2a3a;
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
/* Divider */
|
||||
.aksrt-modal-auth__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin: 20px 0;
|
||||
color: var(--color-ink-4, #8A8F98);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__divider::before,
|
||||
.aksrt-modal-auth__divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-line);
|
||||
}
|
||||
|
||||
@@ -1,22 +0,0 @@
|
||||
/* Footer */
|
||||
.aksrt-modal-auth__footer {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__switch {
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__switch:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
@@ -1,120 +0,0 @@
|
||||
/* Form */
|
||||
.aksrt-modal-auth__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__error {
|
||||
padding: 10px 14px;
|
||||
border-radius: 8px;
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
color: var(--color-error, #EF4444);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__error a {
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__field label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: normal;
|
||||
color: var(--color-ink-2);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__field input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: var(--main-radius, 8px);
|
||||
font-size: 14px;
|
||||
color: var(--color-ink);
|
||||
background: var(--surface-1, #fff);
|
||||
transition: border-color 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__field input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary, var(--color-primary));
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__field input::placeholder {
|
||||
color: var(--color-ink-4, #8A8F98);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__pass-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__pass-wrap input {
|
||||
padding-right: 44px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__pass-toggle {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: none;
|
||||
color: var(--color-ink-4, #8A8F98);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__pass-toggle:hover {
|
||||
background: var(--surface-2, #F4F5F7);
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__link {
|
||||
font-size: 13px;
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__submit {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: none;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--color-primary, var(--color-primary));
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__submit:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__submit:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
/* OAuth */
|
||||
.aksrt-modal-auth__oauth {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__oauth-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--color-ink-2);
|
||||
font-size: 18px;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__oauth-btn:hover {
|
||||
border-color: var(--color-primary, var(--color-primary));
|
||||
color: var(--color-primary, var(--color-primary));
|
||||
background: var(--color-primary-soft, rgba(240, 68, 148, 0.1));
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
/* Panels */
|
||||
.aksrt-modal-auth__panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__panel.is-active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
/* Responsive */
|
||||
@media (max-width: 480px) {
|
||||
.aksrt-modal-auth__card {
|
||||
padding: 24px 20px;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__logo {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__title {
|
||||
font-size: 18px;
|
||||
}
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
/* Tabs — under colorful header */
|
||||
.aksrt-modal-auth__tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--main-border-color, var(--color-line));
|
||||
margin: 8px 0 16px;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__tab {
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 15px;
|
||||
font-weight: normal;
|
||||
color: var(--muted-color, var(--color-ink-3));
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
transition: color 0.15s, opacity 0.15s;
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__tab:hover {
|
||||
opacity: 0.8;
|
||||
color: var(--focus-color, var(--color-primary));
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__tab.is-active {
|
||||
color: var(--focus-color, var(--color-primary));
|
||||
}
|
||||
|
||||
.aksrt-modal-auth__tab.is-active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 20%;
|
||||
right: 20%;
|
||||
height: 2px;
|
||||
background: var(--focus-color, var(--color-primary));
|
||||
border-radius: 2px;
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
/**
|
||||
* AKSRT Auth Pages — 认证页面样式
|
||||
*
|
||||
* 类名对齐实际模板:auth-shell, auth-card, auth-form, form-field, input-wrap
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
/* 认证页面容器 */
|
||||
.auth-shell {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: calc(100vh - 200px);
|
||||
padding: 40px 16px;
|
||||
}
|
||||
|
||||
/* 认证卡片 */
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 440px;
|
||||
background: var(--surface-1);
|
||||
border: none;
|
||||
border-radius: var(--radius-2xl, 16px);
|
||||
padding: 32px;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.auth-card__logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: var(--color-primary);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.auth-card__title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
margin: 0 0 8px;
|
||||
color: var(--color-ink);
|
||||
}
|
||||
|
||||
.auth-card__subtitle {
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-3);
|
||||
margin: 0 0 24px;
|
||||
}
|
||||
|
||||
/* 表单 */
|
||||
.auth-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
/* 表单字段 */
|
||||
.form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.form-field label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
/* 输入框包装 */
|
||||
.input-wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-wrap input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 14px 0 40px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink);
|
||||
background: var(--surface-4);
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 12px;
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.input-wrap input:focus {
|
||||
outline: none;
|
||||
border-color: var(--color-primary);
|
||||
box-shadow: 0 0 2px 3px rgba(253, 83, 161, 0.4);
|
||||
}
|
||||
|
||||
.input-wrap input::placeholder {
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.input-icon {
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--color-ink-4);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* 密码切换按钮 */
|
||||
.toggle-pwd {
|
||||
position: absolute;
|
||||
right: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-ink-4);
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.toggle-pwd:hover {
|
||||
color: var(--color-ink-2);
|
||||
}
|
||||
|
||||
/* 密码字段输入框右侧留空 */
|
||||
.form-field--pwd .input-wrap input {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
/* 记住我 + 忘记密码 */
|
||||
.form-row-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-row-between a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.form-row-between a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* 错误提示 */
|
||||
.alert {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.alert svg {
|
||||
flex-shrink: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.alert--error {
|
||||
background: #fff2f0;
|
||||
border: 1px solid #ffccc7;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.alert--error a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 底部链接 */
|
||||
.auth-footer-link {
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 14px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.auth-footer-link a {
|
||||
color: var(--color-primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-footer-link a:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
/* OAuth 社交登录区域 */
|
||||
.aksrt-oauth-section {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.aksrt-oauth-divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
font-size: 13px;
|
||||
color: var(--color-ink-4);
|
||||
}
|
||||
|
||||
.aksrt-oauth-divider::before,
|
||||
.aksrt-oauth-divider::after {
|
||||
content: '';
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--color-line);
|
||||
}
|
||||
|
||||
.aksrt-oauth-buttons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.aksrt-oauth-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
border: none;
|
||||
background: var(--surface-4);
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--color-ink);
|
||||
font-size: 18px;
|
||||
text-decoration: none;
|
||||
transition: border-color 0.2s, color 0.2s, box-shadow 0.2s, background 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-oauth-btn:hover {
|
||||
border-color: var(--color-primary);
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
}
|
||||
|
||||
/* 成功提示 */
|
||||
.alert--success {
|
||||
background: #f6ffed;
|
||||
border: 1px solid #b7eb8f;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
/* 认证 Tab 切换 */
|
||||
.aksrt-auth-tabs {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--color-line-soft);
|
||||
}
|
||||
|
||||
.aksrt-auth-tab {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
border-radius: 999px;
|
||||
background: transparent;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
color: var(--color-ink-3);
|
||||
border-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.aksrt-auth-tab.is-active {
|
||||
color: var(--color-primary);
|
||||
background: var(--color-primary-soft);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.aksrt-auth-tab:hover {
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* 表单提示文本 */
|
||||
.form-field-hint {
|
||||
display: block;
|
||||
margin-top: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
/* 必填/选填标记 */
|
||||
.aksrt-required-mark {
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
.aksrt-optional-hint {
|
||||
color: var(--color-ink-3);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* 同意条款复选框 */
|
||||
.aksrt-terms-check {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 16px;
|
||||
cursor: pointer;
|
||||
color: var(--color-ink-3);
|
||||
}
|
||||
|
||||
.aksrt-terms-check input {
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
/* 提交按钮全宽 */
|
||||
.auth-form .btn-primary.btn-lg,
|
||||
.auth-form input[type="submit"].btn-primary.btn-lg {
|
||||
width: 100%;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 验证码输入行 */
|
||||
.aksrt-code-row {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.aksrt-code-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.aksrt-sms-trigger {
|
||||
white-space: nowrap;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
/* 响应式 */
|
||||
@media (max-width: 767px) {
|
||||
.auth-shell {
|
||||
padding: 24px 16px;
|
||||
min-height: calc(100vh - 150px);
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
padding: 24px 20px;
|
||||
}
|
||||
|
||||
.aksrt-auth-tab {
|
||||
font-size: 13px;
|
||||
}
|
||||
}
|
||||
+109
-288
@@ -1,295 +1,116 @@
|
||||
/**
|
||||
* AKSRT full-page sign shell (login / register / reset / bind).
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
.page-template-user-sign {
|
||||
min-height: 500px;
|
||||
}
|
||||
|
||||
.sign-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
padding: 70px 16px 40px;
|
||||
}
|
||||
|
||||
.sign-bg {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.sign-topbar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 2;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 12px 20px;
|
||||
background: color-mix(in srgb, var(--main-bg-color, #fff) 86%, transparent);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.sign-topbar a {
|
||||
color: var(--main-color, #4e5358);
|
||||
text-decoration: none;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sign-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
width: 100%;
|
||||
max-width: 420px;
|
||||
background: var(--main-bg-color, #fff);
|
||||
border-radius: var(--main-radius, 8px);
|
||||
box-shadow: 0 0 10px var(--main-shadow, rgba(116, 116, 116, 0.08));
|
||||
padding: 30px 35px;
|
||||
}
|
||||
|
||||
.sign-card--left { margin-right: auto; }
|
||||
.sign-card--right { margin-left: auto; }
|
||||
.sign-card--center { margin: 0 auto; }
|
||||
|
||||
.sign-logo {
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sign-logo img {
|
||||
height: 36px;
|
||||
}
|
||||
|
||||
.sign-logo__text {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 12px;
|
||||
background: var(--focus-color, #f04494);
|
||||
color: #fff;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.sign-tabs {
|
||||
display: flex;
|
||||
border-bottom: 2px solid var(--main-border-color, rgba(50, 50, 50, 0.06));
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sign-tab {
|
||||
flex: 1;
|
||||
padding: 10px 0;
|
||||
border: none;
|
||||
background: none;
|
||||
font-size: 15px;
|
||||
cursor: pointer;
|
||||
color: var(--muted-color, #777);
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -2px;
|
||||
transition: all .2s;
|
||||
text-align: center;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sign-tab.is-active {
|
||||
color: var(--focus-color, #f04494);
|
||||
border-bottom-color: var(--focus-color, #f04494);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.sign-tab:hover { color: var(--focus-color, #f04494); }
|
||||
|
||||
.sign-panel { display: none; }
|
||||
.sign-panel.is-active { display: block; }
|
||||
|
||||
.sign-error {
|
||||
background: rgba(239, 68, 68, 0.08);
|
||||
border: 1px solid rgba(239, 68, 68, 0.2);
|
||||
border-radius: var(--main-radius, 8px);
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
color: #ef4444;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sign-success {
|
||||
background: rgba(16, 185, 129, 0.08);
|
||||
border: 1px solid rgba(16, 185, 129, 0.2);
|
||||
border-radius: var(--main-radius, 8px);
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
color: #10b981;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.sign-notice {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--muted-2-color, #666);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.sign-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.sign-form .form-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.sign-form label {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
/* Shared Zibll-source authentication contract: modal and /sign/. */
|
||||
.sign {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
padding: 30px 0;
|
||||
color: var(--main-color, #4e5358);
|
||||
}
|
||||
|
||||
.sign-form .input-wrap { position: relative; }
|
||||
.sign [hidden] { display: none !important; }
|
||||
.sign button { font: inherit; }
|
||||
.sign-logo { margin-bottom: 24px; text-align: center; }
|
||||
.sign-logo img { max-width: 180px; max-height: 70px; }
|
||||
.sign-logo .sign-logo-dark { display: none; }
|
||||
.dark-theme .sign-logo .sign-logo-light { display: none; }
|
||||
.dark-theme .sign-logo .sign-logo-dark { display: inline-block; }
|
||||
.sign-logo__text { font-size: 22px; font-weight: 700; color: inherit; }
|
||||
.sign-panel { animation: auth-panel-in .24s ease both; }
|
||||
|
||||
.sign-form .input-wrap input {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
padding: 0 14px;
|
||||
border: 1px solid transparent;
|
||||
background: var(--body-bg-color, #f5f6f7);
|
||||
border-radius: var(--main-radius, 8px);
|
||||
font-size: 14px;
|
||||
color: var(--key-color, #333);
|
||||
transition: border-color .15s;
|
||||
@keyframes auth-panel-in {
|
||||
from { opacity: 0; transform: translateY(5px); }
|
||||
to { opacity: 1; transform: none; }
|
||||
}
|
||||
|
||||
.sign-form .input-wrap input:focus {
|
||||
outline: none;
|
||||
border-color: var(--focus-color, #f04494);
|
||||
box-shadow: 0 0 2px 3px var(--focus-shadow-color, rgba(253, 83, 161, 0.4));
|
||||
}
|
||||
|
||||
.sign-form .toggle-pwd {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: var(--muted-2-color, #999);
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.sign-form .form-row-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.sign-form .form-row-between a { color: var(--focus-color, #f04494); }
|
||||
|
||||
.sign-form .form-row-between label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
.sign-form .btn-submit {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: none;
|
||||
border-radius: var(--main-radius, 8px);
|
||||
background: var(--focus-color, #f04494);
|
||||
color: #fff;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: opacity .15s;
|
||||
}
|
||||
|
||||
.sign-form .btn-submit:hover { opacity: .85; }
|
||||
.sign-form .btn-submit:disabled { opacity: .5; cursor: not-allowed; }
|
||||
|
||||
.sign-hint {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
.sign-terms {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
.sign-terms a {
|
||||
color: var(--focus-color, #f04494);
|
||||
}
|
||||
|
||||
.sign-footer {
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: var(--muted-color, #777);
|
||||
}
|
||||
|
||||
.sign-footer a { color: var(--focus-color, #f04494); }
|
||||
|
||||
.sign-footer-bar {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #999);
|
||||
z-index: 1;
|
||||
background: color-mix(in srgb, var(--main-bg-color, #fff) 80%, transparent);
|
||||
}
|
||||
|
||||
.sign-oauth {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.sign-oauth__divider {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 14px;
|
||||
font-size: 12px;
|
||||
color: var(--muted-2-color, #999);
|
||||
}
|
||||
|
||||
.sign-oauth__divider::before,
|
||||
.sign-oauth__divider::after {
|
||||
content: "";
|
||||
flex: 1;
|
||||
height: 1px;
|
||||
background: var(--main-border-color, rgba(50, 50, 50, 0.08));
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.sign-card { padding: 24px 20px; }
|
||||
.sign-page { padding-top: 56px; }
|
||||
.sign-header { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 23px; }
|
||||
.sign-header h2 { margin: 0; font-size: 22px; line-height: 1.4; color: var(--key-color, #333); }
|
||||
.sign-header span { font-size: 12px; color: var(--muted-2-color, #999); }
|
||||
.sign-header button,
|
||||
.sign-options button,
|
||||
.sign-mode-switch { padding: 0; border: 0; background: none; color: var(--focus-color, #f04494); cursor: pointer; }
|
||||
.sign-form { display: grid; gap: 17px; }
|
||||
|
||||
.floating-input { position: relative; min-height: 42px; border-bottom: 1px solid var(--main-border-color, rgba(50, 50, 50, .12)); }
|
||||
.floating-input::after { content: ""; position: absolute; right: 0; bottom: -1px; left: 0; height: 2px; background: var(--focus-color, #f04494); transform: scaleX(0); transition: transform .25s; }
|
||||
.floating-input:focus-within::after { transform: scaleX(1); }
|
||||
.floating-input input { width: 100%; height: 42px; padding: 13px 4px 2px; border: 0; outline: 0; background: transparent; color: inherit; font-size: 14px; box-shadow: none !important; }
|
||||
.floating-input label { position: absolute; top: 12px; left: 4px; color: var(--muted-color, #777); font-size: 14px; font-weight: 400; pointer-events: none; transition: all .18s; }
|
||||
.floating-input input:focus + label,
|
||||
.floating-input input:not(:placeholder-shown) + label,
|
||||
.floating-input.is-filled label { top: 0; font-size: 10px; color: var(--focus-color, #f04494); }
|
||||
.floating-input.has-action input { padding-right: 94px; }
|
||||
.floating-input .password-show,
|
||||
.floating-input .send-code { position: absolute; right: 0; bottom: 6px; z-index: 2; border: 0; background: none; color: var(--muted-2-color, #999); cursor: pointer; }
|
||||
.floating-input .send-code { color: var(--focus-color, #f04494); font-size: 12px; }
|
||||
.floating-input .send-code:disabled { color: var(--muted-2-color, #999); cursor: default; }
|
||||
|
||||
.sign-options { display: flex; align-items: center; justify-content: space-between; color: var(--muted-2-color, #999); font-size: 12px; }
|
||||
.sign-options label { margin: 0; font-weight: 400; }
|
||||
.sign-submit { width: 100%; min-height: 42px; padding: 10px 16px; border: 0; border-radius: 5px; color: #fff; cursor: pointer; transition: opacity .2s, transform .2s; }
|
||||
.sign-submit:hover { opacity: .88; transform: translateY(-1px); }
|
||||
.sign-submit:disabled { opacity: .55; cursor: wait; transform: none; }
|
||||
.sign-submit.jb-blue { background: linear-gradient(135deg, #59c3ff, #2b70ff); }
|
||||
.sign-submit.jb-green { background: linear-gradient(135deg, #50e3a4, #16a085); }
|
||||
.sign-mode-switch { justify-self: center; font-size: 12px; }
|
||||
.sign-message,
|
||||
.sign-notice { margin-bottom: 16px; padding: 10px 12px; border-radius: 5px; background: rgba(255, 86, 86, .08); color: #e55353; font-size: 12px; }
|
||||
.sign-message.is-success { background: rgba(45, 194, 117, .1); color: #1e9d5d; }
|
||||
.sign-hint { margin-top: -10px; color: var(--muted-2-color, #999); font-size: 11px; }
|
||||
.sign-agreement { display: flex; align-items: flex-start; gap: 6px; margin: 0; color: var(--muted-2-color, #999); font-size: 11px; font-weight: 400; }
|
||||
.sign-agreement a { color: var(--focus-color, #f04494); }
|
||||
.sign-agreement-implicit { justify-content: center; text-align: center; }
|
||||
|
||||
.social-login { margin-top: 24px; text-align: center; }
|
||||
.social-login > span { display: flex; align-items: center; gap: 10px; color: var(--muted-2-color, #999); font-size: 11px; }
|
||||
.social-login > span::before,
|
||||
.social-login > span::after { content: ""; flex: 1; height: 1px; background: var(--main-border-color, rgba(50, 50, 50, .08)); }
|
||||
.social-login-buttons { display: flex; flex-wrap: wrap; justify-content: center; gap: 13px; margin-top: 14px; }
|
||||
.social-login-item { display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; padding: 0; border: 0; border-radius: 50px; background: var(--body-bg-color, #f5f6f7); color: #fff; text-decoration: none; cursor: pointer; }
|
||||
.social-login-item:hover { transform: translateY(-2px); color: var(--focus-color, #f04494); }
|
||||
.oauth-button-lg .social-login-item { width: auto; padding: 0 14px; }
|
||||
.oauth-button-lg .social-login-item i,
|
||||
.oauth-button-lg .social-login-item svg { margin-right: 6px; font-size: 1.2em; }
|
||||
.social-login-item.weibo { background: #ea5d5c; }
|
||||
.social-login-item.weixingzh,
|
||||
.social-login-item.weixin { background: #24b76a; }
|
||||
.social-login-item.qq,
|
||||
.social-login-item.alipay { background: #1296db; }
|
||||
.social-login-item.baidu { background: #4e6ef2; }
|
||||
.social-login-item.github { background: #505050; }
|
||||
.social-login-item.gitee { background: #c2292f; }
|
||||
.social-login-item.google { background: var(--main-shadow, rgba(116, 116, 116, .08)); color: var(--main-color, #4e5358); }
|
||||
.auth-qrcode { display: grid; place-items: center; width: 180px; min-height: 180px; margin: 20px auto; background: #fff; color: #38b766; text-align: center; }
|
||||
.auth-qrcode i { font-size: 46px; }
|
||||
.auth-qrcode span { color: #777; font-size: 12px; }
|
||||
|
||||
.aksrt-auth-modal { position: fixed; inset: 0; z-index: 100050; display: none; place-items: center; padding: 18px; }
|
||||
.aksrt-auth-modal.is-open { display: grid; }
|
||||
.auth-modal-backdrop { position: absolute; inset: 0; background: rgba(14, 18, 24, .62); backdrop-filter: blur(2px); }
|
||||
#u_sign .modal-dialog { position: relative; z-index: 1; width: 400px; max-width: calc(100vw - 30px); margin: 0; }
|
||||
#u_sign.has-sign-img .modal-dialog { width: 800px; }
|
||||
#u_sign .modal-content { position: relative; min-height: 300px; max-height: calc(100vh - 36px); overflow-x: hidden; overflow-y: auto; border: 0; border-radius: 8px; background: var(--main-bg-color, #fff); box-shadow: 0 12px 50px rgba(0, 0, 0, .22); }
|
||||
.auth-modal-close { position: absolute; top: 13px; right: 15px; z-index: 4; width: 30px; height: 30px; border: 0; border-radius: 50%; background: rgba(0, 0, 0, .04); color: var(--muted-color, #777); cursor: pointer; }
|
||||
.sign-img { position: absolute; inset: 0 55% 0 0; background-position: center; background-size: cover; }
|
||||
.sign-img + .sign { max-width: unset; margin-left: 55%; padding: 34px 44px; }
|
||||
|
||||
.page-template-user-sign { min-height: 100vh; background: var(--body-bg-color, #f5f6f7); }
|
||||
.sign-page-background { position: fixed; inset: 0; z-index: 0; background-position: center; background-size: cover; }
|
||||
.sign-topbar { position: fixed; top: 0; right: 0; left: 0; z-index: 3; display: flex; justify-content: space-between; padding: 15px 24px; background: rgba(255, 255, 255, .75); backdrop-filter: blur(12px); }
|
||||
.sign-page { position: relative; z-index: 1; display: flex; align-items: center; min-height: 100vh; padding: 76px max(5vw, 20px) 56px; }
|
||||
.sign-page.sign-card-position-left { justify-content: flex-start; }
|
||||
.sign-page.sign-card-position-center { justify-content: center; }
|
||||
.sign-page.sign-card-position-right { justify-content: flex-end; }
|
||||
.sign-page-card { width: 400px; max-width: 100%; padding: 0 35px; border-radius: 8px; background: var(--main-bg-color, #fff); box-shadow: 0 10px 40px rgba(0, 0, 0, .12); }
|
||||
.sign-page-footer { position: fixed; right: 0; bottom: 12px; left: 0; z-index: 2; text-align: center; color: var(--muted-2-color, #999); font-size: 12px; }
|
||||
.white-theme { --main-bg-color: #fff; --body-bg-color: #f5f6f7; --main-color: #4e5358; --key-color: #333; }
|
||||
.dark-theme { --main-bg-color: #24272b; --body-bg-color: #17191c; --main-color: #d7d9dc; --key-color: #fff; --main-border-color: rgba(255, 255, 255, .12); }
|
||||
body.auth-modal-open { overflow: hidden; }
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#u_sign .modal-dialog,
|
||||
#u_sign.has-sign-img .modal-dialog { width: 400px; }
|
||||
.sign-img { display: none; }
|
||||
.sign-img + .sign { max-width: 340px; margin-left: auto; padding: 30px 0; }
|
||||
#u_sign .modal-content { padding: 0 24px; }
|
||||
.sign-page { justify-content: center !important; padding-right: 14px; padding-left: 14px; }
|
||||
.sign-page-card { padding: 0 24px; }
|
||||
}
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
/** Shared authentication state machine for modal and /sign/. */
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
var config = window.aksrtData || {};
|
||||
var apiRoot = String(config.apiUrl || config.restUrl || '/wp-json/aksrt/v1').replace(/\/$/, '');
|
||||
var nonce = config.restNonce || '';
|
||||
|
||||
function request(path, data) {
|
||||
return fetch(apiRoot + '/auth/' + path, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
||||
body: JSON.stringify(data),
|
||||
}).then(function (response) {
|
||||
return response.json().catch(function () { return {}; }).then(function (body) {
|
||||
if (!response.ok || body.success === false) {
|
||||
throw new Error(body.message || (body.data && body.data.message) || '请求失败,请稍后再试');
|
||||
}
|
||||
return body.data || body;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formData(form) {
|
||||
var data = {};
|
||||
new FormData(form).forEach(function (value, key) {
|
||||
data[key] = value;
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function showMessage(root, text, success) {
|
||||
var active = root.querySelector('[data-auth-panel]:not([hidden])');
|
||||
var box = active && active.querySelector('[data-auth-message]');
|
||||
if (!box) return;
|
||||
box.textContent = text || '';
|
||||
box.hidden = !text;
|
||||
box.classList.toggle('is-success', !!success);
|
||||
}
|
||||
|
||||
function activate(root, tab, updateUrl) {
|
||||
var panel = root.querySelector('[data-auth-panel="' + tab + '"]');
|
||||
if (!panel) return;
|
||||
root.querySelectorAll('[data-auth-panel]').forEach(function (item) { item.hidden = item !== panel; });
|
||||
root.dataset.activeTab = tab;
|
||||
showMessage(root, '', false);
|
||||
if (updateUrl && root.dataset.surface === 'page' && window.history && window.URL) {
|
||||
var url = new URL(window.location.href);
|
||||
url.searchParams.set('tab', tab);
|
||||
window.history.replaceState({}, '', url.toString());
|
||||
}
|
||||
var focus = panel.querySelector('input:not([type="hidden"])');
|
||||
if (focus) window.setTimeout(function () { focus.focus(); }, 30);
|
||||
if (tab === 'oauth') loadQr(root);
|
||||
}
|
||||
|
||||
function loadQr(root) {
|
||||
var box = root.querySelector('[data-auth-qrcode]');
|
||||
if (!box || box.dataset.loading === '1' || box.dataset.loaded === '1') return;
|
||||
box.dataset.loading = '1';
|
||||
request('qrcode-login', {}).then(function (result) {
|
||||
box.dataset.loading = '0';
|
||||
box.dataset.loaded = '1';
|
||||
box.innerHTML = result.qr_html || '';
|
||||
if (result.mode === 'scan' && result.token) pollQr(root, box, result.token, 0);
|
||||
}).catch(function (error) {
|
||||
box.dataset.loading = '0';
|
||||
box.innerHTML = '<span>' + String(error.message).replace(/[&<>"']/g, '') + '</span>';
|
||||
});
|
||||
}
|
||||
|
||||
function pollQr(root, box, token, count) {
|
||||
if (!document.documentElement.contains(box) || count >= 100) return;
|
||||
window.setTimeout(function () {
|
||||
request('check-qrcode-login', { token: token }).then(function (result) {
|
||||
if (result.logged_in) {
|
||||
showMessage(root, '登录成功', true);
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
return;
|
||||
}
|
||||
if (result.status === 'expired') {
|
||||
box.dataset.loaded = '0';
|
||||
box.innerHTML = '<button type="button" data-auth-refresh-qr>二维码已过期,点击刷新</button>';
|
||||
return;
|
||||
}
|
||||
pollQr(root, box, token, count + 1);
|
||||
}).catch(function () { pollQr(root, box, token, count + 1); });
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function submitQrCode(root, form) {
|
||||
var data = formData(form);
|
||||
var button = form.querySelector('button[type="submit"]');
|
||||
if (button) button.disabled = true;
|
||||
request('weixingzh-code', data).then(function (result) {
|
||||
showMessage(root, '登录成功', true);
|
||||
window.location.assign(result.redirect || window.location.href);
|
||||
}).catch(function (error) {
|
||||
if (button) button.disabled = false;
|
||||
showMessage(root, error.message, false);
|
||||
});
|
||||
}
|
||||
|
||||
function setBusy(form, busy) {
|
||||
var button = form.querySelector('.sign-submit');
|
||||
if (!button) return;
|
||||
button.disabled = busy;
|
||||
var spinner = button.querySelector('.fa-spinner');
|
||||
if (spinner) spinner.hidden = !busy;
|
||||
}
|
||||
|
||||
function startCountdown(button, seconds) {
|
||||
var original = button.dataset.label || button.textContent;
|
||||
button.dataset.label = original;
|
||||
button.disabled = true;
|
||||
var left = Math.max(1, Number(seconds) || 60);
|
||||
button.textContent = left + 's';
|
||||
var timer = window.setInterval(function () {
|
||||
left -= 1;
|
||||
if (left <= 0) {
|
||||
window.clearInterval(timer);
|
||||
button.disabled = false;
|
||||
button.textContent = original;
|
||||
} else {
|
||||
button.textContent = left + 's';
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function captchaScene(scene) {
|
||||
return scene === 'resetpassword' ? 'lostpassword' : scene;
|
||||
}
|
||||
|
||||
function verifyCaptcha(scene, callback) {
|
||||
var state = window.AKSRTCaptchaState;
|
||||
var captcha = state && state.scenes ? state.scenes[captchaScene(scene)] : null;
|
||||
if (!captcha || typeof captcha.verify !== 'function') {
|
||||
callback(true);
|
||||
return;
|
||||
}
|
||||
captcha.verify(function (verified) { callback(verified !== false); });
|
||||
}
|
||||
|
||||
function resetCaptcha(scene) {
|
||||
var state = window.AKSRTCaptchaState;
|
||||
var captcha = state && state.scenes ? state.scenes[captchaScene(scene)] : null;
|
||||
if (captcha && typeof captcha.reset === 'function') captcha.reset();
|
||||
}
|
||||
|
||||
function sendCode(root, button) {
|
||||
var form = button.closest('form');
|
||||
var target = form && form.querySelector('[name="target"]');
|
||||
if (!target || !target.value.trim()) {
|
||||
showMessage(root, '请先填写邮箱或手机号', false);
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
var payload = formData(form);
|
||||
payload.scene = button.dataset.sendCode;
|
||||
payload.target = target.value.trim();
|
||||
payload.channel = (form.querySelector('[name="channel"]') || {}).value || '';
|
||||
verifyCaptcha(payload.scene, function (verified) {
|
||||
if (!verified) {
|
||||
button.disabled = false;
|
||||
return;
|
||||
}
|
||||
payload = formData(form);
|
||||
payload.scene = button.dataset.sendCode;
|
||||
payload.target = target.value.trim();
|
||||
payload.channel = (form.querySelector('[name="channel"]') || {}).value || '';
|
||||
request('code', payload).then(function (result) {
|
||||
showMessage(root, result.message || '验证码已发送', true);
|
||||
startCountdown(button, result.countdown || 60);
|
||||
}).catch(function (error) {
|
||||
button.disabled = false;
|
||||
showMessage(root, error.message, false);
|
||||
}).finally(function () {
|
||||
resetCaptcha(payload.scene);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function submit(root, form) {
|
||||
var action = form.dataset.authForm;
|
||||
var payload = formData(form);
|
||||
if (action === 'register' && payload.channel !== 'phone') {
|
||||
payload.user_email = payload.target;
|
||||
}
|
||||
setBusy(form, true);
|
||||
showMessage(root, '', false);
|
||||
request(action, payload).then(function (result) {
|
||||
showMessage(root, result.message || '操作成功', true);
|
||||
if (result.redirect) window.setTimeout(function () { window.location.assign(result.redirect); }, 350);
|
||||
}).catch(function (error) {
|
||||
showMessage(root, error.message, false);
|
||||
setBusy(form, false);
|
||||
});
|
||||
}
|
||||
|
||||
function toggleLoginMode(root, button) {
|
||||
var password = root.querySelector('[data-auth-password-fields]');
|
||||
var code = root.querySelector('[data-auth-code-fields]');
|
||||
if (!password || !code) return;
|
||||
var showCode = code.hidden;
|
||||
code.hidden = !showCode;
|
||||
password.hidden = showCode;
|
||||
button.textContent = showCode ? '使用账号密码登录' : '使用验证码登录';
|
||||
}
|
||||
|
||||
function initRoot(root) {
|
||||
root.querySelectorAll('input').forEach(function (input) {
|
||||
if (!input.hasAttribute('placeholder')) input.setAttribute('placeholder', ' ');
|
||||
});
|
||||
var initial = root.dataset.initialTab || 'signin';
|
||||
if (initial === 'oauth' && window.matchMedia && window.matchMedia('(max-width: 767px)').matches) initial = 'signin';
|
||||
activate(root, initial, false);
|
||||
root.addEventListener('click', function (event) {
|
||||
var tab = event.target.closest('[data-auth-tab]');
|
||||
if (tab) { event.preventDefault(); activate(root, tab.dataset.authTab, true); return; }
|
||||
var password = event.target.closest('[data-password-show]');
|
||||
if (password) {
|
||||
var input = password.closest('.floating-input').querySelector('input');
|
||||
input.type = input.type === 'password' ? 'text' : 'password';
|
||||
password.querySelector('i').className = input.type === 'password' ? 'fa fas fa-eye' : 'fa fas fa-eye-slash';
|
||||
return;
|
||||
}
|
||||
var send = event.target.closest('[data-send-code]');
|
||||
if (send) { event.preventDefault(); sendCode(root, send); return; }
|
||||
var mode = event.target.closest('[data-login-mode]');
|
||||
if (mode) { event.preventDefault(); toggleLoginMode(root, mode); }
|
||||
var refreshQr = event.target.closest('[data-auth-refresh-qr]');
|
||||
if (refreshQr) { event.preventDefault(); loadQr(root); }
|
||||
});
|
||||
root.addEventListener('submit', function (event) {
|
||||
var qrForm = event.target.closest('[data-aksrt-weixingzh-code]');
|
||||
if (qrForm) {
|
||||
event.preventDefault();
|
||||
if (!qrForm.reportValidity || qrForm.reportValidity()) submitQrCode(root, qrForm);
|
||||
return;
|
||||
}
|
||||
var form = event.target.closest('[data-auth-form]');
|
||||
if (!form) return;
|
||||
event.preventDefault();
|
||||
if (form.reportValidity && !form.reportValidity()) return;
|
||||
submit(root, form);
|
||||
});
|
||||
}
|
||||
|
||||
var modal;
|
||||
function authPageUrl(tab, redirect) {
|
||||
var routes = config.routes || {};
|
||||
var base = tab === 'signup' ? routes.register : routes.login;
|
||||
var url = new URL(base || '/sign/', window.location.href);
|
||||
url.searchParams.set('tab', tab || 'signin');
|
||||
if (redirect) url.searchParams.set('redirect_to', redirect);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function openModal(tab, redirect) {
|
||||
if (!modal) {
|
||||
window.location.assign(authPageUrl(tab, redirect));
|
||||
return;
|
||||
}
|
||||
modal.classList.add('is-open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.classList.add('auth-modal-open');
|
||||
var root = modal.querySelector('[data-aksrt-auth]');
|
||||
if (root) {
|
||||
activate(root, tab || 'signin', false);
|
||||
var redirectInput = root.querySelector('[name="redirect_to"]');
|
||||
if (redirectInput && redirect) redirectInput.value = redirect;
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal(storeDismissal) {
|
||||
if (!modal) return;
|
||||
modal.classList.remove('is-open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
document.body.classList.remove('auth-modal-open');
|
||||
if (storeDismissal && Number(modal.dataset.autoExpires) > 0) {
|
||||
var until = Date.now() + Number(modal.dataset.autoExpires) * 3600000;
|
||||
window.localStorage.setItem('aksrtAuthAutoUntil', String(until));
|
||||
}
|
||||
}
|
||||
|
||||
function init() {
|
||||
document.querySelectorAll('[data-aksrt-auth]').forEach(initRoot);
|
||||
modal = document.querySelector('[data-auth-modal]');
|
||||
document.addEventListener('click', function (event) {
|
||||
var trigger = event.target.closest('[data-aksrt-auth-required], .aksrt-login-trigger, #nav-login-btn, .signin-loader, .signup-loader, .qrcode-signin');
|
||||
if (!trigger) return;
|
||||
event.preventDefault();
|
||||
var tab = trigger.classList.contains('signup-loader') ? 'signup' : (trigger.classList.contains('qrcode-signin') ? 'oauth' : 'signin');
|
||||
openModal(tab, trigger.dataset.authRedirect || '');
|
||||
});
|
||||
if (modal) {
|
||||
modal.addEventListener('click', function (event) {
|
||||
if (event.target.closest('[data-auth-close]')) closeModal(true);
|
||||
});
|
||||
document.addEventListener('keydown', function (event) { if (event.key === 'Escape') closeModal(true); });
|
||||
if (modal.dataset.auto === '1' && Number(window.localStorage.getItem('aksrtAuthAutoUntil') || 0) < Date.now()) {
|
||||
window.setTimeout(function () { openModal(modal.querySelector('[data-aksrt-auth]').dataset.initialTab || 'signin'); }, Math.max(1, Number(modal.dataset.autoWait) || 5) * 1000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', init);
|
||||
else init();
|
||||
}());
|
||||
@@ -1,610 +0,0 @@
|
||||
/**
|
||||
* AKSRT modal auth interactions.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Resolve REST root for aksrt/v1.
|
||||
* Prefer aksrtData.restUrl (works with plain permalinks: index.php?rest_route=/aksrt/v1).
|
||||
* Do not hardcode /wp-json when rewrite rules are off — that 404s on Apache.
|
||||
*/
|
||||
function resolveApiRoot() {
|
||||
var d = window.aksrtData || {};
|
||||
var raw = d.apiUrl || d.restUrl || d.rest_url || '';
|
||||
if (!raw) {
|
||||
raw = '/index.php?rest_route=/aksrt/v1';
|
||||
}
|
||||
raw = String(raw).trim();
|
||||
// restUrl may be site root rest_url() without namespace in some localizes — ensure aksrt/v1.
|
||||
if (/rest_route=\/?$/.test(raw) || /\/wp-json\/?$/.test(raw)) {
|
||||
if (raw.indexOf('rest_route=') !== -1) {
|
||||
raw = raw.replace(/rest_route=\/?$/, 'rest_route=/aksrt/v1');
|
||||
} else {
|
||||
raw = raw.replace(/\/?$/, '/') + 'aksrt/v1';
|
||||
}
|
||||
}
|
||||
return raw.replace(/\/$/, '');
|
||||
}
|
||||
|
||||
var API_ROOT = resolveApiRoot();
|
||||
var NONCE = (window.aksrtData && window.aksrtData.restNonce) || '';
|
||||
|
||||
var modal = null;
|
||||
var currentTab = 'login';
|
||||
|
||||
function init() {
|
||||
modal = document.getElementById('aksrt-modal-auth');
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
var closeBtn = modal.querySelector('.aksrt-modal-auth__close');
|
||||
var overlay = modal.querySelector('.aksrt-modal-auth__overlay');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', close);
|
||||
}
|
||||
if (overlay) {
|
||||
overlay.addEventListener('click', close);
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', function (event) {
|
||||
if (event.key === 'Escape' && modal.classList.contains('is-open')) {
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__tab').forEach(function (tab) {
|
||||
tab.addEventListener('click', function () {
|
||||
switchTab(this.dataset.tab || 'login');
|
||||
});
|
||||
});
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__switch').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
switchTab(this.dataset.switch || 'login');
|
||||
});
|
||||
});
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__pass-toggle').forEach(function (button) {
|
||||
button.addEventListener('click', function () {
|
||||
var input = this.parentElement.querySelector('input');
|
||||
var icon = this.querySelector('i');
|
||||
if (!input || !icon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
icon.className = 'fa fas fa-eye-slash';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
icon.className = 'fa fas fa-eye';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
var loginForm = document.getElementById('aksrt-modal-login-form');
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
handleLogin(loginForm);
|
||||
});
|
||||
}
|
||||
|
||||
var registerForm = document.getElementById('aksrt-modal-register-form');
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener('submit', function (event) {
|
||||
event.preventDefault();
|
||||
handleRegister(registerForm);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('click', function (event) {
|
||||
var trigger = event.target.closest('[data-aksrt-auth-required], .aksrt-login-trigger, #nav-login-btn, .signin-loader, .signup-loader, .qrcode-signin');
|
||||
if (!trigger) {
|
||||
return;
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
if (trigger.classList.contains('signup-loader')) {
|
||||
open(trigger.dataset.authRedirect || '');
|
||||
switchTab('register');
|
||||
return;
|
||||
}
|
||||
if (trigger.classList.contains('qrcode-signin')) {
|
||||
open(trigger.dataset.authRedirect || '');
|
||||
switchTab('qrcode');
|
||||
return;
|
||||
}
|
||||
|
||||
open(trigger.dataset.authRedirect || '');
|
||||
});
|
||||
|
||||
initQrLogin();
|
||||
initAgreementShake();
|
||||
initSocialPriority();
|
||||
}
|
||||
|
||||
function open(redirectUrl) {
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.classList.add('is-open');
|
||||
modal.setAttribute('aria-hidden', 'false');
|
||||
document.body.style.overflow = 'hidden';
|
||||
|
||||
if (redirectUrl) {
|
||||
modal.dataset.redirect = redirectUrl;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
var firstInput = modal.querySelector('.aksrt-modal-auth__panel.is-active input:not([type="hidden"])');
|
||||
if (firstInput) {
|
||||
firstInput.focus();
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
var qrLoginTimer = null;
|
||||
|
||||
function close() {
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.classList.remove('is-open');
|
||||
modal.setAttribute('aria-hidden', 'true');
|
||||
document.body.style.overflow = '';
|
||||
clearMessages();
|
||||
stopQrPolling();
|
||||
}
|
||||
|
||||
function switchTab(tab) {
|
||||
currentTab = tab;
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__tab').forEach(function (button) {
|
||||
var isActive = button.dataset.tab === tab;
|
||||
button.classList.toggle('is-active', isActive);
|
||||
button.setAttribute('aria-selected', isActive ? 'true' : 'false');
|
||||
});
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__panel').forEach(function (panel) {
|
||||
panel.classList.toggle('is-active', panel.dataset.panel === tab);
|
||||
});
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__footer-text').forEach(function (row) {
|
||||
row.hidden = row.dataset.for !== tab;
|
||||
});
|
||||
|
||||
modal.setAttribute('aria-label', tab === 'register' ? '注册' : '登录');
|
||||
clearMessages();
|
||||
}
|
||||
|
||||
function withCaptcha(scene, form, next) {
|
||||
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
|
||||
? window.AKSRTCaptchaState.scenes
|
||||
: null;
|
||||
var instance = store && store[scene] ? store[scene] : null;
|
||||
if (instance && typeof instance.verify === 'function') {
|
||||
instance.verify(function () {
|
||||
next(serializeForm(form));
|
||||
});
|
||||
return;
|
||||
}
|
||||
next(serializeForm(form));
|
||||
}
|
||||
|
||||
function resetCaptcha(scene) {
|
||||
try {
|
||||
var store = window.AKSRTCaptchaState && window.AKSRTCaptchaState.scenes
|
||||
? window.AKSRTCaptchaState.scenes
|
||||
: null;
|
||||
if (store && store[scene] && typeof store[scene].reset === 'function') {
|
||||
store[scene].reset();
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
}
|
||||
|
||||
function handleLogin(form) {
|
||||
if (!form.querySelector('[name="log"]') || !form.querySelector('[name="pwd"]')) {
|
||||
showMessage('login', '请输入用户名和密码', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(form, true);
|
||||
clearMessages();
|
||||
|
||||
withCaptcha('login', form, function (payload) {
|
||||
if (!payload.log || !payload.pwd) {
|
||||
setLoading(form, false);
|
||||
showMessage('login', '请输入用户名和密码', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
request('/auth/modal-login', payload)
|
||||
.then(function (result) {
|
||||
if (result.ok && result.data.success) {
|
||||
onAuthSuccess(result.data);
|
||||
return;
|
||||
}
|
||||
|
||||
resetCaptcha('login');
|
||||
var message = result.data.message || '登录失败';
|
||||
if (result.data.verify_url) {
|
||||
message += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
|
||||
}
|
||||
showMessage('login', message, 'error');
|
||||
})
|
||||
.catch(function () {
|
||||
resetCaptcha('login');
|
||||
showMessage('login', '网络错误,请稍后重试', 'error');
|
||||
})
|
||||
.finally(function () {
|
||||
setLoading(form, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleRegister(form) {
|
||||
setLoading(form, true);
|
||||
clearMessages();
|
||||
|
||||
withCaptcha('register', form, function (payload) {
|
||||
if (!payload.user_login || !payload.user_email || !payload.user_pass || !payload.user_pass_confirm) {
|
||||
setLoading(form, false);
|
||||
showMessage('register', '请完整填写注册信息', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (payload.user_pass !== payload.user_pass_confirm) {
|
||||
setLoading(form, false);
|
||||
showMessage('register', '两次输入的密码不一致', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
request('/auth/modal-register', payload)
|
||||
.then(function (result) {
|
||||
if (!result.ok || !result.data.success) {
|
||||
resetCaptcha('register');
|
||||
showMessage('register', result.data.message || '注册失败', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.data.user) {
|
||||
onAuthSuccess(result.data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.data.verification_required) {
|
||||
var verifyMessage = result.data.message || '注册成功,请完成邮箱验证';
|
||||
if (result.data.notice) verifyMessage += '<br>' + escapeAttribute(result.data.notice);
|
||||
if (result.data.verify_url) {
|
||||
verifyMessage += ' <a href="' + escapeAttribute(result.data.verify_url) + '">前往验证</a>';
|
||||
}
|
||||
showMessage('register', verifyMessage, 'success');
|
||||
form.reset();
|
||||
resetCaptcha('register');
|
||||
return;
|
||||
}
|
||||
|
||||
var loginMessage = result.data.message || '注册成功,请登录';
|
||||
if (result.data.notice) loginMessage += ' ' + result.data.notice;
|
||||
switchTab('login');
|
||||
prefillLogin(result.data.user_login || result.data.user_email || '');
|
||||
showMessage('login', loginMessage, 'success');
|
||||
form.reset();
|
||||
resetCaptcha('register');
|
||||
})
|
||||
.catch(function () {
|
||||
resetCaptcha('register');
|
||||
showMessage('register', '网络错误,请稍后重试', 'error');
|
||||
})
|
||||
.finally(function () {
|
||||
setLoading(form, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function serializeForm(form) {
|
||||
var payload = {};
|
||||
var formData = new FormData(form);
|
||||
|
||||
formData.forEach(function (value, key) {
|
||||
if (payload[key] !== undefined) {
|
||||
if (!Array.isArray(payload[key])) {
|
||||
payload[key] = [payload[key]];
|
||||
}
|
||||
payload[key].push(value);
|
||||
} else {
|
||||
payload[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
||||
function request(path, payload) {
|
||||
var headers = { 'Content-Type': 'application/json' };
|
||||
if (NONCE) {
|
||||
headers['X-WP-Nonce'] = NONCE;
|
||||
}
|
||||
|
||||
var url = joinRestPath(API_ROOT, path);
|
||||
|
||||
return fetch(url, {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify(payload),
|
||||
}).then(function (response) {
|
||||
return response.json().then(function (data) {
|
||||
return { ok: response.ok, data: data };
|
||||
}).catch(function () {
|
||||
return { ok: false, data: { message: '响应解析失败' } };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Join REST root + path for both pretty (/wp-json/aksrt/v1/x)
|
||||
* and plain (?rest_route=/aksrt/v1/x) permalinks.
|
||||
*/
|
||||
function joinRestPath(root, path) {
|
||||
path = String(path || '');
|
||||
if (path.charAt(0) !== '/') {
|
||||
path = '/' + path;
|
||||
}
|
||||
root = String(root || '').replace(/\/$/, '');
|
||||
if (root.indexOf('rest_route=') !== -1) {
|
||||
// e.g. https://host/index.php?rest_route=/aksrt/v1 + /auth/modal-login
|
||||
return root + path;
|
||||
}
|
||||
return root + path;
|
||||
}
|
||||
|
||||
function prefillLogin(value) {
|
||||
var loginInput = document.getElementById('modal-login-user');
|
||||
if (loginInput && value) {
|
||||
loginInput.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
function onAuthSuccess(data) {
|
||||
close();
|
||||
|
||||
if (data.user) {
|
||||
updateNavUser(data.user);
|
||||
}
|
||||
|
||||
notyf(data.message || '登录成功', 'success');
|
||||
|
||||
var redirect = modal.dataset.redirect || data.redirect;
|
||||
if (redirect && redirect !== window.location.href) {
|
||||
setTimeout(function () {
|
||||
window.location.href = redirect;
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 500);
|
||||
}
|
||||
|
||||
function updateNavUser(user) {
|
||||
document.querySelectorAll('.nav-user__avatar img, .nav-user__toggle img').forEach(function (image) {
|
||||
if (user.avatar_url) {
|
||||
image.src = user.avatar_url;
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('.nav-user__name').forEach(function (el) {
|
||||
if (user.display_name) {
|
||||
el.textContent = user.display_name;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function showMessage(panel, message, type) {
|
||||
var box = document.getElementById('aksrt-modal-' + panel + '-error');
|
||||
if (!box) {
|
||||
return;
|
||||
}
|
||||
|
||||
box.innerHTML = message;
|
||||
box.hidden = false;
|
||||
box.classList.toggle('is-success', type === 'success');
|
||||
}
|
||||
|
||||
function clearMessages() {
|
||||
if (!modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
modal.querySelectorAll('.aksrt-modal-auth__error').forEach(function (box) {
|
||||
box.hidden = true;
|
||||
box.innerHTML = '';
|
||||
box.classList.remove('is-success');
|
||||
});
|
||||
}
|
||||
|
||||
function setLoading(form, loading) {
|
||||
var submit = form.querySelector('.aksrt-modal-auth__submit');
|
||||
if (!submit) {
|
||||
return;
|
||||
}
|
||||
|
||||
var label = submit.querySelector('span');
|
||||
var spinner = submit.querySelector('i');
|
||||
submit.disabled = loading;
|
||||
|
||||
if (label) {
|
||||
label.hidden = loading;
|
||||
}
|
||||
if (spinner) {
|
||||
spinner.hidden = !loading;
|
||||
}
|
||||
}
|
||||
|
||||
function escapeAttribute(value) {
|
||||
return String(value || '')
|
||||
.replace(/&/g, '&')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>');
|
||||
}
|
||||
|
||||
// ── QR Code Login (§10.7) ──
|
||||
|
||||
|
||||
function bindWeixingzhCodeForm(container) {
|
||||
if (!container) return;
|
||||
var form = container.querySelector('[data-aksrt-weixingzh-code]');
|
||||
if (!form || form._bound) return;
|
||||
form._bound = true;
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var input = form.querySelector('[name="code"]');
|
||||
var code = input ? String(input.value || '').trim() : '';
|
||||
if (!code) return;
|
||||
var btn = form.querySelector('button[type="submit"]');
|
||||
if (btn) btn.disabled = true;
|
||||
request('/auth/weixingzh-code', { code: code })
|
||||
.then(function (result) {
|
||||
var data = (result && result.data) || result || {};
|
||||
if ((result && result.ok) || (result && result.success) || data.logged_in) {
|
||||
onAuthSuccess(data.data || data);
|
||||
return;
|
||||
}
|
||||
throw new Error((result && (result.message || result.msg)) || '验证码错误');
|
||||
})
|
||||
.catch(function (err) {
|
||||
alert(err.message || '登录失败');
|
||||
})
|
||||
.finally(function () {
|
||||
if (btn) btn.disabled = false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initQrLogin() {
|
||||
var qrPanel = modal.querySelector('[data-panel="qrcode"]');
|
||||
if (!qrPanel) return;
|
||||
|
||||
var qrContainer = qrPanel.querySelector('.aksrt-modal-auth__qr-code');
|
||||
if (!qrContainer) return;
|
||||
|
||||
// When qrcode tab is activated, fetch QR and start polling
|
||||
var observer = new MutationObserver(function () {
|
||||
if (qrPanel.classList.contains('is-active')) {
|
||||
fetchQrCode(qrContainer);
|
||||
} else {
|
||||
stopQrPolling();
|
||||
}
|
||||
});
|
||||
observer.observe(qrPanel, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
|
||||
function fetchQrCode(container) {
|
||||
container.innerHTML = '<div class="aksrt-modal-auth__qr-loading"><i class="fa fas fa-spinner fa-spin"></i></div>';
|
||||
|
||||
request('/auth/qrcode-login', {})
|
||||
.then(function (result) {
|
||||
var payload = (result && result.data) || result || {};
|
||||
var qrHtml = payload.qr_html || (payload.data && payload.data.qr_html) || '';
|
||||
var checkUrl = payload.check_url || (payload.data && payload.data.check_url) || '/auth/check-qrcode-login';
|
||||
var token = payload.token || (payload.data && payload.data.token) || '';
|
||||
if (qrHtml) {
|
||||
container.innerHTML = qrHtml;
|
||||
bindWeixingzhCodeForm(container);
|
||||
if (checkUrl && token) {
|
||||
startQrPolling(checkUrl, token);
|
||||
}
|
||||
} else {
|
||||
container.innerHTML = '<p class="aksrt-modal-auth__qr-error">扫码登录暂不可用</p>';
|
||||
}
|
||||
})
|
||||
.catch(function () {
|
||||
container.innerHTML = '<p class="aksrt-modal-auth__qr-error">网络错误</p>';
|
||||
});
|
||||
}
|
||||
|
||||
function startQrPolling(checkUrl, token) {
|
||||
stopQrPolling();
|
||||
qrLoginTimer = setInterval(function () {
|
||||
request(checkUrl, { token: token })
|
||||
.then(function (result) {
|
||||
var data = (result && result.data) || result || {};
|
||||
var body = data.data || data;
|
||||
if ((result && result.ok) || data.success || body.logged_in) {
|
||||
if (body.logged_in || (data.data && data.data.logged_in)) {
|
||||
stopQrPolling();
|
||||
onAuthSuccess(body.data || body);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(function () { /* keep polling */ });
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
function stopQrPolling() {
|
||||
if (qrLoginTimer) {
|
||||
clearInterval(qrLoginTimer);
|
||||
qrLoginTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Social Login Priority (§10.7) ──
|
||||
|
||||
function initSocialPriority() {
|
||||
var wxPriority = window.aksrtData && aksrtData.signinWxPriority;
|
||||
if (wxPriority && modal) {
|
||||
var wxBtn = modal.querySelector('.social-login-item.weixingzh, .social-login-item[data-provider="wechat"]');
|
||||
if (wxBtn) {
|
||||
setTimeout(function () { wxBtn.click(); }, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── Agreement Check: shake instead of disable (§10.7) ──
|
||||
|
||||
function initAgreementShake() {
|
||||
if (!modal) return;
|
||||
modal.querySelectorAll('.aksrt-modal-auth__submit').forEach(function (btn) {
|
||||
btn.addEventListener('click', function (e) {
|
||||
var form = btn.closest('form');
|
||||
if (!form) return;
|
||||
var agree = form.querySelector('[name="agree_terms"], [name="user_agreement"]');
|
||||
if (agree && !agree.checked) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
agree.closest('label, .aksrt-modal-auth__agreement').classList.add('ani', 'shake');
|
||||
notyf('请先同意用户协议', 'warning');
|
||||
setTimeout(function () {
|
||||
agree.closest('label, .aksrt-modal-auth__agreement').classList.remove('ani', 'shake');
|
||||
}, 400);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ── Password Visibility: unified (§10.7) ──
|
||||
// Already handled above; added unification note.
|
||||
|
||||
window.AksrtAuth = {
|
||||
open: open,
|
||||
close: close,
|
||||
};
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
} else {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
@@ -901,6 +901,26 @@
|
||||
if (!form) return;
|
||||
if (form.dataset.aksrtSettingsInitialized === 'true') return;
|
||||
form.dataset.aksrtSettingsInitialized = 'true';
|
||||
var sendButton = form.querySelector('[data-aksrt-sms-trigger]');
|
||||
if (sendButton) sendButton.addEventListener('click', function () {
|
||||
var phone = String((form.querySelector('[name="phone"]') || {}).value || '').trim();
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) { notyf('请输入正确的手机号', 'warning'); return; }
|
||||
var toastId = 'aksrt-settings-phone-code-toast';
|
||||
var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1';
|
||||
var nonce = (window.aksrtData && aksrtData.restNonce) || '';
|
||||
sendButton.disabled = true;
|
||||
notyf('正在发送验证码…', 'load', '', toastId);
|
||||
fetch(String(restRoot).replace(/\/$/, '') + '/user/phone-code', {
|
||||
method: 'POST', credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': nonce },
|
||||
body: JSON.stringify({ phone: phone })
|
||||
}).then(function (r) { return r.json().then(function (j) { return { ok: r.ok, json: j }; }); }).then(function (res) {
|
||||
if (!res.ok || !res.json.success) throw new Error(res.json.message || '发送失败');
|
||||
notyf(res.json.message || '验证码已发送', 'success', 5, toastId);
|
||||
var left = Number((res.json.data && res.json.data.countdown) || 60);
|
||||
var timer = setInterval(function () { left -= 1; sendButton.textContent = left + 's'; if (left <= 0) { clearInterval(timer); sendButton.disabled = false; sendButton.textContent = '获取验证码'; } }, 1000);
|
||||
}).catch(function (error) { sendButton.disabled = false; notyf(error.message || '发送失败', 'danger', 5, toastId); });
|
||||
});
|
||||
form.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
var phoneEl = form.querySelector('[name="phone"]');
|
||||
@@ -946,7 +966,7 @@
|
||||
var captcha = collectCaptcha(form);
|
||||
var restRoot = (window.aksrtData && aksrtData.apiUrl) || '/wp-json/aksrt/v1';
|
||||
var nonce = (window.aksrtData && aksrtData.restNonce) || '';
|
||||
fetch(String(restRoot).replace(/\/$/, '') + '/auth/bind-phone', {
|
||||
fetch(String(restRoot).replace(/\/$/, '') + '/user/phone', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
|
||||
@@ -64,7 +64,6 @@ tbquire.config({
|
||||
'main' : 'main.min',
|
||||
'poster-share' : 'poster-share.min',
|
||||
'slidercaptcha' : 'slidercaptcha.min',
|
||||
'sign-register' : 'sign-register',
|
||||
'comment' : 'comment.min',
|
||||
'page-template' : 'page-template.min',
|
||||
'page-edit' : 'page-edit.min',
|
||||
|
||||
@@ -2460,9 +2460,6 @@ function z_auto_fun() {
|
||||
container: 'body',
|
||||
});
|
||||
|
||||
//登录注册
|
||||
$('.signsubmit-loader').length && tbquire(['sign-register']);
|
||||
|
||||
//复制内容
|
||||
$('[data-clipboard-text]').length && tbquire(['clipboard']);
|
||||
|
||||
@@ -3025,114 +3022,6 @@ _win.bd.on('click', '[ajax-action]', function () {
|
||||
});
|
||||
});
|
||||
|
||||
//登录注册
|
||||
_win.bd.on('click', '.signin-loader', function () {
|
||||
if (_win.sign_type == 'page') {
|
||||
window.location.href = _win.signin_url;
|
||||
window.location.reload;
|
||||
} else {
|
||||
$('.modal:not(#u_sign)').modal('hide');
|
||||
$('#u_sign').modal('show');
|
||||
|
||||
if (_win.signin_wx_priority) {
|
||||
$('a[href="#tab-qrcode-signin"]').tab('show');
|
||||
$('.social-login-item.weixingzh:first').click();
|
||||
} else {
|
||||
$('a[href="#tab-sign-in"]').tab('show');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_win.bd.on('click', '.signup-loader', function () {
|
||||
if (_win.sign_type == 'page') {
|
||||
window.location.href = _win.signup_url;
|
||||
window.location.reload;
|
||||
} else {
|
||||
$('.modal:not(#u_sign)').modal('hide');
|
||||
$('#u_sign').modal('show');
|
||||
$('a[href="#tab-sign-up"]').tab('show');
|
||||
}
|
||||
});
|
||||
|
||||
//扫码登录、绑定
|
||||
_win.bd.on('click', '.qrcode-signin', function () {
|
||||
$('.modal:not(#u_sign)').modal('hide');
|
||||
$('#u_sign').modal('show');
|
||||
$('a[href="#tab-qrcode-signin"]').tab('show');
|
||||
|
||||
var _this = $(this);
|
||||
var url = _this.attr('href');
|
||||
var container = $('.qrcode-signin-container');
|
||||
if (container.find('[get-only-one]').length) {
|
||||
container.find('[name="code"]').val('').trigger('change');
|
||||
return !1;
|
||||
}
|
||||
|
||||
container.html('<p class="placeholder" style="height:180px;width:180px;margin:auto;"></p><p class="placeholder" style="height:27px;width:200px;margin:15px auto 0;"></p>');
|
||||
|
||||
$.post(
|
||||
url,
|
||||
null,
|
||||
function (n) {
|
||||
n || notyf(aksrt__('qrcode_failed'), 'danger');
|
||||
var ys = n.ys ? n.ys : n.error ? 'danger' : '';
|
||||
n.msg && notyf(n.msg, ys);
|
||||
if (n && n.html) {
|
||||
container.html(n.html);
|
||||
_win.qrcode_signin = {
|
||||
url: n.url,
|
||||
state: n.state,
|
||||
};
|
||||
checkLogin();
|
||||
}
|
||||
},
|
||||
'json'
|
||||
);
|
||||
return !1;
|
||||
});
|
||||
|
||||
function checkLogin() {
|
||||
if (_win.checkLoginTimer) return;
|
||||
|
||||
//循环执行调整到ajax外部,避免移动端截图扫码,停止查询登录
|
||||
// 2秒后再次查询
|
||||
_win.checkLoginTimer = setInterval(checkLoginAjax, 2000);
|
||||
}
|
||||
|
||||
function checkLoginAjax() {
|
||||
var url = _win.qrcode_signin.url;
|
||||
var state = _win.qrcode_signin.state;
|
||||
if (!url || !state) {
|
||||
clearInterval(_win.checkLoginTimer);
|
||||
_win.checkLoginTimer = false;
|
||||
return;
|
||||
}
|
||||
$.post(
|
||||
url,
|
||||
{
|
||||
state: state,
|
||||
oauth_rurl: window.location.href,
|
||||
action: 'check_callback',
|
||||
},
|
||||
function (n) {
|
||||
//做逻辑判断,登录跳转
|
||||
if (n && n.goto) {
|
||||
window.location.href = n.goto;
|
||||
window.location.reload;
|
||||
}
|
||||
},
|
||||
'json'
|
||||
);
|
||||
}
|
||||
|
||||
//模态框关闭停止查询登录
|
||||
_win.bd.on('hide.bs.modal', '.modal#u_sign', function () {
|
||||
_win.qrcode_signin = {
|
||||
url: false,
|
||||
state: false,
|
||||
};
|
||||
});
|
||||
|
||||
//防抖函数
|
||||
function throttle(fn, delay) {
|
||||
let valid = true;
|
||||
|
||||
Vendored
+1
-3
File diff suppressed because one or more lines are too long
@@ -1,90 +0,0 @@
|
||||
$('.sign form').keydown(function (e) {
|
||||
var e = e || event,
|
||||
keycode = e.which || e.keyCode;
|
||||
if (keycode == 13) {
|
||||
$(this).find('.signsubmit-loader').trigger('click');
|
||||
}
|
||||
});
|
||||
|
||||
//输入检测
|
||||
_win.bd.on('input porpertychange', 'input[change-show]', function () {
|
||||
var _this = $(this);
|
||||
var val = _this.val();
|
||||
if (val.length > 5) {
|
||||
var e = _this.attr('change-show') || '.change-show';
|
||||
_this.parents('form').find(e).slideDown();
|
||||
}
|
||||
});
|
||||
|
||||
/**发送验证码 */
|
||||
_win.bd.on('click', '.captchsubmit', function () {
|
||||
_this = $(this);
|
||||
_win.captchsubmit_wait = 60;
|
||||
var _text = _this.html();
|
||||
var captchsubmit = function () {
|
||||
aksrt_ajax(_this, 0, function (n) {
|
||||
n.error || captchdown();
|
||||
if (n.remaining_time) {
|
||||
_win.captchsubmit_wait = n.remaining_time;
|
||||
captchdown();
|
||||
}
|
||||
});
|
||||
return !1;
|
||||
};
|
||||
|
||||
var captchdown = function () {
|
||||
var _captchsubmit = $('.captchsubmit');
|
||||
if (_win.captchsubmit_wait > 0) {
|
||||
_captchsubmit.html(aksrt_sprintf('captcha_resend_in', _win.captchsubmit_wait)).attr('disabled', !0);
|
||||
_win.captchsubmit_wait--;
|
||||
setTimeout(captchdown, 1000);
|
||||
} else {
|
||||
_captchsubmit.html(_text).attr('disabled', !1);
|
||||
_win.captchsubmit_wait = 60;
|
||||
}
|
||||
};
|
||||
|
||||
captchsubmit();
|
||||
});
|
||||
|
||||
//提交
|
||||
_win.bd.on('click', '.signsubmit-loader', function () {
|
||||
var _this = $(this);
|
||||
var _user_agreement = _this.parent().find('[name="user_agreement"]');
|
||||
|
||||
if (_user_agreement.length && !_user_agreement.is(':checked')) {
|
||||
var _user_agreement_box = _user_agreement.parent();
|
||||
_user_agreement_box.addClass('ani shake');
|
||||
setTimeout(function () {
|
||||
_user_agreement_box.removeClass('ani shake');
|
||||
}, 400);
|
||||
return notyf(aksrt__('agree_terms_first'), 'danger'), !1;
|
||||
}
|
||||
|
||||
aksrt_ajax($(this), 0, function (n) {
|
||||
n.error || window.location.reload();
|
||||
});
|
||||
return !1;
|
||||
});
|
||||
|
||||
//更换绑定的手机号或者邮箱
|
||||
_win.bd.on('click', '.user-verify-submit', function () {
|
||||
var _this = $(this);
|
||||
var _next = _this.parents('.tab-pane').next('.tab-pane').attr('id');
|
||||
|
||||
aksrt_ajax(_this, 0, function (n) {
|
||||
if (!n.error) {
|
||||
$('a[href="#' + _next + '"]').tab('show');
|
||||
if (_win.captchsubmit_wait) {
|
||||
_win.captchsubmit_wait -= 40;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
//邀请码注册
|
||||
_win.bd.on('aksrt_ajax.success', '.invit-code-verify', function (e, n) {
|
||||
if (n && n.code) {
|
||||
$('#tab-signup-signup form').append('<input type="hidden" name="invit_code" value="' + n.code + '">');
|
||||
}
|
||||
});
|
||||
-1
@@ -1 +0,0 @@
|
||||
$(".sign form").keydown(function(i){i=i||event;var t=i.which||i.keyCode;13==t&&$(this).find(".signsubmit-loader").trigger("click")}),_win.bd.on("input porpertychange","input[change-show]",function(){var i=$(this),t=i.val();if(t.length>5){var n=i.attr("change-show")||".change-show";i.parents("form").find(n).slideDown()}}),_win.bd.on("click",".captchsubmit",function(){_this=$(this),_win.captchsubmit_wait=60;var i=_this.html(),t=function(){return aksrt_ajax(_this,0,function(i){i.error||n(),i.remaining_time&&(_win.captchsubmit_wait=i.remaining_time,n())}),!1},n=function(){var t=$(".captchsubmit");_win.captchsubmit_wait>0?(t.html(aksrt_sprintf("captcha_resend_in",_win.captchsubmit_wait)).attr("disabled",!0),_win.captchsubmit_wait--,setTimeout(n,1e3)):(t.html(i).attr("disabled",!1),_win.captchsubmit_wait=60)};t()}),_win.bd.on("click",".signsubmit-loader",function(){var i=$(this),t=i.parent().find('[name="user_agreement"]');if(t.length&&!t.is(":checked")){var n=t.parent();return n.addClass("ani shake"),setTimeout(function(){n.removeClass("ani shake")},400),notyf(aksrt__("agree_terms_first"),"danger"),!1}return aksrt_ajax($(this),0,function(i){i.error||window.location.reload()}),!1}),_win.bd.on("click",".user-verify-submit",function(){var i=$(this),t=i.parents(".tab-pane").next(".tab-pane").attr("id");aksrt_ajax(i,0,function(i){i.error||($('a[href="#'+t+'"]').tab("show"),_win.captchsubmit_wait&&(_win.captchsubmit_wait-=40))})}),_win.bd.on("aksrt_ajax.success",".invit-code-verify",function(i,t){t&&t.code&&$("#tab-signup-signup form").append('<input type="hidden" name="invit_code" value="'+t.code+'">')});
|
||||
@@ -1,427 +0,0 @@
|
||||
/**
|
||||
* AKSRT Sign/Register — 统一认证交互脚本
|
||||
*
|
||||
* 对齐 legacy 的 sign-register.js:
|
||||
* - 短信验证码倒计时(60 秒 + 服务端 remaining_time)
|
||||
* - 手机号注册/登录表单交互
|
||||
* - 人机验证联动
|
||||
* - 密码可见性切换
|
||||
* - 协议勾选校验
|
||||
*/
|
||||
;(() => {
|
||||
'use strict'
|
||||
|
||||
const COUNTDOWN_KEY = 'aksrt_sms_cooldown'
|
||||
const DEFAULT_COOLDOWN = 60
|
||||
const REST_BASE = '/wp-json/aksrt/v1'
|
||||
|
||||
/**
|
||||
* 初始化所有认证页面
|
||||
*/
|
||||
function initAuthPages() {
|
||||
initSmsButtons()
|
||||
initPasswordToggles()
|
||||
initAgreementChecks()
|
||||
initFormValidation()
|
||||
initPhoneAuthTabs()
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* SMS 验证码发送 + 倒计时
|
||||
* ================================================================= */
|
||||
|
||||
function initSmsButtons(root) {
|
||||
root = root || document
|
||||
root.querySelectorAll('[data-aksrt-sms-trigger]').forEach(btn => {
|
||||
if (btn.dataset.aksrtSmsInitialized === 'true') return
|
||||
btn.dataset.aksrtSmsInitialized = 'true'
|
||||
const cooldown = parseInt(btn.dataset.cooldown || DEFAULT_COOLDOWN, 10)
|
||||
|
||||
// 恢复页面刷新后的倒计时
|
||||
const stored = sessionStorage.getItem(COUNTDOWN_KEY)
|
||||
if (stored) {
|
||||
const remaining = Math.ceil((parseInt(stored, 10) - Date.now()) / 1000)
|
||||
if (remaining > 0) {
|
||||
startCountdown(btn, remaining)
|
||||
}
|
||||
}
|
||||
|
||||
btn.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
if (btn.disabled) return
|
||||
sendSmsCode(btn, cooldown)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function sendSmsCode(btn, cooldown) {
|
||||
const phoneInput = document.querySelector(btn.dataset.aksrtSmsPhone || '[data-aksrt-phone-input]')
|
||||
const phone = phoneInput ? phoneInput.value.trim() : ''
|
||||
|
||||
if (!/^1[3-9]\d{9}$/.test(phone)) {
|
||||
notyf('请输入正确的手机号', 'warning')
|
||||
if (phoneInput) phoneInput.focus()
|
||||
return
|
||||
}
|
||||
|
||||
// 人机验证联动
|
||||
const captchaState = window.AKSRTCaptchaState
|
||||
const scene = btn.dataset.aksrtSmsScene || 'register'
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes[scene]) {
|
||||
captchaState.scenes[scene].verify(() => {
|
||||
doSendSms(btn, phone, cooldown)
|
||||
})
|
||||
} else {
|
||||
doSendSms(btn, phone, cooldown)
|
||||
}
|
||||
}
|
||||
|
||||
function doSendSms(btn, phone, cooldown) {
|
||||
btn.disabled = true
|
||||
const original = btn.textContent
|
||||
btn.textContent = '发送中...'
|
||||
|
||||
const scene = btn.dataset.aksrtSmsScene || 'register'
|
||||
const toastId = scene === 'bind_phone'
|
||||
? 'aksrt-settings-phone-code-toast'
|
||||
: 'aksrt-auth-sms-code-toast'
|
||||
notyf('正在发送验证码…', 'load', '', toastId)
|
||||
|
||||
// 收集人机验证字段
|
||||
const form = btn.closest('form')
|
||||
const captchaData = collectCaptchaFields(form)
|
||||
|
||||
fetch(REST_BASE + '/auth/send-sms-code', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ phone, scene, ...captchaData }),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
notyf(data.message || '验证码已发送', 'success', 5, toastId)
|
||||
const seconds = (data.data && data.data.remaining_time) || cooldown
|
||||
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + seconds * 1000))
|
||||
startCountdown(btn, seconds)
|
||||
} else {
|
||||
const msg = (data.data && data.data.message) || data.message || '发送失败'
|
||||
notyf(msg, 'danger', 5, toastId)
|
||||
try {
|
||||
const captchaState = window.AKSRTCaptchaState
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes[scene]) {
|
||||
captchaState.scenes[scene].reset()
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
const remaining = data.data && data.data.remaining_time
|
||||
if (remaining && remaining > 0) {
|
||||
sessionStorage.setItem(COUNTDOWN_KEY, String(Date.now() + remaining * 1000))
|
||||
startCountdown(btn, remaining)
|
||||
} else {
|
||||
btn.disabled = false
|
||||
btn.textContent = original
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notyf('网络错误,请重试', 'danger', 5, toastId)
|
||||
btn.disabled = false
|
||||
btn.textContent = original
|
||||
})
|
||||
}
|
||||
|
||||
function startCountdown(btn, seconds) {
|
||||
let remaining = Math.max(1, Math.floor(seconds))
|
||||
btn.disabled = true
|
||||
btn.textContent = remaining + 's'
|
||||
const timer = setInterval(() => {
|
||||
remaining--
|
||||
if (remaining <= 0) {
|
||||
clearInterval(timer)
|
||||
btn.disabled = false
|
||||
btn.textContent = '获取验证码'
|
||||
sessionStorage.removeItem(COUNTDOWN_KEY)
|
||||
} else {
|
||||
btn.textContent = remaining + 's'
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 手机号注册/登录表单
|
||||
* ================================================================= */
|
||||
|
||||
function initPhoneAuthTabs() {
|
||||
document.querySelectorAll('[data-aksrt-auth-tab]').forEach(tab => {
|
||||
tab.addEventListener('click', e => {
|
||||
e.preventDefault()
|
||||
const target = tab.dataset.aksrtAuthTab
|
||||
switchAuthTab(target)
|
||||
})
|
||||
})
|
||||
|
||||
// 手机号注册表单
|
||||
const phoneRegForm = document.getElementById('aksrt-phone-register-form')
|
||||
if (phoneRegForm) {
|
||||
phoneRegForm.addEventListener('submit', e => {
|
||||
e.preventDefault()
|
||||
handlePhoneRegister(phoneRegForm)
|
||||
})
|
||||
}
|
||||
|
||||
// 手机号登录表单
|
||||
const phoneLoginForm = document.getElementById('aksrt-phone-login-form')
|
||||
if (phoneLoginForm) {
|
||||
phoneLoginForm.addEventListener('submit', e => {
|
||||
e.preventDefault()
|
||||
handlePhoneLogin(phoneLoginForm)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function switchAuthTab(tabName) {
|
||||
document.querySelectorAll('[data-auth-panel]').forEach(panel => {
|
||||
panel.style.display = panel.dataset.authPanel === tabName ? '' : 'none'
|
||||
})
|
||||
document.querySelectorAll('[data-aksrt-auth-tab]').forEach(tab => {
|
||||
tab.classList.toggle('is-active', tab.dataset.aksrtAuthTab === tabName)
|
||||
})
|
||||
}
|
||||
|
||||
function handlePhoneRegister(form) {
|
||||
const phone = form.querySelector('[name="phone"]')?.value?.trim()
|
||||
const code = form.querySelector('[name="code"]')?.value?.trim()
|
||||
const password = form.querySelector('[name="password"]')?.value
|
||||
const confirm = form.querySelector('[name="password_confirm"]')?.value
|
||||
const ref = form.querySelector('[name="ref"]')?.value?.trim()
|
||||
|
||||
if (!phone || !code || !password) {
|
||||
notyf('请填写完整信息', 'warning')
|
||||
return
|
||||
}
|
||||
if (password !== confirm) {
|
||||
notyf('两次密码不一致', 'warning')
|
||||
return
|
||||
}
|
||||
if (password.length < 6) {
|
||||
notyf('密码至少 6 位', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
const submitBtn = form.querySelector('[type="submit"]')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true
|
||||
submitBtn.textContent = '注册中...'
|
||||
}
|
||||
|
||||
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, ref, ...captchaData }),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
notyf(data.message || '注册成功', 'success')
|
||||
if (data.notice) notyf(data.notice, 'warning')
|
||||
setTimeout(() => {
|
||||
window.location.href = data.redirect || '/'
|
||||
}, 500)
|
||||
} else {
|
||||
notyf(data.message || '注册失败', 'danger')
|
||||
try {
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
|
||||
captchaState.scenes.register.reset()
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '注册'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notyf('网络错误', 'danger')
|
||||
try {
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
|
||||
captchaState.scenes.register.reset()
|
||||
}
|
||||
} catch (e) { /* ignore */ }
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '注册'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.register) {
|
||||
captchaState.scenes.register.verify(doRegister)
|
||||
} else {
|
||||
doRegister()
|
||||
}
|
||||
}
|
||||
|
||||
function handlePhoneLogin(form) {
|
||||
const phone = form.querySelector('[name="phone"]')?.value?.trim()
|
||||
const password = form.querySelector('[name="password"]')?.value
|
||||
const loginMode = form.querySelector('[name="login_mode"]')?.value || 'password'
|
||||
|
||||
if (!phone) {
|
||||
notyf('请输入手机号', 'warning')
|
||||
return
|
||||
}
|
||||
|
||||
const submitBtn = form.querySelector('[type="submit"]')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = true
|
||||
submitBtn.textContent = '登录中...'
|
||||
}
|
||||
|
||||
let endpoint, body
|
||||
if (loginMode === 'code') {
|
||||
const code = form.querySelector('[name="code"]')?.value?.trim()
|
||||
if (!code) {
|
||||
notyf('请输入验证码', 'warning')
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' }
|
||||
return
|
||||
}
|
||||
endpoint = '/auth/phone-code-login'
|
||||
body = { phone, code }
|
||||
} else {
|
||||
if (!password) {
|
||||
notyf('请输入密码', 'warning')
|
||||
if (submitBtn) { submitBtn.disabled = false; submitBtn.textContent = '登录' }
|
||||
return
|
||||
}
|
||||
endpoint = '/auth/phone-login'
|
||||
body = { phone, password }
|
||||
}
|
||||
|
||||
const captchaState = window.AKSRTCaptchaState
|
||||
const doLogin = () => {
|
||||
const captchaData = collectCaptchaFields(form)
|
||||
fetch(REST_BASE + endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ ...body, ...captchaData }),
|
||||
})
|
||||
.then(r => r.json().then(data => ({ ok: r.ok, data })))
|
||||
.then(({ ok, data }) => {
|
||||
if (ok && data.success) {
|
||||
notyf('登录成功', 'success')
|
||||
setTimeout(() => {
|
||||
window.location.href = data.data?.redirect || '/'
|
||||
}, 500)
|
||||
} else {
|
||||
notyf(data.message || '登录失败', 'danger')
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
|
||||
captchaState.scenes.login.reset()
|
||||
}
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
notyf('网络错误', 'danger')
|
||||
if (submitBtn) {
|
||||
submitBtn.disabled = false
|
||||
submitBtn.textContent = '登录'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (captchaState && captchaState.scenes && captchaState.scenes.login) {
|
||||
captchaState.scenes.login.verify(doLogin)
|
||||
} else {
|
||||
doLogin()
|
||||
}
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 辅助功能
|
||||
* ================================================================= */
|
||||
|
||||
function collectCaptchaFields(form) {
|
||||
if (!form) return {}
|
||||
const fields = {}
|
||||
const names = [
|
||||
'captcha_verified', 'tencent_ticket', 'tencent_randstr', 'tencent_sid',
|
||||
'cf_turnstile_response', 'geetest_lot_number', 'geetest_captcha_output',
|
||||
'geetest_pass_token', 'geetest_gen_time', 'aliyun_captcha_verify_param'
|
||||
]
|
||||
names.forEach(name => {
|
||||
const el = form.querySelector('[name="' + name + '"]')
|
||||
if (el && el.value) fields[name] = el.value
|
||||
})
|
||||
return fields
|
||||
}
|
||||
|
||||
function initPasswordToggles() {
|
||||
document.querySelectorAll('[data-aksrt-password-toggle]').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const input = btn.closest('.input-wrap')?.querySelector('input')
|
||||
if (!input) return
|
||||
const isPassword = input.type === 'password'
|
||||
input.type = isPassword ? 'text' : 'password'
|
||||
const icon = btn.querySelector('i, svg')
|
||||
if (icon) {
|
||||
if (icon.tagName === 'I') {
|
||||
icon.className = isPassword ? 'fa fas fa-eye-slash' : 'fa fas fa-eye'
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function initAgreementChecks() {
|
||||
document.querySelectorAll('form[data-aksrt-auth-form]').forEach(form => {
|
||||
const checkbox = form.querySelector('[data-aksrt-agreement]')
|
||||
const submitBtn = form.querySelector('[type="submit"]')
|
||||
if (checkbox && submitBtn) {
|
||||
const update = () => { submitBtn.disabled = !checkbox.checked }
|
||||
checkbox.addEventListener('change', update)
|
||||
update()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function initFormValidation() {
|
||||
document.querySelectorAll('form[data-aksrt-auth-form]').forEach(form => {
|
||||
form.addEventListener('submit', e => {
|
||||
const password = form.querySelector('[name="password"]')
|
||||
const confirm = form.querySelector('[name="password_confirm"]')
|
||||
if (password && confirm && password.value !== confirm.value) {
|
||||
e.preventDefault()
|
||||
notyf('两次密码不一致', 'warning')
|
||||
confirm.focus()
|
||||
return
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 初始化
|
||||
if (window.AksrtFrontend && typeof window.AksrtFrontend.register === 'function') {
|
||||
window.AksrtFrontend.register('aksrt-sms-buttons', function(root) {
|
||||
initSmsButtons(root)
|
||||
})
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initAuthPages)
|
||||
} else {
|
||||
initAuthPages()
|
||||
}
|
||||
|
||||
// 暴露给外部
|
||||
window.AKSRTSignRegister = {
|
||||
init: initAuthPages,
|
||||
sendSmsCode,
|
||||
switchAuthTab,
|
||||
}
|
||||
})()
|
||||
+6
-1
@@ -400,7 +400,12 @@ if ($floating_enabled) :
|
||||
|
||||
<?php
|
||||
// 弹窗登录/注册模板(仅未登录用户)
|
||||
if (!is_user_logged_in() && file_exists(AKSRT_DIR . '/templates/parts/modal-auth.php')) {
|
||||
if (
|
||||
!is_user_logged_in()
|
||||
&& class_exists('\AKSRT\Settings\Settings')
|
||||
&& \AKSRT\Settings\Settings::user_sign_type() === 'modal'
|
||||
&& file_exists(AKSRT_DIR . '/templates/parts/modal-auth.php')
|
||||
) {
|
||||
include AKSRT_DIR . '/templates/parts/modal-auth.php';
|
||||
}
|
||||
?>
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ add_filter('query_vars', function ($vars) {
|
||||
|
||||
// 主题切换离开时:清理本主题注册的 cron 任务
|
||||
add_action('switch_theme', function () {
|
||||
$hooks = ['aksrt_dl_token_cleanup', 'aksrt_email_verification_backfill_batch', 'aksrt_notification_cleanup', 'aksrt_creator_income_settlement', 'aksrt_vip_membership_expiry'];
|
||||
$hooks = ['aksrt_dl_token_cleanup', 'aksrt_notification_cleanup', 'aksrt_creator_income_settlement', 'aksrt_vip_membership_expiry'];
|
||||
foreach ($hooks as $hook) {
|
||||
$next = wp_next_scheduled($hook);
|
||||
while ($next) {
|
||||
|
||||
@@ -81,36 +81,5 @@ require_once AKSRT_DIR . '/inc/modules/creator-revenue.php';
|
||||
// Stage 3: core hooks
|
||||
require_once AKSRT_DIR . '/inc/core-hooks.php';
|
||||
|
||||
// Stage 4: auth service hooks
|
||||
if (class_exists(\AKSRT\Auth\Service::class)) {
|
||||
\AKSRT\Auth\Service::init();
|
||||
}
|
||||
|
||||
// Stage 5: email verification backfill
|
||||
add_action('init', function () {
|
||||
if (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {
|
||||
return;
|
||||
}
|
||||
if (!class_exists(\AKSRT\Auth\Service::class) || !class_exists(\AKSRT\Settings\Settings::class)) {
|
||||
return;
|
||||
}
|
||||
if (!\AKSRT\Settings\Settings::registration_requires_email_verification()) {
|
||||
return;
|
||||
}
|
||||
if (get_option('aksrt_email_verification_backfill_done')) {
|
||||
return;
|
||||
}
|
||||
if (!is_admin() && !(defined('DOING_CRON') && DOING_CRON)) {
|
||||
return;
|
||||
}
|
||||
\AKSRT\Auth\Service::backfill_legacy_users_as_verified();
|
||||
}, 5);
|
||||
|
||||
add_action('aksrt_email_verification_backfill_batch', function ($offset = 0) {
|
||||
if (class_exists(\AKSRT\Auth\Service::class)) {
|
||||
\AKSRT\Auth\Service::run_async_backfill_batch((int) $offset);
|
||||
}
|
||||
}, 10, 1);
|
||||
|
||||
add_action('admin_init', function () {
|
||||
}, 5);
|
||||
|
||||
@@ -14,15 +14,19 @@ if (!function_exists('aksrt_get_auth_agreement_view')) {
|
||||
* @return array{enabled:bool,required:bool,label_html:string,field_name:string}
|
||||
*/
|
||||
function aksrt_get_auth_agreement_view(): array {
|
||||
$enabled = true;
|
||||
$required = true;
|
||||
$enabled = false;
|
||||
$privacy_enabled = false;
|
||||
$required = false;
|
||||
$page_ref = '';
|
||||
$privacy_ref = '';
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'user_agreement_settings')) {
|
||||
$cfg = \AKSRT\Settings\Settings::user_agreement_settings();
|
||||
$enabled = !empty($cfg['enabled']);
|
||||
$privacy_enabled = !empty($cfg['privacy']);
|
||||
$required = !empty($cfg['required']);
|
||||
$page_ref = (string) ($cfg['page'] ?? '');
|
||||
$privacy_ref = (string) ($cfg['privacy_page'] ?? '');
|
||||
}
|
||||
|
||||
$url = '';
|
||||
@@ -37,18 +41,21 @@ if (!function_exists('aksrt_get_auth_agreement_view')) {
|
||||
}
|
||||
}
|
||||
|
||||
if ($url !== '') {
|
||||
$label_html = sprintf(
|
||||
/* translators: %s: agreement page link */
|
||||
__('我已阅读并同意<a href="%s" target="_blank" rel="noopener noreferrer">用户协议</a>', 'aksrt'),
|
||||
esc_url($url)
|
||||
);
|
||||
} else {
|
||||
$label_html = esc_html__('我已阅读并同意用户协议', 'aksrt');
|
||||
$privacy_url = '';
|
||||
if ($privacy_ref !== '') {
|
||||
$privacy_url = ctype_digit($privacy_ref) ? (string) get_permalink((int) $privacy_ref) : esc_url_raw($privacy_ref);
|
||||
}
|
||||
$links = [];
|
||||
if ($enabled) {
|
||||
$links[] = $url !== '' ? sprintf('<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc_url($url), esc_html__('用户协议', 'aksrt')) : esc_html__('用户协议', 'aksrt');
|
||||
}
|
||||
if ($privacy_enabled) {
|
||||
$links[] = $privacy_url !== '' ? sprintf('<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc_url($privacy_url), esc_html__('隐私协议', 'aksrt')) : esc_html__('隐私协议', 'aksrt');
|
||||
}
|
||||
$label_html = esc_html__('我已阅读并同意', 'aksrt') . implode(esc_html__('和', 'aksrt'), $links);
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'enabled' => $enabled || $privacy_enabled,
|
||||
'required' => $required,
|
||||
'label_html' => $label_html,
|
||||
'field_name' => 'agree_terms',
|
||||
@@ -62,13 +69,16 @@ if (!function_exists('aksrt_get_auth_sign_logo_html')) {
|
||||
*/
|
||||
function aksrt_get_auth_sign_logo_html(): string {
|
||||
$logo_url = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::user_logo_url() : '';
|
||||
$logo_dark_url = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::user_logo_dark_url() : $logo_url;
|
||||
$home = home_url('/');
|
||||
$name = get_bloginfo('name');
|
||||
if ($logo_url) {
|
||||
return sprintf(
|
||||
'<a href="%s"><img src="%s" alt="%s"></a>',
|
||||
'<a href="%s"><img class="sign-logo-light" src="%s" alt="%s"><img class="sign-logo-dark" src="%s" alt="%s"></a>',
|
||||
esc_url($home),
|
||||
esc_url($logo_url),
|
||||
esc_attr($name),
|
||||
esc_url($logo_dark_url),
|
||||
esc_attr($name)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,11 +25,6 @@ if (!function_exists('aksrt_header_slide_excluded_page_templates')) {
|
||||
function aksrt_header_slide_excluded_page_templates(): array {
|
||||
return [
|
||||
'templates/pages/page-sign.php',
|
||||
'templates/pages/page-login.php',
|
||||
'templates/pages/page-register.php',
|
||||
'templates/pages/page-reset-password.php',
|
||||
'templates/pages/page-bind-account.php',
|
||||
'templates/pages/page-verify-email.php',
|
||||
'templates/pages/page-download.php',
|
||||
'templates/pages/page-download-file.php',
|
||||
];
|
||||
|
||||
@@ -663,15 +663,7 @@ function findForm(){
|
||||
form=box.closest('form');
|
||||
if(!form&&C.scene==='comment') form=document.getElementById('commentform');
|
||||
if(!form&&(C.scene==='login'||C.scene==='register')){
|
||||
form=document.getElementById('aksrt-login-form')
|
||||
||document.getElementById('aksrt-modal-login-form')
|
||||
||document.getElementById('sign-login-form')
|
||||
||document.getElementById('aksrt-phone-login-form')
|
||||
||document.getElementById('aksrt-modal-register-form')
|
||||
||document.getElementById('sign-register-form')
|
||||
||document.getElementById('aksrt-phone-register-form')
|
||||
||document.querySelector('.auth-form')
|
||||
||document.querySelector('.sign-form');
|
||||
form=box.closest('[data-auth-form]')||document.querySelector('[data-auth-form]');
|
||||
}
|
||||
if(!form&&C.scene==='bind_phone'){
|
||||
form=document.getElementById('aksrt-bind-phone-form')
|
||||
|
||||
@@ -13,9 +13,7 @@ if (!function_exists('aksrt_get_user_notifications')) :
|
||||
/**
|
||||
* 获取用户提醒列表(即时提醒,不持久化)
|
||||
*
|
||||
* 当前通知源:
|
||||
* - 邮箱待验证
|
||||
* - 评论待审核
|
||||
* 当前通知源:评论待审核。
|
||||
*
|
||||
* @param int $user_id 用户 ID,默认当前用户
|
||||
* @return array<int, array{type:string,title:string,desc:string,url:string,count:int}>
|
||||
@@ -28,21 +26,6 @@ function aksrt_get_user_notifications(int $user_id = 0): array {
|
||||
|
||||
$items = [];
|
||||
|
||||
$email_verified = true;
|
||||
if (class_exists('\AKSRT\Auth\Service')) {
|
||||
$email_verified = \AKSRT\Auth\Service::user_is_email_verified($user_id);
|
||||
}
|
||||
|
||||
if (!$email_verified && (!class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::notification_enabled('verify_email'))) {
|
||||
$items[] = [
|
||||
'type' => 'verify_email',
|
||||
'title' => __('邮箱待验证', 'aksrt'),
|
||||
'desc' => __('请尽快完成邮箱验证,部分功能需要验证后才能使用。', 'aksrt'),
|
||||
'url' => function_exists('aksrt_get_verify_email_url') ? aksrt_get_verify_email_url($user_id) : home_url('/'),
|
||||
'count' => 1,
|
||||
];
|
||||
}
|
||||
|
||||
$pending_comments = (int) get_comments([
|
||||
'user_id' => $user_id,
|
||||
'count' => true,
|
||||
@@ -72,7 +55,6 @@ function aksrt_notification_type_label(string $type): string {
|
||||
'download_ready' => __('下载就绪', 'aksrt'),
|
||||
'credits_added' => __('积分增加', 'aksrt'),
|
||||
'credits_deducted' => __('积分扣减', 'aksrt'),
|
||||
'verify_email' => __('邮箱验证', 'aksrt'),
|
||||
'pending_comments' => __('待审核评论', 'aksrt'),
|
||||
'notice' => __('通知', 'aksrt'),
|
||||
];
|
||||
|
||||
@@ -302,25 +302,13 @@ add_action('wp_ajax_aksrt_change_email', function () {
|
||||
}
|
||||
}
|
||||
|
||||
// ====== 邮箱变更后重置验证状态(阶段 5)======
|
||||
// 1. 写入 14 天回滚窗口 meta(管理员可在此期内还原旧邮箱)
|
||||
// 2. 把 aksrt_email_verified 重置为 0
|
||||
// 3. 若功能启用 → 立即给新邮箱发送验证邮件
|
||||
// Keep the account-change audit window; registration verification is pre-create only.
|
||||
update_user_meta( $user_id, 'aksrt_email_previous', $old_email );
|
||||
update_user_meta( $user_id, 'aksrt_email_changed_at', time() );
|
||||
|
||||
$verify_url = '';
|
||||
$verify_message = '';
|
||||
if ( class_exists( '\AKSRT\Settings\Settings' ) && class_exists( '\AKSRT\Auth\Service' )
|
||||
&& \AKSRT\Settings\Settings::registration_requires_email_verification() ) {
|
||||
update_user_meta( $user_id, 'aksrt_email_verified', 0 );
|
||||
// 用户对自身的 edit_user 权限映射到 'read',因此 admin_send_verification 会通过权限校验。
|
||||
$verify_result = \AKSRT\Auth\Service::admin_send_verification( $user_id, $user_id );
|
||||
if ( ! empty( $verify_result['success'] ) ) {
|
||||
$verify_url = function_exists('aksrt_get_verify_email_url') ? aksrt_get_verify_email_url( $user_id ) : '';
|
||||
$verify_message = __( '请前往新邮箱完成验证,否则下次登录将被拦截。', 'aksrt' );
|
||||
}
|
||||
}
|
||||
update_user_meta( $user_id, 'aksrt_email_verified', 1 );
|
||||
|
||||
if ( class_exists( 'WP_Session_Tokens' ) ) {
|
||||
WP_Session_Tokens::get_instance( $user_id )->destroy_all();
|
||||
|
||||
@@ -11,58 +11,7 @@ defined('ABSPATH') || exit;
|
||||
// 一、模板注册表 — 所有特殊页面的"身份证"
|
||||
// 修改此处即可增删模板页面,无需改动其他逻辑
|
||||
// ============================================================
|
||||
$_aksrt_special_templates = [
|
||||
'login' => [
|
||||
'file' => 'templates/pages/page-login.php',
|
||||
'slug' => 'login',
|
||||
'title' => '登录',
|
||||
'fallback' => '/login/',
|
||||
],
|
||||
'register' => [
|
||||
'file' => 'templates/pages/page-register.php',
|
||||
'slug' => 'register',
|
||||
'title' => '注册',
|
||||
'fallback' => '/register/',
|
||||
],
|
||||
'bind' => [
|
||||
'file' => 'templates/pages/page-bind-account.php',
|
||||
'slug' => 'bind-account',
|
||||
'title' => '绑定账号',
|
||||
'fallback' => '/bind-account/',
|
||||
],
|
||||
'reset-password' => [
|
||||
'file' => 'templates/pages/page-reset-password.php',
|
||||
'slug' => 'reset-password',
|
||||
'title' => '重置密码',
|
||||
'fallback' => '/reset-password/',
|
||||
],
|
||||
'verify-email' => [
|
||||
'file' => 'templates/pages/page-verify-email.php',
|
||||
'slug' => 'verify-email',
|
||||
'title' => '邮箱验证',
|
||||
'fallback' => '/verify-email/',
|
||||
],
|
||||
'categories' => [
|
||||
'file' => 'templates/pages/page-categories.php',
|
||||
'slug' => 'categories',
|
||||
'title' => '分类目录',
|
||||
'fallback' => '/categories/',
|
||||
],
|
||||
'friends' => [
|
||||
'file' => 'templates/pages/page-friends.php',
|
||||
'slug' => 'friends',
|
||||
'title' => '友情链接',
|
||||
'fallback' => '/friends/',
|
||||
],
|
||||
'profile' => [
|
||||
'file' => 'templates/pages/page-profile.php',
|
||||
'slug' => 'profile',
|
||||
'title' => '个人中心',
|
||||
'fallback' => '/',
|
||||
],
|
||||
];
|
||||
|
||||
// legacy-style v2 routing keeps only true fixed template pages in the registry.
|
||||
// Only true fixed template pages live in the registry.
|
||||
$_aksrt_special_templates = [
|
||||
'sign' => [
|
||||
'file' => 'templates/pages/page-sign.php',
|
||||
@@ -70,24 +19,6 @@ $_aksrt_special_templates = [
|
||||
'title' => '登录/注册',
|
||||
'fallback' => '/sign/',
|
||||
],
|
||||
'bind' => [
|
||||
'file' => 'templates/pages/page-bind-account.php',
|
||||
'slug' => 'bind-account',
|
||||
'title' => '绑定账号',
|
||||
'fallback' => '/bind-account/',
|
||||
],
|
||||
'reset-password' => [
|
||||
'file' => 'templates/pages/page-reset-password.php',
|
||||
'slug' => 'reset-password',
|
||||
'title' => '重置密码',
|
||||
'fallback' => '/reset-password/',
|
||||
],
|
||||
'verify-email' => [
|
||||
'file' => 'templates/pages/page-verify-email.php',
|
||||
'slug' => 'verify-email',
|
||||
'title' => '邮箱验证',
|
||||
'fallback' => '/verify-email/',
|
||||
],
|
||||
'categories' => [
|
||||
'file' => 'templates/pages/page-categories.php',
|
||||
'slug' => 'categories',
|
||||
@@ -203,21 +134,9 @@ function aksrt_resolve_page_template_file(string $template_file = '', string $po
|
||||
$template_file = ltrim($template_file, '/\\');
|
||||
$post_slug = sanitize_title($post_slug);
|
||||
|
||||
$legacy_auth_templates = [
|
||||
'templates/pages/page-login.php' => 'login',
|
||||
'templates/pages/page-register.php' => 'register',
|
||||
'templates/pages/page-bind-account.php' => 'bind',
|
||||
'templates/pages/page-reset-password.php' => 'reset-password',
|
||||
'templates/pages/page-verify-email.php' => 'verify-email',
|
||||
];
|
||||
|
||||
if ($template_file !== '') {
|
||||
if ($template_file === 'templates/pages/page-sign.php') {
|
||||
return aksrt_get_auth_template_file(aksrt_get_auth_tab(), true);
|
||||
}
|
||||
|
||||
if (!empty($legacy_auth_templates[$template_file])) {
|
||||
return aksrt_get_auth_template_file((string) $legacy_auth_templates[$template_file], true);
|
||||
return $template_file;
|
||||
}
|
||||
|
||||
if (strpos($template_file, 'templates/pages/') === 0 && aksrt_locate_template_file($template_file)) {
|
||||
@@ -226,17 +145,8 @@ function aksrt_resolve_page_template_file(string $template_file = '', string $po
|
||||
}
|
||||
|
||||
if ($post_slug !== '') {
|
||||
$auth_slug_map = [
|
||||
'sign' => aksrt_get_auth_tab(),
|
||||
'login' => 'login',
|
||||
'register' => 'register',
|
||||
'bind-account' => 'bind',
|
||||
'reset-password' => 'reset-password',
|
||||
'verify-email' => 'verify-email',
|
||||
];
|
||||
|
||||
if (!empty($auth_slug_map[$post_slug])) {
|
||||
return aksrt_get_auth_template_file((string) $auth_slug_map[$post_slug], true);
|
||||
if ($post_slug === 'sign') {
|
||||
return 'templates/pages/page-sign.php';
|
||||
}
|
||||
|
||||
$slug_map = [
|
||||
@@ -261,87 +171,20 @@ function aksrt_normalize_auth_tab(string $tab): string {
|
||||
|
||||
switch ($tab) {
|
||||
case 'signin':
|
||||
case 'login':
|
||||
return 'login';
|
||||
return 'signin';
|
||||
case 'signup':
|
||||
case 'register':
|
||||
return 'register';
|
||||
return 'signup';
|
||||
case 'bind':
|
||||
case 'bind-account':
|
||||
return 'bind';
|
||||
case 'reset':
|
||||
case 'lostpassword':
|
||||
case 'reset-password':
|
||||
return 'reset-password';
|
||||
case 'verify':
|
||||
case 'verify-email':
|
||||
return 'verify-email';
|
||||
case 'resetpassword':
|
||||
return 'resetpassword';
|
||||
case 'oauth':
|
||||
return 'oauth';
|
||||
default:
|
||||
return 'login';
|
||||
return 'signin';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an auth tab should be rendered by the unified sign page.
|
||||
*/
|
||||
function aksrt_auth_tab_uses_unified_sign_page(string $tab): bool {
|
||||
return in_array(
|
||||
aksrt_normalize_auth_tab($tab),
|
||||
['login', 'register'],
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the auth template file, preferring the unified sign page when present.
|
||||
*/
|
||||
function aksrt_get_auth_template_file(string $tab = 'login', bool $prefer_sign_page = true): string {
|
||||
$normalized_tab = aksrt_normalize_auth_tab($tab);
|
||||
$sign_template = 'templates/pages/page-sign.php';
|
||||
|
||||
if (
|
||||
$prefer_sign_page
|
||||
&& aksrt_auth_tab_uses_unified_sign_page($normalized_tab)
|
||||
&& aksrt_locate_template_file($sign_template)
|
||||
) {
|
||||
return $sign_template;
|
||||
}
|
||||
|
||||
switch ($normalized_tab) {
|
||||
case 'register':
|
||||
return 'templates/pages/page-register.php';
|
||||
case 'bind':
|
||||
return 'templates/pages/page-bind-account.php';
|
||||
case 'reset-password':
|
||||
return 'templates/pages/page-reset-password.php';
|
||||
case 'verify-email':
|
||||
return 'templates/pages/page-verify-email.php';
|
||||
default:
|
||||
return 'templates/pages/page-login.php';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the active auth view for sign routes and the sign page template.
|
||||
*/
|
||||
function aksrt_get_auth_tab(): string {
|
||||
$query_tab = get_query_var('aksrt_auth_tab');
|
||||
if (is_string($query_tab) && $query_tab !== '') {
|
||||
return aksrt_normalize_auth_tab($query_tab);
|
||||
}
|
||||
|
||||
if (!empty($_GET['tab'])) {
|
||||
return aksrt_normalize_auth_tab((string) wp_unslash($_GET['tab']));
|
||||
}
|
||||
|
||||
$post = get_queried_object();
|
||||
if ($post instanceof WP_Post) {
|
||||
return aksrt_normalize_auth_tab((string) $post->post_name);
|
||||
}
|
||||
|
||||
return 'login';
|
||||
}
|
||||
|
||||
/**
|
||||
* 清除模板页面缓存
|
||||
*/
|
||||
@@ -390,8 +233,7 @@ function aksrt_get_current_special_template_path(): string {
|
||||
}
|
||||
|
||||
if (get_query_var('aksrt_sign')) {
|
||||
$auth_template = aksrt_get_auth_template_file((string) get_query_var('aksrt_auth_tab'), true);
|
||||
$auth_template_path = aksrt_locate_template_file($auth_template);
|
||||
$auth_template_path = aksrt_locate_template_file('templates/pages/page-sign.php');
|
||||
return $auth_template_path ?: '';
|
||||
}
|
||||
|
||||
|
||||
@@ -149,36 +149,14 @@ endif;
|
||||
|
||||
if (!function_exists('aksrt_get_sign_page_url')) :
|
||||
/**
|
||||
* Get the unified auth page URL, with legacy route fallback while the page self-heals.
|
||||
* Get the only public authentication page URL.
|
||||
*
|
||||
* @param string $tab
|
||||
* @param array<string, scalar> $args
|
||||
*/
|
||||
function aksrt_get_sign_page_url(string $tab = 'login', array $args = []): string {
|
||||
function aksrt_get_sign_page_url(string $tab = 'signin', array $args = []): string {
|
||||
$tab = aksrt_normalize_auth_tab($tab);
|
||||
|
||||
$legacy_fallbacks = [
|
||||
'login' => home_url('/login/'),
|
||||
'register' => home_url('/register/'),
|
||||
'bind' => home_url('/bind-account/'),
|
||||
'reset-password' => home_url('/reset-password/'),
|
||||
'verify-email' => home_url('/verify-email/'),
|
||||
];
|
||||
|
||||
$page = aksrt_find_template_page('templates/pages/page-sign.php');
|
||||
if (!$page) {
|
||||
aksrt_ensure_template_page('sign');
|
||||
$page = aksrt_find_template_page('templates/pages/page-sign.php');
|
||||
}
|
||||
|
||||
if ($page instanceof WP_Post) {
|
||||
$url = get_permalink($page->ID);
|
||||
$args = ['tab' => $tab] + $args;
|
||||
return add_query_arg($args, $url);
|
||||
}
|
||||
|
||||
$url = $legacy_fallbacks[$tab] ?? home_url('/login/');
|
||||
return !empty($args) ? add_query_arg($args, $url) : $url;
|
||||
return add_query_arg(['tab' => $tab] + $args, home_url('/sign/'));
|
||||
}
|
||||
|
||||
|
||||
@@ -187,19 +165,14 @@ function aksrt_get_sign_page_url(string $tab = 'login', array $args = []): strin
|
||||
*/
|
||||
function aksrt_is_auth_page_request(): bool {
|
||||
return (bool) get_query_var('aksrt_sign')
|
||||
|| is_page_template('templates/pages/page-sign.php')
|
||||
|| is_page_template('templates/pages/page-login.php')
|
||||
|| is_page_template('templates/pages/page-register.php')
|
||||
|| is_page_template('templates/pages/page-bind-account.php')
|
||||
|| is_page_template('templates/pages/page-reset-password.php')
|
||||
|| is_page_template('templates/pages/page-verify-email.php');
|
||||
|| is_page_template('templates/pages/page-sign.php');
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_login_url')) :
|
||||
/** 获取登录页 URL */
|
||||
function aksrt_get_login_url($redirect = '') {
|
||||
$url = aksrt_get_sign_page_url('login');
|
||||
$url = aksrt_get_sign_page_url('signin');
|
||||
if ($redirect) {
|
||||
$url = add_query_arg('redirect_to', urlencode($redirect), $url);
|
||||
}
|
||||
@@ -210,14 +183,14 @@ endif;
|
||||
if (!function_exists('aksrt_get_register_url')) :
|
||||
/** 获取注册页 URL */
|
||||
function aksrt_get_register_url() {
|
||||
return aksrt_get_sign_page_url('register');
|
||||
return aksrt_get_sign_page_url('signup');
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_bind_url')) :
|
||||
/** 获取绑定账号页面 URL */
|
||||
function aksrt_get_bind_url($transient_key = '') {
|
||||
$url = aksrt_get_template_url('bind');
|
||||
$url = aksrt_get_sign_page_url('bind');
|
||||
if ($transient_key) {
|
||||
$url = add_query_arg('obk', $transient_key, $url);
|
||||
}
|
||||
@@ -226,24 +199,12 @@ function aksrt_get_bind_url($transient_key = '') {
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_logout_url')) :
|
||||
/**
|
||||
* 获取退出登录 URL。
|
||||
*
|
||||
* Prefer query-arg form so logout works even when /logout/ rewrite is missing
|
||||
* (Apache would 404 the pretty path before WordPress runs). Pretty /logout/
|
||||
* remains registered as a convenience alias.
|
||||
*
|
||||
* @param string $redirect Redirect after logout.
|
||||
*/
|
||||
/** 获取唯一退出登录 URL。 */
|
||||
function aksrt_get_logout_url($redirect = '') {
|
||||
$target = is_string($redirect) && $redirect !== '' ? $redirect : home_url('/');
|
||||
// home_url('/?aksrt_logout=1') always hits index.php without rewrite rules.
|
||||
$url = add_query_arg(
|
||||
[
|
||||
'aksrt_logout' => '1',
|
||||
'redirect_to' => $target,
|
||||
],
|
||||
home_url('/')
|
||||
['redirect_to' => $target],
|
||||
home_url('/logout/')
|
||||
);
|
||||
return wp_nonce_url($url, 'aksrt-logout');
|
||||
}
|
||||
@@ -253,33 +214,10 @@ if (!function_exists('aksrt_get_reset_password_url')) :
|
||||
/**
|
||||
* 获取重置密码页面 URL
|
||||
*
|
||||
* @param string $token 主题重置 token(可选,设置密码时需要)
|
||||
* @param int $user_id 用户 ID(可选,设置密码时需要)
|
||||
* @return string
|
||||
*/
|
||||
function aksrt_get_reset_password_url(string $token = '', int $user_id = 0): string {
|
||||
$url = aksrt_get_template_url('reset-password');
|
||||
if ($token !== '' && $user_id > 0) {
|
||||
$url = add_query_arg([
|
||||
'token' => $token,
|
||||
'uid' => $user_id,
|
||||
], $url);
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
endif;
|
||||
|
||||
if (!function_exists('aksrt_get_verify_email_url')) :
|
||||
/** 获取邮箱验证页面 URL */
|
||||
function aksrt_get_verify_email_url(int $user_id = 0, array $args = []): string {
|
||||
$url = aksrt_get_template_url('verify-email');
|
||||
if ($user_id > 0) {
|
||||
$args['uid'] = $user_id;
|
||||
}
|
||||
if (!empty($args)) {
|
||||
$url = add_query_arg($args, $url);
|
||||
}
|
||||
return $url;
|
||||
function aksrt_get_reset_password_url(): string {
|
||||
return aksrt_get_sign_page_url('resetpassword');
|
||||
}
|
||||
endif;
|
||||
|
||||
|
||||
@@ -324,14 +324,6 @@ class ImportExportController extends ControllerBase {
|
||||
'status' => empty($missing_tables) ? 'ok' : 'error',
|
||||
];
|
||||
|
||||
// Email verification backfill
|
||||
$backfill_done = get_option('aksrt_email_verification_backfill_done', false);
|
||||
$checks['email_backfill'] = [
|
||||
'label' => '邮箱验证迁移',
|
||||
'value' => $backfill_done ? '已完成' : '未完成',
|
||||
'status' => $backfill_done ? 'ok' : 'info',
|
||||
];
|
||||
|
||||
// Option count
|
||||
$option_count = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->options} WHERE option_name LIKE 'aksrt_%'"
|
||||
|
||||
@@ -32,7 +32,7 @@ class SchemaPermissions {
|
||||
['key' => 'mode', 'label' => '触发模式', 'type' => 'radio', 'default' => 'always', 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
||||
['label' => '始终显示', 'value' => 'always'],
|
||||
]],
|
||||
['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' => [
|
||||
['key' => 'enabled_scenarios', 'label' => '启用场景', 'type' => 'checkbox', 'default' => ['comment', 'login', 'register', 'bind', 'bind_phone', 'lostpassword', 'change_password', 'change_email', 'download', 'signin'], 'dependsOn' => ['key' => 'enabled', 'value' => true], 'options' => [
|
||||
['label' => '发表评论', 'value' => 'comment'],
|
||||
['label' => '用户登录', 'value' => 'login'],
|
||||
['label' => '用户注册', 'value' => 'register'],
|
||||
@@ -41,7 +41,6 @@ class SchemaPermissions {
|
||||
['label' => '找回密码', 'value' => 'lostpassword'],
|
||||
['label' => '修改密码', 'value' => 'change_password'],
|
||||
['label' => '更改邮箱', 'value' => 'change_email'],
|
||||
['label' => '邮箱验证', 'value' => 'verify_email'],
|
||||
['label' => '资源下载', 'value' => 'download'],
|
||||
['label' => '每日签到', 'value' => 'signin'],
|
||||
['label' => '前台投稿', 'value' => 'front_submit'],
|
||||
|
||||
@@ -115,30 +115,29 @@ class SchemaUsers {
|
||||
'groups' => [
|
||||
['key' => 'register', 'title' => '注册设置', 'fields' => [
|
||||
['key' => 'close_sign', 'label' => '一键禁用登录注册', 'type' => 'switch', 'default' => false, 'desc' => '开启后前台禁用全部注册/登录入口(含社交登录),不影响后台登录'],
|
||||
['key' => 'user_close_signup', 'label' => '关闭注册', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'close_signin', 'label' => '禁用账密登录', 'type' => 'switch', 'default' => false, 'desc' => '仅允许社交账号登录'],
|
||||
['key' => 'user_agreement_s', 'label' => '用户协议', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'close_signup', 'label' => '关闭注册', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'close_signin', 'label' => '禁用账号登录/注册', 'type' => 'switch', 'default' => false, 'desc' => '关闭账号密码和验证码登录注册,仅允许社交账号登录'],
|
||||
['key' => 'user_agreement_s', 'label' => '用户协议', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_agreement_page', 'label' => '协议页面', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_privacy_s', 'label' => '隐私政策', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_privacy_s', 'label' => '隐私政策', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_privacy_page', 'label' => '隐私页面', 'type' => 'input', 'default' => ''],
|
||||
['key' => 'user_agreement_check', 'label' => '强制勾选协议', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'no_repetition_name', 'label' => '禁止重复昵称', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_nickname_out', 'label' => '昵称黑名单关键词', 'type' => 'textarea', 'default' => "赌博,博彩,代发,代理,刷单,博彩,六合彩,admin,test,管理员,客服", 'desc' => '一行一个关键词,注册昵称包含关键词将被拒绝'],
|
||||
['key' => 'user_email_limit_type', 'label' => '邮箱限制类型', 'type' => 'radio', 'default' => 'black', 'options' => [
|
||||
['label' => '黑名单(拒绝以下后缀)', 'value' => 'black'],
|
||||
['label' => '白名单(仅允许以下后缀)', 'value' => 'white'],
|
||||
['key' => 'user_agreement_check', 'label' => '强制勾选协议', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'no_repetition_name', 'label' => '禁止重复昵称', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_nickname_out', 'label' => '昵称黑名单关键词', 'type' => 'textarea', 'default' => '赌博,博彩,彩票,性爱,色情,做爱,爱爱,淫秽,傻b,妈的,妈b,admin,test'],
|
||||
['key' => 'user_email_limit_type', 'label' => '邮箱限制类型', 'type' => 'radio', 'default' => 'not', 'options' => [
|
||||
['label' => '黑名单', 'value' => 'not'],
|
||||
['label' => '白名单', 'value' => 'allow'],
|
||||
]],
|
||||
['key' => 'user_email_out', 'label' => '邮箱限制后缀', 'type' => 'textarea', 'default' => "@tempmail.com\n@throwaway.email\n@guerrillamail.com", 'desc' => '一行一个邮箱后缀,如 @tempmail.com'],
|
||||
['key' => 'user_email_out', 'label' => '邮箱限制关键词', 'type' => 'textarea', 'default' => ''],
|
||||
]],
|
||||
['key' => 'login', 'title' => '登录设置', 'fields' => [
|
||||
['key' => 'user_close_login', 'label' => '关闭登录', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_sign_type', 'label' => '登录注册样式', 'type' => 'radio', 'default' => 'page', 'options' => [
|
||||
['key' => 'user_sign_type', 'label' => '登录注册样式', 'type' => 'radio', 'default' => 'modal', 'options' => [
|
||||
['label' => '弹窗', 'value' => 'modal'],
|
||||
['label' => '跳转页面', 'value' => 'page'],
|
||||
]],
|
||||
['key' => 'sign_modal_auto_s', 'label' => '自动弹出登录窗', 'type' => 'switch', 'default' => false, 'desc' => '仅在 user_sign_type=modal 时生效'],
|
||||
['key' => 'sign_modal_auto_waiting_time', 'label' => '弹窗等待秒数', 'type' => 'number', 'default' => 5, 'min' => 0, 'max' => 60, 'desc' => '页面加载多少秒后弹出登录窗'],
|
||||
['key' => 'sign_modal_auto_expires', 'label' => '弹窗周期(小时)', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 720, 'desc' => '关闭弹窗后多少小时内不再自动弹出,0=每次刷新都弹'],
|
||||
['key' => 'sign_modal_auto_waiting_time', 'label' => '弹窗等待秒数', 'type' => 'number', 'default' => 5, 'min' => 1, 'max' => 100, 'desc' => '页面加载多少秒后弹出登录窗'],
|
||||
['key' => 'sign_modal_auto_expires', 'label' => '弹窗周期(小时)', 'type' => 'number', 'default' => 1, 'min' => 0, 'max' => 2000, 'desc' => '关闭弹窗后多少小时内不再自动弹出,0=每次刷新都弹'],
|
||||
['key' => 'replace_wp_login', 'label' => '替换WP登录页', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'admin_sign_key', 'label' => '应急登录 Key', 'type' => 'input', 'default' => '', 'desc' => '留空为禁用。管理员可通过 /wp-login.php?aksrt_bypass=<key> 绕过前台替换'],
|
||||
['key' => 'user_signin_phone_s', 'label' => '手机号登录', 'type' => 'switch', 'default' => false, 'desc' => '允许用手机号作为用户名登录'],
|
||||
@@ -148,20 +147,20 @@ class SchemaUsers {
|
||||
['label' => '手机验证码', 'value' => 'phone'],
|
||||
['label' => '邮箱或手机', 'value' => 'email_phone'],
|
||||
]],
|
||||
['key' => 'user_signin_nopas_active', 'label' => '免密显示模式', 'type' => 'radio', 'default' => 'nopas_first', 'options' => [
|
||||
['label' => '优先免密', 'value' => 'nopas_first'],
|
||||
['label' => '优先密码', 'value' => 'password_first'],
|
||||
['label' => '仅免密', 'value' => 'nopas_only'],
|
||||
['key' => 'user_signin_nopas_active', 'label' => '免密显示模式', 'type' => 'radio', 'default' => 'nopas', 'options' => [
|
||||
['label' => '优先免密', 'value' => 'nopas'],
|
||||
['label' => '优先密码', 'value' => 'pas'],
|
||||
['label' => '仅免密', 'value' => 'only_nopas'],
|
||||
]],
|
||||
]],
|
||||
['key' => 'signup_captcha', 'title' => '注册验证', 'fields' => [
|
||||
['key' => 'user_signup_captch', 'label' => '注册需验证', 'type' => 'switch', 'default' => true, 'desc' => '注册时需完成邮箱或手机验证'],
|
||||
['key' => 'user_signup_captch', 'label' => '注册需验证', 'type' => 'switch', 'default' => false, 'desc' => '验证码通过后才创建并登录用户'],
|
||||
['key' => 'captch_type', 'label' => '验证方式', 'type' => 'radio', 'default' => 'email', 'options' => [
|
||||
['label' => '邮箱', 'value' => 'email'],
|
||||
['label' => '手机', 'value' => 'phone'],
|
||||
['label' => '邮箱或手机', 'value' => 'email_phone'],
|
||||
]],
|
||||
['key' => 'user_signup_no_repas', 'label' => '无需重复密码', 'type' => 'switch', 'default' => true, 'desc' => '注册时不要求二次确认密码'],
|
||||
['key' => 'user_signup_no_repas', 'label' => '无需重复密码', 'type' => 'switch', 'default' => true, 'desc' => '注册时不要求二次确认密码', 'dependsOn' => ['key' => 'user_signup_captch', 'value' => true]],
|
||||
['key' => 'user_repas_captch_type', 'label' => '找回密码验证方式', 'type' => 'radio', 'default' => 'email', 'options' => [
|
||||
['label' => '邮箱', 'value' => 'email'],
|
||||
['label' => '手机', 'value' => 'phone'],
|
||||
@@ -176,8 +175,8 @@ class SchemaUsers {
|
||||
['label' => '提醒绑定邮箱和手机', 'value' => 'email_phone'],
|
||||
['label' => '关闭', 'value' => 'close'],
|
||||
]],
|
||||
['key' => 'bind_reminder_text', 'label' => '提醒文案', 'type' => 'input', 'default' => '请绑定手机号以正常使用'],
|
||||
['key' => 'bind_reminder_expires', 'label' => '提醒周期(小时)', 'type' => 'number', 'default' => 24, 'min' => 1, 'max' => 720, 'desc' => '用户关闭提醒后多少小时内不再提醒'],
|
||||
['key' => 'bind_reminder_text', 'label' => '提醒文案', 'type' => 'textarea', 'default' => '为了您的账户安全,请务必完成账户绑定'],
|
||||
['key' => 'bind_reminder_expires', 'label' => '提醒周期(小时)', 'type' => 'number', 'default' => 24, 'min' => 0, 'max' => 2000],
|
||||
['key' => 'mandatory_bind', 'label' => '强制绑定', 'type' => 'radio', 'default' => 'close', 'options' => [
|
||||
['label' => '强制绑定邮箱', 'value' => 'email'],
|
||||
['label' => '强制绑定手机', 'value' => 'phone'],
|
||||
@@ -187,37 +186,34 @@ class SchemaUsers {
|
||||
['key' => 'email_set_captch', 'label' => '绑定邮箱需验证', 'type' => 'switch', 'default' => true, 'desc' => '绑定邮箱时需发送验证码确认'],
|
||||
]],
|
||||
['key' => 'referral_inline', 'title' => '推广码注册', 'fields' => [
|
||||
['key' => 'referral_registration_enabled', 'label' => '开放推广码注册', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'referral_register_field', 'label' => '注册页推广码字段', 'type' => 'select', 'default' => 'optional', 'options' => [
|
||||
['label' => '隐藏', 'value' => 'hidden'],
|
||||
['label' => '选填', 'value' => 'optional'],
|
||||
['label' => '必填', 'value' => 'required'],
|
||||
['key' => 'invit_code_s', 'label' => '推广码注册模式', 'type' => 'radio', 'default' => 'close', 'options' => [
|
||||
['label' => '关闭', 'value' => 'close'],
|
||||
['label' => '选填', 'value' => 'open'],
|
||||
['label' => '必填', 'value' => 'must'],
|
||||
]],
|
||||
['key' => 'referral_reject_invalid', 'label' => '拒绝无效推广码', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'referral_code_length', 'label' => 'Ref 码长度', 'type' => 'number', 'default' => 12, 'min' => 4, 'max' => 12],
|
||||
['key' => 'referral_window_days', 'label' => '首次触达窗口(天)', 'type' => 'number', 'default' => 30, 'min' => 1, 'max' => 365],
|
||||
['key' => 'referral_open_desc', 'label' => '选填提示', 'type' => 'input', 'default' => '推广码为选填,没有推广码也可以注册'],
|
||||
['key' => 'referral_required_desc', 'label' => '必填提示', 'type' => 'input', 'default' => '请输入推广码'],
|
||||
['key' => 'mandatory_bind_text', 'label' => '强制绑定文案', 'type' => 'textarea', 'default' => '为了您的账户安全,请先完成账户绑定'],
|
||||
['key' => 'invit_code_open_desc', 'label' => '选填提示', 'type' => 'input', 'default' => '如您受到邀请,可填写推广码。部分推广码会有额外积分赠送'],
|
||||
['key' => 'invit_code_must_desc', 'label' => '必填提示', 'type' => 'textarea', 'default' => '<div>本站需使用推广码注册</div><div>获取推广码请与客服联系</div>'],
|
||||
]],
|
||||
['key' => 'sign_page_ui', 'title' => '页面 UI 配置', 'fields' => [
|
||||
['key' => 'sign_page_background', 'label' => '页面背景图', 'type' => 'gallery', 'default' => [], 'desc' => '登录注册页背景图,多图随机'],
|
||||
['key' => 'sign_theme_mode', 'label' => '主题模式', 'type' => 'radio', 'default' => 'default', 'options' => [
|
||||
['label' => '默认', 'value' => 'default'],
|
||||
['label' => '亮色', 'value' => 'light'],
|
||||
['label' => '暗色', 'value' => 'dark'],
|
||||
['key' => 'sign_theme_mode', 'label' => '主题模式', 'type' => 'radio', 'default' => 'no', 'options' => [
|
||||
['label' => '默认', 'value' => 'no'],
|
||||
['label' => '亮色', 'value' => 'white-theme'],
|
||||
['label' => '暗色', 'value' => 'dark-theme'],
|
||||
]],
|
||||
['key' => 'sign_show_header', 'label' => '显示顶部导航', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sign_card_position', 'label' => '卡片位置', 'type' => 'radio', 'default' => 'center', 'options' => [
|
||||
['key' => 'sign_card_position', 'label' => '卡片位置', 'type' => 'radio', 'default' => 'right', 'options' => [
|
||||
['label' => '靠左', 'value' => 'left'],
|
||||
['label' => '居中', 'value' => 'center'],
|
||||
['label' => '靠右', 'value' => 'right'],
|
||||
]],
|
||||
['key' => 'sign_show_logo', 'label' => '显示 LOGO', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sign_show_logo', 'label' => '显示 LOGO', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'sign_page_footer', 'label' => '页脚 HTML', 'type' => 'textarea', 'default' => '', 'desc' => '登录注册页自定义页脚内容,支持 HTML'],
|
||||
]],
|
||||
['key' => 'modal_ui', 'title' => '弹窗 UI 配置', 'fields' => [
|
||||
['key' => 'sign_modal_background', 'label' => '弹窗背景图', 'type' => 'gallery', 'default' => [], 'desc' => '弹窗左侧背景图,多图随机'],
|
||||
['key' => 'sign_modal_show_logo', 'label' => '弹窗显示 LOGO', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'sign_modal_show_logo', 'label' => '弹窗显示 LOGO', 'type' => 'switch', 'default' => false],
|
||||
]],
|
||||
['key' => 'sign_logo', 'title' => '登录框 LOGO', 'fields' => [
|
||||
['key' => 'user_logo', 'label' => '日间 LOGO', 'type' => 'gallery', 'default' => [], 'desc' => '登录框专用日间 LOGO,留空使用站点全局 LOGO'],
|
||||
|
||||
@@ -17,20 +17,6 @@ class ToolsController extends ControllerBase {
|
||||
}
|
||||
|
||||
public function register_routes(): void {
|
||||
// Health: email verification status
|
||||
register_rest_route(self::API_NAMESPACE, '/health/email-verification', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'rest_health_email_verification'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Health: REST block toggle
|
||||
register_rest_route(self::API_NAMESPACE, '/health/rest-block', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'rest_set_rest_block'],
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// Email rollback: list users with recent email changes
|
||||
register_rest_route(self::API_NAMESPACE, '/email-rollback/users', [
|
||||
'methods' => 'GET',
|
||||
@@ -213,35 +199,6 @@ class ToolsController extends ControllerBase {
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_health_email_verification(): \WP_REST_Response {
|
||||
$feature_enabled = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::registration_requires_email_verification();
|
||||
$pending_users = 0;
|
||||
|
||||
if (class_exists('\AKSRT\Auth\EmailVerification')) {
|
||||
global $wpdb;
|
||||
$pending_users = (int) $wpdb->get_var(
|
||||
"SELECT COUNT(*) FROM {$wpdb->usermeta} WHERE meta_key = 'aksrt_email_verified' AND CAST(meta_value AS UNSIGNED) = 0"
|
||||
);
|
||||
}
|
||||
|
||||
return $this->success(['data' => [
|
||||
'feature_enabled' => $feature_enabled,
|
||||
'pending_users' => $pending_users,
|
||||
'rest_blocking_active' => !empty(get_option('aksrt_rest_block_email_verification', false)),
|
||||
]]);
|
||||
}
|
||||
|
||||
public function rest_set_rest_block(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$enabled = !empty($params['enabled']);
|
||||
update_option('aksrt_rest_block_email_verification', $enabled);
|
||||
|
||||
return $this->success([
|
||||
'message' => $enabled ? __('已启用 REST 邮箱验证阻断', 'aksrt') : __('已关闭 REST 邮箱验证阻断', 'aksrt'),
|
||||
'data' => ['enabled' => $enabled],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rest_email_rollback_list(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Auth\EmailRollback')) {
|
||||
return $this->success(['data' => ['items' => [], 'total' => 0, 'page' => 1, 'per_page' => 20]]);
|
||||
|
||||
@@ -207,27 +207,36 @@ class UserController extends ControllerBase {
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// ── Modal Auth (弹窗登录/注册) ──
|
||||
|
||||
// Modal login
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/modal-login', [
|
||||
// Shared public authentication contract for modal and /sign/.
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/code', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'modal_login'],
|
||||
'callback' => [$this, 'auth_code'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Modal register
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/modal-register', [
|
||||
register_rest_route(self::API_NAMESPACE, '/user/phone-code', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'modal_register'],
|
||||
'callback' => [$this, 'user_phone_code'],
|
||||
'permission_callback' => static function () { return is_user_logged_in(); },
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/user/phone', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'user_bind_phone'],
|
||||
'permission_callback' => static function () { return is_user_logged_in(); },
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Current user info (for AJAX refresh)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/me', [
|
||||
'methods' => 'GET',
|
||||
'callback' => [$this, 'get_current_user_info'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/register', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_register'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/reset-password', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_reset_password'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// Identity verification provider settings (admin only)
|
||||
@@ -277,41 +286,6 @@ class UserController extends ControllerBase {
|
||||
'permission_callback' => [$this, 'check_permission'],
|
||||
]);
|
||||
|
||||
// SMS auth: send verification code (public, rate-limited)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/send-sms-code', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_send_sms_code'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: register by phone (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-register', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_register'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: login by phone + password (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: login by phone + SMS code (public)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/phone-code-login', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_phone_code_login'],
|
||||
'permission_callback' => '__return_true',
|
||||
]);
|
||||
|
||||
// SMS auth: bind phone to current user (logged-in)
|
||||
register_rest_route(self::API_NAMESPACE, '/auth/bind-phone', [
|
||||
'methods' => 'POST',
|
||||
'callback' => [$this, 'auth_bind_phone'],
|
||||
'permission_callback' => function () { return is_user_logged_in(); },
|
||||
]);
|
||||
|
||||
// ── User Restrictions / Ban ──
|
||||
|
||||
// List restrictions for a user (admin)
|
||||
@@ -401,22 +375,7 @@ class UserController extends ControllerBase {
|
||||
|
||||
if ($request->get_method() === 'GET') {
|
||||
$data = \AKSRT\Settings\Settings::get_all();
|
||||
$referral = class_exists('\AKSRT\Settings\CreditsRuntime')
|
||||
? \AKSRT\Settings\CreditsRuntime::get_referral_registration_settings()
|
||||
: [];
|
||||
$shared = class_exists('\AKSRT\Settings\CreditsRuntime')
|
||||
? \AKSRT\Settings\CreditsRuntime::get_referral_settings()
|
||||
: [];
|
||||
|
||||
return $this->success(['data' => [
|
||||
'registration_email_verification_enabled' => \AKSRT\Settings\Settings::registration_requires_email_verification(),
|
||||
'registration_email_verification_method' => \AKSRT\Settings\Settings::registration_verification_method(),
|
||||
'referral_registration_enabled' => $referral['enabled'] ?? true,
|
||||
'referral_code_length' => $shared['code_length'] ?? 12,
|
||||
'referral_window_days' => $shared['window_days'] ?? 30,
|
||||
'referral_register_field' => $referral['register_field'] ?? 'optional',
|
||||
'referral_reject_invalid' => $referral['reject_invalid'] ?? true,
|
||||
] + array_intersect_key($data, $allowed)]);
|
||||
return $this->success(['data' => array_intersect_key($data, $allowed)]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
@@ -424,30 +383,6 @@ class UserController extends ControllerBase {
|
||||
$sanitized = $this->sanitize_fields($params, $allowed);
|
||||
$settings = array_merge($settings, $sanitized);
|
||||
|
||||
if (array_key_exists('referral_registration_enabled', $params)) {
|
||||
$settings['referral_registration_enabled'] = !empty($params['referral_registration_enabled']);
|
||||
}
|
||||
if (array_key_exists('referral_code_length', $params)) {
|
||||
$settings['referral_code_length'] = max(4, min(12, (int) $params['referral_code_length']));
|
||||
}
|
||||
if (array_key_exists('referral_window_days', $params)) {
|
||||
$settings['referral_window_days'] = max(1, min(365, (int) $params['referral_window_days']));
|
||||
}
|
||||
if (array_key_exists('referral_register_field', $params)) {
|
||||
$field = sanitize_key((string) $params['referral_register_field']);
|
||||
$settings['referral_register_field'] = in_array($field, ['hidden', 'optional', 'required'], true) ? $field : 'optional';
|
||||
}
|
||||
if (array_key_exists('referral_reject_invalid', $params)) {
|
||||
$settings['referral_reject_invalid'] = !empty($params['referral_reject_invalid']);
|
||||
}
|
||||
if (array_key_exists('registration_email_verification_enabled', $params)) {
|
||||
$settings['registration_email_verification_enabled'] = !empty($params['registration_email_verification_enabled']);
|
||||
}
|
||||
if (array_key_exists('registration_email_verification_method', $params)) {
|
||||
$method = (string) $params['registration_email_verification_method'];
|
||||
$settings['registration_email_verification_method'] = in_array($method, ['link', 'code'], true) ? $method : 'link';
|
||||
}
|
||||
|
||||
update_option('aksrt_settings', $settings);
|
||||
\AKSRT\Settings\Settings::flush_settings_cache();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared public authentication REST operations.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
@@ -9,286 +11,385 @@ defined('ABSPATH') || exit;
|
||||
|
||||
trait UserControllerAuthPhoneOperations {
|
||||
|
||||
/**
|
||||
* POST /auth/modal-login — 弹窗登录
|
||||
*/
|
||||
public function modal_login(\WP_REST_Request $request): \WP_REST_Response {
|
||||
private function auth_payload(\WP_REST_Request $request): array {
|
||||
$params = $this->get_json_params($request);
|
||||
$payload = is_array($params) ? $params : [];
|
||||
$login = sanitize_text_field((string) ($payload['log'] ?? $payload['login'] ?? ''));
|
||||
$password = (string) ($payload['pwd'] ?? $payload['password'] ?? '');
|
||||
|
||||
if ($login === '' || $password === '') {
|
||||
return $this->error('请输入用户名和密码');
|
||||
return is_array($params) ? $params : [];
|
||||
}
|
||||
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用', 500);
|
||||
private function auth_verify_rest_nonce(\WP_REST_Request $request, array $payload): bool {
|
||||
$nonce = sanitize_text_field((string) ($request->get_header('X-WP-Nonce') ?: ($payload['_wpnonce'] ?? '')));
|
||||
return $nonce !== '' && (bool) wp_verify_nonce($nonce, 'wp_rest');
|
||||
}
|
||||
|
||||
$result = \AKSRT\Auth\Service::login_user([
|
||||
...$payload,
|
||||
'log' => $login,
|
||||
'pwd' => $password,
|
||||
'rememberme' => !empty($payload['rememberme']) ? (string) $payload['rememberme'] : 'forever',
|
||||
], true, true);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
$error = $result['error'] ?? '登录失败';
|
||||
$verify_url = $result['verify_url'] ?? '';
|
||||
$response = ['success' => false, 'message' => $error];
|
||||
if ($verify_url) {
|
||||
$response['verify_url'] = $verify_url;
|
||||
}
|
||||
return new \WP_REST_Response($response, 401);
|
||||
private function auth_rate_key(string $action, string $subject = ''): string {
|
||||
$ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'));
|
||||
return 'aksrt_auth_rate_' . md5($action . '|' . strtolower($subject) . '|' . $ip);
|
||||
}
|
||||
|
||||
$user = wp_get_current_user();
|
||||
return $this->success([
|
||||
'message' => __('登录成功', 'aksrt'),
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
'user' => [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
],
|
||||
]);
|
||||
private function auth_rate_limited(string $action, string $subject = '', int $limit = 8, int $ttl = 600): bool {
|
||||
$key = $this->auth_rate_key($action, $subject);
|
||||
$count = (int) get_transient($key);
|
||||
if ($count >= $limit) {
|
||||
return true;
|
||||
}
|
||||
set_transient($key, $count + 1, $ttl);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/modal-register — 弹窗注册
|
||||
*/
|
||||
public function modal_register(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$payload = is_array($params) ? $params : [];
|
||||
$username = sanitize_user((string) ($payload['user_login'] ?? $payload['username'] ?? ''), true);
|
||||
$email = sanitize_email((string) ($payload['user_email'] ?? $payload['email'] ?? ''));
|
||||
$password = (string) ($payload['user_pass'] ?? $payload['password'] ?? '');
|
||||
$confirm = (string) ($payload['user_pass_confirm'] ?? $payload['password_confirm'] ?? $payload['confirm_password'] ?? $password);
|
||||
$ref = sanitize_key((string) ($payload['ref'] ?? ''));
|
||||
$register_nonce = sanitize_text_field((string) ($payload['_register_nonce'] ?? ''));
|
||||
$agree_terms = !empty($payload['agree_terms']) ? '1' : '';
|
||||
|
||||
if ($username === '' || $email === '' || $password === '') {
|
||||
return $this->error('请填写所有必填项');
|
||||
private function auth_code_key(string $scene, string $target): string {
|
||||
return 'aksrt_auth_code_' . md5($scene . '|' . strtolower(trim($target)));
|
||||
}
|
||||
|
||||
if (!is_email($email)) {
|
||||
return $this->error('邮箱格式不正确');
|
||||
}
|
||||
|
||||
if (strlen($password) < 6) {
|
||||
return $this->error('密码至少 6 位');
|
||||
}
|
||||
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用', 500);
|
||||
}
|
||||
|
||||
$payload['user_login'] = $username;
|
||||
$payload['user_email'] = $email;
|
||||
$payload['user_pass'] = $password;
|
||||
$payload['user_pass_confirm'] = $confirm;
|
||||
$payload['ref'] = $ref;
|
||||
$payload['_register_nonce'] = $register_nonce;
|
||||
$payload['agree_terms'] = $agree_terms;
|
||||
|
||||
$result = \AKSRT\Auth\Service::register_user($payload);
|
||||
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($result['error'] ?? '注册失败');
|
||||
}
|
||||
|
||||
$verification_required = !empty($result['verification_required']);
|
||||
$login_url = function_exists('aksrt_get_login_url') ? aksrt_get_login_url() : wp_login_url();
|
||||
$registered_user = null;
|
||||
$current_user = wp_get_current_user();
|
||||
if (
|
||||
$current_user instanceof \WP_User
|
||||
&& $current_user->ID > 0
|
||||
&& !empty($result['user_id'])
|
||||
&& (int) $result['user_id'] === (int) $current_user->ID
|
||||
) {
|
||||
$registered_user = $current_user;
|
||||
}
|
||||
$user = $current_user;
|
||||
$needs_verify = $verification_required;
|
||||
|
||||
return $this->success([
|
||||
'message' => $needs_verify ? __('注册成功,请验证邮箱', 'aksrt') : __('注册成功', 'aksrt'),
|
||||
'needs_verification' => $needs_verify,
|
||||
'verify_url' => $result['verify_url'] ?? '',
|
||||
'redirect' => $verification_required ? ($result['verify_url'] ?? '') : $login_url,
|
||||
'verification_required' => $verification_required,
|
||||
'login_url' => $login_url,
|
||||
'user_login' => $result['user_login'] ?? $username,
|
||||
'user_email' => $email,
|
||||
'notice' => (string) ($result['notice'] ?? ''),
|
||||
'user' => $registered_user ? [
|
||||
'id' => $registered_user->ID,
|
||||
'display_name' => $registered_user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($registered_user->ID, ['size' => 48])
|
||||
: get_avatar_url($registered_user->ID, ['size' => 48]),
|
||||
] : ($user->ID ? [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
] : null),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /auth/me — 当前用户信息(用于 AJAX 刷新)
|
||||
*/
|
||||
public function get_current_user_info(): \WP_REST_Response {
|
||||
$user = wp_get_current_user();
|
||||
if (!$user->ID) {
|
||||
return $this->error('未登录', 401);
|
||||
}
|
||||
|
||||
$credits = 0;
|
||||
if (class_exists('\AKSRT\Credits\Credits')) {
|
||||
$credits = \AKSRT\Credits\Credits::balance($user->ID);
|
||||
}
|
||||
|
||||
return $this->success([
|
||||
'data' => [
|
||||
'id' => $user->ID,
|
||||
'display_name' => $user->display_name,
|
||||
'user_email' => $user->user_email,
|
||||
'avatar_url' => function_exists('aksrt_get_avatar_url')
|
||||
? aksrt_get_avatar_url($user->ID, ['size' => 48])
|
||||
: get_avatar_url($user->ID, ['size' => 48]),
|
||||
'is_admin' => current_user_can('manage_options'),
|
||||
'credits' => $credits,
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/send-sms-code — 发送短信验证码
|
||||
*/
|
||||
public function auth_send_sms_code(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$params = $this->get_json_params($request);
|
||||
$phone = sanitize_text_field((string) ($params['phone'] ?? ''));
|
||||
$scene = sanitize_key((string) ($params['scene'] ?? 'register'));
|
||||
if (!array_key_exists($scene, function_exists('aksrt_captcha_scenes') ? aksrt_captcha_scenes() : [])) {
|
||||
$scene = 'register';
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha($scene, is_array($params) ? $params : []);
|
||||
if ($captcha_result !== true) {
|
||||
return $this->error((string) $captcha_result, 400);
|
||||
}
|
||||
private function auth_store_email_code(string $scene, string $target, string $code): void {
|
||||
set_transient($this->auth_code_key($scene, $target), [
|
||||
'hash' => wp_hash_password($code),
|
||||
'attempts' => 0,
|
||||
], 10 * MINUTE_IN_SECONDS);
|
||||
}
|
||||
|
||||
private function auth_consume_code(string $scene, string $target, string $code, string $channel): bool {
|
||||
if ($channel === 'phone') {
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) {
|
||||
return $this->error('短信服务不可用');
|
||||
return false;
|
||||
}
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->verify_code($target, $code);
|
||||
return !empty($result['success']);
|
||||
}
|
||||
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($phone);
|
||||
$key = $this->auth_code_key($scene, $target);
|
||||
$stored = get_transient($key);
|
||||
if (!is_array($stored) || empty($stored['hash'])) {
|
||||
return false;
|
||||
}
|
||||
$attempts = (int) ($stored['attempts'] ?? 0) + 1;
|
||||
if ($attempts > 6 || !wp_check_password($code, (string) $stored['hash'])) {
|
||||
$stored['attempts'] = $attempts;
|
||||
set_transient($key, $stored, 10 * MINUTE_IN_SECONDS);
|
||||
return false;
|
||||
}
|
||||
delete_transient($key);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'message' => $result['message'],
|
||||
'remaining_time' => $result['remaining_time'] ?? 60,
|
||||
private function auth_channel(string $target, string $requested = ''): string {
|
||||
if ($requested === 'phone' || preg_match('/^1[3-9]\d{9}$/', $target)) {
|
||||
return 'phone';
|
||||
}
|
||||
return 'email';
|
||||
}
|
||||
|
||||
private function auth_channel_allowed(string $configured, string $channel): bool {
|
||||
return $configured === 'email_phone' || $configured === $channel;
|
||||
}
|
||||
|
||||
private function auth_safe_redirect(string $requested, string $fallback = ''): string {
|
||||
$fallback = $fallback !== '' ? $fallback : home_url('/');
|
||||
return wp_validate_redirect($requested, $fallback);
|
||||
}
|
||||
|
||||
private function auth_registration_error(string $error): string {
|
||||
if (str_contains($error, '已被') || str_contains($error, '已注册') || str_contains($error, '已使用')) {
|
||||
return __('注册信息不可用,请检查后重试', 'aksrt');
|
||||
}
|
||||
return $error;
|
||||
}
|
||||
|
||||
private function auth_find_user(string $target, string $channel) {
|
||||
if ($channel === 'phone') {
|
||||
$users = get_users([
|
||||
'meta_key' => 'aksrt_phone',
|
||||
'meta_value' => $target,
|
||||
'number' => 1,
|
||||
]);
|
||||
return $users[0] ?? null;
|
||||
}
|
||||
return is_email($target)
|
||||
? get_user_by('email', sanitize_email($target))
|
||||
: get_user_by('login', sanitize_user($target, true));
|
||||
}
|
||||
|
||||
$response = [
|
||||
'success' => false,
|
||||
'message' => $result['message'],
|
||||
];
|
||||
if (isset($result['remaining_time'])) {
|
||||
$response['data'] = ['remaining_time' => $result['remaining_time']];
|
||||
}
|
||||
return new \WP_REST_Response($response, 400);
|
||||
public function auth_code(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) {
|
||||
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
$scene = sanitize_key((string) ($payload['scene'] ?? ''));
|
||||
$target = trim(sanitize_text_field((string) ($payload['target'] ?? '')));
|
||||
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
|
||||
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
|
||||
$oauth_bind = $obk !== '' && class_exists('\AKSRT\Auth\Service') && is_array(\AKSRT\Auth\Service::get_oauth_bind_context($obk));
|
||||
if (!in_array($scene, ['login', 'register', 'resetpassword'], true)) {
|
||||
return $this->error(__('请求类型无效', 'aksrt'));
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
if (\AKSRT\Settings\Settings::close_sign()) return $this->error(__('登录注册已关闭', 'aksrt'), 403);
|
||||
if (($scene === 'login' || $scene === 'resetpassword') && \AKSRT\Settings\Settings::close_signin() && !$oauth_bind) return $this->error(__('当前登录方式已关闭', 'aksrt'), 403);
|
||||
if ($scene === 'register' && !\AKSRT\Settings\Settings::registration_enabled() && !$oauth_bind) return $this->error(__('当前已关闭新用户注册', 'aksrt'), 403);
|
||||
$type = $scene === 'login' ? \AKSRT\Settings\Settings::passwordless_login_type() : ($scene === 'register' ? \AKSRT\Settings\Settings::signup_verification_type() : \AKSRT\Settings\Settings::reset_password_verification_type());
|
||||
$enabled = $scene === 'login' ? \AKSRT\Settings\Settings::passwordless_login_enabled() : ($scene === 'register' ? \AKSRT\Settings\Settings::signup_verification_enabled() : true);
|
||||
if (!$enabled || ($type !== 'email_phone' && $type !== $channel)) return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
if ($this->auth_rate_limited('code:' . $scene, $target, 5, HOUR_IN_SECONDS)) {
|
||||
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_scene = $scene === 'resetpassword' ? 'lostpassword' : $scene;
|
||||
$captcha = aksrt_require_captcha($captcha_scene, $payload);
|
||||
if ($captcha !== true) {
|
||||
return $this->error((string) $captcha);
|
||||
}
|
||||
}
|
||||
|
||||
$generic = __('如果信息有效,验证码将发送到该账号', 'aksrt');
|
||||
if ($scene !== 'register' && !$this->auth_find_user($target, $channel)) {
|
||||
return $this->success(['message' => $generic, 'countdown' => 60]);
|
||||
}
|
||||
|
||||
if ($channel === 'phone') {
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) {
|
||||
return $this->error(__('短信服务不可用', 'aksrt'), 503);
|
||||
}
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($target);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error((string) ($result['message'] ?? __('验证码发送失败', 'aksrt')));
|
||||
}
|
||||
return $this->success(['message' => $generic, 'countdown' => (int) ($result['remaining_time'] ?? 60)]);
|
||||
}
|
||||
|
||||
$code = (string) random_int(100000, 999999);
|
||||
$this->auth_store_email_code($scene, $target, $code);
|
||||
$subject = sprintf(__('[%s] 登录注册验证码', 'aksrt'), get_bloginfo('name'));
|
||||
$message = sprintf(__('您的验证码是:%s,10 分钟内有效。', 'aksrt'), $code);
|
||||
if (!wp_mail($target, $subject, $message)) {
|
||||
delete_transient($this->auth_code_key($scene, $target));
|
||||
if ($scene !== 'register') {
|
||||
return $this->success(['message' => $generic, 'countdown' => 60]);
|
||||
}
|
||||
return $this->error(__('验证码发送失败,请稍后再试', 'aksrt'), 500);
|
||||
}
|
||||
return $this->success(['message' => $generic, 'countdown' => 60]);
|
||||
}
|
||||
|
||||
public function auth_login(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) {
|
||||
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
|
||||
}
|
||||
$target = trim(sanitize_text_field((string) ($payload['target'] ?? $payload['log'] ?? '')));
|
||||
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
|
||||
$oauth_data = $obk !== '' && class_exists('\AKSRT\Auth\Service')
|
||||
? \AKSRT\Auth\Service::get_oauth_bind_context($obk)
|
||||
: null;
|
||||
if ($obk !== '' && !$oauth_data) {
|
||||
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'aksrt'), 410);
|
||||
}
|
||||
if (
|
||||
class_exists('\AKSRT\Settings\Settings')
|
||||
&& (\AKSRT\Settings\Settings::close_sign() || (!$oauth_data && \AKSRT\Settings\Settings::close_signin()))
|
||||
) {
|
||||
return $this->error(__('当前登录方式已关闭', 'aksrt'), 403);
|
||||
}
|
||||
if ($target === '' || (preg_match('/^1[3-9]\d{9}$/', $target) && empty($payload['code']) && class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::signin_phone_enabled())) {
|
||||
return $this->error(__('账号或密码错误', 'aksrt'), 401);
|
||||
}
|
||||
if ($this->auth_rate_limited('login', $target)) {
|
||||
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha = aksrt_require_captcha($oauth_data ? 'bind' : 'login', $payload);
|
||||
if ($captcha !== true) {
|
||||
return $this->error((string) $captcha);
|
||||
}
|
||||
}
|
||||
$redirect = $this->auth_safe_redirect((string) ($payload['redirect_to'] ?? ''), home_url('/'));
|
||||
|
||||
if (!empty($payload['code'])) {
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || !\AKSRT\Settings\Settings::passwordless_login_enabled()) {
|
||||
return $this->error(__('当前未开启免密登录', 'aksrt'));
|
||||
}
|
||||
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
|
||||
$configured = \AKSRT\Settings\Settings::passwordless_login_type();
|
||||
if (!$this->auth_channel_allowed($configured, $channel)) {
|
||||
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
$user = $this->auth_find_user($target, $channel);
|
||||
if (!$user || !$this->auth_consume_code('login', $target, sanitize_text_field((string) $payload['code']), $channel)) {
|
||||
return $this->error(__('账号或验证码错误', 'aksrt'), 401);
|
||||
}
|
||||
wp_set_current_user((int) $user->ID);
|
||||
wp_set_auth_cookie((int) $user->ID, !empty($payload['rememberme']));
|
||||
do_action('wp_login', $user->user_login, $user);
|
||||
if ($oauth_data) {
|
||||
\AKSRT\Auth\Service::finalize_oauth_binding((int) $user->ID, $oauth_data, $obk);
|
||||
$redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : $redirect;
|
||||
}
|
||||
return $this->success(['message' => $oauth_data ? __('账号绑定成功', 'aksrt') : __('登录成功', 'aksrt'), 'redirect' => $redirect]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-register — 手机号注册
|
||||
*/
|
||||
public function auth_phone_register(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
return $this->error(__('认证服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
$payload['log'] = $target;
|
||||
$payload['pwd'] = (string) ($payload['password'] ?? $payload['pwd'] ?? '');
|
||||
$payload['redirect_to'] = $redirect;
|
||||
$result = \AKSRT\Auth\Service::login_user($payload, $oauth_data ? 'bind' : 'login');
|
||||
if (empty($result['success'])) {
|
||||
return $this->error(__('账号或密码错误', 'aksrt'), 401);
|
||||
}
|
||||
if ($oauth_data) {
|
||||
$user_id = get_current_user_id();
|
||||
if ($user_id <= 0) {
|
||||
return $this->error(__('账号或密码错误', 'aksrt'), 401);
|
||||
}
|
||||
\AKSRT\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
|
||||
$redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : $redirect;
|
||||
}
|
||||
return $this->success(['message' => $oauth_data ? __('账号绑定成功', 'aksrt') : __('登录成功', 'aksrt'), 'redirect' => $redirect]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = \AKSRT\Auth\Service::register_by_phone($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
public function auth_register(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) {
|
||||
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
|
||||
}
|
||||
$obk = sanitize_text_field((string) ($payload['obk'] ?? ''));
|
||||
$oauth_data = $obk !== '' && class_exists('\AKSRT\Auth\Service')
|
||||
? \AKSRT\Auth\Service::get_oauth_bind_context($obk)
|
||||
: null;
|
||||
if ($obk !== '' && !$oauth_data) {
|
||||
return $this->error(__('绑定会话已过期,请重新使用第三方登录', 'aksrt'), 410);
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings') && (\AKSRT\Settings\Settings::close_sign() || (!$oauth_data && !\AKSRT\Settings\Settings::registration_enabled()))) {
|
||||
return $this->error(__('当前已关闭新用户注册', 'aksrt'), 403);
|
||||
}
|
||||
if ($this->auth_rate_limited('register')) {
|
||||
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
|
||||
}
|
||||
$target = trim(sanitize_text_field((string) ($payload['target'] ?? $payload['user_email'] ?? $payload['phone'] ?? '')));
|
||||
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
|
||||
$signup_verification = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::signup_verification_enabled();
|
||||
$configured = $signup_verification ? \AKSRT\Settings\Settings::signup_verification_type() : 'email';
|
||||
if (!$this->auth_channel_allowed($configured, $channel)) {
|
||||
return $this->error(__('当前注册方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha = aksrt_require_captcha('register', $payload);
|
||||
if ($captcha !== true) {
|
||||
return $this->error((string) $captcha);
|
||||
}
|
||||
}
|
||||
if ($signup_verification) {
|
||||
if (!$this->auth_consume_code('register', $target, sanitize_text_field((string) ($payload['code'] ?? '')), $channel)) {
|
||||
return $this->error(__('验证码错误或已过期', 'aksrt'));
|
||||
}
|
||||
$payload['_auth_code_verified'] = true;
|
||||
}
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error(__('认证服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
if ($channel === 'phone') {
|
||||
$payload['phone'] = $target;
|
||||
$result = \AKSRT\Auth\Service::register_by_phone($payload);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'aksrt'))));
|
||||
}
|
||||
$user_id = (int) ($result['user_id'] ?? 0);
|
||||
if ($oauth_data && $user_id > 0) {
|
||||
\AKSRT\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
|
||||
}
|
||||
return $this->success(['message' => $oauth_data ? __('注册并绑定成功', 'aksrt') : __('注册成功', 'aksrt'), 'redirect' => $result['redirect'] ?? home_url('/'), 'notice' => (string) ($result['notice'] ?? '')]);
|
||||
}
|
||||
$payload['user_email'] = sanitize_email($target);
|
||||
$payload['user_pass'] = (string) ($payload['password'] ?? '');
|
||||
$payload['user_pass_confirm'] = (string) ($payload['password_confirm'] ?? $payload['password'] ?? '');
|
||||
$result = \AKSRT\Auth\Service::register_user($payload);
|
||||
if (empty($result['success'])) {
|
||||
return $this->error($this->auth_registration_error((string) ($result['error'] ?? __('注册失败', 'aksrt'))));
|
||||
}
|
||||
$user_id = (int) ($result['user_id'] ?? 0);
|
||||
if ($oauth_data && $user_id > 0) {
|
||||
\AKSRT\Auth\Service::finalize_oauth_binding($user_id, $oauth_data, $obk);
|
||||
}
|
||||
return $this->success([
|
||||
'message' => $result['message'] ?? '注册成功',
|
||||
'message' => $oauth_data ? __('注册并绑定成功', 'aksrt') : __('注册成功', 'aksrt'),
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
'user_id' => $result['user_id'] ?? 0,
|
||||
'notice' => (string) ($result['notice'] ?? ''),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '注册失败');
|
||||
public function auth_reset_password(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) {
|
||||
return $this->error(__('安全验证失败,请刷新页面重试', 'aksrt'), 403);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-login — 手机号 + 密码登录
|
||||
*/
|
||||
public function auth_phone_login(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$target = trim(sanitize_text_field((string) ($payload['target'] ?? '')));
|
||||
$channel = $this->auth_channel($target, sanitize_key((string) ($payload['channel'] ?? '')));
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
if (\AKSRT\Settings\Settings::close_sign() || \AKSRT\Settings\Settings::close_signin() || !$this->auth_channel_allowed(\AKSRT\Settings\Settings::reset_password_verification_type(), $channel)) {
|
||||
return $this->error(__('当前验证方式不可用', 'aksrt'), 403);
|
||||
}
|
||||
}
|
||||
if (($channel === 'email' && !is_email($target)) || ($channel === 'phone' && !preg_match('/^1[3-9]\d{9}$/', $target))) {
|
||||
return $this->error(__('请输入有效的邮箱或手机号', 'aksrt'));
|
||||
}
|
||||
if ($this->auth_rate_limited('reset-password', $target, 8, HOUR_IN_SECONDS)) {
|
||||
return $this->error(__('请求过于频繁,请稍后再试', 'aksrt'), 429);
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha = aksrt_require_captcha('lostpassword', $payload);
|
||||
if ($captcha !== true) {
|
||||
return $this->error((string) $captcha);
|
||||
}
|
||||
}
|
||||
$user = $this->auth_find_user($target, $channel);
|
||||
$password = (string) ($payload['password'] ?? '');
|
||||
$confirm = (string) ($payload['password_confirm'] ?? '');
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
return $this->error(__('认证服务不可用', 'aksrt'), 500);
|
||||
}
|
||||
$password_check = \AKSRT\Auth\Service::validate_password($password, $confirm);
|
||||
if ($password_check !== true) {
|
||||
return $this->error((string) $password_check);
|
||||
}
|
||||
if (!$user || !$this->auth_consume_code('resetpassword', $target, sanitize_text_field((string) ($payload['code'] ?? '')), $channel)) {
|
||||
return $this->error(__('账号或验证码错误', 'aksrt'));
|
||||
}
|
||||
wp_set_password($password, (int) $user->ID);
|
||||
return $this->success(['message' => __('密码已重置,请重新登录', 'aksrt'), 'redirect' => function_exists('aksrt_get_sign_page_url') ? aksrt_get_sign_page_url('signin') : wp_login_url()]);
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = \AKSRT\Auth\Service::login_by_phone_password($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
]);
|
||||
public function user_phone_code(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) {
|
||||
return $this->error(__('安全验证失败', 'aksrt'), 403);
|
||||
}
|
||||
$phone = sanitize_text_field((string) ($payload['phone'] ?? ''));
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return $this->error(__('手机号格式不正确', 'aksrt'));
|
||||
}
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha = aksrt_require_captcha('bind_phone', $payload);
|
||||
if ($captcha !== true) return $this->error((string) $captcha);
|
||||
}
|
||||
if (!class_exists('\AKSRT\Sms\SMS')) return $this->error(__('短信服务不可用', 'aksrt'), 503);
|
||||
$result = \AKSRT\Sms\SMS::get_instance()->send_code($phone);
|
||||
return !empty($result['success'])
|
||||
? $this->success(['message' => __('验证码已发送', 'aksrt'), 'countdown' => (int) ($result['remaining_time'] ?? 60)])
|
||||
: $this->error((string) ($result['message'] ?? __('发送失败', 'aksrt')));
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '登录失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/phone-code-login — 手机号 + 验证码免密登录
|
||||
*/
|
||||
public function auth_phone_code_login(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = \AKSRT\Auth\Service::login_by_phone_code($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success([
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '登录失败');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /auth/bind-phone — 绑定手机号
|
||||
*/
|
||||
public function auth_bind_phone(\WP_REST_Request $request): \WP_REST_Response {
|
||||
if (!class_exists('\AKSRT\Auth\Service')) {
|
||||
return $this->error('认证服务不可用');
|
||||
}
|
||||
|
||||
$params = $this->get_json_params($request);
|
||||
$result = \AKSRT\Auth\Service::bind_phone($params);
|
||||
|
||||
if (!empty($result['success'])) {
|
||||
return $this->success(['message' => $result['message']]);
|
||||
}
|
||||
|
||||
return $this->error($result['error'] ?? '绑定失败');
|
||||
public function user_bind_phone(\WP_REST_Request $request): \WP_REST_Response {
|
||||
$payload = $this->auth_payload($request);
|
||||
if (!$this->auth_verify_rest_nonce($request, $payload)) return $this->error(__('安全验证失败', 'aksrt'), 403);
|
||||
if (!class_exists('\AKSRT\Auth\Service')) return $this->error(__('认证服务不可用', 'aksrt'), 500);
|
||||
$result = \AKSRT\Auth\Service::bind_phone($payload);
|
||||
return !empty($result['success']) ? $this->success(['message' => $result['message'] ?? __('绑定成功', 'aksrt')]) : $this->error((string) ($result['error'] ?? __('绑定失败', 'aksrt')));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,13 +167,6 @@ class EmailRollback {
|
||||
if (count($log) > 10) $log = array_slice($log, -10);
|
||||
update_user_meta($user_id, 'aksrt_email_verification_admin_log', $log);
|
||||
|
||||
if (class_exists('AKSRT\\Auth\\Service')) {
|
||||
Service::log_event('admin.email_rollback', [
|
||||
'uid' => $user_id,
|
||||
'by' => $by_admin_id ?: get_current_user_id(),
|
||||
]);
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => sprintf(__('已将用户邮箱回滚为 %s', 'aksrt'), $previous),
|
||||
|
||||
@@ -1,523 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
namespace AKSRT\Auth;
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
class EmailVerification {
|
||||
|
||||
public static function user_requires_email_verification($user): bool {
|
||||
if (!$user instanceof \WP_User) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!\AKSRT\Settings\Settings::registration_requires_email_verification()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !self::is_email_verified((int) $user->ID);
|
||||
}
|
||||
|
||||
public static function is_email_verified(int $user_id): bool {
|
||||
return (int) get_user_meta($user_id, Service::EMAIL_VERIFIED_META, true) === 1;
|
||||
}
|
||||
|
||||
public static function mark_email_verified(int $user_id): void {
|
||||
$was_verified = (int) get_user_meta($user_id, Service::EMAIL_VERIFIED_META, true) === 1;
|
||||
update_user_meta($user_id, Service::EMAIL_VERIFIED_META, 1);
|
||||
self::clear_email_verification($user_id);
|
||||
// 仅在状态由"未验证 → 已验证"跳变时触发,避免历史 backfill / 重复调用造成奖励重发。
|
||||
// 监听方负责自己幂等(Ref 关系使用原子奖励状态处理重复事件)。
|
||||
if (!$was_verified) {
|
||||
do_action('aksrt_email_verified', $user_id);
|
||||
}
|
||||
}
|
||||
|
||||
public static function clear_email_verification(int $user_id): void {
|
||||
delete_user_meta($user_id, Service::EMAIL_METHOD_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_TOKEN_HASH_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_CODE_HASH_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_EXPIRES_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_REQUESTED_AT_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_TARGET_META);
|
||||
delete_user_meta($user_id, Service::EMAIL_OBK_META);
|
||||
self::reset_email_code_attempt_trackers($user_id);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 公开 API:管理员后台 / 内部业务调用
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* 是否已验证邮箱(公开版本,给后台 UI / 用户列表渲染用)。
|
||||
*/
|
||||
public static function user_is_email_verified(int $user_id): bool {
|
||||
return self::is_email_verified($user_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 取一个用户当前的验证状态摘要,用于后台资料页展示。
|
||||
*
|
||||
* @return array{verified:bool, method:string, target_email:string, expires_at:int, requested_at:int, last_admin_log:array}
|
||||
*/
|
||||
public static function get_user_verification_state(int $user_id): array {
|
||||
return [
|
||||
'verified' => self::is_email_verified($user_id),
|
||||
'method' => (string) get_user_meta($user_id, Service::EMAIL_METHOD_META, true),
|
||||
'target_email' => (string) get_user_meta($user_id, Service::EMAIL_TARGET_META, true),
|
||||
'expires_at' => (int) get_user_meta($user_id, Service::EMAIL_EXPIRES_META, true),
|
||||
'requested_at' => (int) get_user_meta($user_id, Service::EMAIL_REQUESTED_AT_META, true),
|
||||
'last_admin_log' => (array) (get_user_meta($user_id, 'aksrt_email_verification_admin_log', true) ?: []),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员:把指定用户标记为已验证(清除所有进行中的验证 meta)。
|
||||
*/
|
||||
public static function admin_mark_verified(int $user_id, int $by_admin_id = 0): array {
|
||||
if ($user_id <= 0) {
|
||||
return ['success' => false, 'error' => __('无效的用户 ID', 'aksrt')];
|
||||
}
|
||||
if (!current_user_can('edit_user', $user_id)) {
|
||||
return ['success' => false, 'error' => __('权限不足', 'aksrt')];
|
||||
}
|
||||
self::mark_email_verified($user_id);
|
||||
self::record_admin_action($user_id, 'mark_verified', $by_admin_id);
|
||||
Service::log_event('admin.action', ['action' => 'mark_verified', 'uid' => $user_id, 'by' => $by_admin_id]);
|
||||
return ['success' => true, 'message' => __('已标记为已验证', 'aksrt')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 管理员:撤销已验证状态(用于测试 / 回滚 / 应对邮箱泄漏)。
|
||||
*/
|
||||
public static function admin_unmark_verified(int $user_id, int $by_admin_id = 0): array {
|
||||
if ($user_id <= 0) {
|
||||
return ['success' => false, 'error' => __('无效的用户 ID', 'aksrt')];
|
||||
}
|
||||
if (!current_user_can('edit_user', $user_id)) {
|
||||
return ['success' => false, 'error' => __('权限不足', 'aksrt')];
|
||||
}
|
||||
update_user_meta($user_id, Service::EMAIL_VERIFIED_META, 0);
|
||||
self::clear_email_verification($user_id);
|
||||
self::record_admin_action($user_id, 'unmark_verified', $by_admin_id);
|
||||
Service::log_event('admin.action', ['action' => 'unmark_verified', 'uid' => $user_id, 'by' => $by_admin_id]);
|
||||
return ['success' => true, 'message' => __('已撤销验证状态', 'aksrt')];
|
||||
}
|
||||
/**
|
||||
* 管理员:立即发送 / 重发验证邮件(绕过 60s 节流)。
|
||||
*/
|
||||
public static function admin_send_verification(int $user_id, int $by_admin_id = 0): array {
|
||||
if ($user_id <= 0) {
|
||||
return ['success' => false, 'error' => __('无效的用户 ID', 'aksrt')];
|
||||
}
|
||||
if (!current_user_can('edit_user', $user_id)) {
|
||||
return ['success' => false, 'error' => __('权限不足', 'aksrt')];
|
||||
}
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => __('用户不存在', 'aksrt')];
|
||||
}
|
||||
|
||||
$method = Service::current_registration_verification_method();
|
||||
$email = (string) get_user_meta($user_id, Service::EMAIL_TARGET_META, true);
|
||||
if (!in_array($method, ['link', 'code'], true)) {
|
||||
$method = \AKSRT\Settings\Settings::registration_verification_method();
|
||||
}
|
||||
if ($email === '' || !is_email($email)) {
|
||||
$email = (string) $user->user_email;
|
||||
}
|
||||
|
||||
$result = self::begin_email_verification($user, $method, [
|
||||
'email' => $email,
|
||||
'obk' => (string) get_user_meta($user_id, Service::EMAIL_OBK_META, true),
|
||||
'bypass_throttle' => true,
|
||||
'force' => true,
|
||||
]);
|
||||
|
||||
// 标记为待验证(确保 admin 主动发邮件后用户被视为未验证)
|
||||
if (!empty($result['success'])) {
|
||||
update_user_meta($user_id, Service::EMAIL_VERIFIED_META, 0);
|
||||
self::record_admin_action($user_id, 'send_verification', $by_admin_id);
|
||||
Service::log_event('admin.action', ['action' => 'send_verification', 'uid' => $user_id, 'by' => $by_admin_id]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把已存在的用户视为受信任(grandfather):写入 aksrt_email_verified=1。
|
||||
*
|
||||
* 仅写入 meta 不存在的用户;已经显式为 0 的(正在走验证流程的新注册)保持不动。
|
||||
*
|
||||
* 用户量 > 50000 时改为异步分批;否则内联完成。返回处理总数。
|
||||
*
|
||||
* @param bool $force 强制再次执行(即便 backfill_done 标记已存在),调试用
|
||||
*/
|
||||
public static function backfill_legacy_users_as_verified(bool $force = false): int {
|
||||
if (!$force && get_option('aksrt_email_verification_backfill_done')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$total = 0;
|
||||
if (function_exists('count_users')) {
|
||||
$counts = count_users();
|
||||
$total = (int) ($counts['total_users'] ?? 0);
|
||||
}
|
||||
|
||||
// 大表保护:超过 5w 用户改为后台 cron 分批
|
||||
if ($total > 50000) {
|
||||
if (!wp_next_scheduled('aksrt_email_verification_backfill_batch')) {
|
||||
wp_schedule_single_event(time() + 30, 'aksrt_email_verification_backfill_batch', [0]);
|
||||
}
|
||||
update_option('aksrt_email_verification_backfill_log', [
|
||||
'mode' => 'async',
|
||||
'total' => $total,
|
||||
'scheduled' => time(),
|
||||
'by' => get_current_user_id(),
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 内联分批,每批 500
|
||||
$updated = 0;
|
||||
$paged = 1;
|
||||
do {
|
||||
$users = get_users([
|
||||
'number' => 500,
|
||||
'paged' => $paged,
|
||||
'fields' => ['ID'],
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => Service::EMAIL_VERIFIED_META,
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if (empty($users)) {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($users as $u) {
|
||||
update_user_meta((int) $u->ID, Service::EMAIL_VERIFIED_META, 1);
|
||||
$updated++;
|
||||
}
|
||||
|
||||
$paged++;
|
||||
// 让 MySQL 喘口气
|
||||
if (function_exists('usleep')) {
|
||||
usleep(50000);
|
||||
}
|
||||
} while (count($users) === 500 && $paged < 200); // 上限 10w 兜底防呆
|
||||
|
||||
update_option('aksrt_email_verification_backfill_done', time());
|
||||
update_option('aksrt_email_verification_backfill_log', [
|
||||
'mode' => 'inline',
|
||||
'count' => $updated,
|
||||
'ran_at' => time(),
|
||||
'by' => get_current_user_id(),
|
||||
]);
|
||||
Service::log_event('admin.backfill', ['count' => $updated, 'by' => get_current_user_id()]);
|
||||
|
||||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* 异步分批回填:cron 钩子。每批 500,自动调度下一批直到完成。
|
||||
*/
|
||||
public static function run_async_backfill_batch(int $offset = 0): void {
|
||||
$users = get_users([
|
||||
'number' => 500,
|
||||
'offset' => max(0, $offset),
|
||||
'fields' => ['ID'],
|
||||
'orderby' => 'ID',
|
||||
'order' => 'ASC',
|
||||
'meta_query' => [
|
||||
[
|
||||
'key' => Service::EMAIL_VERIFIED_META,
|
||||
'compare' => 'NOT EXISTS',
|
||||
],
|
||||
],
|
||||
]);
|
||||
|
||||
if (empty($users)) {
|
||||
update_option('aksrt_email_verification_backfill_done', time());
|
||||
$log = (array) get_option('aksrt_email_verification_backfill_log', []);
|
||||
$log['finished_at'] = time();
|
||||
update_option('aksrt_email_verification_backfill_log', $log);
|
||||
Service::log_event('admin.backfill.finished', []);
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($users as $u) {
|
||||
update_user_meta((int) $u->ID, Service::EMAIL_VERIFIED_META, 1);
|
||||
}
|
||||
|
||||
// 调度下一批
|
||||
wp_schedule_single_event(time() + 30, 'aksrt_email_verification_backfill_batch', [$offset + 500]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录管理员操作日志(每个用户最多 10 条 ring buffer)。
|
||||
*/
|
||||
public static function record_admin_action(int $user_id, string $action, int $by_admin_id): void {
|
||||
$log = get_user_meta($user_id, 'aksrt_email_verification_admin_log', true);
|
||||
if (!is_array($log)) {
|
||||
$log = [];
|
||||
}
|
||||
$log[] = [
|
||||
't' => time(),
|
||||
'action' => $action,
|
||||
'by' => $by_admin_id ?: get_current_user_id(),
|
||||
];
|
||||
if (count($log) > 10) {
|
||||
$log = array_slice($log, -10);
|
||||
}
|
||||
update_user_meta($user_id, 'aksrt_email_verification_admin_log', $log);
|
||||
}
|
||||
// ============================================================
|
||||
// 验证码失败计数 / IP 节流
|
||||
// ============================================================
|
||||
|
||||
public static function email_code_ip_throttle_key(int $user_id): string {
|
||||
$ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
if ($user_id <= 0 || $ip === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return 'aksrt_email_code_try_' . $user_id . '_' . md5($ip);
|
||||
}
|
||||
|
||||
public static function reset_email_code_attempt_trackers(int $user_id): void {
|
||||
delete_user_meta($user_id, Service::EMAIL_CODE_ATTEMPTS_META);
|
||||
$ip_key = self::email_code_ip_throttle_key($user_id);
|
||||
if ($ip_key !== '') {
|
||||
delete_transient($ip_key);
|
||||
}
|
||||
}
|
||||
|
||||
public static function fail_email_code_attempt(int $user_id): array {
|
||||
$attempts = (int) get_user_meta($user_id, Service::EMAIL_CODE_ATTEMPTS_META, true);
|
||||
$attempts++;
|
||||
update_user_meta($user_id, Service::EMAIL_CODE_ATTEMPTS_META, $attempts);
|
||||
|
||||
$ip_attempts = 0;
|
||||
$ip_key = self::email_code_ip_throttle_key($user_id);
|
||||
if ($ip_key !== '') {
|
||||
$ip_attempts = (int) get_transient($ip_key);
|
||||
$ip_attempts++;
|
||||
set_transient($ip_key, $ip_attempts, Service::EMAIL_CODE_IP_TTL);
|
||||
}
|
||||
|
||||
if ($attempts >= Service::EMAIL_CODE_MAX_ATTEMPTS || $ip_attempts >= Service::EMAIL_CODE_MAX_ATTEMPTS) {
|
||||
delete_user_meta($user_id, Service::EMAIL_CODE_HASH_META);
|
||||
update_user_meta($user_id, Service::EMAIL_REQUESTED_AT_META, 0);
|
||||
Service::log_event('verify.code.locked', ['uid' => $user_id, 'attempts' => $attempts]);
|
||||
return ['success' => false, 'error' => __('验证码错误次数过多,当前验证码已失效,请重新发送。', 'aksrt')];
|
||||
}
|
||||
|
||||
Service::log_event('verify.code.fail', ['uid' => $user_id, 'attempts' => $attempts]);
|
||||
return ['success' => false, 'error' => __('邮箱验证码错误或已过期,请重新输入。', 'aksrt')];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 验证流程:发起 / 校验 / 完成
|
||||
// ============================================================
|
||||
|
||||
public static function begin_email_verification(\WP_User $user, string $method, array $args = []): array {
|
||||
$method = in_array($method, ['link', 'code'], true) ? $method : 'link';
|
||||
$email = sanitize_email((string) ($args['email'] ?? $user->user_email));
|
||||
$obk = sanitize_text_field((string) ($args['obk'] ?? ''));
|
||||
$bypass_throttle = !empty($args['bypass_throttle']);
|
||||
if ($email === '' || !is_email($email)) {
|
||||
return ['success' => false, 'error' => __('当前账号缺少有效邮箱,无法发送验证邮件', 'aksrt')];
|
||||
}
|
||||
|
||||
// 节流:仅在非管理员路径生效(admin_send_verification 会传 bypass_throttle=true)
|
||||
if (!$bypass_throttle) {
|
||||
$last_requested = (int) get_user_meta($user->ID, Service::EMAIL_REQUESTED_AT_META, true);
|
||||
if ($last_requested > 0 && (time() - $last_requested) < 60 && empty($args['force'])) {
|
||||
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
|
||||
}
|
||||
}
|
||||
|
||||
$expires_at = time() + (\AKSRT\Settings\Settings::registration_verification_ttl_minutes() * MINUTE_IN_SECONDS);
|
||||
update_user_meta($user->ID, Service::EMAIL_VERIFIED_META, 0);
|
||||
update_user_meta($user->ID, Service::EMAIL_METHOD_META, $method);
|
||||
update_user_meta($user->ID, Service::EMAIL_TARGET_META, $email);
|
||||
update_user_meta($user->ID, Service::EMAIL_EXPIRES_META, $expires_at);
|
||||
update_user_meta($user->ID, Service::EMAIL_REQUESTED_AT_META, time());
|
||||
if ($obk !== '') {
|
||||
update_user_meta($user->ID, Service::EMAIL_OBK_META, $obk);
|
||||
}
|
||||
|
||||
delete_user_meta($user->ID, Service::EMAIL_TOKEN_HASH_META);
|
||||
delete_user_meta($user->ID, Service::EMAIL_CODE_HASH_META);
|
||||
|
||||
if ($method === 'code') {
|
||||
$code = str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT);
|
||||
update_user_meta($user->ID, Service::EMAIL_CODE_HASH_META, wp_hash_password($code));
|
||||
// 新验证码 = 重置失败计数 / IP 节流,避免被旧计数立即锁死
|
||||
self::reset_email_code_attempt_trackers((int) $user->ID);
|
||||
$sent = self::send_registration_verification_code_email($user, $email, $code);
|
||||
} else {
|
||||
$token = wp_generate_password(48, false, false);
|
||||
update_user_meta($user->ID, Service::EMAIL_TOKEN_HASH_META, hash('sha256', $token));
|
||||
$sent = self::send_registration_verification_link_email($user, $email, $token);
|
||||
}
|
||||
|
||||
if (!$sent) {
|
||||
self::clear_email_verification((int) $user->ID);
|
||||
Service::log_event('email.send.fail', ['uid' => (int) $user->ID, 'method' => $method]);
|
||||
return ['success' => false, 'error' => __('验证邮件发送失败,请稍后重试', 'aksrt')];
|
||||
}
|
||||
|
||||
Service::log_event('email.send.ok', ['uid' => (int) $user->ID, 'method' => $method]);
|
||||
|
||||
$message = $method === 'code'
|
||||
? __('注册成功,验证码已发送到你的邮箱,请完成验证后继续。', 'aksrt')
|
||||
: __('注册成功,验证邮件已发送到你的邮箱,请点击邮件中的链接完成验证。', 'aksrt');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
public static function verify_email_token(int $user_id, string $token): array {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => __('验证账号不存在或已失效', 'aksrt')];
|
||||
}
|
||||
|
||||
$stored_hash = (string) get_user_meta($user_id, Service::EMAIL_TOKEN_HASH_META, true);
|
||||
$expires_at = (int) get_user_meta($user_id, Service::EMAIL_EXPIRES_META, true);
|
||||
if ($stored_hash === '' || $expires_at <= time() || !hash_equals($stored_hash, hash('sha256', $token))) {
|
||||
return ['success' => false, 'error' => __('验证链接无效或已过期,请重新发送验证邮件。', 'aksrt')];
|
||||
}
|
||||
|
||||
return self::complete_email_verification($user);
|
||||
}
|
||||
|
||||
public static function verify_email_code(int $user_id, string $code): array {
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => __('验证账号不存在或已失效', 'aksrt')];
|
||||
}
|
||||
|
||||
// 先做 IP 节流:相同 IP 在 EMAIL_CODE_IP_TTL 内累计失败到上限即直接拒绝
|
||||
$ip_key = self::email_code_ip_throttle_key($user_id);
|
||||
if ($ip_key !== '') {
|
||||
$ip_attempts = (int) get_transient($ip_key);
|
||||
if ($ip_attempts >= Service::EMAIL_CODE_MAX_ATTEMPTS) {
|
||||
Service::log_event('verify.code.ip_throttled', ['uid' => $user_id, 'attempts' => $ip_attempts]);
|
||||
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
|
||||
}
|
||||
}
|
||||
|
||||
// 用户级失败计数:达到上限后该验证码立即作废
|
||||
$user_attempts = (int) get_user_meta($user_id, Service::EMAIL_CODE_ATTEMPTS_META, true);
|
||||
if ($user_attempts >= Service::EMAIL_CODE_MAX_ATTEMPTS) {
|
||||
delete_user_meta($user_id, Service::EMAIL_CODE_HASH_META);
|
||||
update_user_meta($user_id, Service::EMAIL_REQUESTED_AT_META, 0);
|
||||
Service::log_event('verify.code.locked.read', ['uid' => $user_id]);
|
||||
return ['success' => false, 'error' => __('验证码错误次数过多,当前验证码已失效,请重新发送。', 'aksrt')];
|
||||
}
|
||||
|
||||
$stored_hash = (string) get_user_meta($user_id, Service::EMAIL_CODE_HASH_META, true);
|
||||
$expires_at = (int) get_user_meta($user_id, Service::EMAIL_EXPIRES_META, true);
|
||||
if ($stored_hash === '' || $expires_at <= time() || !wp_check_password($code, $stored_hash)) {
|
||||
return self::fail_email_code_attempt($user_id);
|
||||
}
|
||||
|
||||
self::reset_email_code_attempt_trackers($user_id);
|
||||
return self::complete_email_verification($user);
|
||||
}
|
||||
|
||||
public static function complete_email_verification(\WP_User $user): array {
|
||||
$user_id = (int) $user->ID;
|
||||
$obk = (string) get_user_meta($user_id, Service::EMAIL_OBK_META, true);
|
||||
$oauth = $obk !== '' ? Service::get_oauth_bind_context($obk) : null;
|
||||
$email = (string) get_user_meta($user_id, Service::EMAIL_TARGET_META, true);
|
||||
$was_verified = (int) get_user_meta($user_id, Service::EMAIL_VERIFIED_META, true) === 1;
|
||||
|
||||
update_user_meta($user_id, Service::EMAIL_VERIFIED_META, 1);
|
||||
self::clear_email_verification($user_id);
|
||||
Service::log_event('verify.success', ['uid' => $user_id]);
|
||||
// 状态跳变时触发 hook,Ref 推广奖励等模块据此发奖
|
||||
if (!$was_verified) {
|
||||
do_action('aksrt_email_verified', $user_id);
|
||||
}
|
||||
if ($email !== '' && is_email($email)) {
|
||||
self::send_registration_verified_notice_email($user, $email);
|
||||
}
|
||||
|
||||
if ($oauth && is_array($oauth)) {
|
||||
Service::finalize_oauth_registration($user_id, $oauth, $obk);
|
||||
} elseif (\AKSRT\Settings\Settings::registration_auto_login_after_verify()) {
|
||||
wp_set_current_user($user_id);
|
||||
wp_set_auth_cookie($user_id, true);
|
||||
}
|
||||
|
||||
$redirect = function_exists('aksrt_get_user_center_url') ? aksrt_get_user_center_url() : home_url('/');
|
||||
if (!\AKSRT\Settings\Settings::registration_auto_login_after_verify()) {
|
||||
$redirect = aksrt_get_login_url();
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'message' => __('邮箱验证成功,即将进入你的账号。', 'aksrt'),
|
||||
'redirect' => $redirect,
|
||||
];
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 通知邮件
|
||||
// ============================================================
|
||||
|
||||
public static function send_registration_verification_link_email(\WP_User $user, string $email, string $token): bool {
|
||||
$url = Service::get_email_verification_url((int) $user->ID, ['token' => $token]);
|
||||
$site_name = get_bloginfo('name');
|
||||
$default_subject = '[' . $site_name . '] ' . __('验证你的邮箱地址', 'aksrt');
|
||||
$default_content = sprintf(
|
||||
__("您好 %s,\n\n欢迎注册 %s。\n\n请点击以下链接完成邮箱验证:\n\n%s\n\n验证成功后系统将自动为你登录。\n\n如果这不是你的操作,请忽略此邮件。", 'aksrt'),
|
||||
$user->user_login, $site_name, $url
|
||||
);
|
||||
|
||||
return \AKSRT\Settings\Settings::send_event_email(
|
||||
'reg_verify_link', $email, $default_subject, $default_content,
|
||||
['{username}' => $user->user_login, '{site_name}' => $site_name, '{url}' => $url]
|
||||
);
|
||||
}
|
||||
|
||||
public static function send_registration_verification_code_email(\WP_User $user, string $email, string $code): bool {
|
||||
$site_name = get_bloginfo('name');
|
||||
$default_subject = '[' . $site_name . '] ' . __('邮箱验证码', 'aksrt');
|
||||
$default_content = sprintf(
|
||||
__("您好 %s,\n\n欢迎注册 %s。\n\n你的邮箱验证码为:\n\n【%s】\n\n验证码验证成功后系统将自动为你登录。\n\n如果这不是你的操作,请忽略此邮件。", 'aksrt'),
|
||||
$user->user_login, $site_name, $code
|
||||
);
|
||||
|
||||
return \AKSRT\Settings\Settings::send_event_email(
|
||||
'reg_verify_code', $email, $default_subject, $default_content,
|
||||
['{username}' => $user->user_login, '{site_name}' => $site_name, '{code}' => $code]
|
||||
);
|
||||
}
|
||||
|
||||
public static function send_registration_verified_notice_email(\WP_User $user, string $email): bool {
|
||||
$site_name = get_bloginfo('name');
|
||||
$default_subject = '[' . $site_name . '] ' . __('邮箱验证成功', 'aksrt');
|
||||
$default_content = sprintf(
|
||||
__("您好 %s,\n\n你在 %s 的账号邮箱已经验证成功。\n\n如果这不是你的操作,请立即联系站点管理员。", 'aksrt'),
|
||||
$user->user_login, $site_name
|
||||
);
|
||||
|
||||
return \AKSRT\Settings\Settings::send_event_email(
|
||||
'reg_success', $email, $default_subject, $default_content,
|
||||
['{username}' => $user->user_login, '{site_name}' => $site_name]
|
||||
);
|
||||
}
|
||||
}
|
||||
+13
-34
@@ -37,19 +37,14 @@ final class Router {
|
||||
|
||||
public function register_query_vars(array $vars): array {
|
||||
$vars[] = 'aksrt_sign';
|
||||
$vars[] = 'aksrt_auth_tab';
|
||||
$vars[] = 'aksrt_logout';
|
||||
return $vars;
|
||||
}
|
||||
|
||||
private function auth_route_map(): array {
|
||||
return [
|
||||
'login' => 'index.php?aksrt_sign=1&aksrt_auth_tab=login',
|
||||
'register' => 'index.php?aksrt_sign=1&aksrt_auth_tab=register',
|
||||
'bind-account' => 'index.php?aksrt_sign=1&aksrt_auth_tab=bind',
|
||||
'sign' => 'index.php?aksrt_sign=1',
|
||||
'logout' => 'index.php?aksrt_logout=1',
|
||||
'reset-password' => 'index.php?aksrt_sign=1&aksrt_auth_tab=reset-password',
|
||||
'verify-email' => 'index.php?aksrt_sign=1&aksrt_auth_tab=verify-email',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -63,8 +58,7 @@ final class Router {
|
||||
if (get_query_var('aksrt_sign')) {
|
||||
// §4.1 Global sign gate: when close_sign is active, block non-logged-in access to sign page
|
||||
if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::close_sign() && !is_user_logged_in()) {
|
||||
$tab = (string) get_query_var('aksrt_auth_tab');
|
||||
if (!in_array($tab, ['reset-password', 'verify-email', 'bind-account'], true) && empty($_REQUEST['interim-login'])) {
|
||||
if (empty($_REQUEST['interim-login'])) {
|
||||
if (!headers_sent()) {
|
||||
wp_safe_redirect(home_url('/'), 302);
|
||||
exit;
|
||||
@@ -72,10 +66,7 @@ final class Router {
|
||||
}
|
||||
}
|
||||
|
||||
$auth_template = function_exists('aksrt_get_auth_template_file')
|
||||
? aksrt_get_auth_template_file((string) get_query_var('aksrt_auth_tab'), true)
|
||||
: 'templates/pages/page-sign.php';
|
||||
$tpl = aksrt_locate_template_file($auth_template);
|
||||
$tpl = aksrt_locate_template_file('templates/pages/page-sign.php');
|
||||
if ($tpl) {
|
||||
return $tpl;
|
||||
}
|
||||
@@ -85,7 +76,7 @@ final class Router {
|
||||
}
|
||||
|
||||
public function maybe_flush_rewrite_rules(): void {
|
||||
$rewrite_version = AKSRT_VERSION . ':sign-router-v2-logout';
|
||||
$rewrite_version = AKSRT_VERSION . ':sign-router-v3-single-route';
|
||||
if (get_option('aksrt_auth_routes_flushed') !== $rewrite_version) {
|
||||
flush_rewrite_rules(false);
|
||||
update_option('aksrt_auth_routes_flushed', $rewrite_version);
|
||||
@@ -125,7 +116,7 @@ final class Router {
|
||||
}
|
||||
|
||||
public function redirect_native_login(): void {
|
||||
if (wp_doing_ajax()) {
|
||||
if (class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::replace_wp_login()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -139,31 +130,26 @@ final class Router {
|
||||
|
||||
$action = isset($_REQUEST['action']) ? sanitize_key((string) $_REQUEST['action']) : 'login';
|
||||
$redirect_to = isset($_REQUEST['redirect_to']) ? wp_unslash((string) $_REQUEST['redirect_to']) : '';
|
||||
$interim = !empty($_REQUEST['interim-login']);
|
||||
$reauth = !empty($_REQUEST['reauth']);
|
||||
$is_admin_target = $redirect_to !== '' && strpos($redirect_to, admin_url()) === 0;
|
||||
|
||||
if (is_user_logged_in() && current_user_can('manage_options')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($is_admin_target || $interim || $reauth || $action === 'logout') {
|
||||
return;
|
||||
if ($action === 'logout') {
|
||||
wp_safe_redirect(aksrt_get_logout_url($redirect_to ?: home_url('/')), 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
switch ($action) {
|
||||
case 'register':
|
||||
$target = aksrt_get_register_url();
|
||||
$target = aksrt_get_sign_page_url('signup');
|
||||
break;
|
||||
case 'lostpassword':
|
||||
$target = aksrt_get_reset_password_url();
|
||||
$target = aksrt_get_sign_page_url('resetpassword');
|
||||
break;
|
||||
case 'rp':
|
||||
case 'resetpass':
|
||||
$target = add_query_arg('legacy', '1', aksrt_get_reset_password_url());
|
||||
$target = aksrt_get_sign_page_url('resetpassword');
|
||||
break;
|
||||
default:
|
||||
$target = aksrt_get_login_url();
|
||||
$target = aksrt_get_sign_page_url('signin');
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -215,16 +201,9 @@ final class Router {
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect logout via rewrite query var, GET flag, or pretty path.
|
||||
* Detect the unique public logout path.
|
||||
*/
|
||||
private function is_logout_request(): bool {
|
||||
if ((string) get_query_var('aksrt_logout') !== '' && (string) get_query_var('aksrt_logout') !== '0') {
|
||||
return true;
|
||||
}
|
||||
if (isset($_GET['aksrt_logout']) && (string) $_GET['aksrt_logout'] !== '' && (string) $_GET['aksrt_logout'] !== '0') {
|
||||
return true;
|
||||
}
|
||||
|
||||
$request_uri = isset($_SERVER['REQUEST_URI']) ? (string) $_SERVER['REQUEST_URI'] : '';
|
||||
if ($request_uri === '') {
|
||||
return false;
|
||||
|
||||
+18
-250
@@ -12,233 +12,7 @@ final class Service {
|
||||
use ServicePart1b;
|
||||
|
||||
|
||||
private const RESET_TOKEN_HASH_META = 'aksrt_password_reset_token_hash';
|
||||
private const RESET_TOKEN_EXPIRES_META = 'aksrt_password_reset_expires';
|
||||
private const RESET_TOKEN_REQUESTED_AT = 'aksrt_password_reset_requested_at';
|
||||
private const RESET_TOKEN_TTL = 3600;
|
||||
public const EMAIL_VERIFIED_META = 'aksrt_email_verified';
|
||||
/**
|
||||
* 用户当时被发送的验证方式('link' | 'code')。
|
||||
*
|
||||
* 注意:这是 *审计字段*,仅用于后台展示「上一次给该用户发的是哪种验证邮件」。
|
||||
* 路由分支(验证页应该显示链接提示还是验证码输入框、自助/管理员重发使用哪种方式)
|
||||
* 一律读取全局配置 self::current_registration_verification_method(),
|
||||
* 这样管理员把站点从 code 切换到 link 后,所有在途用户都立即跟随新方式。
|
||||
* 不要根据这个 meta 决定页面模式或重发方式。
|
||||
*/
|
||||
public const EMAIL_METHOD_META = 'aksrt_email_verification_method';
|
||||
public const EMAIL_TOKEN_HASH_META = 'aksrt_email_verification_token_hash';
|
||||
public const EMAIL_CODE_HASH_META = 'aksrt_email_verification_code_hash';
|
||||
public const EMAIL_EXPIRES_META = 'aksrt_email_verification_expires';
|
||||
public const EMAIL_REQUESTED_AT_META = 'aksrt_email_verification_requested_at';
|
||||
public const EMAIL_TARGET_META = 'aksrt_email_verification_email';
|
||||
public const EMAIL_OBK_META = 'aksrt_email_verification_obk';
|
||||
private const EMAIL_ACCESS_KEY_META = 'aksrt_email_verification_access_key';
|
||||
public const EMAIL_CODE_ATTEMPTS_META = 'aksrt_email_verification_code_attempts';
|
||||
public const EMAIL_CODE_MAX_ATTEMPTS = 5;
|
||||
public const EMAIL_CODE_IP_TTL = 900;
|
||||
// 自助重发的 IP 节流:每 IP 每 RESEND_IP_WINDOW 秒最多 RESEND_IP_MAX 次。
|
||||
private const RESEND_IP_MAX = 6;
|
||||
private const RESEND_IP_WINDOW = 600;
|
||||
|
||||
/**
|
||||
* 启动钩子:注册全局 authenticate 拦截器(阶段 4)
|
||||
* 调用方:bootstrap.php 在所有类加载完成后调用一次。
|
||||
*/
|
||||
public static function init(): void {
|
||||
add_filter('authenticate', [self::class, 'enforce_email_verification_on_authenticate'], 30, 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* 全局登录拦截器:堵住 wp-login.php / REST / XML-RPC / Application Password 等所有
|
||||
* 不经过 Service::login_user() 的认证路径。
|
||||
*
|
||||
* 多重短路保护,按"成本最低"顺序排列:
|
||||
* 1. 紧急降级常量 AKSRT_NO_EMAIL_GATE
|
||||
* 2. $user 不是 \WP_User(认证失败 / 未开始)
|
||||
* 3. \WP_Error(前置错误,原样返回)
|
||||
* 4. 功能未启用
|
||||
* 5. 管理员豁免(manage_options)
|
||||
* 6. cron / 安装 / 升级豁免
|
||||
* 7. REST/XML-RPC 阶段化(option aksrt_email_verification_rest_block_at 控制)
|
||||
* 8. 自定义豁免过滤器
|
||||
*
|
||||
* @param mixed $user
|
||||
* @param string $username
|
||||
* @param string $password
|
||||
* @return mixed
|
||||
*/
|
||||
public static function enforce_email_verification_on_authenticate($user, $username = '', $password = '') {
|
||||
if (defined('AKSRT_NO_EMAIL_GATE') && AKSRT_NO_EMAIL_GATE) {
|
||||
return $user;
|
||||
}
|
||||
if (!($user instanceof \WP_User)) {
|
||||
return $user;
|
||||
}
|
||||
if (is_wp_error($user)) {
|
||||
return $user;
|
||||
}
|
||||
if (!class_exists('\AKSRT\Settings\Settings') || !\AKSRT\Settings\Settings::registration_requires_email_verification()) {
|
||||
return $user;
|
||||
}
|
||||
if (user_can($user, 'manage_options')) {
|
||||
return $user;
|
||||
}
|
||||
if (defined('DOING_CRON') && DOING_CRON) {
|
||||
return $user;
|
||||
}
|
||||
if (defined('WP_INSTALLING') && WP_INSTALLING) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$is_rest = (defined('REST_REQUEST') && REST_REQUEST);
|
||||
$is_xmlrpc = (defined('XMLRPC_REQUEST') && XMLRPC_REQUEST);
|
||||
$rest_block_at = (int) get_option('aksrt_email_verification_rest_block_at', 0);
|
||||
|
||||
// 阶段化:option=0 时 REST/XML-RPC 仅记录不拦截(Day 0~3 灰度期)
|
||||
if (($is_rest || $is_xmlrpc) && $rest_block_at <= 0) {
|
||||
if (!EmailVerification::is_email_verified((int) $user->ID)) {
|
||||
self::log_event('auth.rest_unverified', [
|
||||
'uid' => (int) $user->ID,
|
||||
'rest' => $is_rest,
|
||||
'xmlrpc' => $is_xmlrpc,
|
||||
]);
|
||||
}
|
||||
return $user;
|
||||
}
|
||||
|
||||
// 已验证 → 直接放行
|
||||
if (EmailVerification::is_email_verified((int) $user->ID)) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
// 自定义豁免:filter 返回 false 即放行
|
||||
$should_block = apply_filters('aksrt_email_verification_should_block', true, $user, [
|
||||
'rest' => $is_rest,
|
||||
'xmlrpc' => $is_xmlrpc,
|
||||
]);
|
||||
if (!$should_block) {
|
||||
return $user;
|
||||
}
|
||||
|
||||
self::log_event('auth.blocked', [
|
||||
'uid' => (int) $user->ID,
|
||||
'rest' => $is_rest,
|
||||
'xmlrpc' => $is_xmlrpc,
|
||||
]);
|
||||
|
||||
return new \WP_Error(
|
||||
'aksrt_email_unverified',
|
||||
__('账号尚未完成邮箱验证,请先完成邮箱验证后再登录。', 'aksrt'),
|
||||
[
|
||||
'verify_url' => self::get_email_verification_url((int) $user->ID),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 轻量事件日志(ring buffer,最近 200 条),用于健康检查路由统计。
|
||||
* 不写入文件,仅写 transient(24h TTL),不影响性能。
|
||||
*/
|
||||
public static function log_event(string $code, array $context = []): void {
|
||||
$key = 'aksrt_email_verify_log';
|
||||
$logs = get_transient($key);
|
||||
if (!is_array($logs)) {
|
||||
$logs = [];
|
||||
}
|
||||
$logs[] = [
|
||||
't' => time(),
|
||||
'c' => $code,
|
||||
'd' => $context,
|
||||
];
|
||||
// 保留最近 200 条
|
||||
if (count($logs) > 200) {
|
||||
$logs = array_slice($logs, -200);
|
||||
}
|
||||
set_transient($key, $logs, DAY_IN_SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取最近 24h 的事件日志聚合。
|
||||
*/
|
||||
public static function get_email_verification_url(int $user_id, array $args = []): string {
|
||||
if ($user_id <= 0 || !function_exists('aksrt_get_verify_email_url')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$access_key = self::get_or_issue_email_access_key($user_id);
|
||||
if ($access_key !== '') {
|
||||
$args['vk'] = $access_key;
|
||||
}
|
||||
|
||||
return aksrt_get_verify_email_url($user_id, $args);
|
||||
}
|
||||
|
||||
public static function current_registration_verification_method(): string {
|
||||
if (!class_exists('\AKSRT\Settings\Settings')) {
|
||||
return 'link';
|
||||
}
|
||||
|
||||
$method = \AKSRT\Settings\Settings::registration_verification_method();
|
||||
return in_array($method, ['link', 'code'], true) ? $method : 'link';
|
||||
}
|
||||
|
||||
private static function get_or_issue_email_access_key(int $user_id, bool $rotate = false): string {
|
||||
if ($user_id <= 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$current = (string) get_user_meta($user_id, self::EMAIL_ACCESS_KEY_META, true);
|
||||
if (!$rotate && $current !== '') {
|
||||
return $current;
|
||||
}
|
||||
|
||||
$key = wp_generate_password(48, false, false);
|
||||
update_user_meta($user_id, self::EMAIL_ACCESS_KEY_META, $key);
|
||||
return $key;
|
||||
}
|
||||
|
||||
private static function has_valid_email_access_key(int $user_id, string $access_key): bool {
|
||||
if ($user_id <= 0 || $access_key === '') {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stored = (string) get_user_meta($user_id, self::EMAIL_ACCESS_KEY_META, true);
|
||||
return $stored !== '' && hash_equals($stored, $access_key);
|
||||
}
|
||||
|
||||
private static function masked_email(string $email): string {
|
||||
if ($email === '' || !is_email($email) || strpos($email, '@') === false) {
|
||||
return '';
|
||||
}
|
||||
|
||||
[$local, $domain] = explode('@', $email, 2);
|
||||
$local_visible = function_exists('mb_substr') ? mb_substr($local, 0, 2, 'UTF-8') : substr($local, 0, 2);
|
||||
$local_masked = $local_visible . str_repeat('*', max(1, strlen($local) - strlen($local_visible)));
|
||||
|
||||
$domain_parts = explode('.', $domain);
|
||||
$domain_head = $domain_parts[0] ?? '';
|
||||
$domain_visible = substr($domain_head, 0, 1);
|
||||
$domain_parts[0] = $domain_visible . str_repeat('*', max(1, strlen($domain_head) - strlen($domain_visible)));
|
||||
|
||||
return $local_masked . '@' . implode('.', $domain_parts);
|
||||
}
|
||||
|
||||
public static function get_recent_log_summary(): array {
|
||||
$logs = get_transient('aksrt_email_verify_log');
|
||||
if (!is_array($logs)) {
|
||||
return [];
|
||||
}
|
||||
$cutoff = time() - DAY_IN_SECONDS;
|
||||
$summary = [];
|
||||
foreach ($logs as $entry) {
|
||||
if (!is_array($entry) || !isset($entry['t'], $entry['c'])) continue;
|
||||
if ((int) $entry['t'] < $cutoff) continue;
|
||||
$code = (string) $entry['c'];
|
||||
$summary[$code] = ($summary[$code] ?? 0) + 1;
|
||||
}
|
||||
return $summary;
|
||||
}
|
||||
|
||||
public static function get_oauth_bind_context(string $obk): ?array {
|
||||
$obk = sanitize_text_field($obk);
|
||||
@@ -264,6 +38,20 @@ final class Service {
|
||||
|
||||
public static function resolve_email_to_username(string $login): string {
|
||||
$login = trim($login);
|
||||
if (
|
||||
preg_match('/^1[3-9]\d{9}$/', $login)
|
||||
&& class_exists('\AKSRT\Settings\Settings')
|
||||
&& \AKSRT\Settings\Settings::signin_phone_enabled()
|
||||
) {
|
||||
$users = get_users([
|
||||
'meta_key' => 'aksrt_phone',
|
||||
'meta_value' => $login,
|
||||
'number' => 1,
|
||||
]);
|
||||
if (!empty($users[0]) && $users[0] instanceof \WP_User) {
|
||||
return $users[0]->user_login;
|
||||
}
|
||||
}
|
||||
if (is_email($login)) {
|
||||
$user = get_user_by('email', $login);
|
||||
if ($user && !is_wp_error($user)) {
|
||||
@@ -319,23 +107,11 @@ final class Service {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function login_user(array $post, bool $is_ajax = false, bool $skip_nonce = false): array {
|
||||
public static function login_user(array $post, string $captcha_scene = 'login'): array {
|
||||
$resolved_login = self::resolve_email_to_username((string) ($post['log'] ?? ''));
|
||||
|
||||
if (!$skip_nonce) {
|
||||
if ($is_ajax) {
|
||||
if (!wp_verify_nonce($post['_ajax_nonce'] ?? '', 'aksrt_ajax_login')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
} else {
|
||||
if (!wp_verify_nonce($post['_login_nonce'] ?? '', 'aksrt_login')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('login', $post);
|
||||
$captcha_result = aksrt_require_captcha($captcha_scene, $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
@@ -362,20 +138,12 @@ final class Service {
|
||||
// REST API 上下文中 wp_signon 可能无法正确设置 Cookie
|
||||
// 手动调用 wp_set_auth_cookie 确保 Cookie 被设置
|
||||
if (defined('REST_REQUEST') && REST_REQUEST) {
|
||||
wp_set_auth_cookie($user->ID, true);
|
||||
wp_set_auth_cookie($user->ID, !empty($post['rememberme']));
|
||||
wp_set_current_user($user->ID);
|
||||
}
|
||||
|
||||
if (self::user_requires_email_verification($user)) {
|
||||
wp_logout();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('账号尚未完成邮箱验证,请先验证邮箱后再登录', 'aksrt'),
|
||||
'verify_url' => self::get_email_verification_url((int) $user->ID),
|
||||
];
|
||||
}
|
||||
|
||||
$redirect = !empty($post['redirect_to']) ? wp_unslash((string) $post['redirect_to']) : home_url('/');
|
||||
$redirect = wp_validate_redirect($redirect, home_url('/'));
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
|
||||
+57
-337
@@ -10,17 +10,24 @@ defined('ABSPATH') || exit;
|
||||
trait ServicePart1a {
|
||||
|
||||
|
||||
public static function register_user(array $post, array $context = []): array {
|
||||
public static function register_user(array $post): array {
|
||||
$user_login = sanitize_user(wp_unslash($post['user_login'] ?? ''), true);
|
||||
$user_email = sanitize_email(wp_unslash($post['user_email'] ?? ''));
|
||||
$password = (string) ($post['user_pass'] ?? '');
|
||||
$confirm = (string) ($post['user_pass_confirm'] ?? '');
|
||||
|
||||
if (!wp_verify_nonce($post['_register_nonce'] ?? '', 'aksrt_register')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
if (
|
||||
class_exists('\AKSRT\Settings\Settings')
|
||||
&& \AKSRT\Settings\Settings::signup_verification_enabled()
|
||||
&& empty($post['_auth_code_verified'])
|
||||
) {
|
||||
return ['success' => false, 'error' => __('请先完成邮箱或手机验证', 'aksrt')];
|
||||
}
|
||||
|
||||
if (empty($post['agree_terms'])) {
|
||||
$agreement = class_exists('\AKSRT\Settings\Settings')
|
||||
? \AKSRT\Settings\Settings::user_agreement_settings()
|
||||
: ['enabled' => false, 'privacy' => false, 'required' => false];
|
||||
if ((!empty($agreement['enabled']) || !empty($agreement['privacy'])) && !empty($agreement['required']) && empty($post['agree_terms'])) {
|
||||
return ['success' => false, 'error' => __('请先同意服务条款', 'aksrt')];
|
||||
}
|
||||
|
||||
@@ -31,6 +38,13 @@ trait ServicePart1a {
|
||||
if (mb_strlen($user_login) < 3 || mb_strlen($user_login) > 60) {
|
||||
return ['success' => false, 'error' => __('用户名长度需在 3-60 个字符之间', 'aksrt')];
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
foreach (\AKSRT\Settings\Settings::nickname_blacklist() as $keyword) {
|
||||
if ($keyword !== '' && mb_stripos($user_login, $keyword) !== false) {
|
||||
return ['success' => false, 'error' => __('用户名包含禁止使用的关键词', 'aksrt')];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (username_exists($user_login)) {
|
||||
return ['success' => false, 'error' => __('该用户名已被使用', 'aksrt')];
|
||||
@@ -39,17 +53,37 @@ trait ServicePart1a {
|
||||
if (empty($user_email) || !is_email($user_email)) {
|
||||
return ['success' => false, 'error' => __('请输入有效的邮箱地址', 'aksrt')];
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$email_limit = \AKSRT\Settings\Settings::email_limit();
|
||||
$matches = false;
|
||||
foreach ((array) ($email_limit['domains'] ?? []) as $keyword) {
|
||||
if ($keyword !== '' && stripos($user_email, (string) $keyword) !== false) {
|
||||
$matches = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((($email_limit['type'] ?? 'not') === 'not' && $matches) || (($email_limit['type'] ?? 'not') === 'allow' && !empty($email_limit['domains']) && !$matches)) {
|
||||
return ['success' => false, 'error' => __('该邮箱不符合本站注册规则', 'aksrt')];
|
||||
}
|
||||
}
|
||||
|
||||
if (email_exists($user_email)) {
|
||||
return ['success' => false, 'error' => __('该邮箱已被其他账号使用', 'aksrt')];
|
||||
}
|
||||
|
||||
if (
|
||||
class_exists('\AKSRT\Settings\Settings')
|
||||
&& \AKSRT\Settings\Settings::signup_verification_enabled()
|
||||
&& \AKSRT\Settings\Settings::signup_omit_password_confirmation()
|
||||
) {
|
||||
$confirm = $password;
|
||||
}
|
||||
$password_check = self::validate_password($password, $confirm);
|
||||
if ($password_check !== true) {
|
||||
return ['success' => false, 'error' => $password_check];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
if (empty($post['_auth_code_verified']) && function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('register', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
@@ -88,6 +122,12 @@ trait ServicePart1a {
|
||||
return ['success' => false, 'error' => __('推广码无效,请检查后重试', 'aksrt')];
|
||||
}
|
||||
}
|
||||
if ($submitted_ref !== '' && empty($ref_context['valid']) && !empty($registration['reject_invalid'])) {
|
||||
if ($ref_service) {
|
||||
$ref_service->clear_registration_context();
|
||||
}
|
||||
return ['success' => false, 'error' => __('推广码无效,请检查后重试', 'aksrt')];
|
||||
}
|
||||
|
||||
$user_id = wp_create_user($user_login, $password, $user_email);
|
||||
if (is_wp_error($user_id)) {
|
||||
@@ -103,115 +143,20 @@ trait ServicePart1a {
|
||||
return ['success' => false, 'error' => __('注册成功,但读取用户信息失败,请联系管理员', 'aksrt')];
|
||||
}
|
||||
|
||||
$oauth_context = isset($context['oauth_bind_data']) && is_array($context['oauth_bind_data']) ? $context['oauth_bind_data'] : [];
|
||||
$oauth_verified = self::oauth_email_is_verified($oauth_context);
|
||||
$requires_verify = \AKSRT\Settings\Settings::registration_requires_email_verification() && !$oauth_verified;
|
||||
|
||||
if (!$requires_verify) {
|
||||
self::mark_email_verified($user_id);
|
||||
wp_set_auth_cookie((int) $user_id, true);
|
||||
do_action('wp_login', $user->user_login, $user);
|
||||
return [
|
||||
'success' => true,
|
||||
'user_id' => (int) $user_id,
|
||||
'user' => $user,
|
||||
'user_login' => $user_login,
|
||||
'redirect' => home_url('/'),
|
||||
'notice' => $ref_notice,
|
||||
];
|
||||
}
|
||||
|
||||
$method = \AKSRT\Settings\Settings::registration_verification_method();
|
||||
$begin = self::begin_email_verification(
|
||||
$user,
|
||||
$method,
|
||||
[
|
||||
'obk' => sanitize_text_field((string) ($context['obk'] ?? '')),
|
||||
]
|
||||
);
|
||||
|
||||
if (empty($begin['success'])) {
|
||||
self::delete_user_account($user_id);
|
||||
return $begin;
|
||||
}
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'user_id' => (int) $user_id,
|
||||
'user' => $user,
|
||||
'user_login' => $user_login,
|
||||
'verification_required' => true,
|
||||
'verification_method' => $method,
|
||||
'verify_url' => self::get_email_verification_url((int) $user_id, ['mode' => $method]),
|
||||
'message' => $begin['message'] ?? '',
|
||||
'notice' => $ref_notice,
|
||||
];
|
||||
}
|
||||
|
||||
public static function bind_oauth_account(array $post, array $oauth_data): array {
|
||||
if (!wp_verify_nonce($post['_bind_nonce'] ?? '', 'aksrt_bind')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('bind', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
$creds = [
|
||||
'user_login' => self::resolve_email_to_username((string) ($post['log'] ?? '')),
|
||||
'user_password' => (string) ($post['pwd'] ?? ''),
|
||||
'remember' => true,
|
||||
];
|
||||
|
||||
$user = wp_signon($creds);
|
||||
if (is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => self::friendly_login_error($user->get_error_message())];
|
||||
}
|
||||
|
||||
if (self::user_requires_email_verification($user)) {
|
||||
wp_logout();
|
||||
return [
|
||||
'success' => false,
|
||||
'error' => __('该账号尚未完成邮箱验证,请先完成验证后再绑定第三方登录。', 'aksrt'),
|
||||
'verify_url' => self::get_email_verification_url((int) $user->ID),
|
||||
];
|
||||
}
|
||||
|
||||
$provider = sanitize_key((string) ($oauth_data['provider'] ?? ''));
|
||||
if ($provider === '') {
|
||||
return ['success' => false, 'error' => __('绑定数据无效,请重新使用第三方登录', 'aksrt')];
|
||||
}
|
||||
|
||||
$uid = (int) $user->ID;
|
||||
update_user_meta($uid, "aksrt_{$provider}_id", $oauth_data['provider_id'] ?? '');
|
||||
|
||||
$bindings = get_user_meta($uid, 'aksrt_oauth_bindings', true) ?: [];
|
||||
if (!is_array($bindings)) {
|
||||
$bindings = [];
|
||||
}
|
||||
if (!in_array($provider, $bindings, true)) {
|
||||
$bindings[] = $provider;
|
||||
}
|
||||
update_user_meta($uid, 'aksrt_oauth_bindings', $bindings);
|
||||
|
||||
if (!empty($oauth_data['user_data']['avatar_url'])) {
|
||||
update_user_meta($uid, 'aksrt_oauth_avatar', esc_url_raw($oauth_data['user_data']['avatar_url']));
|
||||
}
|
||||
|
||||
if (!empty($post['obk'])) {
|
||||
delete_transient(sanitize_text_field((string) $post['obk']));
|
||||
}
|
||||
|
||||
wp_set_auth_cookie($uid, true);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'user' => $user,
|
||||
'provider' => $provider,
|
||||
];
|
||||
}
|
||||
|
||||
public static function finalize_oauth_registration(int $user_id, array $oauth_data, string $obk = ''): void {
|
||||
public static function finalize_oauth_binding(int $user_id, array $oauth_data, string $obk = ''): void {
|
||||
if ($user_id <= 0) {
|
||||
return;
|
||||
}
|
||||
@@ -222,16 +167,17 @@ trait ServicePart1a {
|
||||
}
|
||||
|
||||
update_user_meta($user_id, "aksrt_{$provider}_id", $oauth_data['provider_id'] ?? '');
|
||||
update_user_meta($user_id, 'aksrt_oauth_bindings', [$provider]);
|
||||
$bindings = get_user_meta($user_id, 'aksrt_oauth_bindings', true);
|
||||
$bindings = is_array($bindings) ? $bindings : [];
|
||||
if (!in_array($provider, $bindings, true)) {
|
||||
$bindings[] = $provider;
|
||||
}
|
||||
update_user_meta($user_id, 'aksrt_oauth_bindings', array_values($bindings));
|
||||
|
||||
if (!empty($oauth_data['user_data']['avatar_url'])) {
|
||||
update_user_meta($user_id, 'aksrt_oauth_avatar', esc_url_raw($oauth_data['user_data']['avatar_url']));
|
||||
}
|
||||
|
||||
if (self::oauth_email_is_verified($oauth_data)) {
|
||||
EmailVerification::mark_email_verified($user_id);
|
||||
}
|
||||
|
||||
if ($obk !== '') {
|
||||
delete_transient(sanitize_text_field($obk));
|
||||
}
|
||||
@@ -239,235 +185,9 @@ trait ServicePart1a {
|
||||
wp_set_auth_cookie($user_id, true);
|
||||
}
|
||||
|
||||
public static function get_email_verification_context(array $query): array {
|
||||
$invalid_payload = [
|
||||
'mode' => 'invalid',
|
||||
'user' => null,
|
||||
'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt'),
|
||||
'method' => '',
|
||||
'email' => '',
|
||||
'uid' => 0,
|
||||
'verified' => false,
|
||||
'access_key' => '',
|
||||
];
|
||||
|
||||
$user_id = isset($query['uid']) ? absint($query['uid']) : 0;
|
||||
if ($user_id <= 0) {
|
||||
return $invalid_payload;
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return $invalid_payload;
|
||||
}
|
||||
|
||||
$method = self::current_registration_verification_method();
|
||||
$email = (string) get_user_meta($user_id, self::EMAIL_TARGET_META, true);
|
||||
$token = sanitize_text_field((string) ($query['token'] ?? ''));
|
||||
$access = sanitize_text_field((string) ($query['vk'] ?? ''));
|
||||
|
||||
// 容错:meta 缺失时(例如历史账号被强制要求验证),回退到设置默认值与 user_email
|
||||
if (!in_array($method, ['link', 'code'], true)) {
|
||||
$method = self::current_registration_verification_method();
|
||||
}
|
||||
if ($email === '' || !is_email($email)) {
|
||||
$email = (string) $user->user_email;
|
||||
}
|
||||
$masked_email = self::masked_email($email);
|
||||
$has_access = self::has_valid_email_access_key($user_id, $access);
|
||||
|
||||
// token 路径:链接模式直接走 token 校验;不依赖 vk
|
||||
if ($token !== '') {
|
||||
if (EmailVerification::is_email_verified($user_id)) {
|
||||
return [
|
||||
'mode' => 'invalid',
|
||||
'user' => $user,
|
||||
'error' => __('该验证链接已失效或已使用,请重新发起验证流程', 'aksrt'),
|
||||
'method' => $has_access ? $method : '',
|
||||
'email' => $has_access ? $masked_email : '',
|
||||
'uid' => $has_access ? $user_id : 0,
|
||||
'verified' => true,
|
||||
'access_key' => $has_access ? $access : '',
|
||||
];
|
||||
}
|
||||
|
||||
$result = self::verify_email_token($user_id, $token);
|
||||
if (!empty($result['success'])) {
|
||||
return [
|
||||
'mode' => 'completed',
|
||||
'user' => $user,
|
||||
'error' => '',
|
||||
'method' => $method,
|
||||
'email' => $masked_email,
|
||||
'uid' => $user_id,
|
||||
'verified' => true,
|
||||
'redirect' => $result['redirect'] ?? home_url('/'),
|
||||
'message' => $result['message'] ?? '',
|
||||
'access_key' => $has_access ? $access : '',
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'mode' => 'invalid',
|
||||
'user' => $user,
|
||||
'error' => $result['error'] ?? __('验证链接无效或已过期', 'aksrt'),
|
||||
'method' => $has_access ? $method : '',
|
||||
'email' => $has_access ? $masked_email : '',
|
||||
'uid' => $has_access ? $user_id : 0,
|
||||
'verified' => false,
|
||||
'access_key' => $has_access ? $access : '',
|
||||
];
|
||||
}
|
||||
|
||||
// 没有 token 也没有 vk:返回完全脱敏的通用错误,避免根据 uid 枚举账号状态
|
||||
if (!$has_access) {
|
||||
return $invalid_payload;
|
||||
}
|
||||
|
||||
if (!self::user_requires_email_verification($user)) {
|
||||
return [
|
||||
'mode' => 'verified',
|
||||
'user' => $user,
|
||||
'error' => '',
|
||||
'method' => '',
|
||||
'email' => self::masked_email((string) $user->user_email),
|
||||
'uid' => $user_id,
|
||||
'verified' => true,
|
||||
'access_key' => $access,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'mode' => $method === 'code' ? 'code' : 'pending',
|
||||
'user' => $user,
|
||||
'error' => '',
|
||||
'method' => $method,
|
||||
'email' => $masked_email ?: self::masked_email((string) $user->user_email),
|
||||
'uid' => $user_id,
|
||||
'verified' => false,
|
||||
'access_key' => $access,
|
||||
];
|
||||
}
|
||||
|
||||
public static function verify_email_code_submission(array $post): array {
|
||||
if (!wp_verify_nonce($post['_verify_email_nonce'] ?? '', 'aksrt_verify_email')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
$user_id = isset($post['verify_uid']) ? absint($post['verify_uid']) : 0;
|
||||
$access = sanitize_text_field((string) ($post['vk'] ?? ''));
|
||||
$code = sanitize_text_field((string) ($post['email_code'] ?? ''));
|
||||
if ($user_id <= 0 || $code === '') {
|
||||
return ['success' => false, 'error' => __('请输入完整的邮箱验证码', 'aksrt')];
|
||||
}
|
||||
|
||||
if (!self::has_valid_email_access_key($user_id, $access)) {
|
||||
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('verify_email', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
return self::verify_email_code($user_id, $code);
|
||||
}
|
||||
|
||||
public static function resend_email_verification(array $post): array {
|
||||
if (!wp_verify_nonce($post['_verify_email_nonce'] ?? '', 'aksrt_verify_email')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
$user_id = isset($post['verify_uid']) ? absint($post['verify_uid']) : 0;
|
||||
$access = sanitize_text_field((string) ($post['vk'] ?? ''));
|
||||
|
||||
// 必须先持有有效 vk 才允许重发,避免未登录用户用 uid 枚举触发邮件
|
||||
if ($user_id <= 0 || !self::has_valid_email_access_key($user_id, $access)) {
|
||||
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
|
||||
}
|
||||
|
||||
// IP 节流:每 IP 每 RESEND_IP_WINDOW 秒最多 RESEND_IP_MAX 次(跨账号合并计数)
|
||||
$ip = sanitize_text_field((string) ($_SERVER['REMOTE_ADDR'] ?? ''));
|
||||
if ($ip !== '') {
|
||||
$ip_key = 'aksrt_email_resend_ip_' . md5($ip);
|
||||
$ip_attempts = (int) get_transient($ip_key);
|
||||
if ($ip_attempts >= self::RESEND_IP_MAX) {
|
||||
self::log_event('verify.resend.ip_throttled', ['uid' => $user_id, 'ip' => $ip]);
|
||||
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
|
||||
}
|
||||
set_transient($ip_key, $ip_attempts + 1, self::RESEND_IP_WINDOW);
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => __('验证入口无效或已过期,请重新发起验证流程。', 'aksrt')];
|
||||
}
|
||||
|
||||
if (!self::user_requires_email_verification($user)) {
|
||||
return ['success' => false, 'error' => __('该账号已完成邮箱验证,无需重复发送', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('verify_email', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
$method = self::current_registration_verification_method();
|
||||
$email = (string) get_user_meta($user_id, self::EMAIL_TARGET_META, true);
|
||||
|
||||
// 容错:meta 缺失时回退到设置默认值与 user_email(兼容历史账号 / meta 被外部清理的情况)
|
||||
if (!in_array($method, ['link', 'code'], true)) {
|
||||
$method = \AKSRT\Settings\Settings::registration_verification_method();
|
||||
}
|
||||
if ($email === '' || !is_email($email)) {
|
||||
$email = (string) $user->user_email;
|
||||
}
|
||||
if ($email === '' || !is_email($email)) {
|
||||
return ['success' => false, 'error' => __('当前账号缺少有效邮箱,请联系管理员', 'aksrt')];
|
||||
}
|
||||
|
||||
$requested_at = (int) get_user_meta($user_id, self::EMAIL_REQUESTED_AT_META, true);
|
||||
if ($requested_at > 0 && (time() - $requested_at) < 60) {
|
||||
return ['success' => false, 'error' => __('操作过于频繁,请稍后再试', 'aksrt')];
|
||||
}
|
||||
|
||||
return self::begin_email_verification($user, $method, [
|
||||
'email' => $email,
|
||||
'obk' => (string) get_user_meta($user_id, self::EMAIL_OBK_META, true),
|
||||
'force' => true,
|
||||
]);
|
||||
}
|
||||
|
||||
private static function user_requires_email_verification($user): bool {
|
||||
return EmailVerification::user_requires_email_verification($user);
|
||||
}
|
||||
|
||||
private static function mark_email_verified(int $user_id): void {
|
||||
EmailVerification::mark_email_verified($user_id);
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// 公开 API:管理员后台 / 内部业务调用(委托 EmailVerification)
|
||||
// ============================================================
|
||||
|
||||
public static function user_is_email_verified(int $user_id): bool {
|
||||
return EmailVerification::user_is_email_verified($user_id);
|
||||
}
|
||||
|
||||
public static function get_user_verification_state(int $user_id): array {
|
||||
return EmailVerification::get_user_verification_state($user_id);
|
||||
}
|
||||
|
||||
public static function admin_mark_verified(int $user_id, int $by_admin_id = 0): array {
|
||||
return EmailVerification::admin_mark_verified($user_id, $by_admin_id);
|
||||
}
|
||||
|
||||
public static function admin_unmark_verified(int $user_id, int $by_admin_id = 0): array {
|
||||
return EmailVerification::admin_unmark_verified($user_id, $by_admin_id);
|
||||
update_user_meta($user_id, self::EMAIL_VERIFIED_META, '1');
|
||||
do_action('aksrt_email_verified', $user_id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+35
-350
@@ -9,227 +9,6 @@ defined('ABSPATH') || exit;
|
||||
|
||||
trait ServicePart1b {
|
||||
|
||||
public static function admin_send_verification(int $user_id, int $by_admin_id = 0): array {
|
||||
return EmailVerification::admin_send_verification($user_id, $by_admin_id);
|
||||
}
|
||||
|
||||
public static function backfill_legacy_users_as_verified(bool $force = false): int {
|
||||
return EmailVerification::backfill_legacy_users_as_verified($force);
|
||||
}
|
||||
|
||||
public static function run_async_backfill_batch(int $offset = 0): void {
|
||||
EmailVerification::run_async_backfill_batch($offset);
|
||||
}
|
||||
|
||||
private static function delete_user_account(int $user_id): void {
|
||||
if ($user_id <= 0) {
|
||||
return;
|
||||
}
|
||||
if (!function_exists('wp_delete_user')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/user.php';
|
||||
}
|
||||
if (function_exists('wp_delete_user')) {
|
||||
wp_delete_user($user_id);
|
||||
}
|
||||
}
|
||||
|
||||
private static function oauth_email_is_verified(array $oauth_data): bool {
|
||||
$user_data = isset($oauth_data['user_data']) && is_array($oauth_data['user_data']) ? $oauth_data['user_data'] : $oauth_data;
|
||||
if (empty($user_data['email'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($user_data['email_verified'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$provider = sanitize_key((string) ($user_data['provider'] ?? $oauth_data['provider'] ?? ''));
|
||||
if ($provider === 'google' && !empty($user_data['email'])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($provider === 'github' && isset($user_data['email_verified'])) {
|
||||
return !empty($user_data['email_verified']);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function begin_email_verification(\WP_User $user, string $method, array $args = []): array {
|
||||
return EmailVerification::begin_email_verification($user, $method, $args);
|
||||
}
|
||||
|
||||
private static function verify_email_token(int $user_id, string $token): array {
|
||||
return EmailVerification::verify_email_token($user_id, $token);
|
||||
}
|
||||
|
||||
private static function verify_email_code(int $user_id, string $code): array {
|
||||
return EmailVerification::verify_email_code($user_id, $code);
|
||||
}
|
||||
|
||||
public static function get_reset_context(array $query): array {
|
||||
if (!empty($query['key']) || !empty($query['login']) || !empty($query['legacy'])) {
|
||||
return [
|
||||
'mode' => 'invalid',
|
||||
'user' => null,
|
||||
'error' => __('旧的重置密码链接已失效,请重新申请重置密码。', 'aksrt'),
|
||||
'token' => '',
|
||||
'user_id'=> 0,
|
||||
];
|
||||
}
|
||||
|
||||
$token = sanitize_text_field($query['token'] ?? '');
|
||||
$user_id = isset($query['uid']) ? absint($query['uid']) : 0;
|
||||
|
||||
if ($token === '' || $user_id <= 0) {
|
||||
return [
|
||||
'mode' => 'request',
|
||||
'user' => null,
|
||||
'error' => '',
|
||||
'token' => '',
|
||||
'user_id'=> 0,
|
||||
];
|
||||
}
|
||||
|
||||
$user = self::validate_reset_token($user_id, $token);
|
||||
if (is_wp_error($user)) {
|
||||
return [
|
||||
'mode' => 'invalid',
|
||||
'user' => null,
|
||||
'error' => $user->get_error_message(),
|
||||
'token' => $token,
|
||||
'user_id'=> $user_id,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'mode' => 'reset',
|
||||
'user' => $user,
|
||||
'error' => '',
|
||||
'token' => $token,
|
||||
'user_id'=> $user_id,
|
||||
];
|
||||
}
|
||||
|
||||
public static function request_password_reset(array $post): array {
|
||||
if (!wp_verify_nonce($post['_reset_nonce'] ?? '', 'aksrt_reset_password')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('lostpassword', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
$raw_login = trim((string) ($post['user_login'] ?? ''));
|
||||
if ($raw_login === '') {
|
||||
return ['success' => false, 'error' => __('请输入邮箱地址或用户名', 'aksrt')];
|
||||
}
|
||||
|
||||
$user = null;
|
||||
if (is_email($raw_login)) {
|
||||
$user = get_user_by('email', $raw_login);
|
||||
} else {
|
||||
$user = get_user_by('login', sanitize_user($raw_login, true));
|
||||
}
|
||||
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return ['success' => true, 'message' => __('如果该账号存在,重置邮件已发送到注册邮箱', 'aksrt')];
|
||||
}
|
||||
|
||||
$token = self::create_reset_token($user->ID);
|
||||
$sent = self::send_password_reset_email($user, $token);
|
||||
|
||||
if (!$sent) {
|
||||
return ['success' => false, 'error' => __('重置邮件发送失败,请稍后再试', 'aksrt')];
|
||||
}
|
||||
|
||||
return ['success' => true, 'message' => __('如果该账号存在,重置邮件已发送到注册邮箱', 'aksrt')];
|
||||
}
|
||||
|
||||
public static function reset_password_with_token(array $post): array {
|
||||
if (!wp_verify_nonce($post['_setpw_nonce'] ?? '', 'aksrt_set_password')) {
|
||||
return ['success' => false, 'error' => __('安全验证失败,请刷新页面重试', 'aksrt')];
|
||||
}
|
||||
|
||||
$token = sanitize_text_field($post['reset_token'] ?? '');
|
||||
$user_id = isset($post['reset_uid']) ? absint($post['reset_uid']) : 0;
|
||||
$pass1 = (string) ($post['pass1'] ?? '');
|
||||
$pass2 = (string) ($post['pass2'] ?? '');
|
||||
|
||||
$user = self::validate_reset_token($user_id, $token);
|
||||
if (is_wp_error($user)) {
|
||||
return ['success' => false, 'error' => $user->get_error_message(), 'mode' => 'invalid'];
|
||||
}
|
||||
|
||||
$password_check = self::validate_password($pass1, $pass2);
|
||||
if ($password_check !== true) {
|
||||
return ['success' => false, 'error' => $password_check, 'mode' => 'reset'];
|
||||
}
|
||||
|
||||
wp_set_password($pass1, $user_id);
|
||||
self::clear_reset_token($user_id);
|
||||
|
||||
return ['success' => true, 'message' => __('密码已设置成功!', 'aksrt')];
|
||||
}
|
||||
|
||||
private static function create_reset_token(int $user_id): string {
|
||||
$token = wp_generate_password(48, false, false);
|
||||
update_user_meta($user_id, self::RESET_TOKEN_HASH_META, hash('sha256', $token));
|
||||
update_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META, time() + self::RESET_TOKEN_TTL);
|
||||
update_user_meta($user_id, self::RESET_TOKEN_REQUESTED_AT, time());
|
||||
return $token;
|
||||
}
|
||||
|
||||
private static function validate_reset_token(int $user_id, string $token) {
|
||||
if ($user_id <= 0 || $token === '') {
|
||||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||||
}
|
||||
|
||||
$user = get_userdata($user_id);
|
||||
if (!$user || is_wp_error($user)) {
|
||||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||||
}
|
||||
|
||||
$stored_hash = (string) get_user_meta($user_id, self::RESET_TOKEN_HASH_META, true);
|
||||
$expires_at = (int) get_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META, true);
|
||||
$token_hash = hash('sha256', $token);
|
||||
|
||||
if ($stored_hash === '' || $expires_at <= time() || !hash_equals($stored_hash, $token_hash)) {
|
||||
return new \WP_Error('invalid_reset_token', __('重置链接无效或已过期', 'aksrt'));
|
||||
}
|
||||
|
||||
return $user;
|
||||
}
|
||||
|
||||
private static function clear_reset_token(int $user_id): void {
|
||||
delete_user_meta($user_id, self::RESET_TOKEN_HASH_META);
|
||||
delete_user_meta($user_id, self::RESET_TOKEN_EXPIRES_META);
|
||||
delete_user_meta($user_id, self::RESET_TOKEN_REQUESTED_AT);
|
||||
}
|
||||
|
||||
private static function send_password_reset_email(\WP_User $user, string $token): bool {
|
||||
$reset_url = aksrt_get_reset_password_url($token, $user->ID);
|
||||
$site_name = get_bloginfo('name');
|
||||
$default_subject = '[' . $site_name . '] ' . __('重置密码', 'aksrt');
|
||||
$default_content = sprintf(
|
||||
__("您好 %s,\n\n有人请求重置您在 %s 的密码。\n\n请点击以下链接设置新密码:\n\n%s\n\n如果这不是您本人的操作,请忽略此邮件,您的密码不会被更改。", 'aksrt'),
|
||||
$user->user_login, $site_name, $reset_url
|
||||
);
|
||||
|
||||
return \AKSRT\Settings\Settings::send_event_email(
|
||||
'password_reset', $user->user_email, $default_subject, $default_content,
|
||||
['{username}' => $user->user_login, '{site_name}' => $site_name, '{url}' => $reset_url]
|
||||
);
|
||||
}
|
||||
|
||||
/* =================================================================
|
||||
* 手机号认证(注册 / 登录 / 绑定)
|
||||
* 对齐 legacy 的 signup_captcha / signin_captcha / bind_phone_captcha
|
||||
* ================================================================= */
|
||||
|
||||
/**
|
||||
* 手机号注册
|
||||
*
|
||||
@@ -238,34 +17,39 @@ trait ServicePart1b {
|
||||
*/
|
||||
public static function register_by_phone(array $post): array {
|
||||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||||
$code = sanitize_text_field((string) ($post['code'] ?? ''));
|
||||
$pass = (string) ($post['password'] ?? '');
|
||||
$confirm = (string) ($post['password_confirm'] ?? '');
|
||||
$username = sanitize_user(wp_unslash((string) ($post['user_login'] ?? '')), true);
|
||||
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||||
}
|
||||
if (empty($code)) {
|
||||
return ['success' => false, 'error' => __('请输入验证码', 'aksrt')];
|
||||
if (empty($post['_auth_code_verified'])) {
|
||||
return ['success' => false, 'error' => __('请先完成手机验证', 'aksrt')];
|
||||
}
|
||||
if (strlen($pass) < 6) {
|
||||
return ['success' => false, 'error' => __('密码至少 6 位', 'aksrt')];
|
||||
if ($username === '' || !validate_username($username) || mb_strlen($username) < 3 || mb_strlen($username) > 60) {
|
||||
return ['success' => false, 'error' => __('请输入 3-60 个字符的有效用户名', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('register', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
if (username_exists($username)) {
|
||||
return ['success' => false, 'error' => __('该用户名已被使用', 'aksrt')];
|
||||
}
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
foreach (\AKSRT\Settings\Settings::nickname_blacklist() as $keyword) {
|
||||
if ($keyword !== '' && mb_stripos($username, $keyword) !== false) {
|
||||
return ['success' => false, 'error' => __('用户名包含禁止使用的关键词', 'aksrt')];
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 SMS 验证码
|
||||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||||
$verify_result = \AKSRT\Sms\SMS::get_instance()->verify_code($phone, $code);
|
||||
if (empty($verify_result['success'])) {
|
||||
return ['success' => false, 'error' => $verify_result['message'] ?? __('验证码不正确', 'aksrt')];
|
||||
if (\AKSRT\Settings\Settings::signup_omit_password_confirmation()) {
|
||||
$confirm = $pass;
|
||||
}
|
||||
} else {
|
||||
return ['success' => false, 'error' => __('短信服务不可用', 'aksrt')];
|
||||
$agreement = \AKSRT\Settings\Settings::user_agreement_settings();
|
||||
if ((!empty($agreement['enabled']) || !empty($agreement['privacy'])) && !empty($agreement['required']) && empty($post['agree_terms'])) {
|
||||
return ['success' => false, 'error' => __('请先同意服务条款', 'aksrt')];
|
||||
}
|
||||
}
|
||||
$password_check = self::validate_password($pass, $confirm);
|
||||
if ($password_check !== true) {
|
||||
return ['success' => false, 'error' => $password_check];
|
||||
}
|
||||
|
||||
// 检查手机号是否已注册
|
||||
@@ -274,15 +58,6 @@ trait ServicePart1b {
|
||||
return ['success' => false, 'error' => __('该手机号已注册', 'aksrt')];
|
||||
}
|
||||
|
||||
// 生成用户名(phone_ + 后4位)
|
||||
$username = 'phone_' . substr($phone, -4);
|
||||
$suffix = 1;
|
||||
$base = $username;
|
||||
while (username_exists($username)) {
|
||||
$username = $base . '_' . $suffix;
|
||||
$suffix++;
|
||||
}
|
||||
|
||||
$ref_service = class_exists('\AKSRT\Payment\ReferralAttribution')
|
||||
? \AKSRT\Payment\ReferralAttribution::get_instance()
|
||||
: null;
|
||||
@@ -314,9 +89,15 @@ trait ServicePart1b {
|
||||
return ['success' => false, 'error' => __('推广码无效,请检查后重试', 'aksrt')];
|
||||
}
|
||||
}
|
||||
if ($submitted_ref !== '' && empty($ref_context['valid']) && !empty($registration['reject_invalid'])) {
|
||||
if ($ref_service) {
|
||||
$ref_service->clear_registration_context();
|
||||
}
|
||||
return ['success' => false, 'error' => __('推广码无效,请检查后重试', 'aksrt')];
|
||||
}
|
||||
|
||||
// 创建用户
|
||||
$user_id = wp_create_user($username, $pass, $phone . '@sms.aksrt.local');
|
||||
$user_id = wp_create_user($username, $pass, '');
|
||||
if (is_wp_error($user_id)) {
|
||||
if ($ref_service) {
|
||||
$ref_service->clear_registration_context();
|
||||
@@ -328,16 +109,15 @@ trait ServicePart1b {
|
||||
update_user_meta($user_id, 'aksrt_phone', $phone);
|
||||
update_user_meta($user_id, 'aksrt_phone_verified', '1');
|
||||
|
||||
// 邮箱标记为已验证(手机注册等同于已验证)
|
||||
update_user_meta($user_id, 'aksrt_email_verified', '1');
|
||||
|
||||
if ($ref_service) {
|
||||
$ref_service->reward_registered_user((int) $user_id);
|
||||
}
|
||||
self::mark_email_verified((int) $user_id);
|
||||
|
||||
// 自动登录
|
||||
wp_set_current_user($user_id);
|
||||
wp_set_auth_cookie($user_id, true);
|
||||
$user = get_userdata($user_id);
|
||||
if ($user instanceof \WP_User) {
|
||||
do_action('wp_login', $user->user_login, $user);
|
||||
}
|
||||
|
||||
$redirect = function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
@@ -352,101 +132,6 @@ trait ServicePart1b {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号登录(密码登录)
|
||||
*
|
||||
* @param array $post (phone, password)
|
||||
* @return array
|
||||
*/
|
||||
public static function login_by_phone_password(array $post): array {
|
||||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||||
$pass = (string) ($post['password'] ?? '');
|
||||
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('login', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
$user = self::get_user_by_phone($phone);
|
||||
if (!$user) {
|
||||
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
if (!wp_check_password($pass, $user->data->user_pass, $user->ID)) {
|
||||
return ['success' => false, 'error' => __('密码不正确', 'aksrt')];
|
||||
}
|
||||
|
||||
wp_set_current_user($user->ID);
|
||||
wp_set_auth_cookie($user->ID, true);
|
||||
|
||||
$redirect = function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
: home_url('/');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'user_id' => $user->ID,
|
||||
'redirect' => $redirect,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 手机号免密登录(SMS 验证码登录)
|
||||
*
|
||||
* @param array $post (phone, code)
|
||||
* @return array
|
||||
*/
|
||||
public static function login_by_phone_code(array $post): array {
|
||||
$phone = sanitize_text_field((string) ($post['phone'] ?? ''));
|
||||
$code = sanitize_text_field((string) ($post['code'] ?? ''));
|
||||
|
||||
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
|
||||
return ['success' => false, 'error' => __('手机号格式不正确', 'aksrt')];
|
||||
}
|
||||
|
||||
if (function_exists('aksrt_require_captcha')) {
|
||||
$captcha_result = aksrt_require_captcha('login', $post);
|
||||
if ($captcha_result !== true) {
|
||||
return ['success' => false, 'error' => $captcha_result];
|
||||
}
|
||||
}
|
||||
|
||||
// 验证 SMS 验证码
|
||||
if (class_exists('\AKSRT\Sms\SMS')) {
|
||||
$verify_result = \AKSRT\Sms\SMS::get_instance()->verify_code($phone, $code);
|
||||
if (empty($verify_result['success'])) {
|
||||
return ['success' => false, 'error' => $verify_result['message'] ?? __('验证码不正确', 'aksrt')];
|
||||
}
|
||||
} else {
|
||||
return ['success' => false, 'error' => __('短信服务不可用', 'aksrt')];
|
||||
}
|
||||
|
||||
$user = self::get_user_by_phone($phone);
|
||||
if (!$user) {
|
||||
return ['success' => false, 'error' => __('该手机号未注册', 'aksrt')];
|
||||
}
|
||||
|
||||
wp_set_current_user($user->ID);
|
||||
wp_set_auth_cookie($user->ID, true);
|
||||
|
||||
$redirect = function_exists('aksrt_get_user_center_url')
|
||||
? aksrt_get_user_center_url()
|
||||
: home_url('/');
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'user_id' => $user->ID,
|
||||
'redirect' => $redirect,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定手机号到当前用户
|
||||
*
|
||||
|
||||
+10
-42
@@ -113,6 +113,15 @@ trait ScriptsPart1b {
|
||||
$enqueue_module('footer');
|
||||
$enqueue_module('floating-buttons');
|
||||
$enqueue_module('enhancements');
|
||||
if (!is_user_logged_in() || (bool) get_query_var('aksrt_sign')) {
|
||||
$auth_css = get_template_directory() . '/assets/css/routes/sign.css';
|
||||
wp_enqueue_style(
|
||||
'aksrt-auth',
|
||||
$base_uri . '/assets/css/routes/sign.css',
|
||||
[$core_dep],
|
||||
file_exists($auth_css) ? (string) filemtime($auth_css) : $version
|
||||
);
|
||||
}
|
||||
|
||||
// 4a-b. 全局补充样式(骨架屏、空状态、分页、加载指示器)
|
||||
foreach (['skeleton', 'empty-state', 'pagination', 'ajax-spinner', 'modal-runtime'] as $slug) {
|
||||
@@ -443,17 +452,6 @@ trait ScriptsPart1b {
|
||||
: $version,
|
||||
true
|
||||
);
|
||||
// 绑定手机短信发送按钮依赖 sign-register 的 data-aksrt-sms-trigger 契约
|
||||
$sign_register_js = get_template_directory() . '/assets/js/sign-register.js';
|
||||
if (file_exists($sign_register_js)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-sign-register',
|
||||
$base_uri . '/assets/js/sign-register.js',
|
||||
['aksrt-route-user-center'],
|
||||
(string) filemtime($sign_register_js),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -622,39 +620,9 @@ trait ScriptsPart1b {
|
||||
}
|
||||
}
|
||||
|
||||
// 登录/注册/绑定 — pages + auth 路由专属 CSS
|
||||
// Unified authentication page shell.
|
||||
if ($is_auth_page) {
|
||||
$enqueue_module('pages');
|
||||
// Auth 路由专属样式
|
||||
wp_enqueue_style(
|
||||
'aksrt-route-auth',
|
||||
$base_uri . '/assets/css/routes/auth.css',
|
||||
['aksrt-tokens'],
|
||||
file_exists(get_template_directory() . '/assets/css/routes/auth.css')
|
||||
? (string) filemtime(get_template_directory() . '/assets/css/routes/auth.css')
|
||||
: $version
|
||||
);
|
||||
|
||||
$sign_css = get_template_directory() . '/assets/css/routes/sign.css';
|
||||
if (file_exists($sign_css)) {
|
||||
wp_enqueue_style(
|
||||
'aksrt-route-sign',
|
||||
$base_uri . '/assets/css/routes/sign.css',
|
||||
['aksrt-tokens'],
|
||||
(string) filemtime($sign_css)
|
||||
);
|
||||
}
|
||||
// 手机号注册/登录 + SMS 倒计时 + 人机验证联动
|
||||
$sign_js_path = get_template_directory() . '/assets/js/sign-register.js';
|
||||
if (file_exists($sign_js_path)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-sign-register',
|
||||
$base_uri . '/assets/js/sign-register.js',
|
||||
['loader_js'],
|
||||
(string) filemtime($sign_js_path),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -61,9 +61,6 @@ trait ScriptsPart1c {
|
||||
'imgbox_full' => false,
|
||||
'imgbox_play' => false,
|
||||
'imgbox_down' => false,
|
||||
'sign_type' => 'page',
|
||||
'signin_url' => wp_login_url(home_url('/' . ltrim((string) ($GLOBALS['wp']->request ?? ''), '/'))),
|
||||
'signup_url' => wp_registration_url(),
|
||||
'ajax_url' => admin_url('admin-ajax.php'),
|
||||
'ajaxpager' => '',
|
||||
'ajax_trigger' => __('加载更多', 'aksrt'),
|
||||
@@ -86,7 +83,6 @@ trait ScriptsPart1c {
|
||||
'split_minimum_size' => 10,
|
||||
'comment_upload_img' => false,
|
||||
'translate_config' => [],
|
||||
'qrcode_signin' => ['url' => false, 'state' => false],
|
||||
'i18n' => [
|
||||
'load_more' => __('加载更多', 'aksrt'),
|
||||
'processing' => __('处理中...', 'aksrt'),
|
||||
@@ -402,19 +398,17 @@ trait ScriptsPart1c {
|
||||
);
|
||||
}
|
||||
|
||||
// 弹窗登录/注册(仅未登录用户)
|
||||
if (!is_user_logged_in()) {
|
||||
$modal_auth_js = get_template_directory() . '/assets/js/modal-auth.js';
|
||||
if (file_exists($modal_auth_js)) {
|
||||
// One state machine owns modal and /sign/ authentication.
|
||||
$auth_js = get_template_directory() . '/assets/js/auth.js';
|
||||
if (file_exists($auth_js)) {
|
||||
wp_enqueue_script(
|
||||
'aksrt-modal-auth',
|
||||
$base_uri . '/assets/js/modal-auth.js',
|
||||
['loader_js'],
|
||||
(string) filemtime($modal_auth_js),
|
||||
'aksrt-auth',
|
||||
$base_uri . '/assets/js/auth.js',
|
||||
['aksrt-runtime'],
|
||||
(string) filemtime($auth_js),
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($is_fixed_homepage) {
|
||||
$homepage_js_path = get_template_directory() . '/assets/js/homepage-fixed.js';
|
||||
|
||||
+85
-73
@@ -15,41 +15,9 @@ trait AuthReader {
|
||||
return !class_exists(Capabilities::class) || !Capabilities::user_can('comment_add', $user_id);
|
||||
}
|
||||
|
||||
/** 是否允许注册 */
|
||||
/** 是否允许前台注册。主题设置是唯一真值。 */
|
||||
public static function registration_enabled(): bool {
|
||||
return (bool) get_option('users_can_register');
|
||||
}
|
||||
|
||||
/** 注册是否要求邮箱验证 */
|
||||
public static function registration_requires_email_verification(): bool {
|
||||
$s = self::get_all();
|
||||
return self::registration_enabled() && !empty($s['registration_email_verification_enabled']);
|
||||
}
|
||||
|
||||
/** 注册邮箱验证方式 */
|
||||
public static function registration_verification_method(): string {
|
||||
$s = self::get_all();
|
||||
$method = $s['registration_email_verification_method'] ?? 'link';
|
||||
return in_array($method, ['link', 'code'], true) ? $method : 'link';
|
||||
}
|
||||
|
||||
/** 注册邮箱验证有效期(分钟) */
|
||||
public static function registration_verification_ttl_minutes(): int {
|
||||
$s = self::get_all();
|
||||
$ttl = (int) ($s['registration_verification_ttl_minutes'] ?? 30);
|
||||
return max(5, min(1440, $ttl));
|
||||
}
|
||||
|
||||
/** 验证成功后自动登录 */
|
||||
public static function registration_auto_login_after_verify(): bool {
|
||||
$s = self::get_all();
|
||||
return !isset($s['registration_auto_login_after_verify']) || !empty($s['registration_auto_login_after_verify']);
|
||||
}
|
||||
|
||||
/** 注册邮箱验证功能首次启用时间戳(用于历史账号判定) */
|
||||
public static function registration_verification_enabled_at(): int {
|
||||
$s = self::get_all();
|
||||
return (int) ($s['registration_verification_enabled_at'] ?? 0);
|
||||
return !self::close_signup();
|
||||
}
|
||||
|
||||
/** 主题内置默认图片 URL */
|
||||
@@ -57,21 +25,15 @@ trait AuthReader {
|
||||
return get_template_directory_uri() . '/assets/img/' . $filename;
|
||||
}
|
||||
|
||||
/** 获取Logo URL(留空时使用内置默认;legacy别名 logo_src 回退) */
|
||||
/** 获取 Logo URL(留空时使用内置默认) */
|
||||
public static function logo_url(): string {
|
||||
$url = trim((string) self::get_value('site_logo', ''));
|
||||
if ($url === '') {
|
||||
$url = trim((string) get_option('logo_src', ''));
|
||||
}
|
||||
return $url !== '' ? $url : self::default_asset_url('default-logo.svg');
|
||||
}
|
||||
|
||||
/** 获取暗色模式 Logo URL(留空时跟随浅色模式;legacy别名 logo_src_dark 回退) */
|
||||
/** 获取暗色模式 Logo URL(留空时跟随浅色模式) */
|
||||
public static function logo_dark_url(): string {
|
||||
$url = trim((string) self::get_value('site_logo_dark', ''));
|
||||
if ($url === '') {
|
||||
$url = trim((string) get_option('logo_src_dark', ''));
|
||||
}
|
||||
return $url !== '' ? $url : self::logo_url();
|
||||
}
|
||||
|
||||
@@ -88,10 +50,10 @@ trait AuthReader {
|
||||
return (bool) self::get_value('close_sign', false);
|
||||
}
|
||||
|
||||
/** 是否关闭注册(受 close_sign 总开关影响;兼容 user_close_signup 别名) */
|
||||
/** 是否关闭注册。禁用账密登录时同时关闭账密注册。 */
|
||||
public static function close_signup(): bool {
|
||||
if (self::close_sign()) return true;
|
||||
return (bool) self::get_value('user_close_signup', false);
|
||||
return (bool) self::get_value('close_signup', false) || self::close_signin();
|
||||
}
|
||||
|
||||
/** 是否关闭账密登录(仅允许社交登录) */
|
||||
@@ -100,16 +62,10 @@ trait AuthReader {
|
||||
return (bool) self::get_value('close_signin', false);
|
||||
}
|
||||
|
||||
/** 是否关闭登录(受 close_sign 总开关影响;兼容 user_close_login 别名) */
|
||||
public static function close_login(): bool {
|
||||
if (self::close_sign()) return true;
|
||||
return (bool) self::get_value('user_close_login', false);
|
||||
}
|
||||
|
||||
/** 登录注册样式: 'modal' | 'page'(默认 page;modal 运行时未就绪时自动回退 page) */
|
||||
/** 登录注册样式: modal | page。 */
|
||||
public static function user_sign_type(): string {
|
||||
$type = (string) self::get_value('user_sign_type', 'page');
|
||||
if (!in_array($type, ['modal', 'page'], true)) $type = 'page';
|
||||
$type = (string) self::get_value('user_sign_type', 'modal');
|
||||
if (!in_array($type, ['modal', 'page'], true)) $type = 'modal';
|
||||
return $type;
|
||||
}
|
||||
|
||||
@@ -118,16 +74,24 @@ trait AuthReader {
|
||||
return trim((string) self::get_value('admin_sign_key', ''));
|
||||
}
|
||||
|
||||
/** Whether wp-login.php is replaced by /sign/. */
|
||||
public static function replace_wp_login(): bool {
|
||||
return (bool) self::get_value('replace_wp_login', true);
|
||||
}
|
||||
|
||||
/** 登录页 UI 配置数组 */
|
||||
public static function sign_page_ui(): array {
|
||||
$theme_mode = (string) self::get_value('sign_theme_mode', 'no');
|
||||
if (!in_array($theme_mode, ['no', 'white-theme', 'dark-theme'], true)) {
|
||||
$theme_mode = 'no';
|
||||
}
|
||||
return [
|
||||
'background' => self::get_value('sign_page_background', []),
|
||||
'theme_mode' => in_array((string) self::get_value('sign_theme_mode', 'default'), ['default', 'light', 'dark'], true)
|
||||
? (string) self::get_value('sign_theme_mode', 'default') : 'default',
|
||||
'theme_mode' => $theme_mode,
|
||||
'show_header' => (bool) self::get_value('sign_show_header', true),
|
||||
'card_position' => in_array((string) self::get_value('sign_card_position', 'center'), ['left', 'center', 'right'], true)
|
||||
? (string) self::get_value('sign_card_position', 'center') : 'center',
|
||||
'show_logo' => (bool) self::get_value('sign_show_logo', true),
|
||||
'card_position' => in_array((string) self::get_value('sign_card_position', 'right'), ['left', 'center', 'right'], true)
|
||||
? (string) self::get_value('sign_card_position', 'right') : 'right',
|
||||
'show_logo' => (bool) self::get_value('sign_show_logo', false),
|
||||
'footer' => (string) self::get_value('sign_page_footer', ''),
|
||||
];
|
||||
}
|
||||
@@ -136,7 +100,7 @@ trait AuthReader {
|
||||
public static function modal_ui(): array {
|
||||
return [
|
||||
'background' => self::get_value('sign_modal_background', []),
|
||||
'show_logo' => (bool) self::get_value('sign_modal_show_logo', true),
|
||||
'show_logo' => (bool) self::get_value('sign_modal_show_logo', false),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -165,24 +129,70 @@ trait AuthReader {
|
||||
return (bool) self::get_value('user_signin_phone_s', false);
|
||||
}
|
||||
|
||||
/** 免密登录显示模式 */
|
||||
public static function nopas_active_mode(): string {
|
||||
$mode = (string) self::get_value('user_signin_nopas_active', 'nopas_first');
|
||||
return in_array($mode, ['nopas_first', 'password_first', 'nopas_only'], true) ? $mode : 'nopas_first';
|
||||
/** 是否启用验证码免密登录。 */
|
||||
public static function passwordless_login_enabled(): bool {
|
||||
return (bool) self::get_value('user_signin_nopas_s', false);
|
||||
}
|
||||
|
||||
/** 绑定提醒类型(兼容旧 bool 值:true→'phone',false→'close') */
|
||||
/** 免密登录验证渠道。 */
|
||||
public static function passwordless_login_type(): string {
|
||||
$type = (string) self::get_value('user_signin_nopas_type', 'email');
|
||||
return in_array($type, ['email', 'phone', 'email_phone'], true) ? $type : 'email';
|
||||
}
|
||||
|
||||
/** 免密登录显示模式 */
|
||||
public static function nopas_active_mode(): string {
|
||||
$mode = (string) self::get_value('user_signin_nopas_active', 'nopas');
|
||||
return in_array($mode, ['nopas', 'pas', 'only_nopas'], true) ? $mode : 'nopas';
|
||||
}
|
||||
|
||||
/** 是否在注册前验证邮箱或手机号。 */
|
||||
public static function signup_verification_enabled(): bool {
|
||||
return (bool) self::get_value('user_signup_captch', false);
|
||||
}
|
||||
|
||||
/** 注册验证渠道。 */
|
||||
public static function signup_verification_type(): string {
|
||||
$type = (string) self::get_value('captch_type', 'email');
|
||||
return in_array($type, ['email', 'phone', 'email_phone'], true) ? $type : 'email';
|
||||
}
|
||||
|
||||
/** 找回密码验证渠道。 */
|
||||
public static function reset_password_verification_type(): string {
|
||||
$type = (string) self::get_value('user_repas_captch_type', 'email');
|
||||
return in_array($type, ['email', 'phone', 'email_phone'], true) ? $type : 'email';
|
||||
}
|
||||
|
||||
/** 注册时是否省略重复密码。 */
|
||||
public static function signup_omit_password_confirmation(): bool {
|
||||
return (bool) self::get_value('user_signup_no_repas', true);
|
||||
}
|
||||
|
||||
/** 推广码在注册表单中的唯一显示模式。 */
|
||||
public static function invitation_mode(): string {
|
||||
$mode = (string) self::get_value('invit_code_s', 'close');
|
||||
return in_array($mode, ['close', 'open', 'must'], true) ? $mode : 'close';
|
||||
}
|
||||
|
||||
/** 自动弹窗配置。 */
|
||||
public static function sign_modal_auto(): array {
|
||||
return [
|
||||
'enabled' => self::user_sign_type() === 'modal' && (bool) self::get_value('sign_modal_auto_s', false),
|
||||
'waiting' => max(1, min(100, (int) self::get_value('sign_modal_auto_waiting_time', 5))),
|
||||
'expires' => max(0, min(2000, (float) self::get_value('sign_modal_auto_expires', 1))),
|
||||
];
|
||||
}
|
||||
|
||||
/** 绑定提醒类型 */
|
||||
public static function bind_reminder_type(): string {
|
||||
$val = self::get_value('bind_reminder', 'close');
|
||||
if (is_bool($val)) return $val ? 'phone' : 'close';
|
||||
$val = (string) $val;
|
||||
return in_array($val, ['email', 'phone', 'email_phone', 'close'], true) ? $val : 'close';
|
||||
}
|
||||
|
||||
/** 强制绑定类型(兼容旧 bool 值:true→'phone',false→'close') */
|
||||
/** 强制绑定类型 */
|
||||
public static function mandatory_bind_type(): string {
|
||||
$val = self::get_value('mandatory_bind', 'close');
|
||||
if (is_bool($val)) return $val ? 'phone' : 'close';
|
||||
$val = (string) $val;
|
||||
return in_array($val, ['email', 'phone', 'email_phone', 'close'], true) ? $val : 'close';
|
||||
}
|
||||
@@ -196,10 +206,10 @@ trait AuthReader {
|
||||
return array_values(array_filter(array_map('trim', $items), 'strlen'));
|
||||
}
|
||||
|
||||
/** 邮箱限制配置:['type' => 'black'|'white', 'domains' => [...]] */
|
||||
/** 邮箱限制配置:['type' => 'not'|'allow', 'domains' => [...]] */
|
||||
public static function email_limit(): array {
|
||||
$type = (string) self::get_value('user_email_limit_type', 'black');
|
||||
if (!in_array($type, ['black', 'white'], true)) $type = 'black';
|
||||
$type = (string) self::get_value('user_email_limit_type', 'not');
|
||||
if (!in_array($type, ['not', 'allow'], true)) $type = 'not';
|
||||
$raw = (string) self::get_value('user_email_out', '');
|
||||
$domains = [];
|
||||
if ($raw !== '') {
|
||||
@@ -210,12 +220,14 @@ trait AuthReader {
|
||||
}
|
||||
|
||||
|
||||
/** 用户协议前台配置(enabled / required / page) */
|
||||
/** 用户协议及隐私协议前台配置。 */
|
||||
public static function user_agreement_settings(): array {
|
||||
return [
|
||||
'enabled' => !isset(self::get_all()['user_agreement_s']) || !empty(self::get_value('user_agreement_s', true)),
|
||||
'required' => !isset(self::get_all()['user_agreement_check']) || !empty(self::get_value('user_agreement_check', true)),
|
||||
'enabled' => (bool) self::get_value('user_agreement_s', false),
|
||||
'privacy' => (bool) self::get_value('user_privacy_s', false),
|
||||
'required' => (bool) self::get_value('user_agreement_check', false),
|
||||
'page' => trim((string) self::get_value('user_agreement_page', '')),
|
||||
'privacy_page'=> trim((string) self::get_value('user_privacy_page', '')),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,24 +123,23 @@ class CreditsRuntime {
|
||||
*/
|
||||
public static function get_referral_registration_settings(): array {
|
||||
$all = Settings::get_all();
|
||||
$enabled = !isset($all['referral_registration_enabled']) || !empty($all['referral_registration_enabled']);
|
||||
$register_field = sanitize_key((string) ($all['referral_register_field'] ?? 'optional'));
|
||||
if (!in_array($register_field, ['hidden', 'optional', 'required'], true)) {
|
||||
$register_field = 'optional';
|
||||
$mode = sanitize_key((string) ($all['invit_code_s'] ?? 'close'));
|
||||
if (!in_array($mode, ['close', 'open', 'must'], true)) {
|
||||
$mode = 'close';
|
||||
}
|
||||
$open_desc = trim((string) ($all['referral_open_desc'] ?? ''));
|
||||
$open_desc = trim((string) ($all['invit_code_open_desc'] ?? ''));
|
||||
if ($open_desc === '') {
|
||||
$open_desc = __('推广码为选填,没有推广码也可以注册', 'aksrt');
|
||||
$open_desc = __('如您受到邀请,可填写推广码。部分推广码会有额外积分赠送', 'aksrt');
|
||||
}
|
||||
$required_desc = trim((string) ($all['referral_required_desc'] ?? ''));
|
||||
$required_desc = trim((string) ($all['invit_code_must_desc'] ?? ''));
|
||||
if ($required_desc === '') {
|
||||
$required_desc = __('请输入推广码', 'aksrt');
|
||||
$required_desc = __('<div>本站需使用推广码注册</div><div>获取推广码请与客服联系</div>', 'aksrt');
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'register_field' => $register_field,
|
||||
'reject_invalid' => !isset($all['referral_reject_invalid']) || !empty($all['referral_reject_invalid']),
|
||||
'enabled' => $mode !== 'close',
|
||||
'register_field' => $mode === 'must' ? 'required' : ($mode === 'open' ? 'optional' : 'hidden'),
|
||||
'reject_invalid' => $mode !== 'close',
|
||||
'open_desc' => $open_desc,
|
||||
'required_desc' => $required_desc,
|
||||
];
|
||||
|
||||
@@ -288,7 +288,6 @@ class Integration {
|
||||
'download_ready' => true,
|
||||
'credits_added' => true,
|
||||
'credits_deducted' => true,
|
||||
'verify_email' => true,
|
||||
'pending_comments' => true,
|
||||
'upload_moderation' => true,
|
||||
],
|
||||
|
||||
@@ -174,14 +174,6 @@ class SMS {
|
||||
return ['success' => true, 'message' => __('验证成功', 'aksrt')];
|
||||
}
|
||||
|
||||
/**
|
||||
* 兼容旧接口:verify_code 返回 bool
|
||||
*/
|
||||
public function verify_code_bool(string $phone, string $code): bool {
|
||||
$result = $this->verify_code($phone, $code);
|
||||
return !empty($result['success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成验证码
|
||||
*/
|
||||
|
||||
+1
-1
@@ -387,7 +387,7 @@ class Home {
|
||||
}
|
||||
|
||||
if (!empty($verify_settings['accept_email_verified']) && class_exists('\AKSRT\Auth\Service')) {
|
||||
return \AKSRT\Auth\Service::user_is_email_verified($user_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Bind Account
|
||||
* Template Post Type: page
|
||||
*
|
||||
* Theme auth route template: /bind-account/
|
||||
*
|
||||
* 第三方登录未匹配账号时的绑定页面
|
||||
* 用户输入已有账号密码完成OAuth绑定
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
// ============================================
|
||||
// 处理绑定请求(POST)
|
||||
// ============================================
|
||||
$bind_error = '';
|
||||
$bind_verify_url = '';
|
||||
$oauth_data = null;
|
||||
$provider_name = '';
|
||||
$obk = '';
|
||||
|
||||
if (isset($_GET['obk'])) {
|
||||
$obk = sanitize_text_field($_GET['obk']);
|
||||
$oauth_data = \AKSRT\Auth\Service::get_oauth_bind_context($obk);
|
||||
}
|
||||
|
||||
if (! $oauth_data || ! is_array($oauth_data)) {
|
||||
// 无效或已过期的OAuth数据
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$bind_error = __('绑定会话已过期,请重新使用第三方登录', 'aksrt');
|
||||
}
|
||||
} else {
|
||||
$provider_name = \AKSRT\Auth\Service::get_oauth_provider_name($oauth_data['provider'] ?? '');
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['log'], $_POST['pwd']) && empty($bind_error)) {
|
||||
if ($obk === '' && !empty($_POST['obk'])) {
|
||||
$obk = sanitize_text_field($_POST['obk']);
|
||||
}
|
||||
|
||||
if ($oauth_data === null && $obk !== '') {
|
||||
$oauth_data = \AKSRT\Auth\Service::get_oauth_bind_context($obk);
|
||||
}
|
||||
|
||||
$result = \AKSRT\Auth\Service::bind_oauth_account($_POST, $oauth_data);
|
||||
if (empty($result['success'])) {
|
||||
$bind_error = $result['error'] ?? __('绑定失败,请稍后重试', 'aksrt');
|
||||
$bind_verify_url = $result['verify_url'] ?? '';
|
||||
} else {
|
||||
$user = $result['user'];
|
||||
$provider = $result['provider'];
|
||||
wp_safe_redirect(add_query_arg(['tab' => 'settings', 'oauth_bound' => $provider], aksrt_get_user_center_url()));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
remove_action('wp_footer', 'aksrt_render_footer_tabbar', 20);
|
||||
remove_action('wp_footer', 'aksrt_render_float_buttons', 30);
|
||||
add_filter('body_class', static function ($classes) {
|
||||
$classes[] = 'page-template-user-sign';
|
||||
return $classes;
|
||||
});
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main" tabindex="-1">
|
||||
<div class="auth-shell">
|
||||
<div class="auth-card">
|
||||
|
||||
<?php if (! $oauth_data || ! is_array($oauth_data)) : ?>
|
||||
<!-- 会话过期状态 -->
|
||||
<div class="auth-card__expired">
|
||||
<div class="auth-card__logo sign-logo" aria-hidden="true"><?php if (function_exists('aksrt_get_auth_sign_logo_html')) { echo aksrt_get_auth_sign_logo_html(); } else { echo 'AK'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
||||
<p><?php echo esc_html(__('绑定会话已过期或无效', 'aksrt')); ?></p>
|
||||
<a href="<?php echo esc_url(aksrt_get_login_url()); ?>" class="btn-primary btn-lg auth-card__expired-action">
|
||||
<?php esc_html_e('返回登录', 'aksrt'); ?>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<?php else : ?>
|
||||
<!-- 正常绑定表单 -->
|
||||
|
||||
<div class="auth-card__logo sign-logo" aria-hidden="true"><?php if (function_exists('aksrt_get_auth_sign_logo_html')) { echo aksrt_get_auth_sign_logo_html(); } else { echo 'AK'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
||||
<p class="auth-card__subtitle">
|
||||
<?php printf(esc_html__('通过 %s 登录,请绑定你的已有账号', 'aksrt'), '<strong>' . esc_html($provider_name) . '</strong>'); ?>
|
||||
</p>
|
||||
|
||||
<?php if (!empty($bind_error)) : ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
|
||||
<span>
|
||||
<?php echo esc_html($bind_error); ?>
|
||||
<?php if (!empty($bind_verify_url)) : ?>
|
||||
<a href="<?php echo esc_url($bind_verify_url); ?>"><?php esc_html_e('前往验证邮箱', 'aksrt'); ?></a>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="auth-form" id="aksrt-bind-form" method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_bind', '_bind_nonce'); ?>
|
||||
<input type="hidden" name="obk" value="<?php echo esc_attr($obk); ?>">
|
||||
<div class="form-field">
|
||||
<label for="bind_user_login"><?php esc_html_e('邮箱 / 用户名', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg></span>
|
||||
<input type="text" name="log" id="bind_user_login" autocomplete="username" required>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field form-field--pwd">
|
||||
<label for="bind_user_pass"><?php esc_html_e('密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
|
||||
<input type="password" name="pwd" id="bind_user_pass" autocomplete="current-password" required>
|
||||
<button type="button" class="toggle-pwd" data-target="bind_user_pass" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10 7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('bind'); ?>
|
||||
<?php endif; ?>
|
||||
<button type="submit" name="wp-submit" class="btn-primary btn-lg" style="width:100%;margin-top:8px;">
|
||||
<?php esc_html_e('绑定并登录', 'aksrt'); ?>
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<?php if (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::registration_enabled() : get_option('users_can_register')) :
|
||||
$register_url = aksrt_get_register_url();
|
||||
$register_url = add_query_arg('obk', sanitize_text_field($_GET['obk']), $register_url);
|
||||
?>
|
||||
<p class="auth-footer-link">
|
||||
<?php esc_html_e('还没有账号?', 'aksrt'); ?>
|
||||
<a href="<?php echo esc_url($register_url); ?>"><?php esc_html_e('注册并绑定账号', 'aksrt'); ?></a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php endif; ?><!-- end 正常绑定表单 -->
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Login
|
||||
* Template Post Type: page
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
require AKSRT_DIR . '/templates/pages/page-sign.php';
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Register
|
||||
* Template Post Type: page
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
require AKSRT_DIR . '/templates/pages/page-sign.php';
|
||||
@@ -1,178 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Reset Password
|
||||
* Template Post Type: page
|
||||
*
|
||||
* Theme auth route template: /reset-password/
|
||||
*
|
||||
* 统一处理:
|
||||
* 1. 忘记密码(无 token 参数)— 输入邮箱/用户名请求重置
|
||||
* 2. 设置密码(有 token+uid 参数)— 邮件链接进入后设置新密码
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
// ============================================
|
||||
// 检测当前模式:设置密码 or 请求重置
|
||||
// ============================================
|
||||
$context = \AKSRT\Auth\Service::get_reset_context($_GET);
|
||||
$mode = $context['mode'];
|
||||
$errors = [];
|
||||
$success = '';
|
||||
$token = $context['token'];
|
||||
$user_id = $context['user_id'];
|
||||
|
||||
if (!empty($context['error'])) {
|
||||
$errors[] = $context['error'];
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 处理表单提交
|
||||
// ============================================
|
||||
|
||||
// 模式1:请求重置密码(忘记密码表单提交)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['aksrt_action']) && $_POST['aksrt_action'] === 'request_reset') {
|
||||
$result = \AKSRT\Auth\Service::request_password_reset($_POST);
|
||||
if (!empty($result['success'])) {
|
||||
$success = $result['message'] ?? __('如果该账号存在,重置邮件已发送到注册邮箱', 'aksrt');
|
||||
} else {
|
||||
$errors[] = $result['error'] ?? __('重置请求失败,请稍后重试', 'aksrt');
|
||||
}
|
||||
}
|
||||
|
||||
// 模式2:设置新密码(从邮件链接进入)
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['aksrt_action']) && $_POST['aksrt_action'] === 'set_password') {
|
||||
$result = \AKSRT\Auth\Service::reset_password_with_token($_POST);
|
||||
if (!empty($result['success'])) {
|
||||
$mode = 'success';
|
||||
$success = $result['message'] ?? __('密码已设置成功!', 'aksrt');
|
||||
} else {
|
||||
$errors[] = $result['error'] ?? __('重置密码失败,请稍后重试', 'aksrt');
|
||||
$mode = $result['mode'] ?? 'invalid';
|
||||
}
|
||||
}
|
||||
|
||||
remove_action('wp_footer', 'aksrt_render_footer_tabbar', 20);
|
||||
remove_action('wp_footer', 'aksrt_render_float_buttons', 30);
|
||||
add_filter('body_class', static function ($classes) {
|
||||
$classes[] = 'page-template-user-sign';
|
||||
return $classes;
|
||||
});
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main" tabindex="-1">
|
||||
<div class="auth-shell">
|
||||
<div class="auth-card">
|
||||
<div class="auth-card__logo sign-logo" aria-hidden="true"><?php if (function_exists('aksrt_get_auth_sign_logo_html')) { echo aksrt_get_auth_sign_logo_html(); } else { echo 'AK'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
||||
|
||||
<?php if ($mode === 'success') : ?>
|
||||
<!-- 密码设置成功 -->
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('密码设置成功!', 'aksrt'); ?></p>
|
||||
<div class="alert alert--success" role="alert">
|
||||
<span><?php echo esc_html($success); ?></span>
|
||||
</div>
|
||||
<a href="<?php echo esc_url(aksrt_get_login_url()); ?>" class="btn-primary btn-lg" style="width:100%;display:block;text-align:center;text-decoration:none;margin-top:16px;">
|
||||
<?php esc_html_e('立即登录', 'aksrt'); ?>
|
||||
</a>
|
||||
|
||||
<?php elseif ($mode === 'invalid') : ?>
|
||||
<!-- 链接无效或已过期 -->
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('重置链接无效或已过期', 'aksrt'); ?></p>
|
||||
<?php if (!empty($errors)) : ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
|
||||
<span><?php echo esc_html($errors[0]); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<a href="<?php echo esc_url(aksrt_get_reset_password_url()); ?>" class="btn-primary btn-lg" style="width:100%;display:block;text-align:center;text-decoration:none;margin-top:16px;">
|
||||
<?php esc_html_e('重新申请重置密码', 'aksrt'); ?>
|
||||
</a>
|
||||
|
||||
<?php elseif ($mode === 'reset') : ?>
|
||||
<!-- 设置新密码表单 -->
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('请设置你的新密码', 'aksrt'); ?></p>
|
||||
|
||||
<?php if (!empty($errors)) : ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
|
||||
<span><?php echo esc_html($errors[0]); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="auth-form" id="aksrt-set-password-form" method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_set_password', '_setpw_nonce'); ?>
|
||||
<input type="hidden" name="aksrt_action" value="set_password">
|
||||
<input type="hidden" name="reset_token" value="<?php echo esc_attr($token); ?>">
|
||||
<input type="hidden" name="reset_uid" value="<?php echo esc_attr((string) $user_id); ?>">
|
||||
|
||||
<div class="form-field form-field--pwd">
|
||||
<label for="pass1"><?php esc_html_e('新密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
|
||||
<input type="password" name="pass1" id="pass1" autocomplete="new-password" required minlength="6">
|
||||
<button type="button" class="toggle-pwd" data-target="pass1" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-field form-field--pwd">
|
||||
<label for="pass2"><?php esc_html_e('确认新密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></span>
|
||||
<input type="password" name="pass2" id="pass2" autocomplete="new-password" required minlength="6">
|
||||
<button type="button" class="toggle-pwd" data-target="pass2" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:16px;height:16px;"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/></svg></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn-primary btn-lg btn-block"><?php esc_html_e('设置新密码', 'aksrt'); ?></button>
|
||||
</form>
|
||||
|
||||
<?php else : ?>
|
||||
<!-- 请求重置密码表单(忘记密码) -->
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('输入邮箱或用户名,我们将发送重置链接', 'aksrt'); ?></p>
|
||||
|
||||
<?php if (!empty($errors)) : ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
|
||||
<span><?php echo esc_html($errors[0]); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($success)) : ?>
|
||||
<div class="alert alert--success" role="alert">
|
||||
<span><?php echo esc_html($success); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<form class="auth-form" id="aksrt-request-reset-form" method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_reset_password', '_reset_nonce'); ?>
|
||||
<input type="hidden" name="aksrt_action" value="request_reset">
|
||||
|
||||
<div class="form-field">
|
||||
<label for="user_login"><?php esc_html_e('邮箱 / 用户名', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg></span>
|
||||
<input type="text" name="user_login" id="user_login" autocomplete="username" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php aksrt_render_captcha('lostpassword'); ?>
|
||||
|
||||
<button type="submit" class="btn-primary btn-lg btn-block"><?php esc_html_e('发送重置链接', 'aksrt'); ?></button>
|
||||
</form>
|
||||
|
||||
<p class="auth-footer-link">
|
||||
<?php esc_html_e('想起密码了?', 'aksrt'); ?>
|
||||
<a href="<?php echo esc_url(aksrt_get_login_url()); ?>"><?php esc_html_e('返回登录', 'aksrt'); ?></a>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
+42
-298
@@ -1,328 +1,72 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - 登录/注册
|
||||
* Template Name: AKSRT - 登录注册
|
||||
* Template Post Type: page
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
@session_start();
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$route_tab = function_exists('aksrt_get_auth_tab') ? aksrt_get_auth_tab() : 'login';
|
||||
$requested_tab = isset($_GET['tab']) ? sanitize_text_field(wp_unslash((string) $_GET['tab'])) : '';
|
||||
$tab_aliases = [
|
||||
'login' => 'signin',
|
||||
'signin' => 'signin',
|
||||
'register' => 'signup',
|
||||
'signup' => 'signup',
|
||||
];
|
||||
$tab_source = $requested_tab !== '' ? $requested_tab : $route_tab;
|
||||
$tab = $tab_aliases[$tab_source] ?? 'signin';
|
||||
$redirect_to = !empty($_GET['redirect_to']) ? esc_url_raw(wp_unslash((string) $_GET['redirect_to'])) : home_url();
|
||||
$registration_open = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::registration_enabled();
|
||||
if (class_exists('\AKSRT\Settings\Settings') && method_exists('\AKSRT\Settings\Settings', 'close_signup') && \AKSRT\Settings\Settings::close_signup()) {
|
||||
$registration_open = false;
|
||||
$allowed_tabs = ['signin', 'signup', 'resetpassword', 'bind', 'oauth'];
|
||||
$tab = isset($_GET['tab']) ? sanitize_key(wp_unslash((string) $_GET['tab'])) : 'signin';
|
||||
$tab = in_array($tab, $allowed_tabs, true) ? $tab : 'signin';
|
||||
$wechat = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_weixingzh_settings() : [];
|
||||
if (!isset($_GET['tab']) && !empty($wechat['priority']) && class_exists('\AKSRT\Auth\WeixinGzh') && \AKSRT\Auth\WeixinGzh::is_enabled()) {
|
||||
$tab = 'oauth';
|
||||
}
|
||||
|
||||
$titles = [
|
||||
'signin' => __('用户登录', 'aksrt'),
|
||||
'signup' => __('新用户注册', 'aksrt'),
|
||||
];
|
||||
$page_title = ($titles[$tab] ?? __('登录 / 注册', 'aksrt')) . ' - ' . get_bloginfo('name');
|
||||
|
||||
$interim = isset($_REQUEST['interim-login']);
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::close_sign() && !is_user_logged_in() && !$interim) {
|
||||
wp_safe_redirect(home_url('/'), 302);
|
||||
exit;
|
||||
}
|
||||
|
||||
$close_signin = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::close_signin() : false;
|
||||
if ($close_signin && $tab === 'signin') {
|
||||
$_SERVER['REQUEST_METHOD'] = 'GET';
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
if ($user_id) {
|
||||
if ($interim && is_super_admin()) {
|
||||
echo '<!DOCTYPE HTML><html><body class="login interim-login-success"><div style="padding:60px 0;text-align:center;color:#1a88fd;">' . esc_html__('登录成功', 'aksrt') . '</div></body></html>';
|
||||
exit;
|
||||
}
|
||||
$redirect_to = !empty($_GET['redirect_to']) ? esc_url_raw(wp_unslash((string) $_GET['redirect_to'])) : home_url('/');
|
||||
$obk = !empty($_GET['obk']) ? sanitize_text_field(wp_unslash((string) $_GET['obk'])) : '';
|
||||
if (is_user_logged_in() && $tab !== 'bind' && $tab !== 'oauth') {
|
||||
wp_safe_redirect($redirect_to);
|
||||
exit;
|
||||
}
|
||||
|
||||
if ($tab === 'signup' && !$registration_open) {
|
||||
if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::close_sign() && !$interim) {
|
||||
wp_safe_redirect(home_url('/'));
|
||||
exit;
|
||||
}
|
||||
if ($tab === 'signup' && class_exists('\AKSRT\Settings\Settings') && !\AKSRT\Settings\Settings::registration_enabled()) {
|
||||
$tab = 'signin';
|
||||
}
|
||||
|
||||
remove_action('wp_footer', 'aksrt_render_footer_tabbar', 20);
|
||||
remove_action('wp_footer', 'aksrt_render_float_buttons', 30);
|
||||
|
||||
$background_html = '';
|
||||
$sign_ui = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::sign_page_ui() : [];
|
||||
$card_position = $sign_ui['card_position'] ?? 'center';
|
||||
$sign_show_logo = $sign_ui['show_logo'] ?? true;
|
||||
$sign_show_header = !empty($sign_ui['show_header']);
|
||||
$sign_theme_mode = $sign_ui['theme_mode'] ?? 'default';
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$bg_ids = $sign_ui['background'] ?? [];
|
||||
if (is_array($bg_ids) && !empty($bg_ids)) {
|
||||
$bg_url = wp_get_attachment_url((int) $bg_ids[array_rand($bg_ids)]);
|
||||
if ($bg_url) {
|
||||
$background_html = '<div class="sign-bg" style="background-image:url(' . esc_url($bg_url) . ');"></div>';
|
||||
$ui = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::sign_page_ui() : [];
|
||||
$images = $ui['background'] ?? [];
|
||||
if (is_string($images)) {
|
||||
$images = array_filter(array_map('absint', explode(',', $images)));
|
||||
}
|
||||
} elseif (is_string($bg_ids) && $bg_ids !== '') {
|
||||
$ids = array_filter(array_map('absint', explode(',', $bg_ids)));
|
||||
if (!empty($ids)) {
|
||||
$bg_url = wp_get_attachment_url($ids[array_rand($ids)]);
|
||||
if ($bg_url) {
|
||||
$background_html = '<div class="sign-bg" style="background-image:url(' . esc_url($bg_url) . ');"></div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$login_error = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['log'], $_POST['pwd']) && $tab === 'signin') {
|
||||
if (class_exists('\AKSRT\Auth\Service')) {
|
||||
$result = \AKSRT\Auth\Service::login_user($_POST, false);
|
||||
if (empty($result['success'])) {
|
||||
$login_error = $result['error'] ?? __('登录失败', 'aksrt');
|
||||
} else {
|
||||
wp_safe_redirect($result['redirect'] ?? home_url('/'));
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$register_error = '';
|
||||
$register_success = '';
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['user_login'], $_POST['user_email']) && $tab === 'signup') {
|
||||
if (class_exists('\AKSRT\Auth\Service')) {
|
||||
$result = \AKSRT\Auth\Service::register_user($_POST, ['oauth_bind_data' => null, 'obk' => '']);
|
||||
if (!empty($result['success'])) {
|
||||
$ref_notice = trim((string) ($result['notice'] ?? ''));
|
||||
if (!empty($result['verification_required'])) {
|
||||
$register_success = sprintf(
|
||||
'%s <a href="%s">%s</a>',
|
||||
esc_html($result['message'] ?? __('注册成功,请完成邮箱验证。', 'aksrt')),
|
||||
esc_url($result['verify_url'] ?? ''),
|
||||
esc_html__('前往验证', 'aksrt')
|
||||
);
|
||||
} else {
|
||||
$register_success = sprintf(
|
||||
__('注册成功,请前往<a href="%s">登录页</a>继续。', 'aksrt'),
|
||||
esc_url(aksrt_get_login_url())
|
||||
);
|
||||
}
|
||||
if ($ref_notice !== '') {
|
||||
$register_success .= ' ' . esc_html($ref_notice);
|
||||
}
|
||||
} else {
|
||||
$register_error = $result['error'] ?? __('注册失败', 'aksrt');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$ref_prefill = '';
|
||||
if (!empty($_COOKIE['aksrt_referral'])) {
|
||||
$ref_prefill = sanitize_key(wp_unslash($_COOKIE['aksrt_referral']));
|
||||
} elseif (!empty($_GET['ref'])) {
|
||||
$ref_prefill = sanitize_key(wp_unslash($_GET['ref']));
|
||||
}
|
||||
|
||||
$agreement = function_exists('aksrt_get_auth_agreement_view')
|
||||
? aksrt_get_auth_agreement_view()
|
||||
: ['enabled' => true, 'required' => true, 'label_html' => esc_html__('我已阅读并同意用户协议', 'aksrt'), 'field_name' => 'agree_terms'];
|
||||
|
||||
$sign_css_uri = get_template_directory_uri() . '/assets/css/routes/sign.css';
|
||||
$sign_css_ver = file_exists(get_template_directory() . '/assets/css/routes/sign.css')
|
||||
? (string) filemtime(get_template_directory() . '/assets/css/routes/sign.css')
|
||||
: AKSRT_VERSION;
|
||||
$background_url = is_array($images) && $images ? (string) wp_get_attachment_url((int) $images[array_rand($images)]) : '';
|
||||
$theme_mode = (string) ($ui['theme_mode'] ?? 'no');
|
||||
$position = (string) ($ui['card_position'] ?? 'right');
|
||||
$aksrt_auth_context = [
|
||||
'surface' => 'page',
|
||||
'tab' => $tab,
|
||||
'show_logo' => !empty($ui['show_logo']),
|
||||
'redirect_to' => $redirect_to,
|
||||
'obk' => $obk,
|
||||
];
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="<?php echo esc_attr(str_replace('_', '-', get_bloginfo('language'))); ?>">
|
||||
<!doctype html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo('charset'); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=0">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<title><?php echo esc_html($page_title); ?></title>
|
||||
<title><?php echo esc_html__('登录 / 注册', 'aksrt') . ' - ' . esc_html(get_bloginfo('name')); ?></title>
|
||||
<?php wp_head(); ?>
|
||||
<link rel="stylesheet" href="<?php echo esc_url($sign_css_uri); ?>?ver=<?php echo esc_attr($sign_css_ver); ?>">
|
||||
</head>
|
||||
<body <?php body_class('page-template-user-sign' . ($sign_theme_mode !== 'default' ? ' sign-theme-' . esc_attr($sign_theme_mode) : '')); ?>>
|
||||
<?php echo $background_html; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
|
||||
<?php if ($sign_show_header && !$interim) : ?>
|
||||
<div class="sign-topbar">
|
||||
<a href="<?php echo esc_url(home_url('/')); ?>"><?php echo esc_html(get_bloginfo('name')); ?></a>
|
||||
<a href="<?php echo esc_url(home_url('/')); ?>"><?php esc_html_e('返回首页', 'aksrt'); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<main class="sign-page">
|
||||
<div class="sign-card sign-card--<?php echo esc_attr($card_position); ?>">
|
||||
<?php if ($sign_show_logo) : ?>
|
||||
<div class="sign-logo">
|
||||
<?php echo function_exists('aksrt_get_auth_sign_logo_html') ? aksrt_get_auth_sign_logo_html() : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="sign-tabs">
|
||||
<button type="button" class="sign-tab<?php echo $tab === 'signin' ? ' is-active' : ''; ?>" data-sign-tab="signin"><?php esc_html_e('登录', 'aksrt'); ?></button>
|
||||
<?php if ($registration_open) : ?>
|
||||
<button type="button" class="sign-tab<?php echo $tab === 'signup' ? ' is-active' : ''; ?>" data-sign-tab="signup"><?php esc_html_e('注册', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="sign-panel<?php echo $tab === 'signin' ? ' is-active' : ''; ?>" data-sign-panel="signin">
|
||||
<?php if ($login_error) : ?><div class="sign-error"><?php echo esc_html($login_error); ?></div><?php endif; ?>
|
||||
<?php if ($close_signin) : ?>
|
||||
<div class="sign-notice">
|
||||
<?php esc_html_e('当前站点仅支持第三方账号登录,请使用下方社交登录方式。', 'aksrt'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<form class="sign-form" id="sign-login-form" method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_login', '_login_nonce'); ?>
|
||||
<div class="form-field">
|
||||
<label for="sign-user-login"><?php esc_html_e('邮箱 / 用户名', 'aksrt'); ?></label>
|
||||
<div class="input-wrap"><input type="text" name="log" id="sign-user-login" autocomplete="username" required placeholder="<?php esc_attr_e('请输入邮箱或用户名', 'aksrt'); ?>"></div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="sign-user-pass"><?php esc_html_e('密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<input type="password" name="pwd" id="sign-user-pass" autocomplete="current-password" required>
|
||||
<button type="button" class="toggle-pwd" data-target="sign-user-pass"><i class="fa fas fa-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row-between">
|
||||
<label><input type="checkbox" name="rememberme" value="forever"> <?php esc_html_e('记住我', 'aksrt'); ?></label>
|
||||
<a href="<?php echo esc_url(aksrt_get_reset_password_url()); ?>"><?php esc_html_e('忘记密码?', 'aksrt'); ?></a>
|
||||
</div>
|
||||
<?php if (function_exists('aksrt_render_captcha')) aksrt_render_captcha('login'); ?>
|
||||
<input type="hidden" name="redirect_to" value="<?php echo esc_url($redirect_to); ?>">
|
||||
<button type="submit" class="btn-submit"><?php esc_html_e('登录', 'aksrt'); ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
<?php if (class_exists('\AKSRT\Auth\OAuth')) : ?>
|
||||
<?php $social = \AKSRT\Auth\OAuth::get_instance()->render_social_buttons(); ?>
|
||||
<?php if ($social) : ?>
|
||||
<div class="sign-oauth">
|
||||
<div class="sign-oauth__divider"><span><?php esc_html_e('其他登录方式', 'aksrt'); ?></span></div>
|
||||
<?php echo $social; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
<?php if ($registration_open) : ?>
|
||||
<div class="sign-footer"><?php esc_html_e('还没有账号?', 'aksrt'); ?> <a href="#" data-sign-switch="signup"><?php esc_html_e('立即注册', 'aksrt'); ?></a></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($registration_open) : ?>
|
||||
<div class="sign-panel<?php echo $tab === 'signup' ? ' is-active' : ''; ?>" data-sign-panel="signup">
|
||||
<?php if ($register_error) : ?><div class="sign-error"><?php echo esc_html($register_error); ?></div><?php endif; ?>
|
||||
<?php if ($register_success) : ?><div class="sign-success"><?php echo wp_kses($register_success, ['a' => ['href' => []]]); ?></div><?php endif; ?>
|
||||
<form class="sign-form" id="sign-register-form" method="post" action="" novalidate>
|
||||
<?php wp_nonce_field('aksrt_register', '_register_nonce'); ?>
|
||||
<div class="form-field">
|
||||
<label for="sign-reg-login"><?php esc_html_e('用户名', 'aksrt'); ?></label>
|
||||
<div class="input-wrap"><input type="text" name="user_login" id="sign-reg-login" autocomplete="username" required placeholder="<?php esc_attr_e('请输入用户名', 'aksrt'); ?>"></div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="sign-reg-email"><?php esc_html_e('邮箱', 'aksrt'); ?></label>
|
||||
<div class="input-wrap"><input type="email" name="user_email" id="sign-reg-email" autocomplete="email" required placeholder="<?php esc_attr_e('请输入邮箱', 'aksrt'); ?>"></div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="sign-reg-pass"><?php esc_html_e('登录密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<input type="password" name="user_pass" id="sign-reg-pass" autocomplete="new-password" required minlength="6">
|
||||
<button type="button" class="toggle-pwd" data-target="sign-reg-pass"><i class="fa fas fa-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label for="sign-reg-pass2"><?php esc_html_e('确认密码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<input type="password" name="user_pass_confirm" id="sign-reg-pass2" autocomplete="new-password" required minlength="6">
|
||||
<button type="button" class="toggle-pwd" data-target="sign-reg-pass2"><i class="fa fas fa-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
$ref_cfg = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_referral_registration_settings() : null;
|
||||
if ($ref_cfg && !empty($ref_cfg['enabled']) && ($ref_cfg['register_field'] ?? 'hidden') !== 'hidden') {
|
||||
$mode = (string) $ref_cfg['register_field'];
|
||||
$ref_hint = $mode === 'required'
|
||||
? (string) ($ref_cfg['required_desc'] ?? '')
|
||||
: (string) ($ref_cfg['open_desc'] ?? '');
|
||||
?>
|
||||
<div class="form-field">
|
||||
<label for="sign-ref"><?php esc_html_e('推广码', 'aksrt'); ?><?php echo $mode === 'optional' ? ' <small style="color:var(--muted-color);">(' . esc_html__('选填', 'aksrt') . ')</small>' : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></label>
|
||||
<div class="input-wrap"><input type="text" name="ref" id="sign-ref" maxlength="12" autocomplete="off" value="<?php echo esc_attr($ref_prefill); ?>" <?php echo $mode === 'required' ? 'required' : ''; ?>></div>
|
||||
<?php if ($ref_hint !== '') : ?>
|
||||
<p class="sign-hint"><?php echo wp_kses_post($ref_hint); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<?php if (!empty($agreement['enabled'])) : ?>
|
||||
<label class="sign-terms">
|
||||
<input type="checkbox" name="<?php echo esc_attr($agreement['field_name']); ?>" value="1" <?php echo !empty($agreement['required']) ? 'required' : ''; ?>>
|
||||
<span><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
<?php if (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::registration_requires_email_verification()) : ?>
|
||||
<p class="sign-hint"><?php esc_html_e('注册后需要完成邮箱验证。', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (function_exists('aksrt_render_captcha')) aksrt_render_captcha('register'); ?>
|
||||
<button type="submit" class="btn-submit"><?php esc_html_e('注册', 'aksrt'); ?></button>
|
||||
</form>
|
||||
<div class="sign-footer"><?php esc_html_e('已有账号?', 'aksrt'); ?> <a href="#" data-sign-switch="signin"><?php esc_html_e('立即登录', 'aksrt'); ?></a></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<body <?php body_class('page-template-user-sign ' . ($theme_mode !== 'no' ? $theme_mode : '')); ?>>
|
||||
<?php wp_body_open(); ?>
|
||||
<div class="sign-page-background"<?php echo $background_url !== '' ? ' style="background-image:url(\'' . esc_url($background_url) . '\')"' : ''; ?>></div>
|
||||
<?php if (!empty($ui['show_header']) && !$interim) : ?><header class="sign-topbar"><a href="<?php echo esc_url(home_url('/')); ?>"><?php echo esc_html(get_bloginfo('name')); ?></a><a href="<?php echo esc_url(home_url('/')); ?>"><?php esc_html_e('返回首页', 'aksrt'); ?></a></header><?php endif; ?>
|
||||
<main class="sign-page sign-card-position-<?php echo esc_attr($position); ?>">
|
||||
<div class="sign-page-card">
|
||||
<?php require get_template_directory() . '/templates/parts/auth-content.php'; ?>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
(function(){
|
||||
document.querySelectorAll('[data-sign-tab]').forEach(function(tab){
|
||||
tab.addEventListener('click',function(){
|
||||
var target=this.getAttribute('data-sign-tab');
|
||||
this.parentElement.querySelectorAll('.sign-tab').forEach(function(t){t.classList.remove('is-active');});
|
||||
this.classList.add('is-active');
|
||||
var card=this.closest('.sign-card');
|
||||
card.querySelectorAll('.sign-panel[data-sign-panel]').forEach(function(p){p.classList.remove('is-active');});
|
||||
var panel=card.querySelector('[data-sign-panel="'+target+'"]');
|
||||
if(panel)panel.classList.add('is-active');
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('[data-sign-switch]').forEach(function(link){
|
||||
link.addEventListener('click',function(e){
|
||||
e.preventDefault();
|
||||
var target=this.getAttribute('data-sign-switch');
|
||||
var btn=document.querySelector('[data-sign-tab="'+target+'"]');
|
||||
if(btn)btn.click();
|
||||
});
|
||||
});
|
||||
document.querySelectorAll('.toggle-pwd').forEach(function(btn){
|
||||
btn.addEventListener('click',function(){
|
||||
var input=document.getElementById(this.getAttribute('data-target'));
|
||||
if(!input)return;
|
||||
var isPw=input.type==='password';
|
||||
input.type=isPw?'text':'password';
|
||||
this.querySelector('i').className=isPw?'fa fas fa-eye-slash':'fa fas fa-eye';
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
|
||||
<?php if (!$interim) : ?>
|
||||
<div class="sign-footer-bar">
|
||||
<?php
|
||||
$footer_text = $sign_ui['footer'] ?? '';
|
||||
echo $footer_text ? wp_kses_post($footer_text) : '© ' . date('Y') . ' ' . esc_html(get_bloginfo('name'));
|
||||
?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!$interim) : ?><footer class="sign-page-footer"><?php echo !empty($ui['footer']) ? wp_kses_post((string) $ui['footer']) : '© ' . esc_html(wp_date('Y')) . ' ' . esc_html(get_bloginfo('name')); ?></footer><?php endif; ?>
|
||||
<?php wp_footer(); ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Template Name: AKSRT - Verify Email
|
||||
* Template Post Type: page
|
||||
*
|
||||
* Theme auth route template: /verify-email/
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
$verify_error = '';
|
||||
$verify_success = '';
|
||||
$redirect_url = '';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
if (isset($_POST['verify_email_resend'])) {
|
||||
$result = \AKSRT\Auth\Service::resend_email_verification($_POST);
|
||||
if (!empty($result['success'])) {
|
||||
$verify_success = $result['message'] ?? __('验证邮件已重新发送,请注意查收。', 'aksrt');
|
||||
} else {
|
||||
$verify_error = $result['error'] ?? __('验证邮件发送失败,请稍后重试。', 'aksrt');
|
||||
}
|
||||
} elseif (isset($_POST['verify_email_submit'])) {
|
||||
$result = \AKSRT\Auth\Service::verify_email_code_submission($_POST);
|
||||
if (!empty($result['success'])) {
|
||||
$verify_success = $result['message'] ?? __('邮箱验证成功,即将进入你的账号。', 'aksrt');
|
||||
$redirect_url = $result['redirect'] ?? '';
|
||||
} else {
|
||||
$verify_error = $result['error'] ?? __('邮箱验证码错误或已过期。', 'aksrt');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$verify_context = \AKSRT\Auth\Service::get_email_verification_context($_GET);
|
||||
if ($verify_error === '' && !empty($verify_context['error'])) {
|
||||
$verify_error = $verify_context['error'];
|
||||
}
|
||||
if ($verify_success === '' && !empty($verify_context['message'])) {
|
||||
$verify_success = $verify_context['message'];
|
||||
}
|
||||
if ($redirect_url === '' && !empty($verify_context['redirect'])) {
|
||||
$redirect_url = $verify_context['redirect'];
|
||||
}
|
||||
|
||||
$verify_user = $verify_context['user'] ?? null;
|
||||
$verify_uid = (int) ($verify_context['uid'] ?? 0);
|
||||
$verify_email = (string) ($verify_context['email'] ?? '');
|
||||
$verify_mode = (string) ($verify_context['mode'] ?? 'invalid');
|
||||
$verify_method= (string) ($verify_context['method'] ?? '');
|
||||
$verify_vk = (string) ($verify_context['access_key'] ?? '');
|
||||
|
||||
remove_action('wp_footer', 'aksrt_render_footer_tabbar', 20);
|
||||
remove_action('wp_footer', 'aksrt_render_float_buttons', 30);
|
||||
add_filter('body_class', static function ($classes) {
|
||||
$classes[] = 'page-template-user-sign';
|
||||
return $classes;
|
||||
});
|
||||
get_header();
|
||||
?>
|
||||
|
||||
<main id="main-content" class="site-main sign-page" tabindex="-1">
|
||||
<div class="auth-shell">
|
||||
<div class="auth-card sign-card sign-card--center">
|
||||
<div class="auth-card__logo sign-logo" aria-hidden="true"><?php if (function_exists('aksrt_get_auth_sign_logo_html')) { echo aksrt_get_auth_sign_logo_html(); } else { echo 'AK'; } // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<h1 class="auth-card__title"><?php bloginfo('name'); ?></h1>
|
||||
|
||||
<?php if ($verify_mode === 'completed' || $verify_mode === 'verified') : ?>
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('邮箱验证成功', 'aksrt'); ?></p>
|
||||
<?php elseif ($verify_mode === 'code') : ?>
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('请输入邮箱验证码完成验证', 'aksrt'); ?></p>
|
||||
<?php elseif ($verify_mode === 'pending') : ?>
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('验证邮件已经发出,请点击邮箱中的链接完成验证', 'aksrt'); ?></p>
|
||||
<?php else : ?>
|
||||
<p class="auth-card__subtitle"><?php esc_html_e('邮箱验证状态异常,请重新检查链接或重新发送验证邮件', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($verify_error !== '') : ?>
|
||||
<div class="alert alert--error" role="alert">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="10"/><line x1="12" x2="12" y1="8" y2="12"/><line x1="12" x2="12.01" y1="16" y2="16"/></svg>
|
||||
<span><?php echo esc_html($verify_error); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($verify_success !== '') : ?>
|
||||
<div class="alert alert--success" role="alert">
|
||||
<span><?php echo esc_html($verify_success); ?></span>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (($verify_mode === 'completed' || $verify_mode === 'verified') && $redirect_url !== '') : ?>
|
||||
<p class="auth-footer-link">
|
||||
<a href="<?php echo esc_url($redirect_url); ?>"><?php esc_html_e('立即进入账号', 'aksrt'); ?></a>
|
||||
</p>
|
||||
<script>
|
||||
setTimeout(function(){ window.location.href = <?php echo wp_json_encode($redirect_url); ?>; }, 1800);
|
||||
</script>
|
||||
<?php elseif ($verify_mode === 'code') : ?>
|
||||
<form class="auth-form" method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_verify_email', '_verify_email_nonce'); ?>
|
||||
<input type="hidden" name="verify_uid" value="<?php echo esc_attr((string) $verify_uid); ?>">
|
||||
<input type="hidden" name="vk" value="<?php echo esc_attr($verify_vk); ?>">
|
||||
<div class="form-field">
|
||||
<label for="verify_email_code"><?php esc_html_e('邮箱验证码', 'aksrt'); ?></label>
|
||||
<div class="input-wrap">
|
||||
<span class="input-icon" aria-hidden="true"><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="width:18px;height:18px;"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg></span>
|
||||
<input type="text" name="email_code" id="verify_email_code" inputmode="numeric" maxlength="6" pattern="[0-9]*" autocomplete="one-time-code" required>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($verify_email !== '') : ?>
|
||||
<p class="auth-card__subtitle" style="margin-top:-8px;"><?php echo esc_html(sprintf(__('验证码已发送到 %s', 'aksrt'), $verify_email)); ?></p>
|
||||
<?php endif; ?>
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('verify_email'); ?>
|
||||
<?php endif; ?>
|
||||
<button type="submit" name="verify_email_submit" class="btn-primary btn-lg btn-block"><?php esc_html_e('验证并登录', 'aksrt'); ?></button>
|
||||
</form>
|
||||
<form method="post" action="" style="margin-top:12px;">
|
||||
<?php wp_nonce_field('aksrt_verify_email', '_verify_email_nonce'); ?>
|
||||
<input type="hidden" name="verify_uid" value="<?php echo esc_attr((string) $verify_uid); ?>">
|
||||
<input type="hidden" name="vk" value="<?php echo esc_attr($verify_vk); ?>">
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('verify_email'); ?>
|
||||
<?php endif; ?>
|
||||
<button type="submit" name="verify_email_resend" class="btn-primary btn-lg" style="width:100%;background:transparent;color:var(--color-primary);border:1px solid var(--color-primary);"><?php esc_html_e('重新发送验证码', 'aksrt'); ?></button>
|
||||
</form>
|
||||
<?php elseif ($verify_mode === 'pending' && $verify_uid > 0 && $verify_vk !== '') : ?>
|
||||
<?php
|
||||
// method 缺失(历史账号 / meta 被外部清理)→ 回退到设置默认值
|
||||
$resend_method = in_array($verify_method, ['link', 'code'], true)
|
||||
? $verify_method
|
||||
: (class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::registration_verification_method() : 'link');
|
||||
?>
|
||||
<?php if ($verify_email !== '') : ?>
|
||||
<p class="auth-card__subtitle"><?php echo esc_html(sprintf(__('当前待验证邮箱:%s', 'aksrt'), $verify_email)); ?></p>
|
||||
<?php endif; ?>
|
||||
<form method="post" action="">
|
||||
<?php wp_nonce_field('aksrt_verify_email', '_verify_email_nonce'); ?>
|
||||
<input type="hidden" name="verify_uid" value="<?php echo esc_attr((string) $verify_uid); ?>">
|
||||
<input type="hidden" name="vk" value="<?php echo esc_attr($verify_vk); ?>">
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('verify_email'); ?>
|
||||
<?php endif; ?>
|
||||
<button type="submit" name="verify_email_resend" class="btn-primary btn-lg" style="width:100%;"><?php echo esc_html($resend_method === 'code' ? __('重新发送验证码', 'aksrt') : __('重新发送验证邮件', 'aksrt')); ?></button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="auth-footer-link">
|
||||
<a href="<?php echo esc_url(aksrt_get_login_url()); ?>"><?php esc_html_e('返回登录', 'aksrt'); ?></a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<?php
|
||||
get_footer();
|
||||
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Shared authentication renderer for the modal and /sign/ page.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
$aksrt_auth_context = isset($aksrt_auth_context) && is_array($aksrt_auth_context) ? $aksrt_auth_context : [];
|
||||
$auth_tab = sanitize_key((string) ($aksrt_auth_context['tab'] ?? 'signin'));
|
||||
$allowed_tabs = ['signin', 'signup', 'resetpassword', 'bind', 'oauth'];
|
||||
$auth_tab = in_array($auth_tab, $allowed_tabs, true) ? $auth_tab : 'signin';
|
||||
$is_modal = ($aksrt_auth_context['surface'] ?? '') === 'modal';
|
||||
$settings = class_exists('\AKSRT\Settings\Settings') ? '\AKSRT\Settings\Settings' : null;
|
||||
$obk = sanitize_text_field((string) ($aksrt_auth_context['obk'] ?? ''));
|
||||
$oauth_bind_data = $obk !== '' && class_exists('\AKSRT\Auth\Service') ? \AKSRT\Auth\Service::get_oauth_bind_context($obk) : null;
|
||||
$is_oauth_bind = is_array($oauth_bind_data);
|
||||
$registration_open = ($settings ? $settings::registration_enabled() : (bool) get_option('users_can_register')) || $is_oauth_bind;
|
||||
$close_signin = $settings ? $settings::close_signin() : false;
|
||||
$signin_phone = $settings ? $settings::signin_phone_enabled() : false;
|
||||
$passwordless = $settings ? $settings::passwordless_login_enabled() : false;
|
||||
$passwordless_type = $settings ? $settings::passwordless_login_type() : 'email';
|
||||
$passwordless_mode = $settings ? $settings::nopas_active_mode() : 'pas';
|
||||
$signup_verify = $settings ? $settings::signup_verification_enabled() : false;
|
||||
$signup_type = $settings ? $settings::signup_verification_type() : 'email';
|
||||
$signup_type = $signup_verify ? $signup_type : 'email';
|
||||
$reset_type = $settings ? $settings::reset_password_verification_type() : 'email';
|
||||
$omit_confirm = $signup_verify && ($settings ? $settings::signup_omit_password_confirmation() : false);
|
||||
$agreement = function_exists('aksrt_get_auth_agreement_view') ? aksrt_get_auth_agreement_view() : ['enabled' => false, 'required' => false, 'label_html' => '', 'field_name' => 'agree_terms'];
|
||||
$referral = $settings ? $settings::get_referral_registration_settings() : ['enabled' => false, 'register_field' => 'hidden'];
|
||||
$ref_mode = !empty($referral['enabled']) ? (string) ($referral['register_field'] ?? 'hidden') : 'hidden';
|
||||
$ref_hint = $ref_mode === 'required' ? (string) ($referral['required_desc'] ?? '') : (string) ($referral['open_desc'] ?? '');
|
||||
$ref_value = !empty($_GET['ref']) ? sanitize_key(wp_unslash((string) $_GET['ref'])) : (!empty($_COOKIE['aksrt_referral']) ? sanitize_key(wp_unslash((string) $_COOKIE['aksrt_referral'])) : '');
|
||||
$oauth_providers = class_exists('\AKSRT\Auth\OAuth') ? \AKSRT\Auth\OAuth::get_instance()->get_auth_providers_data() : [];
|
||||
$wechat_qr = false;
|
||||
foreach ($oauth_providers as $provider) {
|
||||
if (($provider['name'] ?? '') === 'weixingzh') {
|
||||
$wechat_qr = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$show_logo = !empty($aksrt_auth_context['show_logo']);
|
||||
$oauth_large = $settings ? (bool) $settings::get_value('oauth_button_lg', false) : false;
|
||||
$redirect_to = esc_url_raw((string) ($aksrt_auth_context['redirect_to'] ?? home_url('/')));
|
||||
$unique = $is_modal ? 'modal' : 'page';
|
||||
$oauth_user_data = $is_oauth_bind && !empty($oauth_bind_data['user_data']) && is_array($oauth_bind_data['user_data']) ? $oauth_bind_data['user_data'] : [];
|
||||
$oauth_name = sanitize_user((string) ($oauth_user_data['name'] ?? $oauth_user_data['nickname'] ?? $oauth_user_data['login'] ?? ''), true);
|
||||
$oauth_email = sanitize_email((string) ($oauth_user_data['email'] ?? ''));
|
||||
$oauth_provider_name = $is_oauth_bind ? \AKSRT\Auth\Service::get_oauth_provider_name((string) ($oauth_bind_data['provider'] ?? '')) : '';
|
||||
?>
|
||||
<div class="sign aksrt-auth<?php echo $oauth_large ? ' oauth-button-lg' : ''; ?>" data-aksrt-auth data-surface="<?php echo esc_attr($unique); ?>" data-initial-tab="<?php echo esc_attr($auth_tab); ?>" data-passwordless-mode="<?php echo esc_attr($passwordless_mode); ?>">
|
||||
<?php if ($show_logo) : ?>
|
||||
<div class="sign-logo"><?php echo function_exists('aksrt_get_auth_sign_logo_html') ? aksrt_get_auth_sign_logo_html() : ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="sign-panel" data-auth-panel="signin" <?php echo $auth_tab !== 'signin' ? 'hidden' : ''; ?>>
|
||||
<div class="sign-header"><h2><?php esc_html_e('登录', 'aksrt'); ?></h2><?php if ($registration_open) : ?><span><?php esc_html_e('没有账号?', 'aksrt'); ?> <button type="button" data-auth-tab="signup"><?php esc_html_e('立即注册', 'aksrt'); ?></button></span><?php endif; ?></div>
|
||||
<div class="sign-message" data-auth-message hidden></div>
|
||||
<?php if (!$close_signin) : ?>
|
||||
<form class="sign-form" data-auth-form="login">
|
||||
<div class="floating-input"><input name="target" id="<?php echo esc_attr($unique); ?>-login-target" autocomplete="username" required><label for="<?php echo esc_attr($unique); ?>-login-target"><?php echo ($passwordless || $signin_phone) ? esc_html__('邮箱 / 手机号 / 用户名', 'aksrt') : esc_html__('邮箱 / 用户名', 'aksrt'); ?></label></div>
|
||||
<div data-auth-password-fields <?php echo $passwordless && $passwordless_mode !== 'pas' ? 'hidden' : ''; ?>>
|
||||
<div class="floating-input has-action"><input type="password" name="password" id="<?php echo esc_attr($unique); ?>-login-password" autocomplete="current-password"><label for="<?php echo esc_attr($unique); ?>-login-password"><?php esc_html_e('密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show aria-label="<?php esc_attr_e('显示密码', 'aksrt'); ?>"><i class="fa fas fa-eye"></i></button></div>
|
||||
</div>
|
||||
<?php if ($passwordless) : ?>
|
||||
<div data-auth-code-fields <?php echo $passwordless_mode === 'pas' ? 'hidden' : ''; ?>>
|
||||
<div class="floating-input has-action"><input name="code" inputmode="numeric" maxlength="6" id="<?php echo esc_attr($unique); ?>-login-code"><label for="<?php echo esc_attr($unique); ?>-login-code"><?php esc_html_e('验证码', 'aksrt'); ?></label><button type="button" class="send-code" data-send-code="login"><?php esc_html_e('发送验证码', 'aksrt'); ?></button></div>
|
||||
<input type="hidden" name="channel" value="<?php echo esc_attr($passwordless_type === 'phone' ? 'phone' : ''); ?>">
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="sign-options"><label><input type="checkbox" name="rememberme" value="1"> <?php esc_html_e('记住登录', 'aksrt'); ?></label><button type="button" data-auth-tab="resetpassword"><?php esc_html_e('忘记密码?', 'aksrt'); ?></button></div>
|
||||
<?php if (function_exists('aksrt_render_captcha')) { aksrt_render_captcha('login'); } ?>
|
||||
<input type="hidden" name="redirect_to" value="<?php echo esc_attr($redirect_to); ?>">
|
||||
<button type="submit" class="sign-submit jb-blue"><span><?php esc_html_e('登录', 'aksrt'); ?></span><i class="fa fas fa-spinner fa-spin" hidden></i></button>
|
||||
<?php if ($passwordless && $passwordless_mode !== 'only_nopas') : ?><button type="button" class="sign-mode-switch" data-login-mode><?php echo $passwordless_mode === 'pas' ? esc_html__('使用验证码登录', 'aksrt') : esc_html__('使用账号密码登录', 'aksrt'); ?></button><?php endif; ?>
|
||||
</form>
|
||||
<?php else : ?><div class="sign-notice"><?php esc_html_e('当前已关闭账号密码登录,请使用第三方登录。', 'aksrt'); ?></div><?php endif; ?>
|
||||
</section>
|
||||
|
||||
<?php if ($registration_open) : ?>
|
||||
<section class="sign-panel" data-auth-panel="signup" <?php echo $auth_tab !== 'signup' ? 'hidden' : ''; ?>>
|
||||
<div class="sign-header"><h2><?php echo $is_oauth_bind ? esc_html__('注册并绑定', 'aksrt') : esc_html__('注册', 'aksrt'); ?></h2><span><?php esc_html_e('已有账号?', 'aksrt'); ?> <button type="button" data-auth-tab="<?php echo $is_oauth_bind ? 'bind' : 'signin'; ?>"><?php esc_html_e('立即登录', 'aksrt'); ?></button></span></div>
|
||||
<div class="sign-message" data-auth-message hidden></div>
|
||||
<form class="sign-form" data-auth-form="register">
|
||||
<div class="floating-input"><input name="user_login" value="<?php echo esc_attr($oauth_name); ?>" id="<?php echo esc_attr($unique); ?>-register-name" autocomplete="username" required><label for="<?php echo esc_attr($unique); ?>-register-name"><?php esc_html_e('用户名', 'aksrt'); ?></label></div>
|
||||
<div class="floating-input"><input name="target" value="<?php echo esc_attr($oauth_email); ?>" id="<?php echo esc_attr($unique); ?>-register-target" autocomplete="<?php echo $signup_type === 'phone' ? 'tel' : 'email'; ?>" required><label for="<?php echo esc_attr($unique); ?>-register-target"><?php echo $signup_type === 'phone' ? esc_html__('手机号', 'aksrt') : ($signup_type === 'email_phone' ? esc_html__('邮箱 / 手机号', 'aksrt') : esc_html__('邮箱', 'aksrt')); ?></label></div>
|
||||
<?php if ($signup_verify) : ?><div class="floating-input has-action"><input name="code" inputmode="numeric" maxlength="6" id="<?php echo esc_attr($unique); ?>-register-code" required><label for="<?php echo esc_attr($unique); ?>-register-code"><?php esc_html_e('验证码', 'aksrt'); ?></label><button type="button" class="send-code" data-send-code="register"><?php esc_html_e('发送验证码', 'aksrt'); ?></button></div><?php endif; ?>
|
||||
<div class="floating-input has-action"><input type="password" name="password" id="<?php echo esc_attr($unique); ?>-register-password" autocomplete="new-password" minlength="6" required><label for="<?php echo esc_attr($unique); ?>-register-password"><?php esc_html_e('密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show><i class="fa fas fa-eye"></i></button></div>
|
||||
<?php if (!$omit_confirm) : ?><div class="floating-input has-action"><input type="password" name="password_confirm" id="<?php echo esc_attr($unique); ?>-register-confirm" autocomplete="new-password" minlength="6" required><label for="<?php echo esc_attr($unique); ?>-register-confirm"><?php esc_html_e('重复密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show><i class="fa fas fa-eye"></i></button></div><?php endif; ?>
|
||||
<?php if ($ref_mode !== 'hidden') : ?><div class="floating-input"><input name="ref" value="<?php echo esc_attr($ref_value); ?>" id="<?php echo esc_attr($unique); ?>-register-ref" <?php echo $ref_mode === 'required' ? 'required' : ''; ?>><label for="<?php echo esc_attr($unique); ?>-register-ref"><?php echo $ref_mode === 'required' ? esc_html__('推广码', 'aksrt') : esc_html__('推广码(选填)', 'aksrt'); ?></label></div><?php if ($ref_hint !== '') : ?><div class="sign-hint"><?php echo wp_kses_post($ref_hint); ?></div><?php endif; ?><?php endif; ?>
|
||||
<?php if (!empty($agreement['enabled']) && !empty($agreement['required'])) : ?><label class="sign-agreement"><input type="checkbox" name="agree_terms" value="1" required><span><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span></label><?php elseif (!empty($agreement['enabled'])) : ?><div class="sign-agreement sign-agreement-implicit"><?php esc_html_e('注册即表示', 'aksrt'); ?><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><?php endif; ?>
|
||||
<?php if (function_exists('aksrt_render_captcha')) { aksrt_render_captcha('register'); } ?>
|
||||
<input type="hidden" name="channel" value="<?php echo esc_attr($signup_type === 'phone' ? 'phone' : ''); ?>">
|
||||
<input type="hidden" name="user_email" value="">
|
||||
<?php if ($is_oauth_bind) : ?><input type="hidden" name="obk" value="<?php echo esc_attr($obk); ?>"><?php endif; ?>
|
||||
<button type="submit" class="sign-submit jb-green"><span><?php esc_html_e('注册', 'aksrt'); ?></span><i class="fa fas fa-spinner fa-spin" hidden></i></button>
|
||||
</form>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="sign-panel" data-auth-panel="resetpassword" <?php echo $auth_tab !== 'resetpassword' ? 'hidden' : ''; ?>>
|
||||
<div class="sign-header"><h2><?php esc_html_e('找回密码', 'aksrt'); ?></h2><span><button type="button" data-auth-tab="signin"><?php esc_html_e('返回登录', 'aksrt'); ?></button></span></div>
|
||||
<div class="sign-message" data-auth-message hidden></div>
|
||||
<form class="sign-form" data-auth-form="reset-password">
|
||||
<div class="floating-input"><input name="target" id="<?php echo esc_attr($unique); ?>-reset-target" required><label for="<?php echo esc_attr($unique); ?>-reset-target"><?php echo $reset_type === 'phone' ? esc_html__('手机号', 'aksrt') : ($reset_type === 'email_phone' ? esc_html__('邮箱 / 手机号', 'aksrt') : esc_html__('邮箱', 'aksrt')); ?></label></div>
|
||||
<div class="floating-input has-action"><input name="code" inputmode="numeric" maxlength="6" id="<?php echo esc_attr($unique); ?>-reset-code" required><label for="<?php echo esc_attr($unique); ?>-reset-code"><?php esc_html_e('验证码', 'aksrt'); ?></label><button type="button" class="send-code" data-send-code="resetpassword"><?php esc_html_e('发送验证码', 'aksrt'); ?></button></div>
|
||||
<div class="floating-input has-action"><input type="password" name="password" id="<?php echo esc_attr($unique); ?>-reset-password" minlength="6" required><label for="<?php echo esc_attr($unique); ?>-reset-password"><?php esc_html_e('新密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show><i class="fa fas fa-eye"></i></button></div>
|
||||
<div class="floating-input has-action"><input type="password" name="password_confirm" id="<?php echo esc_attr($unique); ?>-reset-confirm" minlength="6" required><label for="<?php echo esc_attr($unique); ?>-reset-confirm"><?php esc_html_e('重复新密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show><i class="fa fas fa-eye"></i></button></div>
|
||||
<?php if (function_exists('aksrt_render_captcha')) { aksrt_render_captcha('lostpassword'); } ?>
|
||||
<input type="hidden" name="channel" value="<?php echo esc_attr($reset_type === 'phone' ? 'phone' : ''); ?>">
|
||||
<button type="submit" class="sign-submit jb-blue"><span><?php esc_html_e('重置密码', 'aksrt'); ?></span><i class="fa fas fa-spinner fa-spin" hidden></i></button>
|
||||
</form>
|
||||
</section>
|
||||
|
||||
<section class="sign-panel" data-auth-panel="oauth" <?php echo $auth_tab !== 'oauth' ? 'hidden' : ''; ?>>
|
||||
<div class="sign-header"><h2><?php esc_html_e('社交登录', 'aksrt'); ?></h2><span><button type="button" data-auth-tab="signin"><?php esc_html_e('返回登录', 'aksrt'); ?></button></span></div>
|
||||
<div class="sign-message" data-auth-message hidden></div>
|
||||
<?php if ($wechat_qr) : ?><div class="auth-qrcode" data-auth-qrcode><i class="fa fab fa-weixin"></i><span><?php esc_html_e('获取二维码中…', 'aksrt'); ?></span></div><?php endif; ?>
|
||||
</section>
|
||||
|
||||
<section class="sign-panel" data-auth-panel="bind" <?php echo $auth_tab !== 'bind' ? 'hidden' : ''; ?>>
|
||||
<div class="sign-header"><h2><?php esc_html_e('绑定账号', 'aksrt'); ?></h2><?php if ($is_oauth_bind && $registration_open) : ?><span><?php esc_html_e('没有账号?', 'aksrt'); ?> <button type="button" data-auth-tab="signup"><?php esc_html_e('立即注册', 'aksrt'); ?></button></span><?php endif; ?></div>
|
||||
<div class="sign-message" data-auth-message hidden></div>
|
||||
<?php if ($is_oauth_bind) : ?>
|
||||
<div class="sign-notice"><?php printf(esc_html__('通过 %s 登录,请绑定已有账号。', 'aksrt'), esc_html($oauth_provider_name)); ?></div>
|
||||
<form class="sign-form" data-auth-form="login">
|
||||
<div class="floating-input"><input name="target" id="<?php echo esc_attr($unique); ?>-bind-target" autocomplete="username" required><label for="<?php echo esc_attr($unique); ?>-bind-target"><?php echo $signin_phone ? esc_html__('邮箱 / 手机号 / 用户名', 'aksrt') : esc_html__('邮箱 / 用户名', 'aksrt'); ?></label></div>
|
||||
<div class="floating-input has-action"><input type="password" name="password" id="<?php echo esc_attr($unique); ?>-bind-password" autocomplete="current-password" required><label for="<?php echo esc_attr($unique); ?>-bind-password"><?php esc_html_e('密码', 'aksrt'); ?></label><button type="button" class="password-show" data-password-show><i class="fa fas fa-eye"></i></button></div>
|
||||
<?php if (function_exists('aksrt_render_captcha')) { aksrt_render_captcha('bind'); } ?>
|
||||
<input type="hidden" name="obk" value="<?php echo esc_attr($obk); ?>">
|
||||
<input type="hidden" name="rememberme" value="1">
|
||||
<input type="hidden" name="redirect_to" value="<?php echo esc_attr($redirect_to); ?>">
|
||||
<button type="submit" class="sign-submit jb-blue"><span><?php esc_html_e('登录并绑定', 'aksrt'); ?></span><i class="fa fas fa-spinner fa-spin" hidden></i></button>
|
||||
</form>
|
||||
<?php else : ?><div class="sign-notice"><?php esc_html_e('绑定会话已过期,请重新使用第三方登录。', 'aksrt'); ?></div><?php endif; ?>
|
||||
</section>
|
||||
|
||||
<?php if (!empty($oauth_providers) && !$is_oauth_bind) : ?>
|
||||
<div class="social-login"><span><?php esc_html_e('其他登录方式', 'aksrt'); ?></span><div class="social-login-buttons">
|
||||
<?php foreach ($oauth_providers as $provider) : ?><a class="social-login-item <?php echo esc_attr((string) ($provider['name'] ?? '')); ?>" href="<?php echo esc_url((string) ($provider['url'] ?? '#')); ?>" title="<?php echo esc_attr((string) ($provider['label'] ?? '')); ?>"><?php echo $provider['icon'] ?? ''; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?><?php if ($oauth_large) : ?><span><?php printf(esc_html__('%s登录', 'aksrt'), esc_html((string) ($provider['label'] ?? ''))); ?></span><?php endif; ?></a><?php endforeach; ?>
|
||||
<?php if ($wechat_qr) : ?><button type="button" class="social-login-item weixingzh" data-auth-tab="oauth" title="<?php esc_attr_e('微信扫码登录', 'aksrt'); ?>"><i class="fa fab fa-weixin"></i><?php if ($oauth_large) : ?><span><?php esc_html_e('微信登录', 'aksrt'); ?></span><?php endif; ?></button><?php endif; ?>
|
||||
</div></div>
|
||||
<?php if (!empty($agreement['enabled'])) : ?><div class="sign-agreement sign-agreement-implicit"><?php esc_html_e('使用社交账号登录即表示', 'aksrt'); ?><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></div><?php endif; ?>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
+25
-251
@@ -1,268 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Modal auth dialog.
|
||||
* Shared authentication modal shell.
|
||||
*
|
||||
* @package AKSRT
|
||||
*/
|
||||
|
||||
defined('ABSPATH') || exit;
|
||||
|
||||
if (is_user_logged_in()) {
|
||||
if (is_user_logged_in() || (class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::close_sign())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$oauth_providers = [];
|
||||
if (class_exists('\AKSRT\Auth\OAuth')) {
|
||||
$oauth_providers = \AKSRT\Auth\OAuth::get_instance()->get_auth_providers_data();
|
||||
$modal_ui = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::modal_ui() : [];
|
||||
$images = $modal_ui['background'] ?? [];
|
||||
if (is_string($images)) {
|
||||
$images = array_filter(array_map('absint', explode(',', $images)));
|
||||
}
|
||||
|
||||
$registration_open = true;
|
||||
$email_verification = false;
|
||||
$close_signin = false;
|
||||
$ref_field_mode = 'hidden';
|
||||
$ref_field_hint = '';
|
||||
$ref_prefill = '';
|
||||
$show_qrcode_tab = false;
|
||||
|
||||
if (class_exists('\AKSRT\Settings\Settings')) {
|
||||
$registration_open = \AKSRT\Settings\Settings::registration_enabled();
|
||||
if (method_exists('\AKSRT\Settings\Settings', 'close_signup') && \AKSRT\Settings\Settings::close_signup()) {
|
||||
$registration_open = false;
|
||||
$image_url = '';
|
||||
if (is_array($images) && $images) {
|
||||
$image_url = (string) wp_get_attachment_url((int) $images[array_rand($images)]);
|
||||
}
|
||||
$email_verification = \AKSRT\Settings\Settings::registration_requires_email_verification();
|
||||
$close_signin = \AKSRT\Settings\Settings::close_signin();
|
||||
|
||||
$ref_cfg = \AKSRT\Settings\Settings::get_referral_registration_settings();
|
||||
$ref_field_mode = !empty($ref_cfg['enabled']) ? (string) ($ref_cfg['register_field'] ?? 'hidden') : 'hidden';
|
||||
$ref_field_hint = $ref_field_mode === 'required'
|
||||
? (string) ($ref_cfg['required_desc'] ?? '')
|
||||
: (string) ($ref_cfg['open_desc'] ?? '');
|
||||
}
|
||||
|
||||
if (!empty($_COOKIE['aksrt_referral'])) {
|
||||
$ref_prefill = sanitize_key(wp_unslash($_COOKIE['aksrt_referral']));
|
||||
} elseif (!empty($_GET['ref'])) {
|
||||
$ref_prefill = sanitize_key(wp_unslash($_GET['ref']));
|
||||
}
|
||||
|
||||
foreach ($oauth_providers as $provider) {
|
||||
if (($provider['name'] ?? '') === 'weixingzh') {
|
||||
$show_qrcode_tab = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$site_name = get_bloginfo('name');
|
||||
$reset_url = function_exists('aksrt_get_reset_password_url') ? aksrt_get_reset_password_url() : wp_lostpassword_url();
|
||||
$agreement = function_exists('aksrt_get_auth_agreement_view')
|
||||
? aksrt_get_auth_agreement_view()
|
||||
: ['enabled' => true, 'required' => true, 'label_html' => esc_html__('我已阅读并同意用户协议', 'aksrt'), 'field_name' => 'agree_terms'];
|
||||
$logo_url = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::user_logo_url() : '';
|
||||
$auto = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::sign_modal_auto() : ['enabled' => false, 'waiting' => 5, 'expires' => 1];
|
||||
$wechat = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_weixingzh_settings() : [];
|
||||
$initial_tab = !empty($wechat['priority']) && class_exists('\AKSRT\Auth\WeixinGzh') && \AKSRT\Auth\WeixinGzh::is_enabled() ? 'oauth' : 'signin';
|
||||
$aksrt_auth_context = [
|
||||
'surface' => 'modal',
|
||||
'tab' => $initial_tab,
|
||||
'show_logo' => !empty($modal_ui['show_logo']),
|
||||
'redirect_to' => home_url('/'),
|
||||
];
|
||||
?>
|
||||
|
||||
<div class="aksrt-modal-auth" id="aksrt-modal-auth" aria-hidden="true" role="dialog" aria-modal="true" aria-label="<?php esc_attr_e('登录', 'aksrt'); ?>">
|
||||
<div class="aksrt-modal-auth__overlay"></div>
|
||||
<div class="aksrt-modal-auth__card">
|
||||
<button type="button" class="aksrt-modal-auth__close" aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>">
|
||||
<i class="fa fas fa-times"></i>
|
||||
</button>
|
||||
|
||||
<div class="aksrt-modal-auth__header modal-colorful-header">
|
||||
<?php if ($logo_url) : ?>
|
||||
<div class="aksrt-modal-auth__logo aksrt-modal-auth__logo--image"><img src="<?php echo esc_url($logo_url); ?>" alt=""></div>
|
||||
<?php else : ?>
|
||||
<div class="aksrt-modal-auth__logo"><?php echo esc_html(mb_substr($site_name, 0, 2)); ?></div>
|
||||
<?php endif; ?>
|
||||
<h2 class="aksrt-modal-auth__title"><?php echo esc_html($site_name); ?></h2>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__tabs" role="tablist">
|
||||
<button type="button" class="aksrt-modal-auth__tab is-active" data-tab="login" role="tab" aria-selected="true">
|
||||
<?php esc_html_e('登录', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php if ($registration_open) : ?>
|
||||
<button type="button" class="aksrt-modal-auth__tab" data-tab="register" role="tab" aria-selected="false">
|
||||
<?php esc_html_e('注册', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
<?php if ($show_qrcode_tab) : ?>
|
||||
<button type="button" class="aksrt-modal-auth__tab" data-tab="qrcode" role="tab" aria-selected="false">
|
||||
<?php esc_html_e('扫码登录', 'aksrt'); ?>
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__panel is-active" data-panel="login">
|
||||
<?php if ($close_signin) : ?>
|
||||
<div class="aksrt-modal-auth__error is-success" id="aksrt-modal-login-error">
|
||||
<?php esc_html_e('当前已关闭账密登录,请使用下方第三方登录方式。', 'aksrt'); ?>
|
||||
</div>
|
||||
<?php else : ?>
|
||||
<form class="aksrt-modal-auth__form" id="aksrt-modal-login-form">
|
||||
<div class="aksrt-modal-auth__error" id="aksrt-modal-login-error" hidden></div>
|
||||
<?php wp_nonce_field('aksrt_login', '_login_nonce'); ?>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-login-user"><?php esc_html_e('邮箱 / 用户名', 'aksrt'); ?></label>
|
||||
<input type="text" id="modal-login-user" name="log" autocomplete="username" required placeholder="<?php esc_attr_e('请输入邮箱或用户名', 'aksrt'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-login-pass"><?php esc_html_e('密码', 'aksrt'); ?></label>
|
||||
<div class="aksrt-modal-auth__pass-wrap">
|
||||
<input type="password" id="modal-login-pass" name="pwd" autocomplete="current-password" required placeholder="<?php esc_attr_e('请输入密码', 'aksrt'); ?>">
|
||||
<button type="button" class="aksrt-modal-auth__pass-toggle" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>">
|
||||
<i class="fa fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('login'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-modal-auth__actions">
|
||||
<a href="<?php echo esc_url($reset_url); ?>" class="aksrt-modal-auth__link"><?php esc_html_e('忘记密码?', 'aksrt'); ?></a>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="aksrt-modal-auth__submit">
|
||||
<span><?php esc_html_e('登录', 'aksrt'); ?></span>
|
||||
<i class="fa fas fa-spinner fa-spin" hidden></i>
|
||||
</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<?php if ($registration_open) : ?>
|
||||
<div class="aksrt-modal-auth__panel" data-panel="register">
|
||||
<form class="aksrt-modal-auth__form" id="aksrt-modal-register-form">
|
||||
<div class="aksrt-modal-auth__error" id="aksrt-modal-register-error" hidden></div>
|
||||
<?php wp_nonce_field('aksrt_register', '_register_nonce'); ?>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-reg-user"><?php esc_html_e('用户名', 'aksrt'); ?></label>
|
||||
<input type="text" id="modal-reg-user" name="user_login" autocomplete="username" required placeholder="<?php esc_attr_e('请输入用户名', 'aksrt'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-reg-email"><?php esc_html_e('邮箱', 'aksrt'); ?></label>
|
||||
<input type="email" id="modal-reg-email" name="user_email" autocomplete="email" required placeholder="<?php esc_attr_e('请输入邮箱', 'aksrt'); ?>">
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-reg-pass"><?php esc_html_e('密码', 'aksrt'); ?></label>
|
||||
<div class="aksrt-modal-auth__pass-wrap">
|
||||
<input type="password" id="modal-reg-pass" name="user_pass" autocomplete="new-password" required minlength="6" placeholder="<?php esc_attr_e('至少 6 位字符', 'aksrt'); ?>">
|
||||
<button type="button" class="aksrt-modal-auth__pass-toggle" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>">
|
||||
<i class="fa fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-reg-pass-confirm"><?php esc_html_e('确认密码', 'aksrt'); ?></label>
|
||||
<div class="aksrt-modal-auth__pass-wrap">
|
||||
<input type="password" id="modal-reg-pass-confirm" name="user_pass_confirm" autocomplete="new-password" required minlength="6" placeholder="<?php esc_attr_e('请再次输入密码', 'aksrt'); ?>">
|
||||
<button type="button" class="aksrt-modal-auth__pass-toggle" aria-label="<?php esc_attr_e('显示或隐藏密码', 'aksrt'); ?>">
|
||||
<i class="fa fas fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($ref_field_mode !== 'hidden') : ?>
|
||||
<div class="aksrt-modal-auth__field">
|
||||
<label for="modal-reg-ref">
|
||||
<?php esc_html_e('推广码', 'aksrt'); ?>
|
||||
<?php if ($ref_field_mode === 'optional') : ?>
|
||||
<small><?php esc_html_e('(选填)', 'aksrt'); ?></small>
|
||||
<?php endif; ?>
|
||||
</label>
|
||||
<input type="text" id="modal-reg-ref" name="ref" maxlength="12" value="<?php echo esc_attr($ref_prefill); ?>" <?php echo $ref_field_mode === 'required' ? 'required' : ''; ?> placeholder="<?php esc_attr_e('请输入推广码', 'aksrt'); ?>">
|
||||
<?php if ($ref_field_hint !== '') : ?>
|
||||
<p class="aksrt-modal-auth__hint"><?php echo wp_kses_post($ref_field_hint); ?></p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($agreement['enabled'])) : ?>
|
||||
<label class="aksrt-modal-auth__checkbox aksrt-modal-auth__agreement">
|
||||
<input type="checkbox" name="<?php echo esc_attr($agreement['field_name']); ?>" value="1" <?php echo !empty($agreement['required']) ? 'required' : ''; ?>>
|
||||
<span><?php echo $agreement['label_html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></span>
|
||||
</label>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($email_verification) : ?>
|
||||
<p class="aksrt-modal-auth__hint"><?php esc_html_e('注册后需要完成邮箱验证。', 'aksrt'); ?></p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (function_exists('aksrt_render_captcha')) : ?>
|
||||
<?php aksrt_render_captcha('register'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<button type="submit" class="aksrt-modal-auth__submit">
|
||||
<span><?php esc_html_e('注册', 'aksrt'); ?></span>
|
||||
<i class="fa fas fa-spinner fa-spin" hidden></i>
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($show_qrcode_tab) : ?>
|
||||
<div class="aksrt-modal-auth__panel" data-panel="qrcode">
|
||||
<div class="aksrt-modal-auth__form">
|
||||
<div class="aksrt-modal-auth__error" id="aksrt-modal-qrcode-error" hidden></div>
|
||||
<div class="aksrt-modal-auth__qr-code"></div>
|
||||
<p class="aksrt-modal-auth__hint"><?php esc_html_e('请使用微信扫码,在手机上完成登录。', 'aksrt'); ?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($oauth_providers)) : ?>
|
||||
<div class="aksrt-modal-auth__divider">
|
||||
<span><?php esc_html_e('其他登录方式', 'aksrt'); ?></span>
|
||||
</div>
|
||||
|
||||
<div class="aksrt-modal-auth__oauth">
|
||||
<?php foreach ($oauth_providers as $provider) :
|
||||
$provider_name = (string) ($provider['name'] ?? '');
|
||||
$label = $provider['label'] ?? $provider_name;
|
||||
$url = $provider['url'] ?? '#';
|
||||
$icon = $provider['icon'] ?? '';
|
||||
?>
|
||||
<a
|
||||
href="<?php echo esc_url($url); ?>"
|
||||
class="aksrt-modal-auth__oauth-btn social-login-item <?php echo esc_attr($provider_name); ?>"
|
||||
data-provider="<?php echo esc_attr($provider_name); ?>"
|
||||
title="<?php echo esc_attr($label); ?>"
|
||||
>
|
||||
<?php echo $icon; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="aksrt-modal-auth__footer">
|
||||
<span class="aksrt-modal-auth__footer-text" data-for="login">
|
||||
<?php if ($registration_open) : ?>
|
||||
<?php esc_html_e('还没有账号?', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-modal-auth__switch" data-switch="register"><?php esc_html_e('立即注册', 'aksrt'); ?></button>
|
||||
<?php elseif ($show_qrcode_tab) : ?>
|
||||
<?php esc_html_e('使用扫码登录?', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-modal-auth__switch" data-switch="qrcode"><?php esc_html_e('打开扫码登录', 'aksrt'); ?></button>
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
<?php if ($registration_open) : ?>
|
||||
<span class="aksrt-modal-auth__footer-text" data-for="register" hidden>
|
||||
<?php esc_html_e('已有账号?', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-modal-auth__switch" data-switch="login"><?php esc_html_e('立即登录', 'aksrt'); ?></button>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<?php if ($show_qrcode_tab) : ?>
|
||||
<span class="aksrt-modal-auth__footer-text" data-for="qrcode" hidden>
|
||||
<?php esc_html_e('改用密码登录?', 'aksrt'); ?>
|
||||
<button type="button" class="aksrt-modal-auth__switch" data-switch="login"><?php esc_html_e('返回登录', 'aksrt'); ?></button>
|
||||
</span>
|
||||
<?php endif; ?>
|
||||
<div id="u_sign" class="aksrt-auth-modal<?php echo $image_url !== '' ? ' has-sign-img' : ''; ?>" aria-hidden="true" role="dialog" aria-modal="true" data-auth-modal data-auto="<?php echo !empty($auto['enabled']) ? '1' : '0'; ?>" data-auto-wait="<?php echo esc_attr((string) $auto['waiting']); ?>" data-auto-expires="<?php echo esc_attr((string) $auto['expires']); ?>">
|
||||
<div class="auth-modal-backdrop" data-auth-close></div>
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<button type="button" class="auth-modal-close" data-auth-close aria-label="<?php esc_attr_e('关闭', 'aksrt'); ?>"><i class="fa fas fa-times"></i></button>
|
||||
<?php if ($image_url !== '') : ?><div class="sign-img" style="background-image:url('<?php echo esc_url($image_url); ?>')"></div><?php endif; ?>
|
||||
<?php require get_template_directory() . '/templates/parts/auth-content.php'; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const root = path.resolve(__dirname, '../..');
|
||||
const read = (file) => fs.readFileSync(path.join(root, file), 'utf8');
|
||||
|
||||
test('modal and sign page use one renderer and one state machine', () => {
|
||||
const modal = read('templates/parts/modal-auth.php');
|
||||
const page = read('templates/pages/page-sign.php');
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const scripts = read('src/Core/ScriptsPart1c.php');
|
||||
const runtime = read('assets/js/runtime/main.js') + read('assets/js/runtime/main.min.js');
|
||||
const loader = read('assets/js/runtime/loader.js');
|
||||
|
||||
assert.match(modal, /templates\/parts\/auth-content\.php/);
|
||||
assert.match(page, /templates\/parts\/auth-content\.php/);
|
||||
assert.match(shared, /data-auth-panel="signin"/);
|
||||
assert.match(shared, /data-auth-panel="signup"/);
|
||||
assert.match(shared, /data-auth-panel="resetpassword"/);
|
||||
assert.match(shared, /data-auth-panel="bind"/);
|
||||
assert.match(shared, /data-auth-panel="oauth"/);
|
||||
assert.match(scripts, /assets\/js\/auth\.js/);
|
||||
assert.doesNotMatch(scripts, /modal-auth\.js|sign-register\.js/);
|
||||
assert.doesNotMatch(runtime, /signsubmit-loader|tbquire\(\['sign-register'\]\)|\.on\('click', '\.(?:signin|signup)-loader'/);
|
||||
assert.doesNotMatch(loader, /sign-register/);
|
||||
});
|
||||
|
||||
test('static Zibll authentication dimensions and responsive contract are retained', () => {
|
||||
const css = read('assets/css/routes/sign.css');
|
||||
assert.match(css, /\.sign\s*\{[\s\S]*?max-width:\s*330px/);
|
||||
assert.match(css, /#u_sign\.has-sign-img \.modal-dialog\s*\{\s*width:\s*800px/);
|
||||
assert.match(css, /\.social-login-item\s*\{[\s\S]*?width:\s*32px;[\s\S]*?height:\s*32px/);
|
||||
assert.match(css, /\.auth-qrcode\s*\{[\s\S]*?width:\s*180px;[\s\S]*?min-height:\s*180px/);
|
||||
assert.match(css, /@media \(max-width:\s*767px\)[\s\S]*?\.sign-img\s*\{\s*display:\s*none/);
|
||||
});
|
||||
|
||||
test('authentication settings use reference defaults and canonical keys', () => {
|
||||
const js = read('assets/admin/src/schema/sections/users/auth.js');
|
||||
const php = read('src/Admin/Schema/SchemaUsers.php');
|
||||
const all = js + '\n' + php;
|
||||
|
||||
assert.match(all, /user_sign_type['"]?\s*(?:=>|:)\s*['"]modal/);
|
||||
assert.match(all, /user_signup_captch['"]?\s*(?:=>|:)\s*false/);
|
||||
assert.match(all, /sign_card_position['"]?\s*(?:=>|:)\s*['"]right/);
|
||||
assert.match(all, /sign_show_logo['"]?\s*(?:=>|:)\s*false/);
|
||||
assert.match(all, /sign_modal_show_logo['"]?\s*(?:=>|:)\s*false/);
|
||||
assert.match(all, /invit_code_s/);
|
||||
assert.match(all, /only_nopas/);
|
||||
assert.match(all, /user_signup_no_repas[\s\S]*?dependsOn[\s\S]*?user_signup_captch/);
|
||||
assert.doesNotMatch(all, /user_close_login|user_close_signup|nopas_first|password_first|nopas_only|referral_registration_enabled|referral_register_field/);
|
||||
});
|
||||
|
||||
test('only the shared authentication REST routes and /sign/ renderer are registered', () => {
|
||||
const controller = read('src/Admin/UserController.php');
|
||||
const router = read('src/Auth/Router.php');
|
||||
const urls = read('inc/modules/user-parts/urls.php');
|
||||
|
||||
['/auth/code', '/auth/login', '/auth/register', '/auth/reset-password'].forEach((route) => {
|
||||
assert.match(controller, new RegExp(route.replace('/', '\\/')));
|
||||
});
|
||||
assert.doesNotMatch(controller, /auth\/(?:modal-|phone-|send-sms-code|bind-phone)/);
|
||||
assert.match(router, /'sign'\s*=>\s*'index\.php\?aksrt_sign=1'/);
|
||||
assert.doesNotMatch(router, /'login'\s*=>|'register'\s*=>|'reset-password'\s*=>|'verify-email'\s*=>|'bind-account'\s*=>/);
|
||||
assert.match(router, /aksrt_bypass/);
|
||||
assert.match(router, /\$action === 'logout'[\s\S]*?aksrt_get_logout_url/);
|
||||
assert.doesNotMatch(router, /is_user_logged_in\(\) && current_user_can\('manage_options'\)|wp_doing_ajax\(\)/);
|
||||
assert.match(urls, /home_url\('\/sign\/'\)/);
|
||||
});
|
||||
|
||||
test('legacy authentication templates and scripts are removed', () => {
|
||||
[
|
||||
'assets/js/modal-auth.js',
|
||||
'assets/js/sign-register.js',
|
||||
'assets/js/runtime/sign-register.js',
|
||||
'assets/js/runtime/sign-register.min.js',
|
||||
'assets/css/routes/auth.css',
|
||||
'assets/css/frontend/enhancements/modal-motion.css',
|
||||
'templates/pages/page-login.php',
|
||||
'templates/pages/page-register.php',
|
||||
'templates/pages/page-reset-password.php',
|
||||
'templates/pages/page-bind-account.php',
|
||||
'templates/pages/page-verify-email.php',
|
||||
].forEach((file) => assert.equal(fs.existsSync(path.join(root, file)), false, file));
|
||||
});
|
||||
|
||||
test('unified handlers enforce nonce, captcha, rate limits, configured channels, and generic errors', () => {
|
||||
const handlers = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
||||
const service = read('src/Auth/Service.php');
|
||||
|
||||
assert.match(handlers, /auth_verify_rest_nonce/);
|
||||
assert.match(handlers, /aksrt_require_captcha/);
|
||||
assert.match(handlers, /auth_rate_limited/);
|
||||
assert.match(handlers, /auth_channel_allowed/);
|
||||
['auth_code', 'auth_login', 'auth_register', 'auth_reset_password'].forEach((method) => {
|
||||
const start = handlers.indexOf(`public function ${method}(`);
|
||||
const end = handlers.indexOf('\n\tpublic function ', start + 1);
|
||||
const body = handlers.slice(start, end === -1 ? handlers.length : end);
|
||||
assert.match(body, /aksrt_require_captcha/, method);
|
||||
});
|
||||
assert.match(handlers, /passwordless_login_type/);
|
||||
assert.match(handlers, /signup_verification_type/);
|
||||
assert.match(handlers, /reset_password_verification_type/);
|
||||
assert.match(handlers, /账号或密码错误/);
|
||||
assert.match(handlers, /账号或验证码错误/);
|
||||
assert.match(service, /signin_phone_enabled/);
|
||||
assert.doesNotMatch(service, /login_by_phone_password|login_by_phone_code/);
|
||||
});
|
||||
|
||||
test('OAuth bind and reset flows stay inside the shared page and four auth endpoints', () => {
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const page = read('templates/pages/page-sign.php');
|
||||
const handlers = read('src/Admin/UserControllerAuthPhoneOperations.php');
|
||||
const serviceA = read('src/Auth/ServicePart1a.php');
|
||||
const serviceB = read('src/Auth/ServicePart1b.php');
|
||||
|
||||
assert.match(page, /'obk'\s*=>\s*\$obk/);
|
||||
assert.match(shared, /data-auth-panel="bind"[\s\S]*?data-auth-form="login"/);
|
||||
assert.match(shared, /name="obk"/);
|
||||
assert.match(handlers, /finalize_oauth_binding/);
|
||||
assert.match(serviceA, /finalize_oauth_binding/);
|
||||
assert.doesNotMatch(serviceA + serviceB, /bind_oauth_account|get_reset_context|request_password_reset|reset_password_with_token/);
|
||||
});
|
||||
|
||||
test('shared renderer contracts every configured authentication state', () => {
|
||||
const shared = read('templates/parts/auth-content.php');
|
||||
const modal = read('templates/parts/modal-auth.php');
|
||||
const client = read('assets/js/auth.js');
|
||||
const settings = read('src/Settings/AuthReader.php');
|
||||
|
||||
assert.match(shared, /data-auth-password-fields/);
|
||||
assert.match(shared, /data-auth-code-fields/);
|
||||
assert.match(shared, /data-login-mode/);
|
||||
['login', 'register', 'resetpassword'].forEach((scene) => {
|
||||
assert.match(shared, new RegExp(`data-send-code="${scene}"`));
|
||||
});
|
||||
assert.match(shared, /\$omit_confirm/);
|
||||
assert.match(shared, /\$ref_mode !== 'hidden'/);
|
||||
assert.match(shared, /\$ref_mode === 'required'/);
|
||||
assert.match(shared, /sign-agreement-implicit/);
|
||||
assert.match(shared, /name="agree_terms"[\s\S]*?required/);
|
||||
assert.match(shared, /oauth-button-lg/);
|
||||
assert.match(shared, /data-auth-qrcode/);
|
||||
assert.match(modal, /data-auto=/);
|
||||
assert.match(client, /aksrtAuthAutoUntil/);
|
||||
assert.match(client, /data-password-show/);
|
||||
assert.match(client, /pollQr/);
|
||||
assert.match(client, /window\.location\.assign\(authPageUrl/);
|
||||
assert.match(settings, /invitation_mode/);
|
||||
assert.match(settings, /\['close', 'open', 'must'\]/);
|
||||
});
|
||||
@@ -332,7 +332,6 @@ test('profile save uses AJAX rehydrate and a fixed-id notyf flow without query f
|
||||
|
||||
test('settings actions use rehydrated fixed-id notyf flows', () => {
|
||||
const userCenter = read('assets/js/routes/user-center.js');
|
||||
const signRegister = read('assets/js/sign-register.js');
|
||||
const settings = read('inc/helpers/user-center-renderers/profile-settings.php');
|
||||
const context = read('inc/helpers/aksrt-user-center.php');
|
||||
const page = read('templates/pages/page-profile.php');
|
||||
@@ -354,12 +353,11 @@ test('settings actions use rehydrated fixed-id notyf flows', () => {
|
||||
assert.match(userCenter, /oauth_bound[\s\S]*?notyf\([^;]+?'success'\)[\s\S]*?history\.replaceState/);
|
||||
assert.doesNotMatch(userCenter, /oauth_unbound/);
|
||||
|
||||
assert.match(signRegister, /scene === 'bind_phone'[\s\S]*?'aksrt-settings-phone-code-toast'/);
|
||||
assert.match(signRegister, /notyf\('正在发送验证码…', 'load', '', toastId\)/);
|
||||
assert.match(signRegister, /notyf\([^;]+?'success', 5, toastId\)/);
|
||||
assert.match(signRegister, /notyf\([^;]+?'danger', 5, toastId\)/);
|
||||
assert.match(signRegister, /register\('aksrt-sms-buttons',[\s\S]*?initSmsButtons\(root\)/);
|
||||
assert.match(signRegister, /aksrtSmsInitialized/);
|
||||
assert.match(userCenter, /'aksrt-settings-phone-code-toast'/);
|
||||
assert.match(userCenter, /notyf\('正在发送验证码…', 'load', '', toastId\)/);
|
||||
assert.match(userCenter, /notyf\([^;]+?'success', 5, toastId\)/);
|
||||
assert.match(userCenter, /notyf\([^;]+?'danger', 5, toastId\)/);
|
||||
assert.match(userCenter, /\/user\/phone-code/);
|
||||
|
||||
assert.match(settings, /require \$dir \. '\/profile-account-modals\.php';/);
|
||||
assert.doesNotMatch(context, /account_modals_html|'account-modals'/);
|
||||
|
||||
+1
-2
@@ -60,8 +60,7 @@ $meta_keys = [
|
||||
'aksrt_avatar_attachment_id',
|
||||
'aksrt_background_attachment_id',
|
||||
'aksrt_background_image',
|
||||
'aksrt_email_verification_status',
|
||||
'aksrt_email_verified_at',
|
||||
'aksrt_email_verified',
|
||||
'aksrt_unread_message_count',
|
||||
'_aksrt_active_restrictions',
|
||||
'_aksrt_is_banned',
|
||||
|
||||
Reference in New Issue
Block a user