feat(users): rewrite medal catalog + verify apply SPA contract
Replace user_badge_dictionary textarea with structured user_medal_* catalog (categories/items/get_type). Expand verify SPA/PHP to Zibll apply fields. Reader derives badge.dictionary from medal catalog for existing AKSRT_User_Badges wear/grant runtime. No dual-read of old pipe dictionary. audit-admin-contracts: 0 errors.
This commit is contained in:
@@ -71,7 +71,7 @@ export const usersSection = {
|
||||
component: 'UsersVerify',
|
||||
status: 'available',
|
||||
title: '身份认证',
|
||||
desc: '身份认证字段与展示配置(审核队列在 用户 → 身份认证)。',
|
||||
desc: '认证展示、申请门槛与审核策略(审核队列在 用户 → 身份认证)。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'verify',
|
||||
}),
|
||||
@@ -95,7 +95,7 @@ export const usersSection = {
|
||||
component: 'UsersBadge',
|
||||
status: 'available',
|
||||
title: '用户徽章',
|
||||
desc: '徽章规则、授予条件和前台展示。',
|
||||
desc: '勋章分类、自动授予条件(get_type)与前台佩戴展示。存储键 user_medal_*。',
|
||||
canonicalDomain: 'users',
|
||||
section: 'badge',
|
||||
}),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="aksrt-page-container">
|
||||
<PageHeader
|
||||
title="用户徽章"
|
||||
desc="维护徽章字典(含可选 icon_url)、展示数量。授予/收回在 WP 用户资料页;用户前台可佩戴徽章。"
|
||||
desc="配置勋章分类、自动授予条件(get_type / get_val)与公开展示。路由 /users/badge,存储键 user_medal_*。"
|
||||
>
|
||||
<template #actions>
|
||||
<n-space>
|
||||
@@ -15,24 +15,18 @@
|
||||
<n-spin :show="loading" style="width: 100%; min-height: 200px;">
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>当前联动摘要</h3>
|
||||
<h3>当前摘要</h3>
|
||||
</div>
|
||||
<n-space>
|
||||
<n-tag round :type="form.user_badge_enabled ? 'success' : 'default'">
|
||||
徽章系统:{{ form.user_badge_enabled ? '已启用' : '已关闭' }}
|
||||
<n-tag round :type="form.user_medal_s ? 'success' : 'default'">
|
||||
徽章系统:{{ form.user_medal_s ? '已启用' : '已关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round :type="form.user_badge_public_display ? 'info' : 'default'">
|
||||
公开展示:{{ form.user_badge_public_display ? '开启' : '关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round type="info">
|
||||
最多显示:{{ form.user_badge_limit }} 枚
|
||||
</n-tag>
|
||||
<n-tag round :type="notificationSummary.dynamicBadge ? 'success' : 'default'">
|
||||
通知红点联动:{{ notificationSummary.dynamicBadge ? '启用' : '关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round type="info">
|
||||
字典条目:{{ badgeCount }}
|
||||
<n-tag round :type="form.user_medal_public_display ? 'info' : 'default'">
|
||||
公开展示:{{ form.user_medal_public_display ? '开启' : '关闭' }}
|
||||
</n-tag>
|
||||
<n-tag round type="info">最多显示:{{ form.user_medal_limit }} 枚</n-tag>
|
||||
<n-tag round type="info">分类:{{ form.user_medal_args.length }}</n-tag>
|
||||
<n-tag round type="info">条目:{{ itemCount }}</n-tag>
|
||||
</n-space>
|
||||
</n-card>
|
||||
|
||||
@@ -41,43 +35,77 @@
|
||||
<h3>徽章展示</h3>
|
||||
</div>
|
||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
||||
<n-form-item label="启用徽章系统">
|
||||
<n-switch v-model:value="form.user_badge_enabled" />
|
||||
<n-form-item label="启用徽章/勋章系统">
|
||||
<n-switch v-model:value="form.user_medal_s" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="公开页显示徽章" v-if="form.user_badge_enabled">
|
||||
<n-switch v-model:value="form.user_badge_public_display" />
|
||||
<n-form-item v-if="form.user_medal_s" label="公开页显示徽章">
|
||||
<n-switch v-model:value="form.user_medal_public_display" />
|
||||
</n-form-item>
|
||||
|
||||
<n-form-item label="最多显示数量" v-if="form.user_badge_enabled">
|
||||
<n-input-number v-model:value="form.user_badge_limit" :min="1" :max="8" style="width: 180px;" />
|
||||
<n-form-item v-if="form.user_medal_s" label="最多显示数量">
|
||||
<n-input-number v-model:value="form.user_medal_limit" :min="1" :max="8" style="width: 180px;" />
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<div class="aksrt-section-header">
|
||||
<h3>徽章字典</h3>
|
||||
<n-card v-if="form.user_medal_s" class="aksrt-section-card">
|
||||
<div class="aksrt-section-header" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<h3>徽章明细(分类 → 条目)</h3>
|
||||
<n-button size="small" @click="addCategory">添加分类</n-button>
|
||||
</div>
|
||||
|
||||
<n-empty v-if="!form.user_medal_args.length" description="暂无分类,请添加" />
|
||||
|
||||
<div v-for="(cat, ci) in form.user_medal_args" :key="'cat-' + ci" class="medal-cat-block">
|
||||
<n-card size="small" embedded>
|
||||
<n-space vertical :size="12">
|
||||
<n-space align="center">
|
||||
<n-input v-model:value="cat.cat_name" placeholder="分类名称" style="width: 220px;" />
|
||||
<n-button size="small" @click="addItem(ci)">添加条目</n-button>
|
||||
<n-button size="small" quaternary type="error" @click="removeCategory(ci)">删除分类</n-button>
|
||||
</n-space>
|
||||
|
||||
<n-empty v-if="!(cat.items && cat.items.length)" description="该分类暂无条目" />
|
||||
|
||||
<div v-for="(item, ii) in cat.items || []" :key="'item-' + ci + '-' + ii" class="medal-item-row">
|
||||
<n-grid cols="1 s:2 m:3" :x-gap="12" :y-gap="8" responsive="screen">
|
||||
<n-grid-item>
|
||||
<n-input v-model:value="item.slug" placeholder="slug" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-input v-model:value="item.name" placeholder="名称" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-select v-model:value="item.get_type" :options="getTypeOptions" placeholder="获取条件" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-input-number
|
||||
v-model:value="item.get_val"
|
||||
:min="0"
|
||||
:disabled="item.get_type === 'manually_add'"
|
||||
placeholder="阈值"
|
||||
style="width: 100%;"
|
||||
/>
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-input v-model:value="item.icon" placeholder="图标 URL" />
|
||||
</n-grid-item>
|
||||
<n-grid-item>
|
||||
<n-input v-model:value="item.desc" placeholder="描述" />
|
||||
</n-grid-item>
|
||||
</n-grid>
|
||||
<div style="margin-top: 8px; text-align: right;">
|
||||
<n-button size="tiny" quaternary type="error" @click="removeItem(ci, ii)">删除条目</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-card>
|
||||
</div>
|
||||
<n-form :model="form" label-placement="left" label-width="180" class="aksrt-form">
|
||||
<n-form-item label="徽章列表">
|
||||
<n-input
|
||||
v-model:value="form.user_badge_dictionary"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 5, maxRows: 10 }"
|
||||
placeholder="founder|创始成员|warning staff|站务团队|info|https://example.com/medal.png"
|
||||
:disabled="!form.user_badge_enabled"
|
||||
/>
|
||||
<template #feedback>
|
||||
<span class="aksrt-hint-text">每行:`slug|显示文案|色调|图标URL(可选)`。色调:neutral/info/success/warning/brand。授予在用户资料页;前台「我的徽章」可佩戴。</span>
|
||||
</template>
|
||||
</n-form-item>
|
||||
</n-form>
|
||||
</n-card>
|
||||
|
||||
<n-card class="aksrt-section-card">
|
||||
<n-alert type="info" :show-icon="false">
|
||||
字典定义合法徽章;`aksrt_user_badges` 存已授予 slug;`aksrt_wear_badge` 为当前佩戴。公开页/评论通过 identity chips 展示(有 icon_url 时为勋章图)。
|
||||
前台已授予徽章保存在用户 meta aksrt_user_badges;佩戴为 aksrt_wear_badge(slug)。
|
||||
字典来自 user_medal_args;运行时 AKSRT_User_Badges 读取 identity badge.dictionary 派生列表。
|
||||
</n-alert>
|
||||
</n-card>
|
||||
|
||||
@@ -94,31 +122,25 @@ import { useRouter } from 'vue-router'
|
||||
import { useMessage } from 'naive-ui'
|
||||
import api from '@/api'
|
||||
import PageHeader from '@/components/PageHeader.vue'
|
||||
import { MEDAL_GET_TYPES, defaultMedalArgs } from '@/schema/sections/users/badge.js'
|
||||
|
||||
const router = useRouter()
|
||||
const message = useMessage()
|
||||
|
||||
const loading = ref(true)
|
||||
const saving = ref(false)
|
||||
const getTypeOptions = MEDAL_GET_TYPES
|
||||
|
||||
const form = reactive({
|
||||
user_badge_enabled: false,
|
||||
user_badge_public_display: true,
|
||||
user_badge_limit: 3,
|
||||
user_badge_dictionary: 'founder|创始成员|warning\nstaff|站务团队|info',
|
||||
user_medal_s: false,
|
||||
user_medal_public_display: true,
|
||||
user_medal_limit: 3,
|
||||
user_medal_args: defaultMedalArgs(),
|
||||
})
|
||||
|
||||
const notificationSummary = reactive({
|
||||
dynamicBadge: false,
|
||||
})
|
||||
|
||||
const badgeCount = computed(() => {
|
||||
return String(form.user_badge_dictionary || '')
|
||||
.split(/\r?\n/)
|
||||
.map((item) => item.trim())
|
||||
.filter(Boolean)
|
||||
.length
|
||||
})
|
||||
const itemCount = computed(() =>
|
||||
form.user_medal_args.reduce((n, c) => n + (Array.isArray(c.items) ? c.items.length : 0), 0),
|
||||
)
|
||||
|
||||
function go(path) {
|
||||
router.push(path)
|
||||
@@ -128,23 +150,70 @@ function extractData(response) {
|
||||
return response?.data ?? response ?? {}
|
||||
}
|
||||
|
||||
function emptyItem() {
|
||||
return {
|
||||
slug: '',
|
||||
name: '',
|
||||
desc: '',
|
||||
icon: '',
|
||||
get_type: 'manually_add',
|
||||
get_val: 0,
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeArgs(raw) {
|
||||
if (!Array.isArray(raw)) {
|
||||
return defaultMedalArgs()
|
||||
}
|
||||
return raw.map((cat) => ({
|
||||
cat_name: String(cat?.cat_name || cat?.name || '未命名').trim() || '未命名',
|
||||
items: Array.isArray(cat?.items)
|
||||
? cat.items.map((it) => ({
|
||||
slug: String(it?.slug || '').trim(),
|
||||
name: String(it?.name || '').trim(),
|
||||
desc: String(it?.desc || '').trim(),
|
||||
icon: String(it?.icon || it?.icon_url || '').trim(),
|
||||
get_type: String(it?.get_type || 'manually_add').trim() || 'manually_add',
|
||||
get_val: Math.max(0, Number(it?.get_val ?? 0) || 0),
|
||||
}))
|
||||
: [],
|
||||
}))
|
||||
}
|
||||
|
||||
function addCategory() {
|
||||
form.user_medal_args.push({ cat_name: '新分类', items: [emptyItem()] })
|
||||
}
|
||||
|
||||
function removeCategory(ci) {
|
||||
form.user_medal_args.splice(ci, 1)
|
||||
}
|
||||
|
||||
function addItem(ci) {
|
||||
if (!form.user_medal_args[ci].items) {
|
||||
form.user_medal_args[ci].items = []
|
||||
}
|
||||
form.user_medal_args[ci].items.push(emptyItem())
|
||||
}
|
||||
|
||||
function removeItem(ci, ii) {
|
||||
form.user_medal_args[ci].items.splice(ii, 1)
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [schemaRes, notificationRes] = await Promise.all([
|
||||
api.getSchemaPage('users', 'badge'),
|
||||
api.getNotificationSettings(),
|
||||
])
|
||||
|
||||
const schemaRes = await api.getSchemaPage('users', 'badge')
|
||||
const schemaData = extractData(schemaRes)
|
||||
const notificationData = extractData(notificationRes)
|
||||
|
||||
form.user_badge_enabled = !!schemaData.user_badge_enabled
|
||||
form.user_badge_public_display = schemaData.user_badge_public_display !== false
|
||||
form.user_badge_limit = Number(schemaData.user_badge_limit || 3)
|
||||
form.user_badge_dictionary = schemaData.user_badge_dictionary || 'founder|创始成员|warning\nstaff|站务团队|info'
|
||||
|
||||
notificationSummary.dynamicBadge = !!notificationData.user_badge_enabled
|
||||
form.user_medal_s = !!(schemaData.user_medal_s ?? schemaData.user_badge_enabled)
|
||||
form.user_medal_public_display =
|
||||
schemaData.user_medal_public_display !== undefined
|
||||
? schemaData.user_medal_public_display !== false
|
||||
: schemaData.user_badge_public_display !== false
|
||||
form.user_medal_limit = Number(
|
||||
schemaData.user_medal_limit ?? schemaData.user_badge_limit ?? 3,
|
||||
)
|
||||
form.user_medal_args = normalizeArgs(schemaData.user_medal_args ?? null)
|
||||
} catch (error) {
|
||||
message.error(error.message || '加载用户徽章配置失败')
|
||||
} finally {
|
||||
@@ -155,12 +224,23 @@ async function load() {
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
const args = normalizeArgs(form.user_medal_args)
|
||||
for (const cat of args) {
|
||||
for (const it of cat.items) {
|
||||
if (!it.slug || !it.name || !it.get_type) {
|
||||
message.error('每个条目需要 slug、名称和获取条件')
|
||||
saving.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
await api.saveSchemaPage('users', 'badge', {
|
||||
user_badge_enabled: form.user_badge_enabled,
|
||||
user_badge_public_display: form.user_badge_public_display,
|
||||
user_badge_limit: Number(form.user_badge_limit || 3),
|
||||
user_badge_dictionary: form.user_badge_dictionary,
|
||||
user_medal_s: !!form.user_medal_s,
|
||||
user_medal_public_display: !!form.user_medal_public_display,
|
||||
user_medal_limit: Number(form.user_medal_limit || 3),
|
||||
user_medal_args: args,
|
||||
})
|
||||
form.user_medal_args = args
|
||||
message.success('用户徽章配置已保存')
|
||||
} catch (error) {
|
||||
message.error(error.message || '保存失败')
|
||||
@@ -171,3 +251,15 @@ async function handleSave() {
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.medal-cat-block {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.medal-item-row {
|
||||
padding: 12px;
|
||||
border: 1px solid var(--n-border-color, #efeff5);
|
||||
border-radius: 8px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -54,6 +54,43 @@
|
||||
<n-form-item label="启用人工审核台">
|
||||
<n-switch v-model:value="form.user_verify_require_manual_review" :disabled="!form.user_verify_enabled" />
|
||||
</n-form-item>
|
||||
<n-form-item label="申请类型选项">
|
||||
<n-input
|
||||
v-model:value="form.user_verify_apply_option"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||
:disabled="!form.user_verify_enabled"
|
||||
placeholder="slug|显示名称,每行一条"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item label="允许用户前台申请">
|
||||
<n-switch v-model:value="form.user_verify_allow_apply" :disabled="!form.user_verify_enabled" />
|
||||
</n-form-item>
|
||||
<n-form-item v-if="!form.user_verify_allow_apply" label="停用申请说明">
|
||||
<n-input
|
||||
v-model:value="form.user_verify_disable_desc"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 2, maxRows: 6 }"
|
||||
:disabled="!form.user_verify_enabled"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="form.user_verify_allow_apply" label="申请要求等级">
|
||||
<n-input-number
|
||||
v-model:value="form.user_verify_limit_level"
|
||||
:min="0"
|
||||
:max="99"
|
||||
:disabled="!form.user_verify_enabled"
|
||||
style="width: 180px;"
|
||||
/>
|
||||
</n-form-item>
|
||||
<n-form-item v-if="form.user_verify_allow_apply" label="申请介绍">
|
||||
<n-input
|
||||
v-model:value="form.user_verify_desc"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 3, maxRows: 8 }"
|
||||
:disabled="!form.user_verify_enabled"
|
||||
/>
|
||||
</n-form-item>
|
||||
<div class="aksrt-form-actions">
|
||||
<n-button type="primary" :loading="saving" @click="handleSave">保存配置</n-button>
|
||||
</div>
|
||||
@@ -118,6 +155,11 @@ const form = reactive({
|
||||
user_verify_badge_text: '已认证',
|
||||
user_verify_accept_email_verified: true,
|
||||
user_verify_require_manual_review: false,
|
||||
user_verify_allow_apply: true,
|
||||
user_verify_disable_desc: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_verify_limit_level: 2,
|
||||
user_verify_desc: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
user_verify_apply_option: 'real_name|实名认证\norganization|企业认证',
|
||||
})
|
||||
|
||||
const authSummary = reactive({
|
||||
@@ -276,6 +318,14 @@ async function load() {
|
||||
form.user_verify_badge_text = schemaData.user_verify_badge_text || '已认证'
|
||||
form.user_verify_accept_email_verified = schemaData.user_verify_accept_email_verified !== false
|
||||
form.user_verify_require_manual_review = !!schemaData.user_verify_require_manual_review
|
||||
form.user_verify_allow_apply = schemaData.user_verify_allow_apply !== false
|
||||
form.user_verify_disable_desc = schemaData.user_verify_disable_desc
|
||||
|| '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员'
|
||||
form.user_verify_limit_level = Number(schemaData.user_verify_limit_level ?? 2)
|
||||
form.user_verify_desc = schemaData.user_verify_desc
|
||||
|| '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。'
|
||||
form.user_verify_apply_option = schemaData.user_verify_apply_option
|
||||
|| 'real_name|实名认证\norganization|企业认证'
|
||||
authSummary.emailVerificationEnabled = !!authData.registration_email_verification_enabled
|
||||
|
||||
await loadRequests()
|
||||
@@ -311,6 +361,11 @@ async function handleSave() {
|
||||
user_verify_badge_text: form.user_verify_badge_text,
|
||||
user_verify_accept_email_verified: form.user_verify_accept_email_verified,
|
||||
user_verify_require_manual_review: form.user_verify_require_manual_review,
|
||||
user_verify_allow_apply: form.user_verify_allow_apply,
|
||||
user_verify_disable_desc: form.user_verify_disable_desc,
|
||||
user_verify_limit_level: Number(form.user_verify_limit_level || 0),
|
||||
user_verify_desc: form.user_verify_desc,
|
||||
user_verify_apply_option: form.user_verify_apply_option,
|
||||
})
|
||||
message.success('身份认证配置已保存')
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,13 +1,75 @@
|
||||
/**
|
||||
* User medal catalog (SPA route remains /users/badge).
|
||||
* Storage keys: user_medal_* (Zibll user_medal_s / user_medal_args parity).
|
||||
*/
|
||||
|
||||
export const MEDAL_GET_TYPES = [
|
||||
{ label: '注册天数', value: 'registration_time' },
|
||||
{ label: '累计签到天数', value: 'checkin_all_day' },
|
||||
{ label: '发布文章数', value: 'new_post' },
|
||||
{ label: '论坛发帖数', value: 'bbs_post' },
|
||||
{ label: '评论数', value: 'comment' },
|
||||
{ label: '粉丝数', value: 'followed' },
|
||||
{ label: '文章获赞', value: 'like' },
|
||||
{ label: '评论获赞', value: 'comment_like' },
|
||||
{ label: '回复被采纳', value: 'bbs_adopt' },
|
||||
{ label: '神评次数', value: 'bbs_comment_hot' },
|
||||
{ label: '热帖次数', value: 'bbs_post_hot' },
|
||||
{ label: '精华帖数', value: 'bbs_essence' },
|
||||
{ label: '帖子评分累计', value: 'bbs_score' },
|
||||
{ label: '内容阅读量', value: 'views' },
|
||||
{ label: '创作分成累计(元)', value: 'pay_income' },
|
||||
{ label: '推广佣金累计(元)', value: 'pay_rebate' },
|
||||
{ label: '仅手动授予', value: 'manually_add' },
|
||||
]
|
||||
|
||||
export const defaultMedalArgs = () => [
|
||||
{
|
||||
cat_name: '成长',
|
||||
items: [
|
||||
{
|
||||
slug: 'rookie',
|
||||
name: '初出茅庐',
|
||||
desc: '注册时间超过30天',
|
||||
icon: '',
|
||||
get_type: 'registration_time',
|
||||
get_val: 30,
|
||||
},
|
||||
{
|
||||
slug: 'checkin-30',
|
||||
name: '签到达人',
|
||||
desc: '累计签到30天',
|
||||
icon: '',
|
||||
get_type: 'checkin_all_day',
|
||||
get_val: 30,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
cat_name: '限定',
|
||||
items: [
|
||||
{
|
||||
slug: 'annual-star',
|
||||
name: '年度人气达人',
|
||||
desc: '活动限定徽章',
|
||||
icon: '',
|
||||
get_type: 'manually_add',
|
||||
get_val: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
export const badgeSchema = {
|
||||
key: 'users.badge',
|
||||
title: '用户徽章',
|
||||
icon: 'fas fa-award',
|
||||
description: '配置徽章字典、公开展示数量和前台挂点,具体授予逻辑后续扩展。',
|
||||
description: '配置勋章分类、自动授予条件与前台展示。路由仍为 /users/badge;存储键为 user_medal_*。',
|
||||
defaults: {
|
||||
user_badge_enabled: false,
|
||||
user_badge_public_display: true,
|
||||
user_badge_limit: 3,
|
||||
user_badge_dictionary: 'founder|创始成员|warning\nstaff|站务团队|info',
|
||||
user_medal_s: false,
|
||||
user_medal_public_display: true,
|
||||
user_medal_limit: 3,
|
||||
user_medal_args: defaultMedalArgs(),
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -15,23 +77,37 @@ export const badgeSchema = {
|
||||
title: '徽章展示',
|
||||
icon: 'fas fa-medal',
|
||||
fields: [
|
||||
{ key: 'user_badge_enabled', label: '启用徽章系统', type: 'switch', default: false },
|
||||
{ key: 'user_badge_public_display', label: '公开页显示徽章', type: 'switch', default: true, dependsOn: { key: 'user_badge_enabled', value: true } },
|
||||
{ key: 'user_badge_limit', label: '最多显示数量', type: 'number', default: 3, min: 1, max: 8, dependsOn: { key: 'user_badge_enabled', value: true } },
|
||||
{ key: 'user_medal_s', label: '启用徽章/勋章系统', type: 'switch', default: false },
|
||||
{
|
||||
key: 'user_medal_public_display',
|
||||
label: '公开页显示徽章',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_medal_s', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_medal_limit',
|
||||
label: '最多显示数量',
|
||||
type: 'number',
|
||||
default: 3,
|
||||
min: 1,
|
||||
max: 8,
|
||||
dependsOn: { key: 'user_medal_s', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'dictionary',
|
||||
title: '徽章字典',
|
||||
icon: 'fas fa-bookmark',
|
||||
key: 'catalog',
|
||||
title: '徽章明细',
|
||||
icon: 'fas fa-trophy',
|
||||
fields: [
|
||||
{
|
||||
key: 'user_badge_dictionary',
|
||||
label: '徽章列表',
|
||||
type: 'textarea',
|
||||
default: 'founder|创始成员|warning\nstaff|站务团队|info',
|
||||
help: '每行一个徽章:slug|显示文案|色调(neutral/info/success/warning/brand)|图标URL(可选)。有 icon_url 时前台显示勋章图;用户 meta aksrt_wear_badge 指定当前佩戴一枚。',
|
||||
dependsOn: { key: 'user_badge_enabled', value: true },
|
||||
key: 'user_medal_args',
|
||||
label: '分类与条目',
|
||||
type: 'json',
|
||||
default: defaultMedalArgs(),
|
||||
help: '由自定义页编辑:分类 → 条目(slug/名称/描述/图标/get_type/get_val)。仅手动授予用 manually_add。',
|
||||
dependsOn: { key: 'user_medal_s', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -2,13 +2,20 @@ export const verifySchema = {
|
||||
key: 'users.verify',
|
||||
title: '身份认证',
|
||||
icon: 'fas fa-id-card',
|
||||
description: '配置公开认证徽标、审核策略,以及是否复用现有邮箱验证状态。',
|
||||
description: '配置公开认证徽标、申请门槛与审核策略(审核队列在 用户 → 身份认证 原生菜单)。',
|
||||
defaults: {
|
||||
user_verify_enabled: true,
|
||||
user_verify_public_display: true,
|
||||
user_verify_badge_text: '已认证',
|
||||
user_verify_accept_email_verified: true,
|
||||
user_verify_require_manual_review: false,
|
||||
user_verify_allow_apply: true,
|
||||
user_verify_disable_desc:
|
||||
'由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
user_verify_limit_level: 2,
|
||||
user_verify_desc:
|
||||
'认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
user_verify_apply_option: 'real_name|实名认证\norganization|企业认证',
|
||||
},
|
||||
groups: [
|
||||
{
|
||||
@@ -16,9 +23,21 @@ export const verifySchema = {
|
||||
title: '公开展示',
|
||||
icon: 'fas fa-eye',
|
||||
fields: [
|
||||
{ key: 'user_verify_enabled', label: '启用身份认证结构', type: 'switch', default: true },
|
||||
{ key: 'user_verify_public_display', label: '公开页显示认证徽标', type: 'switch', default: true, dependsOn: { key: 'user_verify_enabled', value: true } },
|
||||
{ key: 'user_verify_badge_text', label: '认证徽标文案', type: 'input', default: '已认证', dependsOn: { key: 'user_verify_public_display', value: true } },
|
||||
{ key: 'user_verify_enabled', label: '启用身份认证', type: 'switch', default: true },
|
||||
{
|
||||
key: 'user_verify_public_display',
|
||||
label: '公开页显示认证徽标',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_badge_text',
|
||||
label: '认证徽标文案',
|
||||
type: 'input',
|
||||
default: '已认证',
|
||||
dependsOn: { key: 'user_verify_public_display', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -26,8 +45,66 @@ export const verifySchema = {
|
||||
title: '审核流程',
|
||||
icon: 'fas fa-tasks',
|
||||
fields: [
|
||||
{ key: 'user_verify_accept_email_verified', label: '邮箱验证通过视为已认证', type: 'switch', default: true, dependsOn: { key: 'user_verify_enabled', value: true } },
|
||||
{ key: 'user_verify_require_manual_review', label: '保留人工审核位', type: 'switch', default: false, dependsOn: { key: 'user_verify_enabled', value: true } },
|
||||
{
|
||||
key: 'user_verify_accept_email_verified',
|
||||
label: '邮箱验证通过视为已认证',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_require_manual_review',
|
||||
label: '保留人工审核位',
|
||||
type: 'switch',
|
||||
default: false,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_apply_option',
|
||||
label: '申请类型选项',
|
||||
type: 'textarea',
|
||||
default: 'real_name|实名认证\norganization|企业认证',
|
||||
help: '每行:slug|显示名称',
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'apply',
|
||||
title: '申请设置',
|
||||
icon: 'fas fa-file-signature',
|
||||
fields: [
|
||||
{
|
||||
key: 'user_verify_allow_apply',
|
||||
label: '允许用户前台申请',
|
||||
type: 'switch',
|
||||
default: true,
|
||||
dependsOn: { key: 'user_verify_enabled', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_disable_desc',
|
||||
label: '停用申请说明',
|
||||
type: 'textarea',
|
||||
default: '由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: false },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_limit_level',
|
||||
label: '申请要求等级',
|
||||
type: 'number',
|
||||
default: 2,
|
||||
min: 0,
|
||||
max: 99,
|
||||
help: '0 表示不限制;依赖用户等级功能',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: true },
|
||||
},
|
||||
{
|
||||
key: 'user_verify_desc',
|
||||
label: '申请介绍',
|
||||
type: 'textarea',
|
||||
default: '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。',
|
||||
dependsOn: { key: 'user_verify_allow_apply', value: true },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -580,7 +580,7 @@ class AKSRT_Admin_Schema_Users {
|
||||
'verify' => [
|
||||
'key' => 'verify',
|
||||
'title' => '身份认证',
|
||||
'version' => '1.0.0',
|
||||
'version' => '1.1.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '公开展示', 'fields' => [
|
||||
@@ -591,12 +591,13 @@ class AKSRT_Admin_Schema_Users {
|
||||
['key' => 'workflow', 'title' => '审核流程', 'fields' => [
|
||||
['key' => 'user_verify_accept_email_verified', 'label' => '邮箱验证通过视为已认证', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_verify_require_manual_review', 'label' => '保留人工审核位', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_verify_apply_option', 'label' => '申请选项', 'type' => 'textarea', 'default' => "real_name|实名认证\norganization|企业认证"],
|
||||
['key' => 'user_verify_apply_option', 'label' => '申请类型选项', 'type' => 'textarea', 'default' => "real_name|实名认证\norganization|企业认证", 'desc' => '每行:slug|显示名称'],
|
||||
]],
|
||||
['key' => 'restrict', 'title' => '限制设置', 'fields' => [
|
||||
['key' => 'user_verify_disable_desc', 'label' => '未认证限制说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'user_verify_limit_level', 'label' => '限制等级', 'type' => 'number', 'default' => 0, 'min' => 0, 'max' => 100],
|
||||
['key' => 'user_verify_desc', 'label' => '认证说明', 'type' => 'textarea', 'default' => ''],
|
||||
['key' => 'apply', 'title' => '申请设置', 'fields' => [
|
||||
['key' => 'user_verify_allow_apply', 'label' => '允许用户前台申请', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_verify_disable_desc', 'label' => '停用申请说明', 'type' => 'textarea', 'default' => "由于系统正在维护,暂时无法申请身份认证\n详情请联系管理员"],
|
||||
['key' => 'user_verify_limit_level', 'label' => '申请要求等级', 'type' => 'number', 'default' => 2, 'min' => 0, 'max' => 99, 'desc' => '0 表示不限制;依赖用户等级'],
|
||||
['key' => 'user_verify_desc', 'label' => '申请介绍', 'type' => 'textarea', 'default' => '认证用户会在昵称后显示认证徽章,同时享受多种优先待遇。申请请确保信息属实。'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
@@ -639,21 +640,56 @@ class AKSRT_Admin_Schema_Users {
|
||||
'badge' => [
|
||||
'key' => 'badge',
|
||||
'title' => '用户徽章',
|
||||
'version' => '1.0.0',
|
||||
'version' => '2.0.0',
|
||||
'option_key' => 'aksrt_settings',
|
||||
'groups' => [
|
||||
['key' => 'display', 'title' => '徽章展示', 'fields' => [
|
||||
['key' => 'user_badge_enabled', 'label' => '启用徽章系统', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_badge_public_display', 'label' => '公开页显示徽章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_badge_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8],
|
||||
['key' => 'user_medal_s', 'label' => '启用徽章/勋章系统', 'type' => 'switch', 'default' => false],
|
||||
['key' => 'user_medal_public_display', 'label' => '公开页显示徽章', 'type' => 'switch', 'default' => true],
|
||||
['key' => 'user_medal_limit', 'label' => '最多显示数量', 'type' => 'number', 'default' => 3, 'min' => 1, 'max' => 8],
|
||||
]],
|
||||
['key' => 'dictionary', 'title' => '徽章字典', 'fields' => [
|
||||
['key' => 'catalog', 'title' => '徽章明细', 'fields' => [
|
||||
[
|
||||
'key' => 'user_badge_dictionary',
|
||||
'label' => '徽章列表',
|
||||
'type' => 'textarea',
|
||||
'default' => "founder|创始成员|warning\nstaff|站务团队|info",
|
||||
'help' => '每行:slug|显示文案|色调(neutral/info/success/warning/brand)|图标URL(可选)。配置 icon_url 后前台显示勋章图;用户 meta aksrt_wear_badge 指定当前佩戴。',
|
||||
'key' => 'user_medal_args',
|
||||
'label' => '分类与条目',
|
||||
'type' => 'json',
|
||||
'default' => [
|
||||
[
|
||||
'cat_name' => '成长',
|
||||
'items' => [
|
||||
[
|
||||
'slug' => 'rookie',
|
||||
'name' => '初出茅庐',
|
||||
'desc' => '注册时间超过30天',
|
||||
'icon' => '',
|
||||
'get_type' => 'registration_time',
|
||||
'get_val' => 30,
|
||||
],
|
||||
[
|
||||
'slug' => 'checkin-30',
|
||||
'name' => '签到达人',
|
||||
'desc' => '累计签到30天',
|
||||
'icon' => '',
|
||||
'get_type' => 'checkin_all_day',
|
||||
'get_val' => 30,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'cat_name' => '限定',
|
||||
'items' => [
|
||||
[
|
||||
'slug' => 'annual-star',
|
||||
'name' => '年度人气达人',
|
||||
'desc' => '活动限定徽章',
|
||||
'icon' => '',
|
||||
'get_type' => 'manually_add',
|
||||
'get_val' => 0,
|
||||
],
|
||||
],
|
||||
],
|
||||
],
|
||||
'help' => '结构化目录:分类 → 条目(slug/name/desc/icon/get_type/get_val)。由 SPA UsersBadge 编辑。',
|
||||
],
|
||||
]],
|
||||
],
|
||||
|
||||
@@ -115,11 +115,12 @@ class AKSRT_Settings_User_Runtime {
|
||||
}
|
||||
|
||||
/**
|
||||
* User identity, level, badge, and report governance settings.
|
||||
* User identity, level, medal, and report governance settings.
|
||||
*
|
||||
* @return array{
|
||||
* verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool},
|
||||
* verify:array{enabled:bool,public_display:bool,badge_text:string,accept_email_verified:bool,require_manual_review:bool,allow_apply:bool,disable_desc:string,limit_level:int,desc:string,apply_option:string},
|
||||
* level:array{enabled:bool,public_display:bool,source:string,label_prefix:string,thresholds:array<int,int>},
|
||||
* medal:array{enabled:bool,public_display:bool,limit:int,categories:array<int,array{cat_name:string,items:array<int,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int}>}>,items_by_slug:array<string,array{slug:string,name:string,desc:string,icon:string,get_type:string,get_val:int,cat_name:string}>},
|
||||
* badge:array{enabled:bool,public_display:bool,limit:int,dictionary:array<int,array{slug:string,label:string,tone:string,icon_url?:string}>},
|
||||
* report:array{enabled:bool,public_entry:bool,public_notice:bool,ban_notice_text:string,categories:array<int,array{slug:string,label:string}>}
|
||||
* }
|
||||
@@ -129,6 +130,7 @@ class AKSRT_Settings_User_Runtime {
|
||||
$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);
|
||||
|
||||
return [
|
||||
'verify' => [
|
||||
@@ -137,22 +139,36 @@ class AKSRT_Settings_User_Runtime {
|
||||
'badge_text' => $verify_badge_text !== '' ? $verify_badge_text : __('已认证', 'aksrt'),
|
||||
'accept_email_verified' => !isset($settings['user_verify_accept_email_verified']) || !empty($settings['user_verify_accept_email_verified']),
|
||||
'require_manual_review' => !empty($settings['user_verify_require_manual_review']),
|
||||
'allow_apply' => !isset($settings['user_verify_allow_apply']) || !empty($settings['user_verify_allow_apply']),
|
||||
'disable_desc' => (string) ($settings['user_verify_disable_desc'] ?? ''),
|
||||
'limit_level' => max(0, (int) ($settings['user_verify_limit_level'] ?? 0)),
|
||||
'desc' => (string) ($settings['user_verify_desc'] ?? ''),
|
||||
'apply_option' => (string) ($settings['user_verify_apply_option'] ?? ''),
|
||||
],
|
||||
'level' => [
|
||||
'enabled' => !empty($settings['user_level_enabled']),
|
||||
'enabled' => !empty($settings['user_level_enabled']) || !empty($settings['user_level_s']),
|
||||
'public_display' => !isset($settings['user_level_public_display']) || !empty($settings['user_level_public_display']),
|
||||
'source' => (function() use ($settings) {
|
||||
'source' => (static function () use ($settings) {
|
||||
$v = $settings['user_level_source'] ?? 'credits_balance';
|
||||
return in_array($v, ['credits_balance', 'manual_meta'], true) ? $v : 'credits_balance';
|
||||
return in_array($v, ['credits_balance', 'manual_meta', 'integral'], true) ? $v : 'credits_balance';
|
||||
})(),
|
||||
'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")),
|
||||
],
|
||||
'medal' => $medal,
|
||||
// Alias for existing call sites that still read badge.*; dictionary is derived from medal catalog.
|
||||
'badge' => [
|
||||
'enabled' => !empty($settings['user_badge_enabled']),
|
||||
'public_display' => !isset($settings['user_badge_public_display']) || !empty($settings['user_badge_public_display']),
|
||||
'limit' => max(1, min(8, (int) ($settings['user_badge_limit'] ?? 3))),
|
||||
'dictionary' => self::parse_badge_dictionary((string) ($settings['user_badge_dictionary'] ?? "founder|创始成员|warning\nstaff|站务团队|info")),
|
||||
'enabled' => $medal['enabled'],
|
||||
'public_display' => $medal['public_display'],
|
||||
'limit' => $medal['limit'],
|
||||
'dictionary' => array_values(array_map(static function (array $item): array {
|
||||
return [
|
||||
'slug' => $item['slug'],
|
||||
'label' => $item['name'],
|
||||
'tone' => 'info',
|
||||
'icon_url' => $item['icon'],
|
||||
];
|
||||
}, $medal['items_by_slug'])),
|
||||
],
|
||||
'report' => [
|
||||
'enabled' => !empty($settings['user_report_enabled']),
|
||||
@@ -207,6 +223,100 @@ class AKSRT_Settings_User_Runtime {
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse structured user_medal_args catalog (rewrite; no badge dictionary dual-read).
|
||||
*
|
||||
* @param array<string,mixed> $settings
|
||||
* @return array{enabled:bool,public_display:bool,limit:int,categories:array,items_by_slug:array}
|
||||
*/
|
||||
public static function parse_medal_catalog(array $settings): array {
|
||||
$enabled = !empty($settings['user_medal_s']);
|
||||
$public = !isset($settings['user_medal_public_display']) || !empty($settings['user_medal_public_display']);
|
||||
$limit = max(1, min(8, (int) ($settings['user_medal_limit'] ?? 3)));
|
||||
|
||||
$allowed_types = [
|
||||
'registration_time',
|
||||
'checkin_all_day',
|
||||
'new_post',
|
||||
'bbs_post',
|
||||
'comment',
|
||||
'followed',
|
||||
'like',
|
||||
'comment_like',
|
||||
'bbs_adopt',
|
||||
'bbs_comment_hot',
|
||||
'bbs_post_hot',
|
||||
'bbs_essence',
|
||||
'bbs_score',
|
||||
'views',
|
||||
'pay_income',
|
||||
'pay_rebate',
|
||||
'manually_add',
|
||||
];
|
||||
|
||||
$raw = $settings['user_medal_args'] ?? null;
|
||||
if (is_string($raw) && $raw !== '') {
|
||||
$decoded = json_decode($raw, true);
|
||||
$raw = is_array($decoded) ? $decoded : [];
|
||||
}
|
||||
if (!is_array($raw)) {
|
||||
$raw = [];
|
||||
}
|
||||
|
||||
$categories = [];
|
||||
$items_by_slug = [];
|
||||
|
||||
foreach ($raw as $cat) {
|
||||
if (!is_array($cat)) {
|
||||
continue;
|
||||
}
|
||||
$cat_name = sanitize_text_field((string) ($cat['cat_name'] ?? $cat['name'] ?? ''));
|
||||
if ($cat_name === '') {
|
||||
$cat_name = __('未命名', 'aksrt');
|
||||
}
|
||||
$items_out = [];
|
||||
$items = is_array($cat['items'] ?? null) ? $cat['items'] : [];
|
||||
foreach ($items as $item) {
|
||||
if (!is_array($item)) {
|
||||
continue;
|
||||
}
|
||||
$slug = sanitize_key((string) ($item['slug'] ?? ''));
|
||||
$name = sanitize_text_field((string) ($item['name'] ?? ''));
|
||||
if ($slug === '' || $name === '') {
|
||||
continue;
|
||||
}
|
||||
$get_type = sanitize_key((string) ($item['get_type'] ?? 'manually_add'));
|
||||
if (!in_array($get_type, $allowed_types, true)) {
|
||||
$get_type = 'manually_add';
|
||||
}
|
||||
$icon = esc_url_raw((string) ($item['icon'] ?? $item['icon_url'] ?? ''));
|
||||
$row = [
|
||||
'slug' => $slug,
|
||||
'name' => $name,
|
||||
'desc' => sanitize_text_field((string) ($item['desc'] ?? '')),
|
||||
'icon' => $icon,
|
||||
'get_type' => $get_type,
|
||||
'get_val' => max(0, (int) ($item['get_val'] ?? 0)),
|
||||
'cat_name' => $cat_name,
|
||||
];
|
||||
$items_out[] = $row;
|
||||
$items_by_slug[$slug] = $row;
|
||||
}
|
||||
$categories[] = [
|
||||
'cat_name' => $cat_name,
|
||||
'items' => $items_out,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'enabled' => $enabled,
|
||||
'public_display' => $public,
|
||||
'limit' => $limit,
|
||||
'categories' => $categories,
|
||||
'items_by_slug' => $items_by_slug,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,int>
|
||||
*/
|
||||
@@ -238,41 +348,13 @@ class AKSRT_Settings_User_Runtime {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Removed — medal catalog is structured user_medal_args. Method kept empty stub intentionally deleted.
|
||||
* @return array<int,array{slug:string,label:string,tone:string,icon_url:string}>
|
||||
*/
|
||||
public static function parse_badge_dictionary(string $raw): array {
|
||||
$lines = preg_split('/[\r\n]+/', $raw, -1, PREG_SPLIT_NO_EMPTY);
|
||||
$allowed_tones = ['neutral', 'info', 'success', 'warning', 'brand'];
|
||||
$items = [];
|
||||
|
||||
foreach ((array) $lines as $line) {
|
||||
$parts = array_map('trim', explode('|', (string) $line));
|
||||
if (count($parts) < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$slug = sanitize_key((string) $parts[0]);
|
||||
$label = sanitize_text_field((string) $parts[1]);
|
||||
$tone = isset($parts[2]) ? sanitize_key((string) $parts[2]) : 'neutral';
|
||||
$icon_url = isset($parts[3]) ? esc_url_raw((string) $parts[3]) : '';
|
||||
|
||||
if ($slug === '' || $label === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!in_array($tone, $allowed_tones, true)) {
|
||||
$tone = 'neutral';
|
||||
}
|
||||
|
||||
$items[] = [
|
||||
'slug' => $slug,
|
||||
'label' => $label,
|
||||
'tone' => $tone,
|
||||
'icon_url' => $icon_url,
|
||||
];
|
||||
}
|
||||
|
||||
return $items;
|
||||
// Rewrite policy: no dual-read of pipe dictionary. Callers must use parse_medal_catalog / get_user_identity_settings()['medal'].
|
||||
unset($raw);
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user