diff --git a/.gitignore b/.gitignore
index 6a76763f..f3b6baed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,6 +23,8 @@
!/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/widget-system/user-badges-contract.test.cjs
+!/tests/widget-system/user-features-contract.test.cjs
!/tests/visual/docker-compose.zibll.yml
!/tests/visual/zibll-Caddyfile
!/tests/visual/README.md
diff --git a/README.md b/README.md
index bf3197d2..e7ce5d87 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ AKSRT 是一款现代化的 WordPress 主题,采用**原生 WordPress 主题
- **支付/下载主线** — 下载/付费内容中心、充值/VIP/分成/返佣/网关结构、10 分钟下载 token
- **用户与增长** — 统一登录注册、OAuth 登录、签到奖励、邀请奖励、等级/徽章/认证/举报结构
- **内容与交互** — 首页模块化、归档/搜索/404、单篇资源页、评论审核与通知中心
-- **运维工具** — 配置导出导入、dry-run 校验、邮件回滚、数据库与环境诊断
+- **运维工具** — 配置导出导入、dry-run 校验、备份、数据库与环境诊断
- **自定义页面模板**:About / Friends / Login / Profile / Register
- **响应式设计**
@@ -165,7 +165,7 @@ WordPress 后台左侧 **Admin Bar** → ⚡ **AKSRT 主题设置**
- `功能&权限`:审核、验证码、评论/附件/下载权限
- `用户&互动`:登录注册、OAuth、通知、签到、认证、等级、徽章、举报、积分
- `支付&付费`:下载/付费内容中心、余额充值、VIP、创作分成、推广返佣、收款接口
-- `备份&导入`:导入导出、健康检查、迁移状态、邮件回滚
+- `备份&导入`:导入导出、健康检查、迁移状态、备份与更新
---
diff --git a/assets/admin/src/api/modules/tools.js b/assets/admin/src/api/modules/tools.js
index e793e95e..674feb70 100644
--- a/assets/admin/src/api/modules/tools.js
+++ b/assets/admin/src/api/modules/tools.js
@@ -7,9 +7,6 @@ export const toolsApiMethods = {
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 }) }) },
- getEmailRollbackUsers() { return this.request('/email-rollback/users') },
- executeEmailRollback(id) { return this.request(`/email-rollback/users/${id}/rollback`, { method: 'POST' }) },
-
/* ================================================================
* Settings Management (Import / Export / Reset / Health / Schema)
* ================================================================ */
diff --git a/assets/admin/src/constants/sections/extensions.js b/assets/admin/src/constants/sections/extensions.js
index 8f12aa49..4be63a92 100644
--- a/assets/admin/src/constants/sections/extensions.js
+++ b/assets/admin/src/constants/sections/extensions.js
@@ -36,15 +36,6 @@ export const extensionsSection = {
canonicalDomain: 'extensions',
section: 'push',
}),
- customPage({
- key: 'extensions-tools',
- name: 'ExtensionsTools',
- label: '系统工具',
- route: '/extensions/tools',
- component: 'ExtensionTools',
- title: '系统工具',
- desc: '额外运维工具与扩展能力。',
- }),
customPage({
key: 'extensions-ai',
name: 'ExtensionAI',
diff --git a/assets/admin/src/constants/sections/tools.js b/assets/admin/src/constants/sections/tools.js
index bd6c6ef5..869611c3 100644
--- a/assets/admin/src/constants/sections/tools.js
+++ b/assets/admin/src/constants/sections/tools.js
@@ -25,16 +25,6 @@ export const toolsSection = {
canonicalDomain: 'tools',
section: 'backup',
}),
- schemaPage({
- key: 'tools-frontend-modal-rollout',
- name: 'ToolsFrontendModalRollout',
- label: '前台弹窗灰度',
- route: '/tools/frontend-modal-rollout',
- title: '前台弹窗灰度',
- desc: '按领域控制前台弹窗新流程的分阶段发布。',
- canonicalDomain: 'tools',
- section: 'frontend-modal-rollout',
- }),
customPage({
key: 'tools-health',
name: 'ToolsHealth',
@@ -44,15 +34,6 @@ export const toolsSection = {
desc: '查看主题环境、数据表和系统诊断状态。',
component: 'ToolsHealth',
}),
- customPage({
- key: 'tools-email-rollback',
- name: 'ToolsEmailRollback',
- label: '邮件回滚',
- route: '/tools/email-rollback',
- title: '邮件回滚',
- desc: '查看 14 天邮箱变更窗口内的账号并执行回滚。',
- component: 'ToolsEmailRollback',
- }),
customPage({
key: 'tools-update',
name: 'ToolsUpdate',
diff --git a/assets/admin/src/constants/sections/users.js b/assets/admin/src/constants/sections/users.js
index e95f804b..04bfa476 100644
--- a/assets/admin/src/constants/sections/users.js
+++ b/assets/admin/src/constants/sections/users.js
@@ -30,28 +30,16 @@ export const usersSection = {
canonicalDomain: 'users',
section: 'oauth',
}),
- customPage({
+ schemaPage({
key: 'users-features',
name: 'UsersFeatures',
label: '用户功能',
route: '/users/features',
- component: 'UsersFeatures',
title: '用户功能',
- desc: '用户中心与个人主页模块配置。',
+ desc: '配置用户头像、封面和默认签名。',
canonicalDomain: 'users',
section: 'features',
}),
- customPage({
- key: 'users-signin',
- name: 'UsersSignin',
- label: '签到奖励',
- route: '/users/signin',
- component: 'UsersSignin',
- title: '签到奖励',
- desc: '签到规则、连续签到奖励和签到页面配置。',
- canonicalDomain: 'users',
- section: 'signin',
- }),
customPage({
key: 'users-credits',
name: 'UsersCredits',
diff --git a/assets/admin/src/pages/docs/DocsPage.vue b/assets/admin/src/pages/docs/DocsPage.vue
index 27e24143..3377b8c0 100644
--- a/assets/admin/src/pages/docs/DocsPage.vue
+++ b/assets/admin/src/pages/docs/DocsPage.vue
@@ -39,8 +39,8 @@
-
系统工具
-
导入导出、健康检查、迁移状态
+
工具
+
导入导出、健康检查、备份与更新
前往配置 →
diff --git a/assets/admin/src/pages/extensions/ExtensionTools.vue b/assets/admin/src/pages/extensions/ExtensionTools.vue
deleted file mode 100644
index 864bb200..00000000
--- a/assets/admin/src/pages/extensions/ExtensionTools.vue
+++ /dev/null
@@ -1,270 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {{ svc.available ? '可用' : '未配置' }}
-
-
-
-
-
-
-
-
-
-
-
-
- 清除缓存
-
-
-
- 优化数据表
-
-
-
- 重建索引
-
-
-
-
-
-
-
-
-
diff --git a/assets/admin/src/pages/tools/ToolsEmailRollback.vue b/assets/admin/src/pages/tools/ToolsEmailRollback.vue
deleted file mode 100644
index 2d0921b2..00000000
--- a/assets/admin/src/pages/tools/ToolsEmailRollback.vue
+++ /dev/null
@@ -1,122 +0,0 @@
-
-
-
-
-
- 健康检查
- 刷新列表
-
-
-
-
-
-
-
-
-
-
-
-
{{ item.display_name || item.login }}
-
- {{ item.current_email }} → {{ item.previous_email }}
-
-
- 剩余窗口:{{ Math.max(0, Math.floor((item.expires_in || 0) / 3600)) }} 小时
-
-
-
- 执行回滚
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/assets/admin/src/pages/tools/ToolsHealth.vue b/assets/admin/src/pages/tools/ToolsHealth.vue
index 0093a97f..65e679bd 100644
--- a/assets/admin/src/pages/tools/ToolsHealth.vue
+++ b/assets/admin/src/pages/tools/ToolsHealth.vue
@@ -7,7 +7,6 @@
导入导出
- 邮件回滚
diff --git a/assets/admin/src/pages/tools/ToolsImportExport.vue b/assets/admin/src/pages/tools/ToolsImportExport.vue
index 79e91023..0f1e6dd7 100644
--- a/assets/admin/src/pages/tools/ToolsImportExport.vue
+++ b/assets/admin/src/pages/tools/ToolsImportExport.vue
@@ -41,7 +41,7 @@
重置所选域
重置全部
- 仅支持重置 schema 托管的业务域。健康检查、迁移状态和邮件回滚属于运维面板,不参与按域回滚。
+ 仅支持重置 schema 托管的业务域。健康检查和迁移状态属于运维面板,不参与按域回滚。
diff --git a/assets/admin/src/pages/tools/ToolsUpdate.vue b/assets/admin/src/pages/tools/ToolsUpdate.vue
index 26ac01f8..584ccbc5 100644
--- a/assets/admin/src/pages/tools/ToolsUpdate.vue
+++ b/assets/admin/src/pages/tools/ToolsUpdate.vue
@@ -92,7 +92,7 @@ const guides = [
{ key: 'cdn', title: '静态资源与 CDN', desc: '核对前端资源地址与缓存策略', url: '#/global/common' },
{ key: 'ip-geo', title: 'IP 归属地', desc: '验证 IP 查询服务与密钥配置', url: '#/extensions/ip' },
{ key: 'payment', title: '支付与会员', desc: '核对网关、会员与充值配置', url: '#/payment/gateway' },
- { key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与推广奖励', url: '#/users/signin' },
+ { key: 'credits', title: '积分与签到', desc: '检查积分规则、签到与推广奖励', url: '#/users/credits' },
{ key: 'badge', title: '等级与徽章', desc: '维护等级、认证与徽章体系', url: '#/users/badge' },
]
diff --git a/assets/admin/src/pages/users/UsersFeatures.vue b/assets/admin/src/pages/users/UsersFeatures.vue
deleted file mode 100644
index 569e67c6..00000000
--- a/assets/admin/src/pages/users/UsersFeatures.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-
-
-
-
-
- 基本权限
- 公开主页
-
-
-
-
-
-
-
-
-
- 个人资料固定为昵称、个人签名、隐私设置、性别、个人网站、QQ、微信和 GitHub。公开资料由访问者身份过滤,原始社交资料不会下发给无权访客。
-
-
- 背景图操作入口始终显示在资料卡右下角的三点菜单中;“上传头像”和“上传用户资料卡背景图”由基本权限矩阵控制。
-
-
-
-
-
-
-
-
-
-
- 我的文章
- 我的评论
- 我的收藏
- 账户余额 / 签到
-
-
-
-
-
-
-
- 保存配置
-
-
-
-
-
-
diff --git a/assets/admin/src/pages/users/UsersSignin.vue b/assets/admin/src/pages/users/UsersSignin.vue
deleted file mode 100644
index 8c2c1c92..00000000
--- a/assets/admin/src/pages/users/UsersSignin.vue
+++ /dev/null
@@ -1,257 +0,0 @@
-
-
-
-
-
-
-
-
- 积分系统:{{ form.credits_enabled ? '已启用' : '未启用' }}
-
-
- 签到能力:{{ form.signin_enabled ? '已启用' : '已关闭' }}
-
-
- 推广奖励:{{ form.referral_reward_enabled ? '已启用' : '已关闭' }}
-
-
- 注册验证码:{{ captchaSummary.register ? '启用' : '未启用' }}
-
-
- 签到验证码:{{ captchaSummary.signin ? '启用' : '未启用' }}
-
-
-
-
-
-
- 当前积分系统处于关闭状态。即使保存签到和推广奖励配置,前台也不会真正发放奖励,直到“用户积分”页重新启用积分系统。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 每次签到基础发放 {{ currencyLabel }} 数量。
-
-
-
-
-
-
- 每行一个周期,格式 `天数|额外奖励`。例如 `7|5` 表示连续 7 天额外获得 5 {{ currencyLabel }}。
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 保存配置
-
-
-
-
-
-
diff --git a/assets/admin/src/router/component-loaders/extensions.js b/assets/admin/src/router/component-loaders/extensions.js
index c90b9b72..fde06c61 100644
--- a/assets/admin/src/router/component-loaders/extensions.js
+++ b/assets/admin/src/router/component-loaders/extensions.js
@@ -1,4 +1,3 @@
export const extensionsPageLoaders = {
- ExtensionTools: () => import('@/pages/extensions/ExtensionTools.vue'),
ExtensionAI: () => import('@/pages/extensions/ExtensionAI.vue'),
}
diff --git a/assets/admin/src/router/component-loaders/tools.js b/assets/admin/src/router/component-loaders/tools.js
index 257fbf4d..6080f149 100644
--- a/assets/admin/src/router/component-loaders/tools.js
+++ b/assets/admin/src/router/component-loaders/tools.js
@@ -1,6 +1,5 @@
export const toolsPageLoaders = {
ToolsImportExport: () => import('@/pages/tools/ToolsImportExport.vue'),
ToolsHealth: () => import('@/pages/tools/ToolsHealth.vue'),
- ToolsEmailRollback: () => import('@/pages/tools/ToolsEmailRollback.vue'),
ToolsUpdate: () => import('@/pages/tools/ToolsUpdate.vue'),
}
diff --git a/assets/admin/src/router/component-loaders/users.js b/assets/admin/src/router/component-loaders/users.js
index f5e91037..a171cab8 100644
--- a/assets/admin/src/router/component-loaders/users.js
+++ b/assets/admin/src/router/component-loaders/users.js
@@ -1,6 +1,4 @@
export const usersPageLoaders = {
- UsersFeatures: () => import('@/pages/users/UsersFeatures.vue'),
- UsersSignin: () => import('@/pages/users/UsersSignin.vue'),
UsersVerify: () => import('@/pages/users/UsersVerify.vue'),
UsersLevel: () => import('@/pages/users/UsersLevel.vue'),
UsersBadge: () => import('@/pages/users/UsersBadge.vue'),
diff --git a/assets/admin/src/schema/sections/extensions/ip.js b/assets/admin/src/schema/sections/extensions/ip.js
index acf8685b..b771e6ef 100644
--- a/assets/admin/src/schema/sections/extensions/ip.js
+++ b/assets/admin/src/schema/sections/extensions/ip.js
@@ -67,7 +67,7 @@ export const ipSchema = {
{ label: '文章详细页', value: 'post' },
],
dependsOn: { key: 'ip_geo_enabled', value: true },
- help: '用户主页/用户中心还需在「用户 → 用户功能」开启「显示城市」;评论区还需在评论设置开启「评论城市」。',
+ help: '用户主页和用户中心由此处直接控制;评论区还需在评论设置开启「评论城市」。',
},
{
key: 'ip_geo_format',
diff --git a/assets/admin/src/schema/sections/tools/email-rollback.js b/assets/admin/src/schema/sections/tools/email-rollback.js
deleted file mode 100644
index 7e8b200c..00000000
--- a/assets/admin/src/schema/sections/tools/email-rollback.js
+++ /dev/null
@@ -1,29 +0,0 @@
-export const emailRollbackSchema = {
- key: 'tools.email-rollback',
- title: '邮件回滚',
- icon: 'fa fas fa-undo',
- description: '批量回滚已发送的通知邮件。',
- defaults: {},
- groups: [
- {
- key: 'rollback',
- title: '邮件回滚',
- icon: 'fa fas fa-envelope-open',
- fields: [
- {
- key: 'rollback_type',
- label: '回滚类型',
- type: 'select',
- options: [
- { label: '按时间范围', value: 'date_range' },
- { label: '按邮件类型', value: 'email_type' },
- { label: '全部回滚', value: 'all' },
- ],
- },
- { key: 'rollback_date_from', label: '开始日期', type: 'date' },
- { key: 'rollback_date_to', label: '结束日期', type: 'date' },
- { key: 'rollback_execute', label: '执行回滚', type: 'button', danger: true, help: '此操作不可逆' },
- ],
- },
- ],
-}
diff --git a/assets/admin/src/schema/sections/tools/frontend-modal-rollout.js b/assets/admin/src/schema/sections/tools/frontend-modal-rollout.js
deleted file mode 100644
index eec4946f..00000000
--- a/assets/admin/src/schema/sections/tools/frontend-modal-rollout.js
+++ /dev/null
@@ -1,38 +0,0 @@
-export const frontendModalRolloutSchema = {
- key: 'tools.frontend-modal-rollout',
- title: '前台弹窗灰度',
- icon: 'fa fas fa-bolt',
- description: '按领域控制前台弹窗新流程的分阶段发布。',
- defaults: {
- frontend_modal_rollout_identity: true,
- frontend_modal_rollout_order: true,
- frontend_modal_rollout_vip: true,
- },
- groups: [
- {
- key: 'rollout',
- title: '弹窗新流程开关',
- icon: 'fa fas fa-toggle-on',
- fields: [
- {
- key: 'frontend_modal_rollout_identity',
- label: '官方认证新流程',
- type: 'switch',
- help: '启用统一弹窗运行时与认证快照流程。',
- },
- {
- key: 'frontend_modal_rollout_order',
- label: '订单新流程',
- type: 'switch',
- help: '启用订单详情快速开壳与状态同步流程。',
- },
- {
- key: 'frontend_modal_rollout_vip',
- label: '会员支付新流程',
- type: 'switch',
- help: '启用统一会员购买、支付和结果同步流程。',
- },
- ],
- },
- ],
-}
diff --git a/assets/admin/src/schema/sections/tools/index.js b/assets/admin/src/schema/sections/tools/index.js
index 047e4c75..ffa4056c 100644
--- a/assets/admin/src/schema/sections/tools/index.js
+++ b/assets/admin/src/schema/sections/tools/index.js
@@ -5,14 +5,10 @@
import { importExportSchema } from './import-export.js'
import { healthSchema } from './health.js'
-import { emailRollbackSchema } from './email-rollback.js'
import { backupSchema } from './backup.js'
-import { frontendModalRolloutSchema } from './frontend-modal-rollout.js'
export const toolsSchemas = {
'import-export': importExportSchema,
health: healthSchema,
- 'email-rollback': emailRollbackSchema,
backup: backupSchema,
- 'frontend-modal-rollout': frontendModalRolloutSchema,
}
diff --git a/assets/admin/src/schema/sections/users/features.js b/assets/admin/src/schema/sections/users/features.js
index fbe9d507..6a005991 100644
--- a/assets/admin/src/schema/sections/users/features.js
+++ b/assets/admin/src/schema/sections/users/features.js
@@ -1,47 +1,48 @@
+const themeRoot = typeof window !== 'undefined' && window.aksrtAdminData?.themeUri
+ ? String(window.aksrtAdminData.themeUri).replace(/\/$/, '')
+ : ''
+
export const featuresSchema = {
key: 'users.features',
title: '用户功能',
icon: 'fa fas fa-user',
- description: '配置用户中心和个人主页的功能模块。',
+ description: '配置用户头像、封面和默认签名。',
defaults: {
- user_center_tabs: 'profile,level,posts,comments,favorites,vip,credits,revenue,auth',
- user_desc_std: '这家伙很懒…',
- user_city_s: true,
- user_city_type: 'province',
- user_join_day_my_name: '本站',
- user_cover_img: '',
- user_rewrite_slug: 'user',
- user_follow_s: true,
- user_online_s: true,
- user_last_active_s: true,
+ avatar_upload_s: true,
+ avatar_external: '',
+ avatar_default_img: `${themeRoot}/assets/img/avatar-default.png`,
+ user_cover_upload_s: true,
+ user_cover_img: `${themeRoot}/assets/img/user_t.jpg`,
+ user_desc_std: '这家伙很懒,什么都没有写...',
},
groups: [
{
- key: 'center',
- title: '用户中心',
- icon: 'fa fas fa-user',
+ key: 'profile-media',
+ title: '用户资料',
+ icon: 'fa fas fa-user-circle',
fields: [
- { key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,level,comments,favorites,vip,credits,revenue,auth', help: '逗号分隔。level=我的等级;creator=创作中心;revenue=推广收益中心;auth=官方认证;等级、VIP、订单、创作中心、收益中心和认证会按功能开关自动注入' },
- { key: 'user_desc_std', label: '默认签名', type: 'input', default: '这家伙很懒…', help: '用户未设置个人简介时显示的默认文案' },
- { key: 'user_city_s', label: '显示城市(IP 归属地)', type: 'switch', default: true, help: '根据用户登录 IP 在用户主页与用户中心显示归属地;需同时在「扩展 → IP归属地」启用并勾选对应展示位置' },
+ { key: 'avatar_upload_s', label: '允许用户上传头像', type: 'switch', default: true },
{
- key: 'user_city_type',
- label: '位置精度',
+ key: 'avatar_external',
+ label: '外部头像源',
type: 'radio',
- default: 'province',
+ default: '',
options: [
- { label: '省份', value: 'province' },
- { label: '省份+城市', value: 'province_city' },
- { label: '国家+省份', value: 'country_province' },
+ { label: '关闭', value: '' },
+ { label: 'Gravatar', value: 'gravatar' },
+ { label: 'WeAvatar', value: 'weavatar' },
+ { label: 'Cravatar', value: 'cravatar' },
+ { label: '极客族 Gravatar 服务', value: 'geekzu' },
+ { label: 'loli Gravatar 服务', value: 'loli' },
+ { label: 'sep.cc Gravatar 服务', value: 'sep' },
+ { label: 'v2ex Gravatar 服务', value: 'v2ex' },
],
- dependsOn: { key: 'user_city_s', value: true },
+ help: '启用后,用户没有自定义头像时按邮箱获取外部头像。',
},
- { key: 'user_join_day_my_name', label: '站名', type: 'input', default: '本站', help: '用于「已加入 X 天」显示中的站点名称' },
- { key: 'user_cover_img', label: '默认封面', type: 'media', default: '' },
- { key: 'user_rewrite_slug', label: '用户页 slug', type: 'input', default: 'user' },
- { key: 'user_follow_s', label: '启用关注功能', type: 'switch', default: true },
- { key: 'user_online_s', label: '显示在线状态', type: 'switch', default: true },
- { key: 'user_last_active_s', label: '显示最后活跃', type: 'switch', default: true },
+ { key: 'avatar_default_img', label: '用户默认头像', type: 'media', default: `${themeRoot}/assets/img/avatar-default.png`, help: '建议尺寸 100×100。' },
+ { key: 'user_cover_upload_s', label: '允许用户上传封面', type: 'switch', default: true },
+ { key: 'user_cover_img', label: '用户默认封面', type: 'media', default: `${themeRoot}/assets/img/user_t.jpg`, help: '建议尺寸 1000×400;用户未设置封面时显示此图。' },
+ { key: 'user_desc_std', label: '用户默认签名', type: 'input', default: '这家伙很懒,什么都没有写...', help: '用户未设置个人简介时显示的默认文案。' },
],
},
],
diff --git a/assets/admin/src/schema/sections/users/index.js b/assets/admin/src/schema/sections/users/index.js
index 90e56c0c..023dae92 100644
--- a/assets/admin/src/schema/sections/users/index.js
+++ b/assets/admin/src/schema/sections/users/index.js
@@ -7,7 +7,6 @@ import { oauthSchema } from './oauth.js'
import { notificationsSchema } from './notifications.js'
import { creditsSchema } from './credits.js'
import { featuresSchema } from './features.js'
-import { signinSchema } from './signin.js'
import { verifySchema } from './verify.js'
import { levelSchema } from './level.js'
import { badgeSchema } from './badge.js'
@@ -21,7 +20,6 @@ export const usersSchemas = {
notifications: notificationsSchema,
credits: creditsSchema,
features: featuresSchema,
- signin: signinSchema,
verify: verifySchema,
level: levelSchema,
badge: badgeSchema,
diff --git a/assets/admin/src/schema/sections/users/signin.js b/assets/admin/src/schema/sections/users/signin.js
deleted file mode 100644
index 88dacaa3..00000000
--- a/assets/admin/src/schema/sections/users/signin.js
+++ /dev/null
@@ -1,27 +0,0 @@
-export const signinSchema = {
- key: 'users.signin',
- title: '签到奖励',
- icon: 'fa fas fa-calendar-check',
- description: '配置签到规则、连续签到奖励和签到页面。',
- defaults: {
- signin_enabled: true,
- signin_reward: 1,
- signin_consecutive_bonus: true,
- signin_consecutive_days: 7,
- signin_bonus_reward: 5,
- },
- groups: [
- {
- key: 'rules',
- title: '签到规则',
- icon: 'fa fas fa-calendar',
- fields: [
- { key: 'signin_enabled', label: '启用签到', type: 'switch', default: true },
- { key: 'signin_reward', label: '每日签到奖励', type: 'number', default: 1, min: 0, max: 100, dependsOn: { key: 'signin_enabled', value: true } },
- { key: 'signin_consecutive_bonus', label: '连续签到加成', type: 'switch', default: true, dependsOn: { key: 'signin_enabled', value: true } },
- { key: 'signin_consecutive_days', label: '加成周期 (天)', type: 'number', default: 7, min: 2, max: 30, dependsOn: { key: 'signin_consecutive_bonus', value: true } },
- { key: 'signin_bonus_reward', label: '周期奖励', type: 'number', default: 5, min: 1, max: 100, dependsOn: { key: 'signin_consecutive_bonus', value: true } },
- ],
- },
- ],
-}
diff --git a/assets/js/core/modal-runtime.js b/assets/js/core/modal-runtime.js
index 8258e1fa..4393709a 100644
--- a/assets/js/core/modal-runtime.js
+++ b/assets/js/core/modal-runtime.js
@@ -409,13 +409,6 @@
return window.AksrtModalCache.invalidate(tags);
}
- function rolloutEnabled(kind) {
- var config = window.aksrtData || window.aksrt || {};
- var rollout = config.frontendModalRollout;
- if (!rollout || typeof rollout[kind] === "undefined") return true;
- return rollout[kind] === true;
- }
-
window.AksrtModalRuntime = {
open: open,
openRemote: openRemote,
@@ -424,7 +417,6 @@
registerLoader: registerLoader,
openLoader: openLoader,
invalidate: invalidate,
- isRolloutEnabled: rolloutEnabled,
getActive: function () { return active ? active.shell : null; },
getState: function () { return active ? active.state : null; },
};
diff --git a/assets/js/credits.js b/assets/js/credits.js
index 2d65f064..0805a35e 100644
--- a/assets/js/credits.js
+++ b/assets/js/credits.js
@@ -97,7 +97,7 @@
// ===== VIP 积分兑换 =====
function initVipPointsExchange() {
var root = document.querySelector('[data-aksrt-vip-points]');
- if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
+ if (!root) return;
var tip = root.querySelector('[data-aksrt-vip-points-tip]');
var busy = false;
var coordinator = window.AksrtVipPurchaseCoordinator;
@@ -139,7 +139,7 @@
// ===== VIP 开通 =====
function initVipShop() {
var root = document.querySelector('[data-aksrt-vip-shop]');
- if (!root || !window.AksrtModalRuntime.isRolloutEnabled('vip')) return;
+ if (!root) return;
var tip = root.querySelector('[data-aksrt-vip-tip]');
var busy = false;
var coordinator = window.AksrtVipPurchaseCoordinator;
diff --git a/assets/js/order-center.js b/assets/js/order-center.js
index 3370bdeb..0a108347 100644
--- a/assets/js/order-center.js
+++ b/assets/js/order-center.js
@@ -293,7 +293,7 @@
}
function initRoot(root) {
- if (instances.has(root) || !window.AksrtModalRuntime.isRolloutEnabled("order")) return;
+ if (instances.has(root)) return;
var state = {
root: root,
detailOrderNumber: "",
diff --git a/assets/js/pay-vip.js b/assets/js/pay-vip.js
index c8590390..5e7fc54a 100644
--- a/assets/js/pay-vip.js
+++ b/assets/js/pay-vip.js
@@ -473,10 +473,6 @@
}
function vipPointsPay() {
- if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) {
- notyf("会员兑换暂不可用", "danger");
- return;
- }
window.AksrtModalRuntime.openRemote({
url: "/pay/vip/modal",
query: { vip_level: 1, mode: "points" },
@@ -505,10 +501,6 @@
}
function vipPay(trigger) {
- if (!window.AksrtModalRuntime.isRolloutEnabled("vip")) {
- notyf("会员购买暂不可用", "danger");
- return;
- }
var level = trigger
? parseInt(trigger.getAttribute("vip-level") || trigger.getAttribute("data-vip-level") || "1", 10) || 1
: 1;
diff --git a/assets/js/routes/user-center-auth.js b/assets/js/routes/user-center-auth.js
index c8edce11..33bfc469 100644
--- a/assets/js/routes/user-center-auth.js
+++ b/assets/js/routes/user-center-auth.js
@@ -318,7 +318,6 @@
}
function open() {
- if (!runtime.isRolloutEnabled('identity')) return;
stopPolling();
abortCurrent();
abortRefresh();
diff --git a/functions.php b/functions.php
index 72f0359b..0b45a479 100644
--- a/functions.php
+++ b/functions.php
@@ -351,11 +351,6 @@ if (class_exists('\AKSRT\Core\StatsTracker')) {
\AKSRT\Core\StatsTracker::init();
}
-// Email rollback window (24h): user list row action + admin-post handler.
-if (class_exists('\AKSRT\Auth\EmailRollback')) {
- \AKSRT\Auth\EmailRollback::get_instance()->init();
-}
-
// WordPress 原生"用户"菜单:积分管理
if (class_exists('\AKSRT\User\CreditsAdmin')) {
\AKSRT\User\CreditsAdmin::get_instance()->init();
diff --git a/image.php b/image.php
index 69bc6b34..6c109c68 100644
--- a/image.php
+++ b/image.php
@@ -7,87 +7,9 @@
defined('ABSPATH') || exit;
-$system_tools = class_exists('\AKSRT\Settings\Settings')
- ? \AKSRT\Settings\Settings::get_system_tools_settings()
- : ['close_attachment_page' => true];
-
-if (!empty($system_tools['close_attachment_page'])) {
- $attachment_id = get_queried_object_id();
- $parent_id = (int) wp_get_post_parent_id($attachment_id);
- $redirect_url = $parent_id > 0 ? get_permalink($parent_id) : home_url('/');
-
- wp_safe_redirect($redirect_url, 301);
- exit;
-}
-
-get_header();
-
$attachment_id = get_queried_object_id();
$parent_id = (int) wp_get_post_parent_id($attachment_id);
-$image_url = wp_get_attachment_url($attachment_id);
-$image_alt = trim((string) get_post_meta($attachment_id, '_wp_attachment_image_alt', true));
-$image_caption = wp_get_attachment_caption($attachment_id);
-$image_title = get_the_title($attachment_id);
-$parent_title = $parent_id > 0 ? get_the_title($parent_id) : '';
-?>
+$redirect_url = $parent_id > 0 ? get_permalink($parent_id) : home_url('/');
-
-
-
-
-
-
-user_email);
+ }
+ if ($subject instanceof WP_Comment && is_email((string) $subject->comment_author_email)) {
+ return sanitize_email((string) $subject->comment_author_email);
+ }
+ if (is_object($subject) && isset($subject->comment_author_email) && is_email((string) $subject->comment_author_email)) {
+ return sanitize_email((string) $subject->comment_author_email);
+ }
+ if (is_string($subject) && is_email($subject)) {
+ return sanitize_email($subject);
+ }
+
+ $user_id = aksrt_resolve_avatar_user_id($subject);
+ $user = $user_id > 0 ? get_userdata($user_id) : false;
+ return $user instanceof WP_User ? sanitize_email((string) $user->user_email) : '';
+ }
+}
+
+if (!function_exists('aksrt_get_user_feature_settings')) {
+ /**
+ * Return the canonical user media and signature settings.
+ *
+ * @return array{avatar_upload_s:bool,avatar_external:string,avatar_default_img:string,user_cover_upload_s:bool,user_cover_img:string,user_desc_std:string}
+ */
+ function aksrt_get_user_feature_settings(): array {
+ if (class_exists('\AKSRT\Settings\Settings')) {
+ return \AKSRT\Settings\Settings::get_user_feature_settings();
+ }
+
+ return [
+ 'avatar_upload_s' => true,
+ 'avatar_external' => '',
+ 'avatar_default_img' => get_theme_file_uri('assets/img/avatar-default.png'),
+ 'user_cover_upload_s' => true,
+ 'user_cover_img' => get_theme_file_uri('assets/img/user_t.jpg'),
+ 'user_desc_std' => '这家伙很懒,什么都没有写...',
+ ];
+ }
+}
+
if (!function_exists('aksrt_get_default_avatar_url')) {
/**
* Return the bundled avatar used when a user has no custom image.
*/
function aksrt_get_default_avatar_url(): string {
+ $settings = aksrt_get_user_feature_settings();
return (string) apply_filters(
'aksrt_default_avatar_url',
- get_theme_file_uri('assets/img/user/default-avatar.svg')
+ (string) $settings['avatar_default_img']
);
}
}
+if (!function_exists('aksrt_get_external_avatar_url')) {
+ /**
+ * Return the configured external avatar for a subject.
+ *
+ * @param mixed $subject User subject.
+ */
+ function aksrt_get_external_avatar_url($subject, int $size = 96): string {
+ $settings = aksrt_get_user_feature_settings();
+ $hosts = [
+ 'gravatar' => 'https://www.gravatar.com/avatar/',
+ 'weavatar' => 'https://weavatar.com/avatar/',
+ 'cravatar' => 'https://cn.cravatar.com/avatar/',
+ 'geekzu' => 'https://sdn.geekzu.org/avatar/',
+ 'loli' => 'https://gravatar.loli.net/avatar/',
+ 'sep' => 'https://cdn.sep.cc/avatar/',
+ 'v2ex' => 'https://cdn.v2ex.com/gravatar/',
+ ];
+ $host = $hosts[(string) $settings['avatar_external']] ?? '';
+ $email = aksrt_resolve_avatar_email($subject);
+ if ($host === '' || $email === '') {
+ return '';
+ }
+
+ $size = max(1, min(2048, $size));
+ return add_query_arg([
+ 's' => $size,
+ 'd' => aksrt_get_default_avatar_url(),
+ ], $host . hash('sha256', strtolower(trim($email))));
+ }
+}
+
if (!function_exists('aksrt_get_custom_avatar_url')) {
/**
* Return the locally uploaded avatar URL.
@@ -91,6 +171,10 @@ if (!function_exists('aksrt_get_avatar_url')) {
if ($custom !== '') {
return $custom;
}
+ $external = aksrt_get_external_avatar_url($subject, $size);
+ if ($external !== '') {
+ return $external;
+ }
return aksrt_get_default_avatar_url();
}
}
@@ -133,9 +217,7 @@ if (!function_exists('aksrt_get_avatar_html')) {
}
add_filter('get_avatar_data', static function (array $args, $subject): array {
- $user_id = aksrt_resolve_avatar_user_id($subject);
- $custom = aksrt_get_custom_avatar_url($user_id, (int) ($args['size'] ?? 96));
- $args['url'] = $custom !== '' ? $custom : aksrt_get_default_avatar_url();
+ $args['url'] = aksrt_get_avatar_url($subject, ['size' => (int) ($args['size'] ?? 96)]);
$args['found_avatar'] = true;
return $args;
}, 20, 2);
@@ -151,7 +233,37 @@ if (!function_exists('aksrt_is_avatar_upload_enabled')) {
* Whether local avatar uploads are enabled by attachment policy.
*/
function aksrt_is_avatar_upload_enabled(): bool {
- return function_exists('aksrt_user_can') && aksrt_user_can('upload_avatar', get_current_user_id());
+ $settings = aksrt_get_user_feature_settings();
+ return !empty($settings['avatar_upload_s']);
+ }
+}
+
+if (!function_exists('aksrt_is_user_cover_upload_enabled')) {
+ function aksrt_is_user_cover_upload_enabled(): bool {
+ $settings = aksrt_get_user_feature_settings();
+ return !empty($settings['user_cover_upload_s']);
+ }
+}
+
+if (!function_exists('aksrt_get_user_cover_url')) {
+ function aksrt_get_user_cover_url(int $user_id): string {
+ $custom = $user_id > 0 ? esc_url_raw((string) get_user_meta($user_id, 'aksrt_background_image', true)) : '';
+ if ($custom !== '') {
+ return $custom;
+ }
+ $settings = aksrt_get_user_feature_settings();
+ return (string) $settings['user_cover_img'];
+ }
+}
+
+if (!function_exists('aksrt_get_user_description')) {
+ function aksrt_get_user_description(int $user_id): string {
+ $description = $user_id > 0 ? trim((string) get_user_meta($user_id, 'description', true)) : '';
+ if ($description !== '') {
+ return $description;
+ }
+ $settings = aksrt_get_user_feature_settings();
+ return (string) $settings['user_desc_std'];
}
}
diff --git a/inc/helpers/aksrt-graphic-card.php b/inc/helpers/aksrt-graphic-card.php
index 393262cb..c54a6fb4 100644
--- a/inc/helpers/aksrt-graphic-card.php
+++ b/inc/helpers/aksrt-graphic-card.php
@@ -261,10 +261,7 @@ if (!function_exists('aksrt_author_minicard')) :
}
}
- $desc = trim((string) get_user_meta($user_id, 'description', true));
- if ($desc === '') {
- $desc = __('这家伙很懒,什么都没有写…', 'aksrt');
- }
+ $desc = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($user_id) : trim((string) get_user_meta($user_id, 'description', true));
$blur = '';
if ($blur_bg) {
diff --git a/inc/helpers/aksrt-ip-geo.php b/inc/helpers/aksrt-ip-geo.php
index 8eb18347..3d9e8dfa 100644
--- a/inc/helpers/aksrt-ip-geo.php
+++ b/inc/helpers/aksrt-ip-geo.php
@@ -5,7 +5,6 @@
* Gates:
* master: ip_geo_enabled
* surface: ip_geo_display ∈ {comment, user_home, user_center, post}
- * users: user_city_s (+ user_city_type)
* comments: comment_city_s (+ comment_city_type) via comments/geo.php
*
* Meta:
@@ -74,9 +73,6 @@ if (!function_exists('aksrt_ip_geo_display_enabled')) :
if (!in_array($location, aksrt_ip_geo_display_list(), true)) {
return false;
}
- if (in_array($location, ['user_home', 'user_center'], true) && !aksrt_user_city_enabled()) {
- return false;
- }
return true;
}
endif;
@@ -90,37 +86,6 @@ if (!function_exists('aksrt_ip_geo_format_key')) :
}
endif;
-if (!function_exists('aksrt_user_city_enabled')) :
- /**
- * Users feature: show city on user home / user center.
- * Default true when key absent (legacy parity for new installs).
- */
- function aksrt_user_city_enabled(): bool {
- if (!class_exists('\AKSRT\Settings\Settings')) {
- return true;
- }
- $all = \AKSRT\Settings\Settings::get_all();
- if (!array_key_exists('user_city_s', $all)) {
- return true;
- }
- return !empty($all['user_city_s']);
- }
-endif;
-
-if (!function_exists('aksrt_user_city_type')) :
- function aksrt_user_city_type(): string {
- if (!class_exists('\AKSRT\Settings\Settings')) {
- return aksrt_ip_geo_format_key();
- }
- $all = \AKSRT\Settings\Settings::get_all();
- $type = sanitize_key((string) ($all['user_city_type'] ?? ''));
- if (in_array($type, ['province_city', 'province', 'country_province'], true)) {
- return $type;
- }
- return aksrt_ip_geo_format_key();
- }
-endif;
-
if (!function_exists('aksrt_normalize_geo_addr')) :
/**
* @param mixed $raw Meta value.
@@ -229,8 +194,7 @@ if (!function_exists('aksrt_store_user_addr')) :
if ($user_id <= 0 || !aksrt_ip_geo_master_enabled()) {
return;
}
- $need = aksrt_user_city_enabled()
- || in_array('post', aksrt_ip_geo_display_list(), true)
+ $need = in_array('post', aksrt_ip_geo_display_list(), true)
|| in_array('user_home', aksrt_ip_geo_display_list(), true)
|| in_array('user_center', aksrt_ip_geo_display_list(), true);
if (!$need) {
@@ -286,7 +250,7 @@ if (!function_exists('aksrt_get_user_city_badge_html')) :
if ($user_id <= 0 || !aksrt_ip_geo_display_enabled($location)) {
return '';
}
- return aksrt_get_geo_badge_html(aksrt_get_user_addr($user_id), aksrt_user_city_type(), $class);
+ return aksrt_get_geo_badge_html(aksrt_get_user_addr($user_id), aksrt_ip_geo_format_key(), $class);
}
endif;
@@ -304,8 +268,7 @@ if (!function_exists('aksrt_get_post_author_city_badge_html')) :
if ($author_id <= 0) {
return '';
}
- $type = aksrt_user_city_enabled() ? aksrt_user_city_type() : aksrt_ip_geo_format_key();
- return aksrt_get_geo_badge_html(aksrt_get_user_addr($author_id), $type, $class);
+ return aksrt_get_geo_badge_html(aksrt_get_user_addr($author_id), aksrt_ip_geo_format_key(), $class);
}
endif;
diff --git a/inc/helpers/aksrt-single-render.php b/inc/helpers/aksrt-single-render.php
index f6414b04..7915958f 100644
--- a/inc/helpers/aksrt-single-render.php
+++ b/inc/helpers/aksrt-single-render.php
@@ -330,7 +330,7 @@ if (!function_exists('aksrt_single_render_author_card')) :
}
$author_url = function_exists('aksrt_get_user_home_url') ? aksrt_get_user_home_url($author_id) : get_author_posts_url($author_id);
$author_name = get_the_author();
- $bio = trim((string) get_the_author_meta('description'));
+ $bio = function_exists('aksrt_get_user_description') ? aksrt_get_user_description($author_id) : trim((string) get_the_author_meta('description'));
$name_html = function_exists('aksrt_get_user_name')
? aksrt_get_user_name([
'id' => $author_id,
diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php
index 9df9bac5..f86d0fd9 100644
--- a/inc/helpers/aksrt-user-center.php
+++ b/inc/helpers/aksrt-user-center.php
@@ -137,11 +137,11 @@ if (!function_exists('aksrt_get_user_center_tab_url')) {
}
}
-if (!function_exists('aksrt_normalize_user_center_tabs')) {
+if (!function_exists('aksrt_normalize_user_center_panels')) {
/**
* Normalize the dashboard tab contract.
*/
- function aksrt_normalize_user_center_tabs(array $tabs): array {
+ function aksrt_normalize_user_center_panels(array $tabs): array {
$normalized = [];
foreach ($tabs as $key => $tab) {
$key = sanitize_key((string) $key);
@@ -296,7 +296,7 @@ if (!function_exists('aksrt_user_center_resolve_tabs')) {
'favorites' => ['label' => __('我的收藏', 'aksrt'), 'icon' => 'star'],
'settings' => ['label' => __('账号设置', 'aksrt'), 'icon' => 'cog'],
];
- $tabs = aksrt_normalize_user_center_tabs($tabs);
+ $tabs = aksrt_normalize_user_center_panels($tabs);
$keys = array_keys($tabs);
$default = in_array('vip', $keys, true)
? 'vip'
@@ -420,7 +420,7 @@ if (!function_exists('aksrt_user_center_cover_upload_config')) {
'imageId' => 'aksrt-cover-img',
'fieldName' => 'background',
'maxSize' => $max_size,
- 'fallbackImage' => AKSRT_URI . '/assets/img/person_background.png',
+ 'fallbackImage' => function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url(get_current_user_id()) : get_theme_file_uri('assets/img/user_t.jpg'),
'idleText' => __('修改背景图', 'aksrt'),
'loadingText' => __('上传中…', 'aksrt'),
'tooLargeText' => sprintf(__('图片大小不能超过 %dMB', 'aksrt'), (int) ($max_size / MB_IN_BYTES)),
@@ -459,9 +459,9 @@ if (!function_exists('aksrt_get_user_center_render_context')) {
$comment_count = (int) ($dashboard['comment_count'] ?? get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']));
$favorite_post_ids = $dashboard['favorite_post_ids'] ?? [];
$favorite_count = (int) ($dashboard['favorite_count'] ?? count($favorite_post_ids));
- $user_background_url = (string) ($dashboard['background_url'] ?? get_user_meta($user_id, 'aksrt_background_image', true));
- $allow_avatar_upload = function_exists('aksrt_user_can') && aksrt_user_can('upload_avatar', $user_id);
- $allow_bg_upload = function_exists('aksrt_user_can') && aksrt_user_can('upload_profile_background', $user_id);
+ $user_background_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
+ $allow_avatar_upload = function_exists('aksrt_is_avatar_upload_enabled') && aksrt_is_avatar_upload_enabled();
+ $allow_bg_upload = function_exists('aksrt_is_user_cover_upload_enabled') && aksrt_is_user_cover_upload_enabled();
$messages_enabled = !class_exists('\AKSRT\Settings\Settings') || \AKSRT\Settings\Settings::message_enabled();
$follower_count = class_exists('\AKSRT\User\Follow')
? \AKSRT\User\Follow::get_instance()->get_follower_count($user_id)
diff --git a/inc/helpers/aksrt-user-dashboard.php b/inc/helpers/aksrt-user-dashboard.php
index b371121e..c9d10c13 100644
--- a/inc/helpers/aksrt-user-dashboard.php
+++ b/inc/helpers/aksrt-user-dashboard.php
@@ -67,7 +67,7 @@ function aksrt_get_user_dashboard_view(int $user_id, array $args = []): array {
? aksrt_get_visible_user_profile_details($user_id, $viewer_id)
: ['gender' => '', 'website' => '', 'qq' => '', 'wechat' => '', 'github' => ''];
- $background_url = get_user_meta($user_id, 'aksrt_background_image', true) ?: '';
+ $background_url = function_exists('aksrt_get_user_cover_url') ? aksrt_get_user_cover_url($user_id) : '';
$credits_enabled = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::credits_enabled();
$credit_balance = $credits_enabled && $is_own_profile && function_exists('aksrt_user_credits')
diff --git a/inc/helpers/header/aksrt-header-render.php b/inc/helpers/header/aksrt-header-render.php
index f24f35d7..17129cb0 100644
--- a/inc/helpers/header/aksrt-header-render.php
+++ b/inc/helpers/header/aksrt-header-render.php
@@ -67,10 +67,7 @@ if (!function_exists('aksrt_get_header_nav_context')) :
$current_user = wp_get_current_user();
$user_id = (int) $current_user->ID;
- $user_desc = $user_id > 0 ? (string) get_user_meta($user_id, 'description', true) : '';
- if ($user_desc === '' && $user_id > 0) {
- $user_desc = (string) $current_user->user_email;
- }
+ $user_desc = $user_id > 0 && function_exists('aksrt_get_user_description') ? aksrt_get_user_description($user_id) : '';
$ctx = [
'settings' => $settings,
diff --git a/inc/helpers/user-center-renderers/profile-header.php b/inc/helpers/user-center-renderers/profile-header.php
index ad6aaf16..f6218f44 100644
--- a/inc/helpers/user-center-renderers/profile-header.php
+++ b/inc/helpers/user-center-renderers/profile-header.php
@@ -12,7 +12,7 @@ extract($context, EXTR_SKIP); // phpcs:ignore WordPress.PHP.DontExtract.extract_
?>