From 20c19dfe3164b6ed210ff349f1d302014d816f60 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jul 2026 09:38:45 +0800 Subject: [PATCH 1/3] feat(user): complete growth level chain --- assets/admin/src/pages/users/UsersLevel.vue | 33 +- .../src/schema/sections/users/features.js | 4 +- .../admin/src/schema/sections/users/level.js | 12 +- assets/css/routes/profile/layout.css | 8 + assets/css/routes/user-center.css | 1 + assets/css/routes/user-center/credits.css | 32 -- assets/css/routes/user-center/level.css | 91 ++++ assets/img/user-level-1.png | Bin 0 -> 11183 bytes assets/img/user-level-10.png | Bin 0 -> 12681 bytes assets/img/user-level-2.png | Bin 0 -> 13327 bytes assets/img/user-level-3.png | Bin 0 -> 10913 bytes assets/img/user-level-4.png | Bin 0 -> 12441 bytes assets/img/user-level-5.png | Bin 0 -> 12126 bytes assets/img/user-level-6.png | Bin 0 -> 12192 bytes assets/img/user-level-7.png | Bin 0 -> 11013 bytes assets/img/user-level-8.png | Bin 0 -> 13294 bytes assets/img/user-level-9.png | Bin 0 -> 12678 bytes functions.php | 3 + inc/helpers/aksrt-identity.php | 6 +- inc/helpers/aksrt-single-components.php | 2 +- inc/helpers/aksrt-user-center.php | 3 +- .../user-center-renderers/credits-level.php | 107 ---- .../user-center-renderers/profile-auth.php | 2 +- .../user-center-renderers/profile-credits.php | 3 - .../user-center-renderers/profile-level.php | 109 ++++ .../user-center-renderers/sidebar-nav.php | 4 +- src/AKSRT/Admin/Schema/SchemaUsers.php | 12 +- src/AKSRT/Admin/SchemaProvider.php | 1 + src/AKSRT/Forum/BoardServicePart2.php | 9 +- src/AKSRT/Settings/Capabilities.php | 4 +- src/AKSRT/Settings/UserRuntime.php | 13 +- src/AKSRT/User/BadgeAutoAward.php | 88 +-- src/AKSRT/User/Badges.php | 8 +- src/AKSRT/User/Center.php | 23 +- src/AKSRT/User/Home.php | 28 +- src/AKSRT/User/Level.php | 512 ++++++++++-------- src/AKSRT/User/LevelAdmin.php | 136 +++++ src/AKSRT/User/Metrics.php | 109 ---- templates/parts/user/author-hero.php | 9 +- 39 files changed, 739 insertions(+), 633 deletions(-) create mode 100644 assets/css/routes/user-center/level.css create mode 100644 assets/img/user-level-1.png create mode 100644 assets/img/user-level-10.png create mode 100644 assets/img/user-level-2.png create mode 100644 assets/img/user-level-3.png create mode 100644 assets/img/user-level-4.png create mode 100644 assets/img/user-level-5.png create mode 100644 assets/img/user-level-6.png create mode 100644 assets/img/user-level-7.png create mode 100644 assets/img/user-level-8.png create mode 100644 assets/img/user-level-9.png delete mode 100644 inc/helpers/user-center-renderers/credits-level.php create mode 100644 inc/helpers/user-center-renderers/profile-level.php create mode 100644 src/AKSRT/User/LevelAdmin.php diff --git a/assets/admin/src/pages/users/UsersLevel.vue b/assets/admin/src/pages/users/UsersLevel.vue index 9a216053..def142e8 100644 --- a/assets/admin/src/pages/users/UsersLevel.vue +++ b/assets/admin/src/pages/users/UsersLevel.vue @@ -33,9 +33,6 @@ - - - @@ -101,7 +98,6 @@ const saving = ref(false) const form = reactive({ user_level_s: true, user_level_public_display: true, - user_level_prefix: 'Lv.', user_level_max: 10, user_level_opt: defaultLevelOpt(10), user_integral_opt: defaultIntegralOpt(), @@ -116,7 +112,7 @@ const levelRows = computed(() => { for (let i = 1; i <= max; i++) { rows.push({ level: i, - name: form.user_level_opt['name_' + i] ?? form.user_level_prefix + i, + name: form.user_level_opt['name_' + i] ?? 'LV' + i, upgrade: Number(form.user_level_opt['upgrade_integral_' + i] ?? 0), }) } @@ -137,6 +133,19 @@ const levelColumns = [ }, }), }, + { + title: '等级图标 URL', + key: 'icon', + render: (row) => + h(NInput, { + value: form.user_level_opt['icon_img_' + row.level] ?? '', + size: 'small', + placeholder: row.level <= 10 ? '留空使用主题内置图标' : '请输入图片 URL', + onUpdateValue: (v) => { + form.user_level_opt['icon_img_' + row.level] = v + }, + }), + }, { title: '升级所需累计经验', key: 'upgrade', @@ -173,7 +182,10 @@ function onMaxChange(val) { next['upgrade_integral_' + i] = filled['upgrade_integral_' + i] } if (next['name_' + i] === undefined) { - next['name_' + i] = form.user_level_prefix + i + next['name_' + i] = 'LV' + i + } + if (next['icon_img_' + i] === undefined) { + next['icon_img_' + i] = '' } } // drop extras @@ -214,6 +226,9 @@ function normalizeLevelOpt(raw, max) { if (raw['name_' + i] !== undefined) { base['name_' + i] = String(raw['name_' + i] || '') } + if (raw['icon_img_' + i] !== undefined) { + base['icon_img_' + i] = String(raw['icon_img_' + i] || '') + } } return base } @@ -223,10 +238,9 @@ async function load() { try { const schemaRes = await api.getSchemaPage('users', 'level') const d = extractData(schemaRes) - const enabled = d.user_level_s !== undefined ? !!d.user_level_s : !!d.user_level_enabled + const enabled = d.user_level_s !== false form.user_level_s = enabled form.user_level_public_display = d.user_level_public_display !== false - form.user_level_prefix = d.user_level_prefix || 'Lv.' form.user_level_max = Math.max(3, Math.min(30, Number(d.user_level_max || 10))) form.user_level_notify = d.user_level_notify !== false form.user_level_opt = normalizeLevelOpt(d.user_level_opt, form.user_level_max) @@ -244,12 +258,9 @@ async function handleSave() { const max = Math.max(3, Math.min(30, Number(form.user_level_max) || 10)) const payload = { user_level_s: !!form.user_level_s, - user_level_enabled: !!form.user_level_s, user_level_public_display: !!form.user_level_public_display, - user_level_prefix: form.user_level_prefix || 'Lv.', user_level_max: max, user_level_notify: !!form.user_level_notify, - user_level_source: 'integral', user_level_opt: normalizeLevelOpt(form.user_level_opt, max), user_integral_opt: normalizeIntegral(form.user_integral_opt), } diff --git a/assets/admin/src/schema/sections/users/features.js b/assets/admin/src/schema/sections/users/features.js index 84b663ad..7598d059 100644 --- a/assets/admin/src/schema/sections/users/features.js +++ b/assets/admin/src/schema/sections/users/features.js @@ -4,7 +4,7 @@ export const featuresSchema = { icon: 'fas fa-user-cog', description: '配置用户中心和个人主页的功能模块。', defaults: { - user_center_tabs: 'profile,posts,comments,favorites,vip,credits,auth', + user_center_tabs: 'profile,level,posts,comments,favorites,vip,credits,auth', user_cover_upload: true, user_social_links: true, user_desc_std: '这家伙很懒…', @@ -24,7 +24,7 @@ export const featuresSchema = { title: '用户中心', icon: 'fas fa-user-shield', fields: [ - { key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,posts,comments,favorites,vip,credits,auth', help: '逗号分隔。auth=官方认证;vip、订单和认证会按功能开关自动注入' }, + { key: 'user_center_tabs', label: '用户中心 Tab', type: 'input', default: 'profile,level,posts,comments,favorites,vip,credits,auth', help: '逗号分隔。level=我的等级;auth=官方认证;等级、VIP、订单和认证会按功能开关自动注入' }, { key: 'user_cover_upload', label: '封面上传', type: 'switch', default: true }, { key: 'user_social_links', label: '社交链接', type: 'switch', default: true }, { key: 'user_desc_std', label: '默认签名', type: 'input', default: '这家伙很懒…', help: '用户未设置个人简介时显示的默认文案' }, diff --git a/assets/admin/src/schema/sections/users/level.js b/assets/admin/src/schema/sections/users/level.js index f87c8543..22003336 100644 --- a/assets/admin/src/schema/sections/users/level.js +++ b/assets/admin/src/schema/sections/users/level.js @@ -24,9 +24,9 @@ export const INTEGRAL_CATALOG = [ export function defaultLevelOpt(max = 10) { const opt = {} for (let i = 1; i <= max; i++) { - // Rough Zibll-like curve: LV1=0, then grows - opt['upgrade_integral_' + i] = i === 1 ? 0 : Math.round(20 * Math.pow(i - 1, 1.6)) - opt['name_' + i] = 'Lv.' + i + opt['upgrade_integral_' + i] = i === 1 ? 0 : (i - 1) * 500 * i + opt['name_' + i] = 'LV' + i + opt['icon_img_' + i] = '' } return opt } @@ -47,7 +47,6 @@ export const levelSchema = { defaults: { user_level_s: true, user_level_public_display: true, - user_level_prefix: 'Lv.', user_level_max: 10, user_level_opt: defaultLevelOpt(10), user_integral_opt: defaultIntegralOpt(), @@ -60,7 +59,6 @@ export const levelSchema = { fields: [ { key: 'user_level_s', label: '启用用户等级', type: 'switch', default: true }, { key: 'user_level_public_display', label: '公开页显示等级', type: 'switch', default: true, dependsOn: { key: 'user_level_s', value: true } }, - { key: 'user_level_prefix', label: '等级前缀', type: 'input', default: 'Lv.', dependsOn: { key: 'user_level_s', value: true } }, { key: 'user_level_max', label: '最高等级', type: 'number', default: 10, min: 3, max: 30, dependsOn: { key: 'user_level_s', value: true } }, { key: 'user_level_notify', label: '升级通知', type: 'switch', default: true, dependsOn: { key: 'user_level_s', value: true } }, ], @@ -69,14 +67,14 @@ export const levelSchema = { key: 'level_opt', title: '等级升级阈值', fields: [ - { key: 'user_level_opt', label: '升级经验配置', type: 'json', default: defaultLevelOpt(10), help: 'upgrade_integral_N 为升到 N 级所需累计经验' }, + { key: 'user_level_opt', label: '升级经验配置', type: 'json', sanitizeType: 'array', default: defaultLevelOpt(10), help: 'upgrade_integral_N 为升到 N 级所需累计经验' }, ], }, { key: 'integral', title: '经验值得分', fields: [ - { key: 'user_integral_opt', label: '经验值获取', type: 'json', default: defaultIntegralOpt(), help: 'day_max + 各行为得分;对应功能未开则将分值设 0' }, + { key: 'user_integral_opt', label: '经验值获取', type: 'json', sanitizeType: 'array', default: defaultIntegralOpt(), help: 'day_max + 各行为得分;对应功能未开则将分值设 0' }, ], }, ], diff --git a/assets/css/routes/profile/layout.css b/assets/css/routes/profile/layout.css index ff255199..42d9a1f7 100644 --- a/assets/css/routes/profile/layout.css +++ b/assets/css/routes/profile/layout.css @@ -535,6 +535,14 @@ color: #fff; } +.aksrt-user-level-icon { + display: inline-block; + width: auto; + height: 1.15em; + max-width: 6em; + object-fit: contain; + vertical-align: -.18em; +} /* 用户中心 layout 间距;双栏 / sticky 见 user-center/shell.css(body.aksrt-user-center)。 */ .aksrt-profile__layout { diff --git a/assets/css/routes/user-center.css b/assets/css/routes/user-center.css index bab4e92d..95bc6463 100644 --- a/assets/css/routes/user-center.css +++ b/assets/css/routes/user-center.css @@ -1,4 +1,5 @@ @import url("user-center/shell.css"); @import url("user-center/credits.css"); +@import url("user-center/level.css"); @import url("user-center/settings.css"); @import url("user-center/responsive.css"); diff --git a/assets/css/routes/user-center/credits.css b/assets/css/routes/user-center/credits.css index 1a97108b..500647f9 100644 --- a/assets/css/routes/user-center/credits.css +++ b/assets/css/routes/user-center/credits.css @@ -1031,38 +1031,6 @@ body.uc-modal-open { .uc-badges-empty { padding: 16px 0; text-align: center; } -/* Level / integral card */ -.uc-level-card { - margin: 16px 0; - padding: 16px; - border-radius: 12px; - background: linear-gradient(135deg, rgba(240,68,148,.12), rgba(99,102,241,.10)); - position: relative; - overflow: hidden; -} -.uc-level-card__badge { margin-top: 12px; } -.uc-level-card .progress, -.integral-progress.progress { - height: 8px; - border-radius: 999px; - background: rgba(127,127,127,.18); - overflow: hidden; -} -.uc-level-card .progress-bar { - height: 100%; - border-radius: 999px; - background: linear-gradient(90deg, #f04494, #6366f1); -} -.uc-level-growth__row { - display: grid; - grid-template-columns: 56px 1fr auto; - gap: 8px; - padding: 6px 0; - border-bottom: 1px solid rgba(127,127,127,.12); - font-size: 12px; -} -.uc-level-growth__row.is-done { opacity: .7; } -.uc-level-growth__row.is-next { font-weight: 700; color: var(--focus-color, #f04494); } .uc-points-tasks__row.is-done { opacity: .72; } .uc-points-tasks__progress-bar { height: 6px; diff --git a/assets/css/routes/user-center/level.css b/assets/css/routes/user-center/level.css new file mode 100644 index 00000000..0750e8e8 --- /dev/null +++ b/assets/css/routes/user-center/level.css @@ -0,0 +1,91 @@ +/* User level — dedicated center panel */ +.aksrt-user-level-icon { + display: inline-block; + width: auto; + height: 1.35em; + max-width: 6em; + object-fit: contain; + vertical-align: -.2em; +} +.aksrt-user-level-icon--admin { height: 22px; } + +.uc-level-page { display: grid; gap: 16px; } +.uc-level-page .zib-widget { + margin: 0; + padding: 18px; + border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); + border-radius: var(--main-radius, 10px); + background: var(--main-bg-color, var(--surface-1, #fff)); + box-shadow: 0 0 10px var(--main-shadow, rgba(116,116,116,.08)); +} +.uc-level-hero { + position: relative; + overflow: hidden; + background: linear-gradient(135deg, rgba(124,58,237,.12), rgba(236,72,153,.10)) !important; +} +.uc-level-hero::after { + content: ''; + position: absolute; + width: 220px; + height: 220px; + right: -80px; + top: -100px; + border-radius: 50%; + background: rgba(255,255,255,.2); + pointer-events: none; +} +.uc-level-hero__identity { display: flex; align-items: center; gap: 14px; position: relative; z-index: 1; } +.uc-level-hero__identity p { margin: 0 0 3px; color: var(--muted-2-color, #8b9198); font-size: 12px; } +.uc-level-hero__identity h2 { margin: 0; font-size: 22px; } +.uc-level-hero__icon { + display: grid; + place-items: center; + width: 62px; + height: 62px; + border-radius: 16px; + background: rgba(255,255,255,.62); + box-shadow: inset 0 0 0 1px rgba(255,255,255,.55); +} +.uc-level-hero__icon .aksrt-user-level-icon { max-width: 48px; height: 42px; } +.uc-level-hero__stats { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 10px; margin: 20px 0 16px; position: relative; z-index: 1; } +.uc-level-hero__stats > div { padding: 12px; border-radius: 10px; background: rgba(255,255,255,.46); } +.uc-level-hero__stats strong, .uc-level-hero__stats span { display: block; } +.uc-level-hero__stats strong { font-size: 18px; } +.uc-level-hero__stats span { margin-top: 3px; font-size: 11px; color: var(--muted-2-color, #747b83); } +.uc-level-hero__progress { position: relative; z-index: 1; } +.uc-level-hero__progress-meta { display: flex; justify-content: space-between; margin-bottom: 7px; font-size: 12px; font-weight: 700; } +.uc-level-progress { height: 9px; overflow: hidden; border-radius: 999px; background: rgba(127,127,127,.18); } +.uc-level-progress > span { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, #7c3aed, #ec4899); } + +.uc-level-section__head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 14px; } +.uc-level-section__head h3 { margin: 0; font-size: 16px; } +.uc-level-section__head span { color: var(--muted-2-color, #8b9198); font-size: 11px; } +.uc-level-road { display: grid; grid-template-columns: repeat(auto-fit, minmax(92px, 1fr)); gap: 10px; } +.uc-level-road__item { position: relative; display: flex; flex-direction: column; align-items: center; gap: 5px; padding: 14px 8px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); border-radius: 10px; opacity: .58; text-align: center; } +.uc-level-road__item.is-done { opacity: .78; } +.uc-level-road__item.is-current { opacity: 1; border-color: #8b5cf6; box-shadow: 0 0 0 2px rgba(139,92,246,.12); } +.uc-level-road__item.is-current::after { content: '当前'; position: absolute; top: 6px; right: 6px; padding: 1px 5px; border-radius: 999px; background: #8b5cf6; color: #fff; font-size: 9px; } +.uc-level-road__icon { display: grid; place-items: center; height: 40px; } +.uc-level-road__icon img { max-width: 52px; max-height: 38px; object-fit: contain; } +.uc-level-road__icon span { font-weight: 800; color: #7c3aed; } +.uc-level-road__item strong { font-size: 12px; } +.uc-level-road__item small { color: var(--muted-2-color, #8b9198); font-size: 10px; } + +.uc-level-history-grid { display: grid; grid-template-columns: minmax(0,1.35fr) minmax(240px,.65fr); gap: 16px; } +.uc-level-ledger, .uc-level-dates { display: grid; } +.uc-level-ledger__row { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 10px; padding: 11px 0; border-bottom: 1px solid var(--color-line-soft, rgba(0,0,0,.05)); } +.uc-level-ledger__row:last-child, .uc-level-dates > div:last-child { border-bottom: 0; } +.uc-level-ledger__icon { display: grid; place-items: center; width: 34px; height: 34px; border-radius: 9px; background: rgba(124,58,237,.10); color: #7c3aed; } +.uc-level-ledger__row strong, .uc-level-ledger__row time { display: block; } +.uc-level-ledger__row strong { font-size: 13px; } +.uc-level-ledger__row time { margin-top: 2px; color: var(--muted-2-color, #8b9198); font-size: 10px; } +.uc-level-ledger__row b { color: #16a34a; font-size: 13px; } +.uc-level-dates > div { display: flex; justify-content: space-between; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--color-line-soft, rgba(0,0,0,.05)); font-size: 12px; } +.uc-level-dates span { color: #16a34a; font-weight: 700; } +.uc-level-empty { margin: 0; padding: 24px 0; color: var(--muted-2-color, #8b9198); text-align: center; } + +@media (max-width: 767px) { + .uc-level-hero__stats { grid-template-columns: 1fr; } + .uc-level-history-grid { grid-template-columns: 1fr; } + .uc-level-road { grid-template-columns: repeat(2, minmax(0,1fr)); } +} diff --git a/assets/img/user-level-1.png b/assets/img/user-level-1.png new file mode 100644 index 0000000000000000000000000000000000000000..1306b8198674d3ca03b7b47ce41ea6bcfc00ebf2 GIT binary patch literal 11183 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 N002ovPDHLkV1mZsbh`im literal 0 HcmV?d00001 diff --git a/assets/img/user-level-10.png b/assets/img/user-level-10.png new file mode 100644 index 0000000000000000000000000000000000000000..8e4bd8ec0a07bf219ff6052e05a8b0194d4458db GIT binary patch literal 12681 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 D-V3cz literal 0 HcmV?d00001 diff --git a/assets/img/user-level-2.png b/assets/img/user-level-2.png new file mode 100644 index 0000000000000000000000000000000000000000..4301a48ecd12336ee3a02c1810224eb545324a90 GIT binary patch literal 13327 zcmV+qH1NxbP)-jI5ST~I*K?hbX8GhGA>4eeU@x!f0B->dUlPCilHzpMP6BO zHZf2xCRRv0m9VSNvaZRBhM=99u}?~JN=0C2VVXHFbvrPDtf<7JpT0LObX8D%n3c3T zF>cY#<7;M>dUT{OCtzJxie+Di!@tx{MT~cIh_9=_j)}9Pp0I;{u6=l)%*fnvZhu}> zqM4P)oSDg8QlWfyw3m~{Sx%syn#+@q#g&o8X=0;)dbE&>!fRxwe|flHR-;%=pN@;a zl8wTHeYbRNtDBd{j)=Z~d9Z|lwTFSZg?_naUZ`?vs)>TTUR0ikfV^8#pjS+so0rOy zkiuYCrEO)a%E#n=cDJ9M%6oLPpq5=BlO9k&VBYmBoU6 zvwU~0$i?B8lfj&v#haPLdUdIbg}sc3yPu!MV_K<*gS2aApr4z_gMYYqaF6>&V3Aa&@!b+w)XPnE3bp#K7Y8^ZbQ?uClGzdv>L8YNuOLnUIXR>gn~}+wH)- z;N|1;qMpQrX|IiiNFaWTLaM(1Lrx>*@QLlFO;4$3r=U;okLMU!YxBnq^>- zN(2u9cz8L@jFnCEsK~#9!?3O)i+du$@b2@#U4vGa;5y+OKk0BBs zY$%$HgrWI2nKkuaw3!Mvh7KL`3kt

`FftG!5L2^2i{Jsm2kV<=6gpgic_d+y}v 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 Z=QGrVcS3V13p@Y-002ovPDHLkV1mDdcuD{O literal 0 HcmV?d00001 diff --git a/assets/img/user-level-3.png b/assets/img/user-level-3.png new file mode 100644 index 0000000000000000000000000000000000000000..34f08b9f168fb76a0eeb718e9430e45b7fc4797f GIT binary patch literal 10913 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 DmTMni literal 0 HcmV?d00001 diff --git a/assets/img/user-level-4.png b/assets/img/user-level-4.png new file mode 100644 index 0000000000000000000000000000000000000000..a8c94ea5dad0bd87ec90e31584699f34523f616a GIT binary patch literal 12441 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 literal 0 HcmV?d00001 diff --git a/assets/img/user-level-5.png b/assets/img/user-level-5.png new file mode 100644 index 0000000000000000000000000000000000000000..649043656cee7dc598889eaaa1a4f6617ef92c04 GIT binary patch literal 12126 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∨3MxP(qtIlw^34sz4;oPF zvJ>Y3#55DXtZY^qhhg{y96B_U8$K{=Qr^T7ua6-}oKX~SAR*!D{e+l{s(7vIPgFCfS`*gMfpCROoo|aiko0pGx2R6%JG9UpaLR44#%$ydz6q3P_O6GkXRg zG?QZnxjWc}fiO7`R(FB305VmqF%Xbgr8!)y2(O8y7}j`0hrO6u;m{Ed&DI&}(sfc^ zGKpvGSidPJ^JshQVZWhK-d* zLMgDhy^-8LI3NN9q6A8)2nbEmnWfc3)E+9X2nmEGpc!aSszrFhGenFJJ0bDtkpqE% z5C>6MG}j-i8`gJp?vze_5*(WKj|thn<@XQ=fEX!QR|jP)8WN7fQ#tk#37c1>T0_Dw z)H6h+vH?OcysA0Ox&;sm^n@w)z_TP5;f8>a)J96mkO|DEEc+7>DstJ>AW(`$tlUt( zseF^lNQiI1!DJk&0;uFDUcvAnxzLGu^AG*QP^`x&p6LoAP;yRCuw)@}HVk-0iZ!Hy zaC4{3oMI6*h&4SN+>v7h2q~g7%Yh)rQFIEaYcOfD7s`-nY2dcvio1oFgJ7^30|7$E z2n2;Fs)`mB2121&3=E@la;Jz}q_wDQBPgk*=gp;TT{L=@7w;0S3lN6Y~ZX^f1kqii&xR6bn@4 z2nZYGz8SGwYcV_*Azsu|)rt}n2sMT=1}4o>$^v0@&VU|M zhIdNWDpV~Nl?yS73HZkYK-35do_?4g;k-gQ;1v1!9)!WksuXos^+FFNC6uGumsGt| z#mTE3e3o584tdJyMU|w0@OJ#`$D$V(DQ;tW%jo^(%jqwO!K)ty-}43Wk~Bb+!zH&I9@dweb#rq^P3b08n`LZB`?s3ES(%{NDXn(IK|GSwts56DS5pGP@J{{vv_>6oDTXak#nnnO zbpkREH8wIvDhK~j!O*2+pU!=F#Fd^Zifd9*oV!!JFNHVh($bYa9TnZ*_Vru(bJ1wG zzjX)F(${|vlx{kr?5P%(ZJi>g3>CiacK{Li_ALYj4?{uMEGhq+qITA{YiAd-yC=TZ zx@ynW#Q)g=wO-v@=N|?6cbJbLU>J6ZR|tlrfjCV*xVe^#RvP<<%0AV!>}&3Sas8A} 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 s>elU_^#)J6MWX+IdpjH8K@ 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 } +if (class_exists('\AKSRT\User\LevelAdmin')) { + \AKSRT\User\LevelAdmin::get_instance()->init(); +} if (class_exists('\AKSRT\User\Metrics')) { \AKSRT\User\Metrics::get_instance()->init(); } diff --git a/inc/helpers/aksrt-identity.php b/inc/helpers/aksrt-identity.php index 98416aeb..1c9642b4 100644 --- a/inc/helpers/aksrt-identity.php +++ b/inc/helpers/aksrt-identity.php @@ -159,7 +159,11 @@ if (!function_exists('aksrt_get_user_name')) { if ($type === 'verify' && !empty($args['auth']) && $auth_html === '') { $auth_html = ' ' . esc_html($label) . ''; } elseif ($type === 'level' && !empty($args['level']) && $level_html === '') { - $level_html = ' ' . esc_html($label) . ''; + if ($icon_url !== '') { + $level_html = '' . esc_attr($label) . ''; + } else { + $level_html = ' ' . esc_html($label) . ''; + } } elseif ($type === 'badge' && !empty($args['medal']) && $medal_html === '' && !empty($signal['wear'])) { if ($icon_url !== '') { $medal_html = '' . esc_attr($label) . ''; diff --git a/inc/helpers/aksrt-single-components.php b/inc/helpers/aksrt-single-components.php index 549a3b79..0b8c06fc 100644 --- a/inc/helpers/aksrt-single-components.php +++ b/inc/helpers/aksrt-single-components.php @@ -409,7 +409,7 @@ if (!function_exists('aksrt_resolve_allow_view_rule')) : $ok = true; } if (!$ok && $need_level > 0) { - $user_level = (int) get_user_meta($user_id, 'aksrt_user_level', true); + $user_level = class_exists('\AKSRT\User\Level') ? \AKSRT\User\Level::get_level($user_id) : 0; if ($user_level >= $need_level) { $ok = true; } diff --git a/inc/helpers/aksrt-user-center.php b/inc/helpers/aksrt-user-center.php index 8d2f81a3..a071718d 100644 --- a/inc/helpers/aksrt-user-center.php +++ b/inc/helpers/aksrt-user-center.php @@ -141,6 +141,7 @@ if (!function_exists('aksrt_capture_user_center_view')) { function aksrt_capture_user_center_view(string $view, array $context): string { // Built-in panels map to renderer files; "profile" content lives in profile-form.php. $map = [ + 'level' => 'level', 'profile' => 'form', 'posts' => 'posts', 'comments' => 'comments', @@ -173,7 +174,7 @@ if (!function_exists('aksrt_capture_user_center_panel')) { * Capture a built-in or registered user-center panel body. */ function aksrt_capture_user_center_panel(string $tab_key, array $tab, array $context): string { - $built_in_tabs = ['profile', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'settings']; + $built_in_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'settings']; if (in_array($tab_key, $built_in_tabs, true)) { return aksrt_capture_user_center_view($tab_key, $context); } diff --git a/inc/helpers/user-center-renderers/credits-level.php b/inc/helpers/user-center-renderers/credits-level.php deleted file mode 100644 index 0a3523f4..00000000 --- a/inc/helpers/user-center-renderers/credits-level.php +++ /dev/null @@ -1,107 +0,0 @@ -= $max; -if (!$is_max) { - $next_need = (int) ($opt['upgrade_integral_' . ($level + 1)] ?? 0); - if ($next_need <= $integral) { - // Resolve again in case meta lag. - $level = \AKSRT\User\Level::resolve_level($integral, $cfg); - $is_max = $level >= $max; - $next_need = $is_max ? $integral : (int) ($opt['upgrade_integral_' . ($level + 1)] ?? $integral); - $name = (string) ($opt['name_' . $level] ?? ($prefix . $level)); - } -} - -if ($is_max) { - $proportion = 100; - $need_html = esc_html__('已为最高等级', 'aksrt'); - $frac_html = esc_html((string) $integral) . '/' . esc_html((string) $integral); -} else { - $den = max(1, $next_need); - $proportion = (int) min(100, max(0, ($integral / $den) * 100)); - $need_left = max(0, $next_need - $integral); - $need_html = sprintf( - /* translators: %s: remaining XP */ - esc_html__('升级还需%s经验', 'aksrt'), - '' . esc_html((string) $need_left) . '' - ); - $frac_html = esc_html((string) $integral) . '/' . esc_html((string) $next_need); -} - -$avatar = function_exists('aksrt_get_avatar_html') - ? aksrt_get_avatar_html($user_id, 42, ['class' => 'avatar avatar-mini']) - : get_avatar($user_id, 42); - -// Growth rows (next few levels). -$growth = ''; -$from = max(1, $level); -$to = min($max, $level + 5); -for ($i = $from; $i <= $to; $i++) { - $need_i = (int) ($opt['upgrade_integral_' . $i] ?? 0); - $nm = (string) ($opt['name_' . $i] ?? ($prefix . $i)); - $state = $integral >= $need_i ? 'is-done' : ($i === $level + 1 ? 'is-next' : ''); - $growth .= '
'; - $growth .= '' . esc_html($prefix . $i) . ''; - $growth .= '' . esc_html($nm) . ''; - $growth .= '' . esc_html((string) $need_i) . ''; - $growth .= '
'; -} -?> -
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- -
-
- -
- -
diff --git a/inc/helpers/user-center-renderers/profile-auth.php b/inc/helpers/user-center-renderers/profile-auth.php index 14da9724..eab75d0e 100644 --- a/inc/helpers/user-center-renderers/profile-auth.php +++ b/inc/helpers/user-center-renderers/profile-auth.php @@ -54,7 +54,7 @@ $can_apply = !empty($settings['enabled']) && !empty($settings['allow_apply']) &&
-
+

diff --git a/inc/helpers/user-center-renderers/profile-credits.php b/inc/helpers/user-center-renderers/profile-credits.php index 7bbd7b98..e525af63 100644 --- a/inc/helpers/user-center-renderers/profile-credits.php +++ b/inc/helpers/user-center-renderers/profile-credits.php @@ -52,9 +52,6 @@ if (!empty($show_vip)) { if ($show_credits) { require $dir . '/credits-signin.php'; } -if (class_exists('\AKSRT\User\Level')) { - require $dir . '/credits-level.php'; -} // Tabbed ledgers (Zibll record tabs). if ($show_wallet || $show_credits) : diff --git a/inc/helpers/user-center-renderers/profile-level.php b/inc/helpers/user-center-renderers/profile-level.php new file mode 100644 index 00000000..d7a2abbf --- /dev/null +++ b/inc/helpers/user-center-renderers/profile-level.php @@ -0,0 +1,109 @@ + +
+
+
+
+ +
+
+

+

+
+
+
+
+
0 ? '/' . esc_html((string) $day_max) : ''; ?>
+
+
+
+
+ EXP + +
+
+
+
+ +
+

+
+ +
+
+ + LV +
+ + EXP +
+ +
+
+ +
+
+

+ +
+ +
+ +
+ + +
+ +
+ +

+ +
+ +
+

+ +
+ $value) : ?> +
+ EXP
+ +
+ +

+ +
+
+
diff --git a/inc/helpers/user-center-renderers/sidebar-nav.php b/inc/helpers/user-center-renderers/sidebar-nav.php index 717802e7..e9520b48 100644 --- a/inc/helpers/user-center-renderers/sidebar-nav.php +++ b/inc/helpers/user-center-renderers/sidebar-nav.php @@ -8,8 +8,8 @@ defined('ABSPATH') || exit; $cart_url = function_exists('aksrt_get_cart_page_url') ? aksrt_get_cart_page_url() : ''; -$level_url = isset($dashboard_tabs['credits']) - ? add_query_arg('asset_tab', 'points', aksrt_get_user_center_tab_url('credits')) +$level_url = isset($dashboard_tabs['level']) + ? aksrt_get_user_center_tab_url('level') : ''; $auth_url = isset($dashboard_tabs['auth']) ? aksrt_get_user_center_tab_url('auth') : ''; diff --git a/src/AKSRT/Admin/Schema/SchemaUsers.php b/src/AKSRT/Admin/Schema/SchemaUsers.php index d41e4097..61801b76 100644 --- a/src/AKSRT/Admin/Schema/SchemaUsers.php +++ b/src/AKSRT/Admin/Schema/SchemaUsers.php @@ -515,7 +515,7 @@ class SchemaUsers { 'option_key' => 'aksrt_settings', 'groups' => [ ['key' => 'center', 'title' => '用户中心', 'fields' => [ - ['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,posts,comments,favorites,vip,credits,auth', 'help' => '逗号分隔。auth=官方认证;vip、订单和认证会按功能开关自动注入'], + ['key' => 'user_center_tabs', 'label' => '用户中心 Tab', 'type' => 'input', 'default' => 'profile,level,posts,comments,favorites,vip,credits,auth', 'help' => '逗号分隔。level=我的等级;auth=官方认证;等级、VIP、订单和认证会按功能开关自动注入'], ['key' => 'user_cover_upload', 'label' => '封面上传', 'type' => 'switch', 'default' => true], ['key' => 'user_social_links', 'label' => '社交链接', 'type' => 'switch', 'default' => true], ['key' => 'user_desc_std', 'label' => '默认签名', 'type' => 'input', 'default' => '这家伙很懒…', 'desc' => '用户未设置个人简介时显示的默认文案'], @@ -616,21 +616,15 @@ class SchemaUsers { 'groups' => [ ['key' => 'display', 'title' => '等级显示', 'fields' => [ ['key' => 'user_level_s', 'label' => '启用用户等级', 'type' => 'switch', 'default' => true], - ['key' => 'user_level_enabled', 'label' => '启用等级系统(别名)', 'type' => 'switch', 'default' => true, 'help' => '与 user_level_s 同步写入'], ['key' => 'user_level_public_display', 'label' => '公开页显示等级', 'type' => 'switch', 'default' => true], - ['key' => 'user_level_prefix', 'label' => '等级前缀', 'type' => 'input', 'default' => 'Lv.'], ['key' => 'user_level_max', 'label' => '最高等级', 'type' => 'number', 'default' => 10, 'min' => 3, 'max' => 30], ['key' => 'user_level_notify', 'label' => '升级通知', 'type' => 'switch', 'default' => true], - ['key' => 'user_level_source', 'label' => '等级来源', 'type' => 'radio', 'default' => 'integral', 'options' => [ - ['label' => '经验值', 'value' => 'integral'], - ['label' => '手动 Meta', 'value' => 'manual_meta'], - ]], ]], ['key' => 'level_opt', 'title' => '升级阈值', 'fields' => [ - ['key' => 'user_level_opt', 'label' => '等级参数', 'type' => 'json', 'default' => [], 'help' => 'upgrade_integral_N / name_N'], + ['key' => 'user_level_opt', 'label' => '等级参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => [], 'help' => 'name_N / icon_img_N / upgrade_integral_N'], ]], ['key' => 'integral', 'title' => '经验值得分', 'fields' => [ - ['key' => 'user_integral_opt', 'label' => '经验值配置', 'type' => 'json', 'default' => [], 'help' => 'day_max + 行为得分键'], + ['key' => 'user_integral_opt', 'label' => '经验值配置', 'type' => 'json', 'sanitize_type' => 'array', 'default' => [], 'help' => 'day_max + 行为得分键'], ]], ], ], diff --git a/src/AKSRT/Admin/SchemaProvider.php b/src/AKSRT/Admin/SchemaProvider.php index ebc0cd68..cd5a2a4a 100644 --- a/src/AKSRT/Admin/SchemaProvider.php +++ b/src/AKSRT/Admin/SchemaProvider.php @@ -442,6 +442,7 @@ class SchemaProvider { 'radio' => 'string', 'checkbox' => 'array', 'repeater' => 'array', + 'json' => 'array', // 自定义面板(验证码服务商等)按对象存储 'custom' => 'array', 'color' => 'string', diff --git a/src/AKSRT/Forum/BoardServicePart2.php b/src/AKSRT/Forum/BoardServicePart2.php index 44419cee..87bbce8d 100644 --- a/src/AKSRT/Forum/BoardServicePart2.php +++ b/src/AKSRT/Forum/BoardServicePart2.php @@ -745,7 +745,14 @@ trait BoardServicePart2 { $threshold = (int) ($config['other']['hot_plate_threshold'] ?? 100); $posts_count = (int) get_post_meta($plate_id, '_aksrt_posts_count', true); $follow_count = (int) get_post_meta($plate_id, '_aksrt_follow_count', true); - return ($posts_count + $follow_count * 2) >= $threshold; + $is_hot = ($posts_count + $follow_count * 2) >= $threshold; + if ($is_hot && get_post_meta($plate_id, '_aksrt_level_hot_awarded', true) !== '1') { + $plate = get_post($plate_id); + $author_id = $plate instanceof \WP_Post ? (int) $plate->post_author : 0; + update_post_meta($plate_id, '_aksrt_level_hot_awarded', '1'); + do_action('aksrt_forum_plate_hot', $plate_id, $author_id); + } + return $is_hot; } /** diff --git a/src/AKSRT/Settings/Capabilities.php b/src/AKSRT/Settings/Capabilities.php index a1a5edca..b7228a23 100644 --- a/src/AKSRT/Settings/Capabilities.php +++ b/src/AKSRT/Settings/Capabilities.php @@ -132,7 +132,7 @@ class Capabilities { $level_threshold = (int) ($row['level'] ?? -1); if ($level_threshold >= 0) { - $user_level = (int) get_user_meta($user_id, 'aksrt_user_level', true); + $user_level = class_exists('\AKSRT\User\Level') ? \AKSRT\User\Level::get_level($user_id) : 0; if ($user_level >= $level_threshold) { return true; } @@ -140,7 +140,7 @@ class Capabilities { $vip_threshold = (int) ($row['vip'] ?? -1); if ($vip_threshold >= 0) { - $user_vip = (int) get_user_meta($user_id, 'aksrt_vip_level', true); + $user_vip = function_exists('aksrt_get_user_vip_level') ? aksrt_get_user_vip_level($user_id) : 0; if ($user_vip >= $vip_threshold) { return true; } diff --git a/src/AKSRT/Settings/UserRuntime.php b/src/AKSRT/Settings/UserRuntime.php index ac3fc77b..001272ad 100644 --- a/src/AKSRT/Settings/UserRuntime.php +++ b/src/AKSRT/Settings/UserRuntime.php @@ -83,14 +83,14 @@ class UserRuntime { */ public static function get_user_center_settings(): array { $settings = Settings::get_all(); - $raw_tabs = $settings['user_center_tabs'] ?? 'profile,posts,comments,credits,favorites,auth'; + $raw_tabs = $settings['user_center_tabs'] ?? 'profile,level,posts,comments,credits,favorites,auth'; $tabs = is_array($raw_tabs) ? $raw_tabs : preg_split('/[\s,]+/', (string) $raw_tabs, -1, PREG_SPLIT_NO_EMPTY); // Built-in switchable panels. VIP is always auto-injected when enabled // by AKSRT\User\Center — keep it allowed for explicit config too. - $allowed_tabs = ['profile', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'settings']; + $allowed_tabs = ['profile', 'level', 'posts', 'comments', 'favorites', 'credits', 'vip', 'orders', 'auth', 'settings']; $normalized_tabs = ['profile']; foreach ((array) $tabs as $tab) { @@ -152,7 +152,6 @@ class UserRuntime { public static function get_user_identity_settings(): array { $settings = Settings::get_all(); $verify_badge_text = trim((string) ($settings['user_verify_badge_text'] ?? '')); - $level_prefix = trim((string) ($settings['user_level_prefix'] ?? '')); $ban_notice_text = trim((string) ($settings['user_report_ban_notice_text'] ?? '')); $medal = self::parse_medal_catalog($settings); @@ -169,14 +168,8 @@ class UserRuntime { 'apply_option' => (string) ($settings['user_verify_apply_option'] ?? ''), ], 'level' => [ - 'enabled' => !empty($settings['user_level_enabled']) || !empty($settings['user_level_s']), + 'enabled' => !isset($settings['user_level_s']) || !empty($settings['user_level_s']), 'public_display' => !isset($settings['user_level_public_display']) || !empty($settings['user_level_public_display']), - 'source' => (static function () use ($settings) { - $v = $settings['user_level_source'] ?? 'integral'; - return in_array($v, ['credits_balance', 'manual_meta', 'integral'], true) ? $v : 'integral'; - })(), - 'label_prefix' => $level_prefix !== '' ? $level_prefix : 'Lv.', - 'thresholds' => self::parse_level_thresholds((string) ($settings['user_level_thresholds'] ?? "1|0\n2|30\n3|100\n4|300")), 'max' => max(3, min(30, (int) ($settings['user_level_max'] ?? 10))), ], 'medal' => $medal, diff --git a/src/AKSRT/User/BadgeAutoAward.php b/src/AKSRT/User/BadgeAutoAward.php index e04af9d7..beb4c368 100644 --- a/src/AKSRT/User/BadgeAutoAward.php +++ b/src/AKSRT/User/BadgeAutoAward.php @@ -77,92 +77,6 @@ class BadgeAutoAward { if ($user_id <= 0 || !class_exists('\AKSRT\User\Badges') || !Badges::enabled()) { return; } - if (!class_exists('\AKSRT\Settings\Settings')) { - return; - } - $identity = \AKSRT\Settings\Settings::get_user_identity_settings(); - $medal = is_array($identity['medal'] ?? null) ? $identity['medal'] : []; - $items = is_array($medal['items_by_slug'] ?? null) ? $medal['items_by_slug'] : []; - if (!$items) { - // Fall back to badge dictionary without thresholds. - return; - } - - foreach ($items as $slug => $item) { - $get_type = sanitize_key((string) ($item['get_type'] ?? 'manually_add')); - if ($get_type === 'manually_add' || $get_type === '') { - continue; - } - $need = max(0, (int) ($item['get_val'] ?? 0)); - if ($need <= 0) { - continue; - } - $val = $this->metric($user_id, $get_type); - if ($val >= $need) { - Badges::grant($user_id, (string) $slug, 0); - } - } - } - - private function metric(int $user_id, string $type): int { - switch ($type) { - case 'registration_time': - $user = get_userdata($user_id); - if (!$user) { - return 0; - } - $reg = strtotime((string) $user->user_registered); - if ($reg <= 0) { - return 0; - } - return max(0, (int) floor((time() - $reg) / DAY_IN_SECONDS)); - - case 'checkin_all_day': - $v = (int) get_user_meta($user_id, 'aksrt_checkin_all_day', true); - if ($v <= 0) { - $v = (int) get_user_meta($user_id, 'checkin_all_day', true); - } - return $v; - - case 'new_post': - return (int) count_user_posts($user_id, 'post', true); - - case 'bbs_post': - return post_type_exists('aksrt_forum_topic') - ? (int) count_user_posts($user_id, 'aksrt_forum_topic', true) - : 0; - - case 'comment': - return (int) get_comments(['user_id' => $user_id, 'count' => true, 'status' => 'approve']); - - case 'followed': - if (class_exists('\AKSRT\User\Follow')) { - return (int) Follow::get_instance()->get_follower_count($user_id); - } - return 0; - - case 'like': - return (int) get_user_meta($user_id, 'aksrt_like_count', true) - ?: (int) get_user_meta($user_id, 'like-posts', true); - - case 'views': - return (int) get_user_meta($user_id, 'aksrt_user_views', true); - - case 'pay_income': - return (int) get_user_meta($user_id, 'aksrt_pay_income', true); - - case 'pay_rebate': - return (int) get_user_meta($user_id, 'aksrt_pay_rebate', true); - - default: - /** - * Allow extensions to provide metric values. - * - * @param int $value - * @param int $user_id - * @param string $type - */ - return (int) apply_filters('aksrt_badge_metric_value', 0, $user_id, $type); - } + Badges::auto_evaluate($user_id); } } diff --git a/src/AKSRT/User/Badges.php b/src/AKSRT/User/Badges.php index b172641f..671852f7 100644 --- a/src/AKSRT/User/Badges.php +++ b/src/AKSRT/User/Badges.php @@ -419,7 +419,6 @@ class Badges { return new \WP_Error('aksrt_badge_not_owned', __('尚未获得该徽章', 'aksrt'), ['status' => 403]); } update_user_meta($user_id, self::META_WEAR, $slug); - update_user_meta($user_id, 'wear_medal', $slug); // zibll alias return true; } @@ -439,10 +438,11 @@ class Badges { return new \WP_Error('aksrt_badge_unknown', __('徽章不存在', 'aksrt'), ['status' => 404]); } $owned = self::get_owned($user_id); - if (!in_array($slug, $owned, true)) { - $owned[] = $slug; - self::set_owned($user_id, $owned); + if (in_array($slug, $owned, true)) { + return true; } + $owned[] = $slug; + self::set_owned($user_id, $owned); /** * Fires after a badge is granted. * diff --git a/src/AKSRT/User/Center.php b/src/AKSRT/User/Center.php index 21cfb58e..6487d25a 100644 --- a/src/AKSRT/User/Center.php +++ b/src/AKSRT/User/Center.php @@ -47,6 +47,12 @@ class Center { 'priority' => 10, 'type' => 'panel', ], + 'level' => [ + 'label' => __('我的等级', 'aksrt'), + 'icon' => 'chart-line', + 'priority' => 15, + 'type' => 'panel', + ], 'posts' => [ 'label' => __('我的文章', 'aksrt'), 'icon' => 'file-alt', @@ -111,13 +117,25 @@ class Center { $configured = ['profile', 'posts', 'comments', 'favorites']; } + $level_on = class_exists('\AKSRT\User\Level') + && !empty(\AKSRT\User\Level::settings()['enabled']); $credits_on = class_exists('\AKSRT\Settings\Settings') && \AKSRT\Settings\Settings::credits_enabled(); $balance_on = class_exists('\AKSRT\Credits\Balance') && \AKSRT\Credits\Balance::enabled(); $vip_on = class_exists('\AKSRT\Settings\Settings') && !empty(\AKSRT\Settings\Settings::get_payment_vip_settings()['enabled']); - // Follow configured order; always auto-include VIP/orders/credits when feature on. + // Follow configured order; always auto-include enabled product surfaces. $order = $configured; + + // Growth level is a dedicated user-center surface, separate from spendable points. + if ($level_on && !in_array('level', $order, true)) { + $insert_at = array_search('profile', $order, true); + array_splice($order, $insert_at === false ? 0 : (int) $insert_at + 1, 0, ['level']); + } + if (!$level_on) { + $order = array_values(array_filter($order, static fn(string $key): bool => $key !== 'level')); + } + if ($vip_on && !in_array('vip', $order, true)) { $insert_at = count($order); foreach (['credits', 'favorites', 'posts'] as $anchor) { @@ -163,6 +181,9 @@ class Center { if (!isset($definitions[$key])) { continue; } + if ($key === 'level' && !$level_on) { + continue; + } if ($key === 'credits' && !$credits_on && !$balance_on) { continue; } diff --git a/src/AKSRT/User/Home.php b/src/AKSRT/User/Home.php index 647a8d3e..69132c85 100644 --- a/src/AKSRT/User/Home.php +++ b/src/AKSRT/User/Home.php @@ -151,10 +151,12 @@ class Home { $level = $this->get_user_level($user_id, $settings['level']); if ($level > 0) { $signals[] = [ - 'type' => 'level', - 'label' => (string) $settings['level']['label_prefix'] . $level, - 'icon' => 'layer-group', - 'tone' => 'info', + 'type' => 'level', + 'label' => Level::get_level_name($level), + 'title' => Level::get_level_name($level), + 'icon' => 'layer-group', + 'icon_url' => Level::get_level_icon_url($level), + 'tone' => 'info', ]; } } @@ -280,22 +282,8 @@ class Home { * @param array{source?:string,thresholds?:array} $level_settings */ private function get_user_level(int $user_id, array $level_settings): int { - $source = (string) ($level_settings['source'] ?? 'integral'); - if ($source === 'manual_meta') { - return max(0, (int) get_user_meta($user_id, 'aksrt_user_level', true)); - } - if (class_exists('AKSRT\\User\\Level')) { - return Level::get_level($user_id); - } - // Fallback: credits_balance thresholds (legacy) if integral class missing. - $score = function_exists('aksrt_user_credits') ? (int) aksrt_user_credits($user_id) : 0; - $level = 0; - foreach ((array) ($level_settings['thresholds'] ?? []) as $candidate => $threshold) { - if ($score >= (int) $threshold) { - $level = (int) $candidate; - } - } - return $level; + unset($level_settings); + return class_exists('AKSRT\\User\\Level') ? Level::get_level($user_id) : 0; } /** diff --git a/src/AKSRT/User/Level.php b/src/AKSRT/User/Level.php index d50a8ecf..ac95caa7 100644 --- a/src/AKSRT/User/Level.php +++ b/src/AKSRT/User/Level.php @@ -1,5 +1,7 @@ ,integral_opt:array,notify:bool} + * @return array{enabled:bool,public_display:bool,max:int,level_opt:array,integral_opt:array} */ public static function settings(): array { $all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []; - $enabled = !empty($all['user_level_s']) || !empty($all['user_level_enabled']); - $max = max(3, min(30, (int) ($all['user_level_max'] ?? 10))); - $source = (string) ($all['user_level_source'] ?? 'integral'); - if (!in_array($source, ['integral', 'manual_meta'], true)) { - $source = 'integral'; - } - $level_opt = $all['user_level_opt'] ?? []; - if (is_string($level_opt)) { - $decoded = json_decode($level_opt, true); - $level_opt = is_array($decoded) ? $decoded : []; - } - if (!is_array($level_opt)) { - $level_opt = []; - } - $integral_opt = $all['user_integral_opt'] ?? []; - if (is_string($integral_opt)) { - $decoded = json_decode($integral_opt, true); - $integral_opt = is_array($decoded) ? $decoded : []; - } - if (!is_array($integral_opt)) { - $integral_opt = []; - } - $prefix = trim((string) ($all['user_level_prefix'] ?? 'Lv.')); + $max = max(3, min(30, (int) ($all['user_level_max'] ?? 10))); return [ - 'enabled' => $enabled, + 'enabled' => !isset($all['user_level_s']) || !empty($all['user_level_s']), 'public_display' => !isset($all['user_level_public_display']) || !empty($all['user_level_public_display']), - 'prefix' => $prefix !== '' ? $prefix : 'Lv.', 'max' => $max, - 'source' => $source, - 'level_opt' => $level_opt, - 'integral_opt' => $integral_opt, - 'notify' => !isset($all['user_level_notify']) || !empty($all['user_level_notify']), + 'level_opt' => self::normalize_array_option($all['user_level_opt'] ?? []), + 'integral_opt' => self::normalize_array_option($all['user_integral_opt'] ?? []), ]; } - /** - * Catalog of integral earn keys (defaults). - * - * @return array - */ + /** @return array */ + private static function normalize_array_option($value): array { + if (is_string($value) && $value !== '') { + $decoded = json_decode($value, true); + $value = is_array($decoded) ? $decoded : []; + } + return is_array($value) ? $value : []; + } + + /** @return array */ public static function integral_defaults(): array { return [ 'day_max' => 100, @@ -105,269 +96,360 @@ class Level { ]; } + /** @return array */ + public static function integral_labels(): array { + return [ + 'sign_up' => __('首次注册', 'aksrt'), + 'sign_in' => __('每日登录', 'aksrt'), + 'followed' => __('被关注', 'aksrt'), + 'post_new' => __('发布文章', 'aksrt'), + 'post_like' => __('文章获赞', 'aksrt'), + 'post_favorite' => __('文章被收藏', 'aksrt'), + 'comment_new' => __('发表评论', 'aksrt'), + 'comment_like' => __('评论获赞', 'aksrt'), + 'bbs_posts_new' => __('发布帖子', 'aksrt'), + 'bbs_score_extra' => __('帖子被加分', 'aksrt'), + 'bbs_essence' => __('帖子评为精华', 'aksrt'), + 'bbs_posts_hot' => __('帖子成为热门', 'aksrt'), + 'bbs_plate_new' => __('创建版块', 'aksrt'), + 'bbs_plate_hot' => __('版块成为热门', 'aksrt'), + 'bbs_adopt' => __('回答被采纳', 'aksrt'), + 'bbs_comment_hot' => __('评论成为神评', 'aksrt'), + ]; + } + public static function task_points(string $key): int { - $cfg = self::settings(); - $opt = $cfg['integral_opt']; - $def = self::integral_defaults(); - if (array_key_exists($key, $opt)) { - return max(0, (int) $opt[$key]); - } - return max(0, (int) ($def[$key] ?? 0)); + $opt = self::settings()['integral_opt']; + $defaults = self::integral_defaults(); + return max(0, (int) ($opt[$key] ?? $defaults[$key] ?? 0)); } public static function day_max(): int { - $cfg = self::settings(); - $opt = $cfg['integral_opt']; - if (array_key_exists('day_max', $opt)) { - return max(0, (int) $opt['day_max']); - } - return 100; + $opt = self::settings()['integral_opt']; + return max(0, (int) ($opt['day_max'] ?? self::integral_defaults()['day_max'])); } public static function get_integral(int $user_id): int { - if ($user_id <= 0) { - return 0; - } - return max(0, (int) get_user_meta($user_id, self::META_INTEGRAL, true)); + return $user_id > 0 ? max(0, (int) get_user_meta($user_id, self::META_INTEGRAL, true)) : 0; } public static function get_level(int $user_id): int { - if ($user_id <= 0) { + if ($user_id <= 0 || !self::settings()['enabled']) { return 0; } - $cfg = self::settings(); - if (!$cfg['enabled']) { - return 0; - } - if ($cfg['source'] === 'manual_meta') { - return max(0, (int) get_user_meta($user_id, self::META_LEVEL, true)); - } - return self::resolve_level(self::get_integral($user_id), $cfg); + return self::resolve_level(self::get_integral($user_id)); } - /** - * @param array{max:int,level_opt:array} $cfg - */ + public static function get_level_name(int $level): string { + $cfg = self::settings(); + $level = max(1, min($cfg['max'], $level)); + return sanitize_text_field((string) ($cfg['level_opt']['name_' . $level] ?? ('LV' . $level))); + } + + public static function get_level_icon_url(int $level): string { + $cfg = self::settings(); + $level = max(1, min($cfg['max'], $level)); + $custom = esc_url_raw((string) ($cfg['level_opt']['icon_img_' . $level] ?? '')); + if ($custom !== '') { + return $custom; + } + $path = AKSRT_DIR . '/assets/img/user-level-' . $level . '.png'; + return is_file($path) ? AKSRT_URI . '/assets/img/user-level-' . $level . '.png' : ''; + } + + public static function get_badge_html(int $user_id, string $class = ''): string { + $cfg = self::settings(); + if (!$cfg['enabled'] || !$cfg['public_display']) { + return ''; + } + $level = self::get_level($user_id); + if ($level <= 0) { + return ''; + } + $name = self::get_level_name($level); + $icon = self::get_level_icon_url($level); + if ($icon !== '') { + return '' . esc_attr($name) . ''; + } + return 'LV' . esc_html((string) $level) . ''; + } + + /** @param array{max?:int,level_opt?:array}|null $cfg */ public static function resolve_level(int $integral, ?array $cfg = null): int { $cfg = $cfg ?? self::settings(); - $max = (int) $cfg['max']; - $opt = $cfg['level_opt']; + $max = max(1, (int) ($cfg['max'] ?? 10)); + $opt = is_array($cfg['level_opt'] ?? null) ? $cfg['level_opt'] : []; $level = 1; - for ($i = 1; $i <= $max; $i++) { - $need = (int) ($opt['upgrade_integral_' . $i] ?? ($i === 1 ? 0 : PHP_INT_MAX)); - if ($integral >= $need) { - $level = $i; - } else { + for ($i = 2; $i <= $max; $i++) { + $need = max(0, (int) ($opt['upgrade_integral_' . $i] ?? (($i - 1) * 500 * $i))); + if ($integral < $need) { break; } + $level = $i; } - return max(0, min($max, $level)); + return $level; + } + + /** @return array{level:int,name:string,icon:string,integral:int,current_need:int,next_need:int,remaining:int,progress:int,is_max:bool} */ + public static function get_progress(int $user_id): array { + $cfg = self::settings(); + $integral = self::get_integral($user_id); + $level = self::resolve_level($integral, $cfg); + $current_need = $level <= 1 ? 0 : max(0, (int) ($cfg['level_opt']['upgrade_integral_' . $level] ?? (($level - 1) * 500 * $level))); + $is_max = $level >= $cfg['max']; + $next_need = $is_max ? $integral : max($current_need + 1, (int) ($cfg['level_opt']['upgrade_integral_' . ($level + 1)] ?? ($level * 500 * ($level + 1)))); + $span = max(1, $next_need - $current_need); + $progress = $is_max ? 100 : (int) min(100, max(0, (($integral - $current_need) / $span) * 100)); + + return [ + 'level' => $level, + 'name' => self::get_level_name($level), + 'icon' => self::get_level_icon_url($level), + 'integral' => $integral, + 'current_need' => $current_need, + 'next_need' => $next_need, + 'remaining' => $is_max ? 0 : max(0, $next_need - $integral), + 'progress' => $progress, + 'is_max' => $is_max, + ]; + } + + /** @return array */ + public static function get_details(int $user_id): array { + $details = get_user_meta($user_id, self::META_DETAIL, true); + return is_array($details) ? array_values($details) : []; + } + + /** @return array */ + public static function get_date_details(int $user_id): array { + $details = get_user_meta($user_id, self::META_DATE_DETAIL, true); + return is_array($details) ? $details : []; } /** - * Grant integral for a task key. Respects day_max. + * Award experience and append its immutable display ledger. * - * @return int|false points granted or false + * @return int|false Granted amount or false. */ - public static function add(int $user_id, string $key, int $points = 0) { - if ($user_id <= 0) { - return false; - } + public static function add(int $user_id, string $key, int $points = 0, bool $bypass_day_max = false) { $cfg = self::settings(); - if (!$cfg['enabled'] || $cfg['source'] !== 'integral') { - return false; - } $key = sanitize_key($key); - if ($key === '' || $key === 'day_max') { + if ($user_id <= 0 || !$cfg['enabled'] || $key === '' || $key === 'day_max') { return false; } - if ($points <= 0) { - $points = self::task_points($key); - } + $points = $points > 0 ? $points : self::task_points($key); if ($points <= 0) { return false; } + $today = current_time('Y-m-d'); + $date_details = self::get_date_details($user_id); + $today_total = max(0, (int) ($date_details[$today] ?? 0)); $day_max = self::day_max(); - $today = gmdate('Y-m-d', current_time('timestamp')); - $date_detail = get_user_meta($user_id, self::META_DATE_DETAIL, true); - $date_detail = is_array($date_detail) ? $date_detail : []; - $today_sum = max(0, (int) ($date_detail[$today] ?? 0)); - if ($day_max > 0 && $today_sum >= $day_max) { - return false; - } - if ($day_max > 0) { - $points = min($points, $day_max - $today_sum); + if (!$bypass_day_max && $day_max > 0) { + if ($today_total >= $day_max) { + return false; + } + $points = min($points, $day_max - $today_total); } if ($points <= 0) { return false; } $before = self::get_integral($user_id); - $after = $before + $points; + $after = $before + $points; update_user_meta($user_id, self::META_INTEGRAL, $after); - $date_detail[$today] = $today_sum + $points; - // Keep last 90 days. - if (count($date_detail) > 90) { - krsort($date_detail); - $date_detail = array_slice($date_detail, 0, 90, true); - } - update_user_meta($user_id, self::META_DATE_DETAIL, $date_detail); + $date_details[$today] = $today_total + $points; + uksort($date_details, static fn(string $a, string $b): int => strcmp($b, $a)); + update_user_meta($user_id, self::META_DATE_DETAIL, array_slice($date_details, 0, 30, true)); - $detail = get_user_meta($user_id, self::META_DETAIL, true); - $detail = is_array($detail) ? $detail : []; - $prev = is_array($detail[$key] ?? null) ? $detail[$key] : ['value' => 0, 'integral' => 0]; - $detail[$key] = [ - 'value' => (int) ($prev['value'] ?? 0) + $points, - 'integral' => (int) ($prev['integral'] ?? 0) + $points, - ]; - update_user_meta($user_id, self::META_DETAIL, $detail); + $details = self::get_details($user_id); + array_unshift($details, [ + 'integral' => $after, + 'key' => $key, + 'value' => $points, + 'time' => current_time('mysql'), + ]); + update_user_meta($user_id, self::META_DETAIL, array_slice($details, 0, 50)); - $old_level = (int) get_user_meta($user_id, self::META_LEVEL, true); + $old_level = max(1, (int) get_user_meta($user_id, self::META_LEVEL, true)); $new_level = self::resolve_level($after, $cfg); + update_user_meta($user_id, self::META_LEVEL, $new_level); if ($new_level !== $old_level) { - update_user_meta($user_id, self::META_LEVEL, $new_level); - /** - * @param int $user_id - * @param int $new_level - * @param int $old_level - */ do_action('aksrt_user_level_changed', $user_id, $new_level, $old_level); - } else { - update_user_meta($user_id, self::META_LEVEL, $new_level); } - return $points; } + private static function award_once(int $user_id, string $key, string $meta_type, int $entity_id, string $marker, int $limit = 1, int $actor_id = 0): void { + if ($user_id <= 0 || $entity_id <= 0) { + return; + } + $get = $meta_type === 'comment' ? 'get_comment_meta' : 'get_post_meta'; + $update = $meta_type === 'comment' ? 'update_comment_meta' : 'update_post_meta'; + $awarded = $get($entity_id, $marker, true); + $awarded = is_array($awarded) ? array_values(array_unique(array_map('intval', $awarded))) : []; + if ($actor_id > 0) { + if (in_array($actor_id, $awarded, true) || count($awarded) >= $limit) { + return; + } + } elseif ($awarded !== []) { + return; + } + if (self::add($user_id, $key)) { + $awarded[] = $actor_id > 0 ? $actor_id : 0; + $update($entity_id, $marker, $awarded); + } + } + public function on_register(int $user_id): void { - self::add($user_id, 'sign_up'); + self::add($user_id, 'sign_up', 0, true); } - /** - * @param string $user_login - * @param \WP_User $user - */ public function on_login($user_login, $user): void { $user_id = $user instanceof \WP_User ? (int) $user->ID : 0; - if ($user_id <= 0) { + $today = current_time('Ymd'); + if ($user_id <= 0 || get_user_meta($user_id, self::META_LOGIN_DAY, true) === $today) { return; } - // Once per calendar day for login XP. - $today = gmdate('Y-m-d', current_time('timestamp')); - $flag = (string) get_user_meta($user_id, '_aksrt_integral_sign_in_day', true); - if ($flag === $today) { - return; - } - $granted = self::add($user_id, 'sign_in'); - if ($granted) { - update_user_meta($user_id, '_aksrt_integral_sign_in_day', $today); + if (self::add($user_id, 'sign_in')) { + update_user_meta($user_id, self::META_LOGIN_DAY, $today); } } - public function on_sign_in(int $user_id): void { - // Dedicated check-in hook can also grant if separate from login; reuse sign_in key once/day. - $this->on_login('', get_userdata($user_id) ?: new \WP_User($user_id)); + public function on_checkin(int $user_id): void { + $this->on_login('', get_userdata($user_id)); } - /** - * @param int $post_id - * @param \WP_Post $post - */ - public function on_publish_post($post_id, $post): void { - if (!$post instanceof \WP_Post || $post->post_type !== 'post' || $post->post_status !== 'publish') { + public function on_post_status(string $new_status, string $old_status, $post): void { + if (!$post instanceof \WP_Post || $new_status !== 'publish' || $old_status === 'publish') { return; } - // Only when transitioning to publish first time. - if ((string) get_post_meta($post_id, '_aksrt_integral_post_new', true) === '1') { - return; + $key = ''; + if ($post->post_type === 'post') { + $key = 'post_new'; + } elseif ($post->post_type === 'aksrt_forum_topic') { + $key = 'bbs_posts_new'; + } elseif ($post->post_type === 'aksrt_forum_plate') { + $key = 'bbs_plate_new'; } - $author = (int) $post->post_author; - if ($author <= 0) { - return; - } - if (self::add($author, 'post_new')) { - update_post_meta($post_id, '_aksrt_integral_post_new', '1'); + if ($key !== '') { + self::award_once((int) $post->post_author, $key, 'post', (int) $post->ID, '_aksrt_level_' . $key); } } - /** - * @param int $comment_id - * @param int|string $approved - * @param array $commentdata - */ public function on_comment_post($comment_id, $approved, $commentdata): void { - if ((string) $approved !== '1' && $approved !== 1) { + if ((string) $approved !== '1') { return; } - $user_id = (int) ($commentdata['user_id'] ?? 0); - if ($user_id <= 0) { - $user_id = (int) get_comment($comment_id)->user_id; + $comment = get_comment((int) $comment_id); + $this->award_comment_new($comment); + } + + public function on_comment_status(string $new_status, string $old_status, $comment): void { + if ($new_status === 'approved' && $old_status !== 'approved') { + $this->award_comment_new($comment); } - if ($user_id <= 0) { + } + + private function award_comment_new($comment): void { + if (!$comment instanceof \WP_Comment || (int) $comment->user_id <= 0) { return; } - self::add($user_id, 'comment_new'); + $post = get_post((int) $comment->comment_post_ID); + $key = $post instanceof \WP_Post && $post->post_type === 'aksrt_forum_topic' ? 'bbs_posts_new' : 'comment_new'; + if ($key === 'comment_new') { + self::award_once((int) $comment->user_id, $key, 'comment', (int) $comment->comment_ID, '_aksrt_level_comment_new'); + } } - /** - * @param int $follower_id - * @param int $followed_id - */ public function on_followed($follower_id, $followed_id): void { + $follower_id = (int) $follower_id; $followed_id = (int) $followed_id; - if ($followed_id <= 0) { + $awarded = get_user_meta($followed_id, self::META_FOLLOWED, true); + $awarded = is_array($awarded) ? array_map('intval', $awarded) : []; + if ($follower_id <= 0 || $followed_id <= 0 || in_array($follower_id, $awarded, true)) { return; } - self::add($followed_id, 'followed'); + if (self::add($followed_id, 'followed')) { + $awarded[] = $follower_id; + update_user_meta($followed_id, self::META_FOLLOWED, array_values(array_unique($awarded))); + } } - /** - * @param int $post_id - * @param int $user_id liker - */ - public function on_post_like($post_id, $user_id): void { + public function on_post_like($post_id, $user_id, $active = true): void { + if (!$active) { + return; + } $post = get_post((int) $post_id); - if (!$post instanceof \WP_Post) { - return; + if ($post instanceof \WP_Post && (int) $post->post_author !== (int) $user_id) { + self::award_once((int) $post->post_author, 'post_like', 'post', (int) $post_id, '_aksrt_level_post_like', 5, (int) $user_id); } - $author = (int) $post->post_author; - if ($author <= 0 || $author === (int) $user_id) { - return; - } - self::add($author, 'post_like'); } - /** - * @param int $comment_id - * @param int $user_id - */ - public function on_comment_like($comment_id, $user_id): void { - $c = get_comment((int) $comment_id); - if (!$c) { + public function on_post_favorite($post_id, $user_id, $active = true): void { + if (!$active) { return; } - $author = (int) $c->user_id; - if ($author <= 0 || $author === (int) $user_id) { - return; - } - self::add($author, 'comment_like'); - } - - /** - * @param int $post_id - * @param int $user_id - */ - public function on_post_favorite($post_id, $user_id): void { $post = get_post((int) $post_id); - if (!$post instanceof \WP_Post) { + if ($post instanceof \WP_Post && (int) $post->post_author !== (int) $user_id) { + self::award_once((int) $post->post_author, 'post_favorite', 'post', (int) $post_id, '_aksrt_level_post_favorite', 5, (int) $user_id); + } + } + + public function on_comment_like($comment_id, $user_id, $active = true): void { + if (!$active) { return; } - $author = (int) $post->post_author; - if ($author <= 0 || $author === (int) $user_id) { + $comment = get_comment((int) $comment_id); + if ($comment instanceof \WP_Comment && (int) $comment->user_id !== (int) $user_id) { + self::award_once((int) $comment->user_id, 'comment_like', 'comment', (int) $comment_id, '_aksrt_level_comment_like', 2, (int) $user_id); + } + } + + public function on_forum_scored($topic_id, $author_id, $amount): void { + if ((float) $amount > 0) { + self::award_once((int) $author_id, 'bbs_score_extra', 'post', (int) $topic_id, '_aksrt_level_bbs_score_extra', 5); + } + } + + public function on_forum_featured($topic_id, $author_id): void { + self::award_once((int) $author_id, 'bbs_essence', 'post', (int) $topic_id, '_aksrt_level_bbs_essence'); + } + + public function on_forum_hot($topic_id, $author_id): void { + self::award_once((int) $author_id, 'bbs_posts_hot', 'post', (int) $topic_id, '_aksrt_level_bbs_posts_hot'); + } + + public function on_forum_plate_hot($plate_id, $author_id): void { + self::award_once((int) $author_id, 'bbs_plate_hot', 'post', (int) $plate_id, '_aksrt_level_bbs_plate_hot'); + } + + public function on_forum_adopted($topic_id, $comment_id, $author_id): void { + self::award_once((int) $author_id, 'bbs_adopt', 'comment', (int) $comment_id, '_aksrt_level_bbs_adopt'); + } + + public function on_forum_comment_hot($comment_id, $author_id): void { + self::award_once((int) $author_id, 'bbs_comment_hot', 'comment', (int) $comment_id, '_aksrt_level_bbs_comment_hot'); + } + + public function on_level_changed(int $user_id, int $new_level, int $old_level): void { + $all = class_exists('\AKSRT\Settings\Settings') ? \AKSRT\Settings\Settings::get_all() : []; + if ($new_level <= $old_level || isset($all['user_level_notify']) && empty($all['user_level_notify'])) { return; } - self::add($author, 'post_favorite'); + if (class_exists('\AKSRT\Messages\Message')) { + $name = self::get_level_name($new_level); + \AKSRT\Messages\Message::get_instance()->send( + $user_id, + 'level_up', + sprintf(__('恭喜你升级至「%s」', 'aksrt'), $name), + [ + 'title' => __('等级提升', 'aksrt'), + 'target_url' => function_exists('aksrt_get_user_center_tab_url') ? aksrt_get_user_center_tab_url('level') : home_url('/'), + ] + ); + } } } diff --git a/src/AKSRT/User/LevelAdmin.php b/src/AKSRT/User/LevelAdmin.php new file mode 100644 index 00000000..40d12e31 --- /dev/null +++ b/src/AKSRT/User/LevelAdmin.php @@ -0,0 +1,136 @@ +enabled()) { + $columns['aksrt_user_level'] = __('用户等级', 'aksrt'); + } + return $columns; + } + + public function render_column(string $output, string $column_name, int $user_id): string { + if ($column_name !== 'aksrt_user_level' || !$this->enabled()) { + return $output; + } + $level = Level::get_level($user_id); + $icon = Level::get_badge_html($user_id, 'aksrt-user-level-icon--admin'); + return '' . $icon + . '' . esc_html(Level::get_level_name($level)) . '' + . '(' . esc_html((string) Level::get_integral($user_id)) . ' EXP)'; + } + + public function sortable_columns(array $columns): array { + if ($this->enabled()) { + $columns['aksrt_user_level'] = 'aksrt_user_level'; + } + return $columns; + } + + public function sort_users($query): void { + if (!is_admin() || $query->get('orderby') !== 'aksrt_user_level') { + return; + } + $query->set('meta_key', Level::META_INTEGRAL); + $query->set('orderby', 'meta_value_num'); + } + + public function add_row_action(array $actions, \WP_User $user): array { + if ($this->enabled() && $this->can_manage((int) $user->ID)) { + $url = get_edit_user_link((int) $user->ID); + if ($url) { + $actions['aksrt_edit_level'] = '' . esc_html__('调整等级经验', 'aksrt') . ''; + } + } + return $actions; + } + + public function render_section(\WP_User $user): void { + if (!$this->enabled()) { + return; + } + $progress = Level::get_progress((int) $user->ID); + $can_manage = $this->can_manage((int) $user->ID); + ?> + +

+ + + + + + + + + + + + + enabled() || !$this->can_manage($user_id) || !isset($_POST['aksrt_user_level_nonce'])) { + return; + } + $nonce = sanitize_text_field(wp_unslash((string) $_POST['aksrt_user_level_nonce'])); + if (!wp_verify_nonce($nonce, 'aksrt_user_level_admin_' . $user_id)) { + return; + } + $integral = max(0, (int) ($_POST['aksrt_level_integral'] ?? 0)); + update_user_meta($user_id, Level::META_INTEGRAL, $integral); + update_user_meta($user_id, Level::META_LEVEL, Level::resolve_level($integral)); + } +} diff --git a/src/AKSRT/User/Metrics.php b/src/AKSRT/User/Metrics.php index 5a4aa81e..b50e6c9d 100644 --- a/src/AKSRT/User/Metrics.php +++ b/src/AKSRT/User/Metrics.php @@ -47,19 +47,6 @@ class Metrics { add_action('aksrt_user_signed_in', [$this, 'on_user_signed_in'], 20, 1); add_action('aksrt_creator_income_added', [$this, 'on_creator_income_added'], 20, 3); add_action('aksrt_pay_rebate_added', [$this, 'on_pay_rebate_added'], 20, 3); - - // Free-points / social hooks also feed level XP. - add_action('aksrt_post_liked', [$this, 'level_on_post_liked'], 25, 3); - add_action('aksrt_comment_liked', [$this, 'level_on_comment_liked'], 25, 3); - add_action('aksrt_post_favorited', [$this, 'level_on_post_favorited'], 25, 3); - add_action('aksrt_user_followed', [$this, 'level_on_user_followed'], 25, 2); - add_action('aksrt_forum_topic_featured', [$this, 'level_on_forum_topic_featured'], 25, 2); - add_action('aksrt_forum_reply_adopted', [$this, 'level_on_forum_reply_adopted'], 25, 3); - add_action('aksrt_forum_comment_hot', [$this, 'level_on_forum_comment_hot'], 25, 2); - add_action('aksrt_forum_topic_hot', [$this, 'level_on_forum_topic_hot'], 25, 2); - add_action('aksrt_forum_topic_scored', [$this, 'level_on_forum_topic_scored'], 25, 3); - add_action('aksrt_forum_topic_created', [$this, 'level_on_forum_topic_created'], 25, 2); - add_action('aksrt_user_signed_in', [$this, 'level_on_user_signed_in'], 25, 1); } public static function bump_user_meta(int $user_id, string $meta_key, float $delta = 1.0): float { @@ -259,100 +246,4 @@ class Metrics { self::bump_user_meta($user_id, self::META_PAY_REBATE, $amount); self::reevaluate_medals($user_id); } - - /* ---------- level XP bridges ---------- */ - - private static function level_add(int $user_id, string $key): void { - if ($user_id > 0 && class_exists('AKSRT\\User\\Level')) { - Level::add($user_id, $key); - } - } - - public function level_on_post_liked(int $post_id, int $actor_id, bool $active): void { - if (!$active) { - return; - } - $post = get_post($post_id); - if (!$post instanceof \WP_Post) { - return; - } - $author = (int) $post->post_author; - if ($author > 0 && $author !== $actor_id) { - self::level_add($author, 'post_like'); - } - } - - public function level_on_comment_liked(int $comment_id, int $actor_id, bool $active): void { - if (!$active) { - return; - } - $c = get_comment($comment_id); - if (!$c) { - return; - } - $author = (int) $c->user_id; - if ($author > 0 && $author !== $actor_id) { - self::level_add($author, 'comment_like'); - } - } - - public function level_on_post_favorited(int $post_id, int $actor_id, bool $active): void { - if (!$active) { - return; - } - $post = get_post($post_id); - if (!$post instanceof \WP_Post) { - return; - } - $author = (int) $post->post_author; - if ($author > 0 && $author !== $actor_id) { - self::level_add($author, 'post_favorite'); - } - } - - public function level_on_user_followed(int $follower_id, int $following_id): void { - unset($follower_id); - self::level_add((int) $following_id, 'followed'); - } - - public function level_on_forum_topic_featured($topic_id, $author_id = 0): void { - unset($topic_id); - self::level_add((int) $author_id, 'bbs_essence'); - } - - public function level_on_forum_reply_adopted($topic_id, $comment_id, $adoptee_id): void { - unset($topic_id, $comment_id); - self::level_add((int) $adoptee_id, 'bbs_adopt'); - } - - public function level_on_forum_comment_hot($comment_id, $author_id = 0): void { - unset($comment_id); - self::level_add((int) $author_id, 'bbs_comment_hot'); - } - - public function level_on_forum_topic_hot($topic_id, $author_id = 0): void { - unset($topic_id); - self::level_add((int) $author_id, 'bbs_posts_hot'); - } - - public function level_on_forum_topic_scored($topic_id, $author_id, $delta = 0): void { - unset($topic_id); - if ((float) $delta > 0) { - self::level_add((int) $author_id, 'bbs_score_extra'); - } - } - - /** - * @param int $user_id - * @param int $topic_id - */ - public function level_on_forum_topic_created($user_id, $topic_id = 0): void { - unset($topic_id); - self::level_add((int) $user_id, 'bbs_posts_new'); - } - - public function level_on_user_signed_in(int $user_id): void { - // Sign-in XP: use sign_in key once/day via existing login guard path as well. - self::level_add($user_id, 'sign_in'); - } } diff --git a/templates/parts/user/author-hero.php b/templates/parts/user/author-hero.php index 32535af0..ff6683d8 100644 --- a/templates/parts/user/author-hero.php +++ b/templates/parts/user/author-hero.php @@ -123,13 +123,8 @@ $pm_url = ($pm_enabled && $messages_url !== '') - - - - - - - + + false]); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
From a57b44bd61276274fc003a8f5ffb9c035d4481db Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jul 2026 09:44:32 +0800 Subject: [PATCH 2/3] fix(user): serialize level experience awards --- src/AKSRT/User/Level.php | 79 +++++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 9 deletions(-) diff --git a/src/AKSRT/User/Level.php b/src/AKSRT/User/Level.php index ac95caa7..5308479b 100644 --- a/src/AKSRT/User/Level.php +++ b/src/AKSRT/User/Level.php @@ -232,6 +232,21 @@ class Level { * @return int|false Granted amount or false. */ public static function add(int $user_id, string $key, int $points = 0, bool $bypass_day_max = false) { + if ($user_id <= 0) { + return false; + } + $lock = self::acquire_lock('user:' . $user_id); + if ($lock === '') { + return false; + } + try { + return self::add_locked($user_id, $key, $points, $bypass_day_max); + } finally { + self::release_lock($lock); + } + } + + private static function add_locked(int $user_id, string $key, int $points, bool $bypass_day_max) { $cfg = self::settings(); $key = sanitize_key($key); if ($user_id <= 0 || !$cfg['enabled'] || $key === '' || $key === 'day_max') { @@ -282,10 +297,34 @@ class Level { return $points; } + private static function acquire_lock(string $scope): string { + global $wpdb; + $name = substr($wpdb->prefix . 'aksrt_level_' . md5($scope), 0, 64); + $acquired = (int) $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, %d)', $name, 5)); + return $acquired === 1 ? $name : ''; + } + + private static function release_lock(string $name): void { + global $wpdb; + $wpdb->get_var($wpdb->prepare('SELECT RELEASE_LOCK(%s)', $name)); + } + private static function award_once(int $user_id, string $key, string $meta_type, int $entity_id, string $marker, int $limit = 1, int $actor_id = 0): void { if ($user_id <= 0 || $entity_id <= 0) { return; } + $lock = self::acquire_lock($meta_type . ':' . $entity_id . ':' . $marker); + if ($lock === '') { + return; + } + try { + self::award_once_locked($user_id, $key, $meta_type, $entity_id, $marker, $limit, $actor_id); + } finally { + self::release_lock($lock); + } + } + + private static function award_once_locked(int $user_id, string $key, string $meta_type, int $entity_id, string $marker, int $limit, int $actor_id): void { $get = $meta_type === 'comment' ? 'get_comment_meta' : 'get_post_meta'; $update = $meta_type === 'comment' ? 'update_comment_meta' : 'update_post_meta'; $awarded = $get($entity_id, $marker, true); @@ -310,11 +349,22 @@ class Level { public function on_login($user_login, $user): void { $user_id = $user instanceof \WP_User ? (int) $user->ID : 0; $today = current_time('Ymd'); - if ($user_id <= 0 || get_user_meta($user_id, self::META_LOGIN_DAY, true) === $today) { + if ($user_id <= 0) { return; } - if (self::add($user_id, 'sign_in')) { - update_user_meta($user_id, self::META_LOGIN_DAY, $today); + $lock = self::acquire_lock('login:' . $user_id . ':' . $today); + if ($lock === '') { + return; + } + try { + if (get_user_meta($user_id, self::META_LOGIN_DAY, true) === $today) { + return; + } + if (self::add($user_id, 'sign_in')) { + update_user_meta($user_id, self::META_LOGIN_DAY, $today); + } + } finally { + self::release_lock($lock); } } @@ -367,14 +417,25 @@ class Level { public function on_followed($follower_id, $followed_id): void { $follower_id = (int) $follower_id; $followed_id = (int) $followed_id; - $awarded = get_user_meta($followed_id, self::META_FOLLOWED, true); - $awarded = is_array($awarded) ? array_map('intval', $awarded) : []; - if ($follower_id <= 0 || $followed_id <= 0 || in_array($follower_id, $awarded, true)) { + if ($follower_id <= 0 || $followed_id <= 0) { return; } - if (self::add($followed_id, 'followed')) { - $awarded[] = $follower_id; - update_user_meta($followed_id, self::META_FOLLOWED, array_values(array_unique($awarded))); + $lock = self::acquire_lock('follow:' . $followed_id); + if ($lock === '') { + return; + } + try { + $awarded = get_user_meta($followed_id, self::META_FOLLOWED, true); + $awarded = is_array($awarded) ? array_map('intval', $awarded) : []; + if (in_array($follower_id, $awarded, true)) { + return; + } + if (self::add($followed_id, 'followed')) { + $awarded[] = $follower_id; + update_user_meta($followed_id, self::META_FOLLOWED, array_values(array_unique($awarded))); + } + } finally { + self::release_lock($lock); } } From d4946ee5547576eeead5a67506f0038c3aeea214 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 30 Jul 2026 10:21:48 +0800 Subject: [PATCH 3/3] fix(user): close level parity gaps --- assets/css/routes/user-center/level.css | 10 ++++ .../user-center-renderers/profile-level.php | 26 ++++++++- src/AKSRT/Admin/Schema/SchemaUsers.php | 26 ++++++++- src/AKSRT/Credits/Invite.php | 36 ++++++++---- src/AKSRT/Credits/Signin.php | 2 +- src/AKSRT/Shop/Part3.php | 5 ++ src/AKSRT/User/Level.php | 56 +++++++++++++------ src/AKSRT/User/LevelAdmin.php | 13 ++++- 8 files changed, 136 insertions(+), 38 deletions(-) diff --git a/assets/css/routes/user-center/level.css b/assets/css/routes/user-center/level.css index 0750e8e8..f22b45f5 100644 --- a/assets/css/routes/user-center/level.css +++ b/assets/css/routes/user-center/level.css @@ -71,6 +71,15 @@ .uc-level-road__item strong { font-size: 12px; } .uc-level-road__item small { color: var(--muted-2-color, #8b9198); font-size: 10px; } +.uc-level-methods { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 10px; } +.uc-level-methods__item { display: grid; grid-template-columns: 34px 1fr auto; align-items: center; gap: 10px; padding: 11px; border: 1px solid var(--color-line-soft, rgba(0,0,0,.06)); border-radius: 9px; } +.uc-level-methods__item > span { display: grid; place-items: center; width: 32px; height: 32px; border-radius: 9px; background: rgba(245,158,11,.12); color: #d97706; } +.uc-level-methods__item strong, .uc-level-methods__item small { display: block; } +.uc-level-methods__item strong { font-size: 12px; } +.uc-level-methods__item small { margin-top: 2px; color: var(--muted-2-color, #8b9198); font-size: 10px; } +.uc-level-methods__item b { color: #7c3aed; } +.uc-level-ledger__row time + small { display: block; margin-top: 2px; color: var(--muted-2-color, #8b9198); font-size: 10px; } + .uc-level-history-grid { display: grid; grid-template-columns: minmax(0,1.35fr) minmax(240px,.65fr); gap: 16px; } .uc-level-ledger, .uc-level-dates { display: grid; } .uc-level-ledger__row { display: grid; grid-template-columns: 36px 1fr auto; align-items: center; gap: 10px; padding: 11px 0; border-bottom: 1px solid var(--color-line-soft, rgba(0,0,0,.05)); } @@ -87,5 +96,6 @@ @media (max-width: 767px) { .uc-level-hero__stats { grid-template-columns: 1fr; } .uc-level-history-grid { grid-template-columns: 1fr; } + .uc-level-methods { grid-template-columns: 1fr; } .uc-level-road { grid-template-columns: repeat(2, minmax(0,1fr)); } } diff --git a/inc/helpers/user-center-renderers/profile-level.php b/inc/helpers/user-center-renderers/profile-level.php index d7a2abbf..03867b2b 100644 --- a/inc/helpers/user-center-renderers/profile-level.php +++ b/inc/helpers/user-center-renderers/profile-level.php @@ -72,6 +72,30 @@ $day_max = \AKSRT\User\Level::day_max(); +
+

+
+ $default_value) : + if ($key === 'day_max') continue; + $value = in_array($key, ['checkin', 'shop_gift'], true) ? 0 : ($key === 'invitation' ? (int) $default_value : \AKSRT\User\Level::task_points($key)); + if ($value <= 0 && !in_array($key, ['checkin', 'shop_gift'], true)) continue; + ?> +
+ +
+ 0 ? '+' . esc_html((string) $value) : esc_html__('动态', 'aksrt'); ?> +
+ +
+
+

@@ -83,7 +107,7 @@ $day_max = \AKSRT\User\Level::day_max(); ?>
-
+
+
diff --git a/src/AKSRT/Admin/Schema/SchemaUsers.php b/src/AKSRT/Admin/Schema/SchemaUsers.php index 61801b76..686b3eee 100644 --- a/src/AKSRT/Admin/Schema/SchemaUsers.php +++ b/src/AKSRT/Admin/Schema/SchemaUsers.php @@ -9,6 +9,27 @@ defined('ABSPATH') || exit; class SchemaUsers { + private static function default_level_options(): array { + $options = []; + for ($level = 1; $level <= 10; $level++) { + $options['name_' . $level] = 'LV' . $level; + $options['icon_img_' . $level] = ''; + $options['upgrade_integral_' . $level] = $level === 1 ? 0 : ($level - 1) * 500 * $level; + } + return $options; + } + + private static function default_integral_options(): array { + return [ + 'day_max' => 100, 'sign_up' => 20, 'sign_in' => 5, 'followed' => 5, + 'post_new' => 5, 'post_like' => 1, 'post_favorite' => 2, + 'comment_new' => 2, 'comment_like' => 1, 'bbs_posts_new' => 3, + 'bbs_score_extra' => 1, 'bbs_essence' => 2, 'bbs_posts_hot' => 2, + 'bbs_plate_new' => 2, 'bbs_plate_hot' => 2, 'bbs_adopt' => 2, + 'bbs_comment_hot' => 2, + ]; + } + public static function build(): array { return [ 'domain' => 'users', @@ -576,7 +597,6 @@ class SchemaUsers { ['key' => 'user_privacy_comments', 'label' => '隐藏评论', 'type' => 'switch', 'default' => false], ['key' => 'user_privacy_favorites', 'label' => '隐藏收藏', 'type' => 'switch', 'default' => false], ['key' => 'user_privacy_credits', 'label' => '隐藏积分', 'type' => 'switch', 'default' => false], - ['key' => 'user_privacy_level', 'label' => '隐藏等级', 'type' => 'switch', 'default' => false], ['key' => 'user_privacy_badge', 'label' => '隐藏徽章', 'type' => 'switch', 'default' => false], ['key' => 'user_privacy_online', 'label' => '隐藏在线状态', 'type' => 'switch', 'default' => false], ['key' => 'user_privacy_last_active', 'label' => '隐藏最后活跃', 'type' => 'switch', 'default' => false], @@ -621,10 +641,10 @@ class SchemaUsers { ['key' => 'user_level_notify', 'label' => '升级通知', 'type' => 'switch', 'default' => true], ]], ['key' => 'level_opt', 'title' => '升级阈值', 'fields' => [ - ['key' => 'user_level_opt', 'label' => '等级参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => [], 'help' => 'name_N / icon_img_N / upgrade_integral_N'], + ['key' => 'user_level_opt', 'label' => '等级参数', 'type' => 'json', 'sanitize_type' => 'array', 'default' => self::default_level_options(), 'help' => 'name_N / icon_img_N / upgrade_integral_N'], ]], ['key' => 'integral', 'title' => '经验值得分', 'fields' => [ - ['key' => 'user_integral_opt', 'label' => '经验值配置', 'type' => 'json', 'sanitize_type' => 'array', 'default' => [], 'help' => 'day_max + 行为得分键'], + ['key' => 'user_integral_opt', 'label' => '经验值配置', 'type' => 'json', 'sanitize_type' => 'array', 'default' => self::default_integral_options(), 'help' => 'day_max + 行为得分键'], ]], ], ], diff --git a/src/AKSRT/Credits/Invite.php b/src/AKSRT/Credits/Invite.php index 9fe02d91..2141f6cd 100644 --- a/src/AKSRT/Credits/Invite.php +++ b/src/AKSRT/Credits/Invite.php @@ -190,14 +190,27 @@ class Invite { private function reward_pair(int $inviter_id, int $invitee_id): void { if (!class_exists('AKSRT\\Credits\\Credits') || !class_exists('\AKSRT\Settings\Settings')) return; - $invite_cfg = \AKSRT\Settings\Settings::get_invite_reward_settings(); - if (empty($invite_cfg['enabled'])) return; + global $wpdb; + $table = \AKSRT\Core\DBMigrator::table('invite'); + if (!$table) return; + $claimed = $wpdb->update( + $table, + ['status' => 'rewarding'], + ['inviter_id' => $inviter_id, 'invitee_id' => $invitee_id, 'status' => 'verified'], + ['%s'], + ['%d', '%d', '%s'] + ); + if ($claimed !== 1) return; - // 反作弊:同 IP 当日上限 - if (!$this->ip_daily_check($invite_cfg)) return; + $invite_cfg = \AKSRT\Settings\Settings::get_invite_reward_settings(); + if (empty($invite_cfg['enabled']) || !$this->ip_daily_check($invite_cfg)) { + $wpdb->update($table, ['status' => 'verified'], ['inviter_id' => $inviter_id, 'invitee_id' => $invitee_id], ['%s'], ['%d', '%d']); + return; + } $inviter_amount = (int) ($invite_cfg['inviter_amount'] ?? 0); $invitee_amount = (int) ($invite_cfg['invitee_amount'] ?? 0); + $level_integral = max(0, (int) ($invite_cfg['invit_reward_level'] ?? 0)); if ($inviter_amount > 0) { Credits::add($inviter_id, $inviter_amount, 'invite', ['type' => 'user', 'id' => $invitee_id]); @@ -205,12 +218,12 @@ class Invite { if ($invitee_amount > 0) { Credits::add($invitee_id, $invitee_amount, 'invitee', ['type' => 'user', 'id' => $inviter_id]); } + if ($level_integral > 0 && class_exists('\AKSRT\User\Level')) { + \AKSRT\User\Level::add($inviter_id, 'invitation', $level_integral, true); + } // 标记 rewarded - global $wpdb; - $table = \AKSRT\Core\DBMigrator::table('invite'); - if ($table) { - $wpdb->update( + $wpdb->update( $table, [ 'status' => 'rewarded', @@ -220,10 +233,9 @@ class Invite { 'inviter_id' => $inviter_id, 'invitee_id' => $invitee_id, ], - ['%s', '%s'], - ['%d', '%d'] - ); - } + ['%s', '%s'], + ['%d', '%d'] + ); } /** 同 IP 当日邀请兑现次数限制 */ diff --git a/src/AKSRT/Credits/Signin.php b/src/AKSRT/Credits/Signin.php index ffd5684c..cfb6bff3 100644 --- a/src/AKSRT/Credits/Signin.php +++ b/src/AKSRT/Credits/Signin.php @@ -139,7 +139,7 @@ class Signin { return new \WP_Error('aksrt_signin_dup', __('签到失败', 'aksrt'), ['status' => 409]); } - do_action('aksrt_user_signed_in', $user_id); + do_action('aksrt_user_signed_in', $user_id, $reward); if ($reward > 0) { if (class_exists('AKSRT\\Credits\\PointsFree')) { diff --git a/src/AKSRT/Shop/Part3.php b/src/AKSRT/Shop/Part3.php index cbc0be77..37127959 100644 --- a/src/AKSRT/Shop/Part3.php +++ b/src/AKSRT/Shop/Part3.php @@ -367,6 +367,8 @@ trait Part3 { private function process_gifts(string $order_number, array $order, array $items): void { $user_id = (int) ($order['user_id'] ?? 0); if ($user_id <= 0) return; + $claim_key = '_aksrt_shop_gifts_' . sanitize_key($order_number); + if (!add_user_meta($user_id, $claim_key, 'processing', true)) return; foreach ($items as $item) { $gifts = $item['gift_data'] ?? []; @@ -378,6 +380,8 @@ trait Part3 { if ($gift_type === 'credits' && class_exists('\AKSRT\Credits\Credits') && (int) $gift_value > 0) { \AKSRT\Credits\Credits::add((int) $gift_value, $user_id, 'shop_gift', sprintf(__('订单 %s 赠品', 'aksrt'), $order_number)); + } elseif (in_array($gift_type, ['level_integral', 'experience'], true) && class_exists('\AKSRT\User\Level') && (int) $gift_value > 0) { + \AKSRT\User\Level::add($user_id, 'shop_gift', (int) $gift_value, true); } elseif ($gift_type === 'vip_days' && (int) $gift_value > 0) { $current = (int) get_user_meta($user_id, 'aksrt_vip_expires', true); $base = max($current, time()); @@ -390,5 +394,6 @@ trait Part3 { } } } + update_user_meta($user_id, $claim_key, '1'); } } diff --git a/src/AKSRT/User/Level.php b/src/AKSRT/User/Level.php index 5308479b..6708cf41 100644 --- a/src/AKSRT/User/Level.php +++ b/src/AKSRT/User/Level.php @@ -31,7 +31,7 @@ class Level { public function init(): void { add_action('user_register', [$this, 'on_register'], 20, 1); add_action('wp_login', [$this, 'on_login'], 20, 2); - add_action('aksrt_user_signed_in', [$this, 'on_checkin'], 20, 1); + add_action('aksrt_user_signed_in', [$this, 'on_checkin'], 20, 2); add_action('transition_post_status', [$this, 'on_post_status'], 20, 3); add_action('comment_post', [$this, 'on_comment_post'], 20, 3); add_action('transition_comment_status', [$this, 'on_comment_status'], 20, 3); @@ -101,6 +101,9 @@ class Level { return [ 'sign_up' => __('首次注册', 'aksrt'), 'sign_in' => __('每日登录', 'aksrt'), + 'checkin' => __('每日签到', 'aksrt'), + 'invitation' => __('邀请奖励', 'aksrt'), + 'shop_gift' => __('购物赠送', 'aksrt'), 'followed' => __('被关注', 'aksrt'), 'post_new' => __('发布文章', 'aksrt'), 'post_like' => __('文章获赞', 'aksrt'), @@ -134,10 +137,16 @@ class Level { } public static function get_level(int $user_id): int { - if ($user_id <= 0 || !self::settings()['enabled']) { + $cfg = self::settings(); + if ($user_id <= 0 || !$cfg['enabled']) { return 0; } - return self::resolve_level(self::get_integral($user_id)); + $level = (int) get_user_meta($user_id, self::META_LEVEL, true); + if ($level <= 0) { + $level = self::resolve_level(self::get_integral($user_id), $cfg); + update_user_meta($user_id, self::META_LEVEL, $level); + } + return max(1, min($cfg['max'], $level)); } public static function get_level_name(int $level): string { @@ -194,7 +203,7 @@ class Level { public static function get_progress(int $user_id): array { $cfg = self::settings(); $integral = self::get_integral($user_id); - $level = self::resolve_level($integral, $cfg); + $level = self::get_level($user_id); $current_need = $level <= 1 ? 0 : max(0, (int) ($cfg['level_opt']['upgrade_integral_' . $level] ?? (($level - 1) * 500 * $level))); $is_max = $level >= $cfg['max']; $next_need = $is_max ? $integral : max($current_need + 1, (int) ($cfg['level_opt']['upgrade_integral_' . ($level + 1)] ?? ($level * 500 * ($level + 1)))); @@ -252,6 +261,9 @@ class Level { if ($user_id <= 0 || !$cfg['enabled'] || $key === '' || $key === 'day_max') { return false; } + if (class_exists('\AKSRT\User\Restriction') && \AKSRT\User\Restriction::get_instance()->is_banned($user_id)) { + return false; + } $points = $points > 0 ? $points : self::task_points($key); if ($points <= 0) { return false; @@ -261,11 +273,8 @@ class Level { $date_details = self::get_date_details($user_id); $today_total = max(0, (int) ($date_details[$today] ?? 0)); $day_max = self::day_max(); - if (!$bypass_day_max && $day_max > 0) { - if ($today_total >= $day_max) { - return false; - } - $points = min($points, $day_max - $today_total); + if (!$bypass_day_max && $day_max > 0 && $today_total >= $day_max) { + return false; } if ($points <= 0) { return false; @@ -289,7 +298,11 @@ class Level { update_user_meta($user_id, self::META_DETAIL, array_slice($details, 0, 50)); $old_level = max(1, (int) get_user_meta($user_id, self::META_LEVEL, true)); - $new_level = self::resolve_level($after, $cfg); + $new_level = $old_level; + $next_need = max(0, (int) ($cfg['level_opt']['upgrade_integral_' . ($old_level + 1)] ?? PHP_INT_MAX)); + if ($old_level < $cfg['max'] && $after >= $next_need) { + $new_level = self::resolve_level($after, $cfg); + } update_user_meta($user_id, self::META_LEVEL, $new_level); if ($new_level !== $old_level) { do_action('aksrt_user_level_changed', $user_id, $new_level, $old_level); @@ -368,8 +381,10 @@ class Level { } } - public function on_checkin(int $user_id): void { - $this->on_login('', get_userdata($user_id)); + public function on_checkin(int $user_id, int $reward = 0): void { + if ($reward > 0) { + self::add($user_id, 'checkin', $reward); + } } public function on_post_status(string $new_status, string $old_status, $post): void { @@ -407,11 +422,7 @@ class Level { if (!$comment instanceof \WP_Comment || (int) $comment->user_id <= 0) { return; } - $post = get_post((int) $comment->comment_post_ID); - $key = $post instanceof \WP_Post && $post->post_type === 'aksrt_forum_topic' ? 'bbs_posts_new' : 'comment_new'; - if ($key === 'comment_new') { - self::award_once((int) $comment->user_id, $key, 'comment', (int) $comment->comment_ID, '_aksrt_level_comment_new'); - } + self::award_once((int) $comment->user_id, 'comment_new', 'comment', (int) $comment->comment_ID, '_aksrt_level_comment_new'); } public function on_followed($follower_id, $followed_id): void { @@ -471,7 +482,16 @@ class Level { public function on_forum_scored($topic_id, $author_id, $amount): void { if ((float) $amount > 0) { - self::award_once((int) $author_id, 'bbs_score_extra', 'post', (int) $topic_id, '_aksrt_level_bbs_score_extra', 5); + $score_detail = get_post_meta((int) $topic_id, '_aksrt_score_detail', true); + $score_detail = is_array($score_detail) ? $score_detail : []; + $actor_id = 0; + foreach (array_reverse($score_detail, true) as $candidate => $entry) { + if ((float) ($entry['amount'] ?? 0) === (float) $amount) { + $actor_id = (int) $candidate; + break; + } + } + self::award_once((int) $author_id, 'bbs_score_extra', 'post', (int) $topic_id, '_aksrt_level_bbs_score_extra', 5, $actor_id); } } diff --git a/src/AKSRT/User/LevelAdmin.php b/src/AKSRT/User/LevelAdmin.php index 40d12e31..aa1853e8 100644 --- a/src/AKSRT/User/LevelAdmin.php +++ b/src/AKSRT/User/LevelAdmin.php @@ -109,11 +109,17 @@ class LevelAdmin { - + ID, 'aksrt_user_level_nonce'); ?> + +

+ + + + + -

@@ -130,7 +136,8 @@ class LevelAdmin { return; } $integral = max(0, (int) ($_POST['aksrt_level_integral'] ?? 0)); + $level = max(1, min(Level::settings()['max'], (int) ($_POST['aksrt_user_level'] ?? 1))); update_user_meta($user_id, Level::META_INTEGRAL, $integral); - update_user_meta($user_id, Level::META_LEVEL, Level::resolve_level($integral)); + update_user_meta($user_id, Level::META_LEVEL, $level); } }