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 @@ - - - - - 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 @@ - - - - - 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 @@ - - - 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 @@ - - - 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_ ?>
- +