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:
@@ -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